From 8da8fefc68431685822191423ee4ae9761d8abff Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Thu, 4 Sep 2014 23:34:43 +0200 Subject: [PATCH 01/21] Natural whitespace handling for SBT configuration parser AKA 'no more blankies' --- .../scala/sbt/EvaluateConfigurations.scala | 33 +- .../sbt/SplitExpressionsNoBlankies.scala | 385 ++++++++++++++++++ .../src/test/resources/error-format/1.sbt.txt | 1 + .../src/test/resources/error-format/2.sbt.txt | 13 + .../src/test/resources/error-format/3.sbt.txt | 3 + main/src/test/resources/new-format/1.sbt.txt | 19 + main/src/test/resources/new-format/2.sbt.txt | 18 + main/src/test/resources/new-format/3.sbt.txt | 7 + main/src/test/resources/old-format/1.sbt.txt | 66 +++ main/src/test/resources/old-format/10.sbt.txt | 81 ++++ main/src/test/resources/old-format/11.sbt.txt | 52 +++ main/src/test/resources/old-format/12.sbt.txt | 55 +++ main/src/test/resources/old-format/13.sbt.txt | 75 ++++ main/src/test/resources/old-format/14.sbt.txt | 41 ++ main/src/test/resources/old-format/15.sbt.txt | 21 + main/src/test/resources/old-format/16.sbt.txt | 141 +++++++ main/src/test/resources/old-format/17.sbt.txt | 52 +++ main/src/test/resources/old-format/18.sbt.txt | 93 +++++ main/src/test/resources/old-format/19.sbt.txt | 2 + main/src/test/resources/old-format/2.sbt.txt | 39 ++ main/src/test/resources/old-format/20.sbt.txt | 18 + main/src/test/resources/old-format/3.sbt.txt | 51 +++ main/src/test/resources/old-format/4.sbt.txt | 74 ++++ main/src/test/resources/old-format/5.sbt.txt | 41 ++ main/src/test/resources/old-format/6.sbt.txt | 67 +++ main/src/test/resources/old-format/7.sbt.txt | 160 ++++++++ main/src/test/resources/old-format/8.sbt.txt | 55 +++ main/src/test/resources/old-format/9.sbt.txt | 40 ++ main/src/test/scala/PluginCommandTest.scala | 2 +- main/src/test/scala/sbt/AbstractSpec.scala | 5 + .../test/scala/sbt/CheckIfParsedSpec.scala | 36 ++ .../src/test/scala/sbt/CommentedXmlSpec.scala | 67 +++ main/src/test/scala/sbt/EmbeddedXmlSpec.scala | 143 +++++++ main/src/test/scala/sbt/ErrorSpec.scala | 61 +++ .../sbt/EvaluateConfigurationsOriginal.scala | 39 ++ main/src/test/scala/sbt/NewFormatSpec.scala | 27 ++ .../src/test/scala/sbt/SplitExpressions.scala | 7 + .../scala/sbt/SplitExpressionsBehavior.scala | 91 +++++ .../scala/sbt/SplitExpressionsFilesTest.scala | 150 +++++++ .../test/scala/sbt/SplitExpressionsTest.scala | 13 + 40 files changed, 2317 insertions(+), 27 deletions(-) create mode 100644 main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala create mode 100644 main/src/test/resources/error-format/1.sbt.txt create mode 100644 main/src/test/resources/error-format/2.sbt.txt create mode 100644 main/src/test/resources/error-format/3.sbt.txt create mode 100644 main/src/test/resources/new-format/1.sbt.txt create mode 100644 main/src/test/resources/new-format/2.sbt.txt create mode 100644 main/src/test/resources/new-format/3.sbt.txt create mode 100644 main/src/test/resources/old-format/1.sbt.txt create mode 100644 main/src/test/resources/old-format/10.sbt.txt create mode 100644 main/src/test/resources/old-format/11.sbt.txt create mode 100644 main/src/test/resources/old-format/12.sbt.txt create mode 100644 main/src/test/resources/old-format/13.sbt.txt create mode 100644 main/src/test/resources/old-format/14.sbt.txt create mode 100644 main/src/test/resources/old-format/15.sbt.txt create mode 100644 main/src/test/resources/old-format/16.sbt.txt create mode 100644 main/src/test/resources/old-format/17.sbt.txt create mode 100644 main/src/test/resources/old-format/18.sbt.txt create mode 100644 main/src/test/resources/old-format/19.sbt.txt create mode 100644 main/src/test/resources/old-format/2.sbt.txt create mode 100644 main/src/test/resources/old-format/20.sbt.txt create mode 100644 main/src/test/resources/old-format/3.sbt.txt create mode 100644 main/src/test/resources/old-format/4.sbt.txt create mode 100644 main/src/test/resources/old-format/5.sbt.txt create mode 100644 main/src/test/resources/old-format/6.sbt.txt create mode 100644 main/src/test/resources/old-format/7.sbt.txt create mode 100644 main/src/test/resources/old-format/8.sbt.txt create mode 100644 main/src/test/resources/old-format/9.sbt.txt create mode 100644 main/src/test/scala/sbt/AbstractSpec.scala create mode 100644 main/src/test/scala/sbt/CheckIfParsedSpec.scala create mode 100644 main/src/test/scala/sbt/CommentedXmlSpec.scala create mode 100644 main/src/test/scala/sbt/EmbeddedXmlSpec.scala create mode 100644 main/src/test/scala/sbt/ErrorSpec.scala create mode 100644 main/src/test/scala/sbt/EvaluateConfigurationsOriginal.scala create mode 100644 main/src/test/scala/sbt/NewFormatSpec.scala create mode 100644 main/src/test/scala/sbt/SplitExpressions.scala create mode 100644 main/src/test/scala/sbt/SplitExpressionsBehavior.scala create mode 100644 main/src/test/scala/sbt/SplitExpressionsFilesTest.scala create mode 100644 main/src/test/scala/sbt/SplitExpressionsTest.scala diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index 54531c5bd..621a20d8f 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -63,9 +63,9 @@ object EvaluateConfigurations { * * @param buildinImports The set of import statements to add to those parsed in the .sbt file. */ - private[this] def parseConfiguration(lines: Seq[String], builtinImports: Seq[String], offset: Int): ParsedFile = + private[this] def parseConfiguration(file: File, lines: Seq[String], builtinImports: Seq[String], offset: Int): ParsedFile = { - val (importStatements, settingsAndDefinitions) = splitExpressions(lines) + val (importStatements, settingsAndDefinitions) = splitExpressions(file, lines) val allImports = builtinImports.map(s => (s, -1)) ++ addOffset(offset, importStatements) val (definitions, settings) = splitSettingsDefinitions(addOffsetToRange(offset, settingsAndDefinitions)) new ParsedFile(allImports, definitions, settings) @@ -103,7 +103,7 @@ object EvaluateConfigurations { // TODO - Store the file on the LoadedSbtFile (or the parent dir) so we can accurately do // detection for which project project manipulations should be applied. val name = file.getPath - val parsed = parseConfiguration(lines, imports, offset) + val parsed = parseConfiguration(file, lines, imports, offset) val (importDefs, definitions) = if (parsed.definitions.isEmpty) (Nil, DefinedSbtValues.empty) else { val definitions = evaluateDefinitions(eval, name, parsed.imports, parsed.definitions, Some(file)) @@ -208,37 +208,18 @@ object EvaluateConfigurations { } } private[this] def isSpace = (c: Char) => Character isWhitespace c - private[this] def fstS(f: String => Boolean): ((String, Int)) => Boolean = { case (s, i) => f(s) } private[this] def firstNonSpaceIs(lit: String) = (_: String).view.dropWhile(isSpace).startsWith(lit) private[this] def or[A](a: A => Boolean, b: A => Boolean): A => Boolean = in => a(in) || b(in) /** * Splits a set of lines into (imports, expressions). That is, * anything on the right of the tuple is a scala expression (definition or setting). */ - def splitExpressions(lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = + def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { - val blank = (_: String).forall(isSpace) - val isImport = firstNonSpaceIs("import ") - val comment = firstNonSpaceIs("//") - val blankOrComment = or(blank, comment) - val importOrBlank = fstS(or(blankOrComment, isImport)) + val split = SplitExpressionsNoBlankies(null, lines) + (split.imports, split.settings) + } - val (imports, settings) = lines.zipWithIndex span importOrBlank - (imports filterNot fstS(blankOrComment), groupedLines(settings, blank, blankOrComment)) - } - def groupedLines(lines: Seq[(String, Int)], delimiter: String => Boolean, skipInitial: String => Boolean): Seq[(String, LineRange)] = - { - val fdelim = fstS(delimiter) - @tailrec def group0(lines: Seq[(String, Int)], accum: Seq[(String, LineRange)]): Seq[(String, LineRange)] = - if (lines.isEmpty) accum.reverse - else { - val start = lines dropWhile fstS(skipInitial) - val (next, tail) = start.span { case (s, _) => !delimiter(s) } - val grouped = if (next.isEmpty) accum else (next.map(_._1).mkString("\n"), LineRange(next.head._2, next.last._2 + 1)) +: accum - group0(tail, grouped) - } - group0(lines, Nil) - } private[this] def splitSettingsDefinitions(lines: Seq[(String, LineRange)]): (Seq[(String, LineRange)], Seq[(String, LineRange)]) = lines partition { case (line, range) => isDefinition(line) } private[this] def isDefinition(line: String): Boolean = diff --git a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala new file mode 100644 index 000000000..02ed24419 --- /dev/null +++ b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala @@ -0,0 +1,385 @@ +package sbt + +import java.io.File + +import scala.annotation.tailrec +import SplitExpressionsNoBlankies._ + +object SplitExpressionsNoBlankies { + val END_OF_LINE = "\n" +} + +case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { + val (imports, settings) = splitExpressions(file, lines) + + private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { + import scala.reflect.runtime._ + import scala.reflect.runtime.universe._ + import scala.tools.reflect.ToolBoxError + import scala.tools.reflect.ToolBox + import scala.compat.Platform.EOL + import BugInParser._ + import XmlContent._ + + val mirror = universe.runtimeMirror(this.getClass.getClassLoader) + val toolbox = mirror.mkToolBox(options = "-Yrangepos") + val indexedLines = lines.toIndexedSeq + val original = indexedLines.mkString(END_OF_LINE) + val merged = handleXmlContent(original) + val fileName = if (file == null) "Here should be file name" else file.getAbsolutePath + + val parsed = + try { + toolbox.parse(merged) + } catch { + case e: ToolBoxError => + val seq = toolbox.frontEnd.infos.map { i => + s"""[$fileName]:${i.pos.line}: ${i.msg}""" + } + throw new MessageOnlyException( + s"""${seq.mkString(EOL)}""".stripMargin) + } + val parsedTrees = parsed match { + case Block(stmt, expr) => + stmt :+ expr + case t: Tree => + Seq(t) + } + + val (imports, statements) = parsedTrees partition { + case _: Import => true + case _ => false + } + + def convertImport(t: Tree): (String, Int) = + (merged.substring(t.pos.start, t.pos.end), t.pos.line - 1) + + def convertStatement(t: Tree): Option[(String, LineRange)] = + if (t.pos.isDefined) { + val originalStatement = merged.substring(t.pos.start, t.pos.end) + val statement = util.Try(toolbox.parse(originalStatement)) match { + case util.Failure(th) => + val missingText = tryWithNextStatement(merged, t.pos.end, t.pos.line, fileName, th) + originalStatement + missingText + case _ => + originalStatement + } + val numberLines = statement.count(c => c == '\n') + Some((statement, LineRange(t.pos.line - 1, t.pos.line + numberLines))) + } else { + None + } + + (imports map convertImport, statements flatMap convertStatement) + } + +} + +/** + * Scala parser cuts last bracket - + * @see http://stackoverflow.com/questions/25547149/scala-parser-cuts-last-bracket + */ +private[sbt] object BugInParser { + /** + * + * @param content - parsed file + * @param positionEnd - from index + * @param positionLine - number of start position line + * @param fileName - + * @param th - original exception + * @return + */ + private[sbt] def tryWithNextStatement(content: String, positionEnd: Int, positionLine: Int, fileName: String, th: Throwable): String = { + findFirstNotBlankNotCommentedIndex(content, positionEnd) match { + case Some(index) => + content.substring(positionEnd, index + 1) + case _ => + throw new MessageOnlyException(s"""[$fileName]:$positionLine: ${th.getMessage}""".stripMargin) + } + } + + /** + * + * @param content - parsed file + * @param from - start index + * @return first not commented index or None + */ + private def findFirstNotBlankNotCommentedIndex(content: String, from: Int): Option[Int] = { + val index = content.indexWhere(c => !c.isWhitespace, from) + if (index == -1) { + None + } else { + val c = content.charAt(index) + if (c == '/' && content.size > index + 1) { + val nextChar = content.charAt(index + 1) + if (nextChar == '/') { + val endOfLine = content.indexOf('\n', index) + findFirstNotBlankNotCommentedIndex(content, endOfLine) + } else { + //if (nextChar == '*') + val endOfCommented = content.indexOf("*/", index + 1) + findFirstNotBlankNotCommentedIndex(content, endOfCommented + 2) + } + } else { + Some(index) + } + } + } +} + +/** + * #ToolBox#parse(String) will fail for xml sequence: + *
+ *   val xml = 
txt
+ * rr + *
+ * At least brackets have to be added + *
+ *   val xml = (
txt
+ * rr) + *
+ */ +private object XmlContent { + /** + * + * @param original - file content + * @return original content or content with brackets added to xml parts + */ + private[sbt] def handleXmlContent(original: String): String = { + val xmlParts = findXmlParts(original) + if (xmlParts.isEmpty) { + original + } else { + addExplicitXmlContent(original, xmlParts) + } + } + + /** + * Cut file for normal text - xml - normal text - xml .... + * @param content - + * @param ts - import/statements + * @return (text,true) - is xml (text,false) - if normal text + */ + private def splitFile(content: String, ts: Seq[(String, Int, Int)]): Seq[(String, Boolean)] = { + val (statements, index) = ts.foldLeft((Seq.empty[(String, Boolean)], 0)) { + (accSeqIndex, el) => + val (statement, startIndex, endIndex) = el + val (accSeq, index) = accSeqIndex + val textStatementOption = if (index >= startIndex) { + None + } else { + val s = content.substring(index, startIndex) + Some((s, false)) + } + val newAccSeq = (statement, true) +: addOptionToCollection(accSeq, textStatementOption) + (newAccSeq, endIndex) + } + val endOfFile = content.substring(index, content.length) + val withEndOfFile = (endOfFile, false) +: statements + withEndOfFile.reverse + } + + /** + * Cut potential xmls from content + * @param content - + * @return sorted by openIndex xml parts + */ + private def findXmlParts(content: String): Seq[(String, Int, Int)] = { + val xmlParts = findModifiedOpeningTags(content, 0, Seq.empty) ++ findNotModifiedOpeningTags(content, 0, Seq.empty) + val rootXmlParts = removeEmbeddedXmlParts(xmlParts) + rootXmlParts.sortBy(z => z._2) + + } + + private def searchForTagName(text: String, startIndex: Int, endIndex: Int) = { + val subs = text.substring(startIndex, endIndex) + val spaceIndex = subs.indexWhere(c => c.isWhitespace, 1) + if (spaceIndex == -1) { + subs + } else { + subs.substring(0, spaceIndex) + } + } + + /** + * Modified Opening Tag - + * @param offsetIndex - index + * @param acc - result + * @return Set with tags and positions + */ + @tailrec + private def findModifiedOpeningTags(content: String, offsetIndex: Int, acc: Seq[(String, Int, Int)]): Seq[(String, Int, Int)] = { + val endIndex = content.indexOf("/>", offsetIndex) + if (endIndex == -1) { + acc + } else { + val xmlFragment = findModifiedOpeningTag(content, offsetIndex, endIndex) + val newAcc = addOptionToCollection(acc, xmlFragment) + findModifiedOpeningTags(content, endIndex + 2, newAcc) + } + } + + private def findModifiedOpeningTag(content: String, offsetIndex: Int, endIndex: Int): Option[(String, Int, Int)] = { + val startIndex = content.substring(offsetIndex, endIndex).lastIndexOf("<") + if (startIndex == -1) { + None + } else { + val tagName = searchForTagName(content, startIndex + 1 + offsetIndex, endIndex) + if (xml.Utility.isName(tagName)) { + xmlFragmentOption(content, startIndex + offsetIndex, endIndex + 2) + } else { + None + } + } + + } + + private def searchForOpeningIndex(text: String, closeTagStartIndex: Int, tagName: String) = { + val subs = text.substring(0, closeTagStartIndex) + val index = subs.lastIndexOf(s"<$tagName>") + if (index == -1) { + subs.lastIndexOf(s"<$tagName ") + } else { + index + } + } + + /** + * Xml like - ... + * @param current - index + * @param acc - result + * @return Set with tags and positions + */ + @tailrec + private def findNotModifiedOpeningTags(content: String, current: Int, acc: Seq[(String, Int, Int)]): Seq[(String, Int, Int)] = { + val closeTagStartIndex = content.indexOf("", closeTagStartIndex) + if (closeTagEndIndex == -1) { + findNotModifiedOpeningTags(content, closeTagStartIndex + 2, acc) + } else { + val xmlFragment = findNotModifiedOpeningTag(content, closeTagStartIndex, closeTagEndIndex) + val newAcc = addOptionToCollection(acc, xmlFragment) + findNotModifiedOpeningTags(content, closeTagEndIndex + 1, newAcc) + } + } + } + + private def removeEmbeddedXmlParts(xmlParts: Seq[(String, Int, Int)]) = { + def isElementBetween(el: (String, Int, Int), open: Int, close: Int): Boolean = { + xmlParts.exists { + element => + val (_, openIndex, closeIndex) = element + el != element && (open > openIndex) && (close < closeIndex) + } + } + xmlParts.filterNot { el => + val (_, open, close) = el + isElementBetween(el, open, close) + } + } + + /** + * + * @param content - + * @param xmlParts - + * @return + */ + private def addExplicitXmlContent(content: String, xmlParts: Seq[(String, Int, Int)]): String = { + val statements: Seq[(String, Boolean)] = splitFile(content, xmlParts) + val (builder, wasPreviousXml, wasXml, _) = statements.foldLeft((Seq.empty[String], false, false, "")) { + (acc, el) => + val (bAcc, wasXml, _, previous) = acc + val (content, isXml) = el + val contentEmpty = content.trim.isEmpty + val (isNotCommentedXml, newAcc) = if (isXml) { + if (!wasXml) { + if (areBracketsNecessary(previous)) { + (true, " ( " +: bAcc) + } else { + (false, bAcc) + } + } else { + (true, bAcc) + } + } else if (wasXml && !contentEmpty) { + (false, " ) " +: bAcc) + } else { + (false, bAcc) + } + + (content +: newAcc, isNotCommentedXml || (wasXml && contentEmpty), isXml, content) + } + val closeIfNecessaryBuilder = + if (wasPreviousXml && !wasXml) { + builder.head +: " ) " +: builder.tail + } else { + builder + } + closeIfNecessaryBuilder.reverse.mkString + } + + /** + * Add to head if option is not empty + * @param acc - seq + * @param option - + * @tparam T - type + * @return original seq for None, add to head for Some[T] + */ + private def addOptionToCollection[T](acc: Seq[T], option: Option[T]) = option.fold(acc)(el => el +: acc) + + private def findNotModifiedOpeningTag(content: String, closeTagStartIndex: Int, closeTagEndIndex: Int): Option[(String, Int, Int)] = { + + val tagName = content.substring(closeTagStartIndex + 2, closeTagEndIndex) + if (xml.Utility.isName(tagName)) { + val openTagIndex = searchForOpeningIndex(content, closeTagStartIndex, tagName) + if (openTagIndex == -1) { + None + } else { + xmlFragmentOption(content, openTagIndex, closeTagEndIndex + 1) + } + } else { + None + } + } + + /** + * Check, if xmlPart is valid xml. If not - None is returned + * @param content - file content + * @param openIndex - + * @param closeIndex - + * @return Some((String,Int,Int)) + */ + private def xmlFragmentOption(content: String, openIndex: Int, closeIndex: Int): Option[(String, Int, Int)] = { + val xmlPart = content.substring(openIndex, closeIndex) + util.Try(xml.XML.loadString(xmlPart)) match { + case util.Success(_) => Some((xmlPart, openIndex, closeIndex)) + case util.Failure(th) => None + } + } + + /** + * If xml is in brackets - we do not need to add them + * @param statement - + * @return are brackets necessary? + */ + private def areBracketsNecessary(statement: String): Boolean = { + val doubleSlash = statement.indexOf("//") + val endOfLine = statement.indexOf(END_OF_LINE) + if (doubleSlash == -1 || (doubleSlash < endOfLine)) { + val roundBrackets = statement.lastIndexOf("(") + val braces = statement.lastIndexOf("{") + val max = roundBrackets.max(braces) + if (max == -1) { + true + } else { + val trimmed = statement.substring(max + 1).trim + trimmed.nonEmpty + } + } else { + false + } + } +} \ No newline at end of file diff --git a/main/src/test/resources/error-format/1.sbt.txt b/main/src/test/resources/error-format/1.sbt.txt new file mode 100644 index 000000000..c03539261 --- /dev/null +++ b/main/src/test/resources/error-format/1.sbt.txt @@ -0,0 +1 @@ +val a = "ss \ No newline at end of file diff --git a/main/src/test/resources/error-format/2.sbt.txt b/main/src/test/resources/error-format/2.sbt.txt new file mode 100644 index 000000000..aab5484bb --- /dev/null +++ b/main/src/test/resources/error-format/2.sbt.txt @@ -0,0 +1,13 @@ +import sbt._ +import aaa._ + +import scala._ + +scalaVersion in Global := "2.11.2" + +ala + +libraryDependencies in Global ++= Seq( + errorText + "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0", + "ch.qos.logback" % "logback-classic" % "1.1.2") diff --git a/main/src/test/resources/error-format/3.sbt.txt b/main/src/test/resources/error-format/3.sbt.txt new file mode 100644 index 000000000..1b027e254 --- /dev/null +++ b/main/src/test/resources/error-format/3.sbt.txt @@ -0,0 +1,3 @@ +import sbt._ + +val s = \ No newline at end of file diff --git a/main/src/test/resources/new-format/1.sbt.txt b/main/src/test/resources/new-format/1.sbt.txt new file mode 100644 index 000000000..c7aacc410 --- /dev/null +++ b/main/src/test/resources/new-format/1.sbt.txt @@ -0,0 +1,19 @@ +scalaVersion := "2.10.2"; libraryDependencies += "org.scala-sbt" %% "sbinary" % "0.4.1" withSources() withJavadoc() +lazy val checkPom = taskKey[Unit]("check pom to ensure no sections are generated") +checkPom := { + val pomFile = makePom.value + + val pom = xml.XML.loadFile(pomFile) + val tpe = pom \\ "type" + + if(!tpe.isEmpty) + error("Expected no sections, got: " + tpe + " in \n\n" + pom) +} +val b = ( ) +val a = + + + + + diff --git a/main/src/test/resources/new-format/2.sbt.txt b/main/src/test/resources/new-format/2.sbt.txt new file mode 100644 index 000000000..899c65995 --- /dev/null +++ b/main/src/test/resources/new-format/2.sbt.txt @@ -0,0 +1,18 @@ +scalaVersion := "2.10.2"; name := "test" +libraryDependencies += "org.scala-sbt" %% "sbinary" % "0.4.1" withSources() withJavadoc() + +lazy val checkPom = taskKey[Unit]("check pom to ensure no sections are generated"); checkPom := { + val pomFile = makePom.value + val pom = xml.XML.loadFile(pomFile) + val tpe = pom \\ "type" + if(!tpe.isEmpty) + error("Expected no sections, got: " + tpe + " in \n\n" + pom) +} + +val b = ( ) +val a = + + + + + diff --git a/main/src/test/resources/new-format/3.sbt.txt b/main/src/test/resources/new-format/3.sbt.txt new file mode 100644 index 000000000..672319b8c --- /dev/null +++ b/main/src/test/resources/new-format/3.sbt.txt @@ -0,0 +1,7 @@ + +/* +OKOK +*/ + +val b = "*/" +val a = 4 \ No newline at end of file diff --git a/main/src/test/resources/old-format/1.sbt.txt b/main/src/test/resources/old-format/1.sbt.txt new file mode 100644 index 000000000..aa5bf9483 --- /dev/null +++ b/main/src/test/resources/old-format/1.sbt.txt @@ -0,0 +1,66 @@ +import SonatypeKeys._ + +xerial.sbt.Sonatype.sonatypeSettings + +organization := "me.benoitguigal" + +name := "twitter" + +version := "1.1-SNAPSHOT" + +scalaVersion := "2.10.2" + +resolvers += "spray repo" at "http://repo.spray.io" + +libraryDependencies ++= { + val sprayV = "1.2.1" + Seq( + "com.typesafe.akka" %% "akka-actor" % "2.2.3", + "joda-time" % "joda-time" % "2.3", + "org.joda" % "joda-convert" % "1.2", + "io.spray" % "spray-http" % sprayV, + "io.spray" % "spray-httpx" % sprayV, + "io.spray" % "spray-util" % sprayV, + "io.spray" % "spray-client" % sprayV, + "io.spray" % "spray-can" % sprayV, + "com.netflix.rxjava" % "rxjava-scala" % "0.19.6", + "io.spray" %% "spray-json" % "1.2.6", + "org.scalatest" % "scalatest_2.10" % "2.1.3" % "test", + "org.mockito" % "mockito-core" % "1.9.5" % "test") +} + +publishMavenStyle := true + +publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") +} + +pomIncludeRepository := { _ => false } + +pomExtra := ( + https://github.com/benoitguigal/twitter-spray + + + BSD-style + http://www.opensource.org/licenses/bsd-license.php + repo + + + + git@github.com:benoitguigal/twitter-spray.git + scm:git:git@github.com:benoitguigal/twitter-spray.git + + + + BGuigal + Benoit Guigal + http://benoitguigal.me + + + ) + + diff --git a/main/src/test/resources/old-format/10.sbt.txt b/main/src/test/resources/old-format/10.sbt.txt new file mode 100644 index 000000000..38ef793e2 --- /dev/null +++ b/main/src/test/resources/old-format/10.sbt.txt @@ -0,0 +1,81 @@ + +////////////////////////////////////// +// Root project settings +////////////////////////////////////// + +name := Common.PROJECT_NAME + +unidocSettings + +Common.commonSettings + +////////////////////////////////////// +// Subproject definitions +////////////////////////////////////// + +lazy val core = Common.subproject("core").dependsOn(util) + +lazy val util = Common.subproject("util") + +////////////////////////////////////// +// Common settings shared by all projects +////////////////////////////////////// + +version in ThisBuild := Common.PROJECT_VERSION + +organization in ThisBuild := Common.ORGANIZATION + +scalaVersion in ThisBuild := Common.SCALA_VERSION + +libraryDependencies in ThisBuild ++= Seq( + "org.scala-lang" % "scala-reflect" % Common.SCALA_VERSION, + "org.slf4j" % "slf4j-api" % Common.SLF4J_VERSION, + "ch.qos.logback" % "logback-classic" % Common.LOGBACK_VERSION % "runtime", + "org.scalatest" %% "scalatest" % Common.SCALATEST_VERSION % "test" + ) + +scalacOptions in (ThisBuild, Compile) ++= Seq( + "-unchecked", + "-deprecation", + "-feature" + ) + +parallelExecution in (ThisBuild, Test) := false + +fork in (ThisBuild, Test) := true + +////////////////////////////////////// +// Publishing settings +////////////////////////////////////// + +publishMavenStyle in (ThisBuild) := true + +pomIncludeRepository in (ThisBuild) := { _ => false } + +licenses in (ThisBuild) := Seq( + "BSD-style" -> url("http://opensource.org/licenses/BSD-2-Clause") + ) + +homepage in (ThisBuild) := Some(url("http://genslerappspod.github.io/scalavro/")) + +pomExtra in (ThisBuild) := ( + + git@github.com:GenslerAppsPod/scalavro.git + scm:git:git@github.com:GenslerAppsPod/scalavro.git + + + + ConnorDoyle + Connor Doyle + http://gensler.com + + + ) + +publishTo in (ThisBuild) <<= version { (v: String) => + val nexus = "https://oss.sonatype.org/" + if (v.trim.endsWith("SNAPSHOT")) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") + } diff --git a/main/src/test/resources/old-format/11.sbt.txt b/main/src/test/resources/old-format/11.sbt.txt new file mode 100644 index 000000000..a76615a42 --- /dev/null +++ b/main/src/test/resources/old-format/11.sbt.txt @@ -0,0 +1,52 @@ +name := "core" + +resolvers += "spray repo" at "http://repo.spray.io" + +libraryDependencies ++= Seq( + "com.github.scala-incubator.io" %% "scala-io-core" % "0.4.2", + "com.github.scala-incubator.io" %% "scala-io-file" % "0.4.2", + "com.typesafe.akka" %% "akka-actor" % "2.2.4", + "com.typesafe.akka" %% "akka-slf4j" % "2.2.4", + "com.typesafe.scala-logging" %% "scala-logging-slf4j" % "2.1.2", + "io.spray" % "spray-client" % "1.2.1", + "jline" % "jline" % "2.12", + "org.apache.curator" % "curator-recipes" % "2.4.2", + "org.apache.zookeeper" % "zookeeper" % "3.4.6", + "org.fusesource" % "sigar" % "1.6.4" classifier "native" classifier "", + "org.mozilla" % "rhino" % "1.7R4", + "org.reactivemongo" %% "play2-reactivemongo" % "0.10.2", + "org.reactivemongo" %% "reactivemongo" % "0.10.0", + "org.scalaz" %% "scalaz-core" % "7.0.6" +).map(_.exclude("org.slf4j", "slf4j-log4j12")) + +val copyNativeLibraries = taskKey[Set[File]]("Copy native libraries to native libraries directory") + +copyNativeLibraries := { + val cp = (managedClasspath in Runtime).value + // FIXME: Currently, only sigar has a native library. + // Extract this as a setting when more native libraries are added. + val nativeJarFile = cp.map(_.data).find(_.getName == "sigar-1.6.4-native.jar").get + val nativeLibrariesDirectory = target.value / "native_libraries" / (System.getProperty("sun.arch.data.model") + "bits") + IO.unzip(nativeJarFile, nativeLibrariesDirectory) +} + +run <<= (run in Runtime) dependsOn copyNativeLibraries + +(test in Test) <<= (test in Test) dependsOn copyNativeLibraries + +val toolsJar = if (System.getProperty("os.name") != "Mac OS X") { + Seq(Attributed.blank(file(System.getProperty("java.home").dropRight(3) + "lib/tools.jar"))) +} else { + Nil +} + +// adding the tools.jar to the unmanaged-jars seq +unmanagedJars in Compile ~= (toolsJar ++ _) + +lazy val root = (project in file(".")).enablePlugins(PlayScala) + +org.scalastyle.sbt.ScalastylePlugin.Settings + +scalariformSettings + +Common.settings diff --git a/main/src/test/resources/old-format/12.sbt.txt b/main/src/test/resources/old-format/12.sbt.txt new file mode 100644 index 000000000..eb3d1c1c4 --- /dev/null +++ b/main/src/test/resources/old-format/12.sbt.txt @@ -0,0 +1,55 @@ +name := "play-recaptcha" + +description := "Google reCAPTCHA integration for Play Framework" + +organization := "com.nappin" + +version := "0.9-SNAPSHOT" + +lazy val root = (project in file(".")).enablePlugins(PlayScala) + +crossScalaVersions := Seq("2.10.4", "2.11.1") + +libraryDependencies ++= Seq( + ws, + "org.mockito" % "mockito-core" % "1.+" % "test" +) + +// needed to publish to maven central +publishMavenStyle := true + +publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") +} + +publishArtifact in Test := false + +pomIncludeRepository := { _ => false } + +pomExtra := ( + http://chrisnappin.github.io/play-recaptcha + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + scm:git:git@github.com:chrisnappin/play-recaptcha.git + scm:git:git@github.com:chrisnappin/play-recaptcha.git + git@github.com:chrisnappin/play-recaptcha.git + + + + chrisnappin + Chris Nappin + chris@nappin.com + UTC + + ) + \ No newline at end of file diff --git a/main/src/test/resources/old-format/13.sbt.txt b/main/src/test/resources/old-format/13.sbt.txt new file mode 100644 index 000000000..12586ccb9 --- /dev/null +++ b/main/src/test/resources/old-format/13.sbt.txt @@ -0,0 +1,75 @@ +name := """FTL""" + +version := "1.0-SNAPSHOT" + +lazy val root = (project in file(".")).enablePlugins(PlayScala) + +scalaVersion := "2.11.1" + +resolvers += "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/" + +libraryDependencies ++= Seq( + jdbc, + anorm, + cache, + ws, + filters, + "org.webjars" % "bootstrap" % "3.0.2", + "com.typesafe.play.plugins" %% "play-plugins-util" % "2.3.0", + "com.typesafe.play.plugins" %% "play-plugins-mailer" % "2.3.0", + "org.mindrot" % "jbcrypt" % "0.3m", + "org.specs2" %% "specs2" % "2.3.12" % "test", + "org.mockito" % "mockito-all" % "1.9.5" % "test", + "org.reactivemongo" %% "play2-reactivemongo" % "0.11.0-SNAPSHOT", + "org.reactivemongo" %% "reactivemongo" % "0.11.0-SNAPSHOT" +) + +resolvers ++= Seq( + Resolver.typesafeRepo("releases") +) + +organization := "ws.securesocial" + +organizationName := "SecureSocial" + +organizationHomepage := Some(new URL("http://www.securesocial.ws")) + +publishMavenStyle := true + +publishArtifact in Test := false + +pomIncludeRepository := { _ => false } + +publishTo := { + val nexus = "https://oss.sonatype.org/" + if (version.value.trim.endsWith("SNAPSHOT")) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") +} + +startYear := Some(2012) + +description := "An authentication module for Play Framework applications supporting OAuth, OAuth2, OpenID, Username/Password and custom authentication schemes." + +licenses := Seq("The Apache Software License, Version 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt")) + +homepage := Some(url("http://www.securesocial.ws")) + +pomExtra := ( + + https://github.com/jaliss/securesocial + scm:git:git@github.com:jaliss/securesocial.git + scm:git:https://github.com/jaliss/securesocial.git + + + + jaliss + Jorge Aliss + jaliss [at] gmail.com + https://twitter.com/jaliss + + +) + +scalacOptions := Seq("-feature", "-deprecation") diff --git a/main/src/test/resources/old-format/14.sbt.txt b/main/src/test/resources/old-format/14.sbt.txt new file mode 100644 index 000000000..63c7182a0 --- /dev/null +++ b/main/src/test/resources/old-format/14.sbt.txt @@ -0,0 +1,41 @@ +name := "cms" + +organization := "org.qirx" + +scalaVersion := "2.11.1" + +resolvers += "Typesafe Releases" at "http://repo.typesafe.com/typesafe/releases/" + +libraryDependencies ++= Seq( + "com.typesafe.play" %% "play" % "2.3.0", + "com.typesafe.play" %% "play-test" % "2.3.0", + "com.typesafe.play" %% "play-json" % "2.3.0", + "org.qirx" %% "little-spec" % "0.4-SNAPSHOT" % "test", + "org.qirx" %% "little-spec-extra-documentation" % "0.4-SNAPSHOT" % "test" +) + +unmanagedSourceDirectories in Compile := Seq((scalaSource in Compile).value) + +unmanagedSourceDirectories in Test := Seq((scalaSource in Test).value) + +testFrameworks += new TestFramework("org.qirx.littlespec.sbt.TestFramework") + +val x = taskKey[Unit /* Geef niets terug */]("test") + +x := { + val cacheDir = (streams in x).value.cacheDirectory / "unzipped" + val dependencies = (externalDependencyClasspath in Compile).value + val possibleJar = dependencies + .map(a => a.data) + .filter(f => f.getName contains "play-json") + .headOption + possibleJar match { + case Some(file) => + val unzippedFiles = IO.unzip(from = file, toDirectory = cacheDir) + // use flatmap because relativize returns an option, which can be flattened + val names = unzippedFiles.flatMap(file => IO.relativize(base = cacheDir, file)) + println(s"Unzipped the following files in `$cacheDir`") + names.foreach(println) + case None => sys.error("Could not find jar") + } +} diff --git a/main/src/test/resources/old-format/15.sbt.txt b/main/src/test/resources/old-format/15.sbt.txt new file mode 100644 index 000000000..dce8e063b --- /dev/null +++ b/main/src/test/resources/old-format/15.sbt.txt @@ -0,0 +1,21 @@ + +name := "play-html-compressor" + +scalaVersion := "2.11.1" + +val pom = + git@github.com:mohiva/play-html-compressor.git + scm:git:git@github.com:mohiva/play-html-compressor.git + + + + akkie + Christian Kaps + http://mohiva.com + + + +publishMavenStyle := true + + + diff --git a/main/src/test/resources/old-format/16.sbt.txt b/main/src/test/resources/old-format/16.sbt.txt new file mode 100644 index 000000000..123e05ab4 --- /dev/null +++ b/main/src/test/resources/old-format/16.sbt.txt @@ -0,0 +1,141 @@ +import sbt._ +import AssemblyKeys._ +import aether.Aether._ + +name := "conjecture" + +version := "0.1.2-SNAPSHOT" + +organization := "com.etsy" + +scalaVersion := "2.9.3" + +sbtVersion := "0.12.1" + +scalacOptions ++= Seq("-unchecked", "-deprecation") + +compileOrder := CompileOrder.JavaThenScala + +javaHome := Some(file("/usr/java/latest")) + +publishArtifact in packageDoc := false + +resolvers ++= { + Seq( + "Concurrent Maven Repo" at "http://conjars.org/repo", + "cloudera" at "https://repository.cloudera.com/artifactory/cloudera-repos/" + ) +} + +libraryDependencies += "cascading" % "cascading-core" % "2.0.0" + +libraryDependencies += "cascading" % "cascading-local" % "2.0.0" exclude("com.google.guava", "guava") + +libraryDependencies += "cascading" % "cascading-hadoop" % "2.0.0" + +libraryDependencies += "cascading.kryo" % "cascading.kryo" % "0.4.6" + +libraryDependencies += "com.google.code.gson" % "gson" % "2.2.2" + +libraryDependencies += "com.twitter" % "maple" % "0.2.4" + +libraryDependencies += "com.twitter" % "algebird-core_2.9.2" % "0.1.12" + +libraryDependencies += "com.twitter" % "scalding-core_2.9.2" % "0.8.5" + +libraryDependencies += "commons-lang" % "commons-lang" % "2.4" + +libraryDependencies += "com.joestelmach" % "natty" % "0.7" + +libraryDependencies += "io.backchat.jerkson" % "jerkson_2.9.2" % "0.7.0" + +libraryDependencies += "com.google.guava" % "guava" % "13.0.1" + +libraryDependencies += "org.apache.commons" % "commons-math3" % "3.2" + +libraryDependencies += "org.apache.hadoop" % "hadoop-common" % "2.0.0-cdh4.1.1" exclude("commons-daemon", "commons-daemon") + +libraryDependencies += "org.apache.hadoop" % "hadoop-hdfs" % "2.0.0-cdh4.1.1" exclude("commons-daemon", "commons-daemon") + +libraryDependencies += "org.apache.hadoop" % "hadoop-tools" % "2.0.0-mr1-cdh4.1.1" exclude("commons-daemon", "commons-daemon") + +libraryDependencies += "net.sf.trove4j" % "trove4j" % "3.0.3" + +libraryDependencies += "com.esotericsoftware.kryo" % "kryo" % "2.21" + +libraryDependencies += "com.novocode" % "junit-interface" % "0.10" % "test" + +parallelExecution in Test := false + +publishArtifact in Test := false + +seq(assemblySettings: _*) + +publishTo <<= version { v : String => + val nexus = "https://oss.sonatype.org/" + if (v.trim.endsWith("SNAPSHOT")) { + Some("snapshots" at nexus + "content/repositories/snapshots") + } else { + Some("releases" at nexus + "service/local/staging/deploy/maven2") + } +} + +publishMavenStyle := true + +pomIncludeRepository := { x => false } + +pomExtra := ( + https://github.com/etsy/Conjecture + + + MIT License + http://opensource.org/licenses/MIT + repo + + + + git@github.com:etsy/Conjecture.git + scm:git:git@github.com:etsy/Conjecture.git + + + + jattenberg + Josh Attenberg + github.com/jattenberg + + + rjhall + Rob Hall + github.com/rjhall + + +) + + +credentials += Credentials(Path.userHome / ".sbt" / ".credentials") + + +seq(aetherPublishSettings: _*) + +pomIncludeRepository := { _ => false } + +// Uncomment if you don't want to run all the tests before building assembly +// test in assembly := {} + +// Janino includes a broken signature, and is not needed: +excludedJars in assembly <<= (fullClasspath in assembly) map { cp => + val excludes = Set("jsp-api-2.1-6.1.14.jar", "jsp-2.1-6.1.14.jar", + "jasper-compiler-5.5.12.jar", "janino-2.5.16.jar") + cp filter { jar => excludes(jar.data.getName)} +} + +// Some of these files have duplicates, let's ignore: +mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) => + { + case s if s.endsWith(".class") => MergeStrategy.last + case s if s.endsWith("project.clj") => MergeStrategy.concat + case s if s.endsWith(".html") => MergeStrategy.last + case s if s.contains("servlet") => MergeStrategy.last + case x => old(x) + } +} diff --git a/main/src/test/resources/old-format/17.sbt.txt b/main/src/test/resources/old-format/17.sbt.txt new file mode 100644 index 000000000..1ade452a0 --- /dev/null +++ b/main/src/test/resources/old-format/17.sbt.txt @@ -0,0 +1,52 @@ +name := "Programming Scala, Second Edition: Code examples" + +version := "2.0" + +organization := "org.programming-scala" + +scalaVersion := "2.11.2" + +// Build against several versions of Scala +crossScalaVersions := Seq("2.11.2", "2.10.4") + +// Scala 2.11 split the standard library into smaller components. +// The XML and parser combinator support are now separate jars. +// I use the if condition to conditionally add these extra dependencies. +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor" % "2.3.3", + "org.scalaz" %% "scalaz-core" % "7.0.6", + "org.scalacheck" %% "scalacheck" % "1.11.4" % "test", + "org.scalatest" %% "scalatest" % "2.2.0" % "test", + "org.specs2" %% "specs2" % "2.3.12" % "test", + // JUnit is used for some Java interop. examples. A driver for JUnit: + "junit" % "junit-dep" % "4.10" % "test", + "com.novocode" % "junit-interface" % "0.10" % "test" +) ++ ( + if (scalaVersion.value startsWith "2.11") + Seq( + // Could use this to get everything: + // "org.scala-lang.modules" %% "scala-library-all" % "2.11.1") + "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1", + "org.scala-lang.modules" %% "scala-xml" % "1.0.2") + else Seq.empty) + +val commonOptions = Seq( + "-encoding", "UTF-8", "-optimise", + "-deprecation", "-unchecked", "-feature", "-Xlint") +// "-explaintypes" - Use when you need more detailed messages for type errors. +// "-Yinline-warnings" - Warns if constructs have the @inline annotation, but +// inlining isn't possible. Can be more annoying than useful most of the time, +// but consider using it for performance critical code. + +// Options passed to the Scala and Java compilers: +scalacOptions <<= scalaVersion map { version: String => + if (version.startsWith("2.10")) commonOptions + else commonOptions ++ Seq("-Ywarn-infer-any") // Warn if "Any" is inferred +} + +javacOptions ++= Seq( + "-Xlint:unchecked", "-Xlint:deprecation") // Java 8: "-Xdiags:verbose") + +// Enable improved incremental compilation feature in 2.11.X. +// see http://www.scala-lang.org/news/2.11.1 +incOptions := incOptions.value.withNameHashing(true) diff --git a/main/src/test/resources/old-format/18.sbt.txt b/main/src/test/resources/old-format/18.sbt.txt new file mode 100644 index 000000000..1cc373cb9 --- /dev/null +++ b/main/src/test/resources/old-format/18.sbt.txt @@ -0,0 +1,93 @@ +// +// Copyright (c) 2013-2014 Alexey Aksenov ezh@ezh.msk.ru +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +ScriptedPlugin.scriptedSettings + +name := "sbt-osgi-manager" + +description := "OSGi development bridge based on Bndtools and Tycho." + +licenses := Seq("The Apache Software License, Version 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt")) + +organization := "org.digimead" + +organizationHomepage := Some(url("http://digimead.org")) + +homepage := Some(url("https://github.com/digimead/sbt-osgi-manager")) + +version <<= (baseDirectory) { (b) => scala.io.Source.fromFile(b / "version").mkString.trim } + +// There is no "-Xfatal-warnings" because we have cross compilation against different Scala versions +scalacOptions ++= Seq("-encoding", "UTF-8", "-unchecked", "-deprecation", "-Xcheckinit") + +// http://vanillajava.blogspot.ru/2012/02/using-java-7-to-target-much-older-jvms.html +javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation", "-source", "1.6", "-target", "1.6") + +javacOptions in doc := Seq("-source", "1.6") + +if (sys.env.contains("XBOOTCLASSPATH")) Seq(javacOptions += "-Xbootclasspath:" + sys.env("XBOOTCLASSPATH")) else Seq() + +sbtPlugin := true + +resourceGenerators in Compile <+= + (resourceManaged in Compile, name, version) map { (dir, n, v) => + val file = dir / "version-%s.properties".format(n) + val contents = "name=%s\nversion=%s\nbuild=%s\n".format(n, v, ((System.currentTimeMillis / 1000).toInt).toString) + IO.write(file, contents) + Seq(file) + } + +libraryDependencies ++= { + val mavenVersion = "3.0" // based on Tycho, MUST be the same + val mavenWagonVersion = "2.4" + val tychoVersion = "0.18.0" + val aetherAPIVersion = "1.7" // based on Tycho, MUST be the same + Seq( + "biz.aQute.bnd" % "bndlib" % "2.1.0", + "org.apache.felix" % "org.apache.felix.resolver" % "1.0.0", + "org.apache.maven" % "maven-aether-provider" % mavenVersion, + "org.apache.maven" % "maven-artifact" % mavenVersion, + "org.apache.maven" % "maven-compat" % mavenVersion, + "org.apache.maven" % "maven-core" % mavenVersion, + "org.apache.maven" % "maven-plugin-api" % mavenVersion, + "org.apache.maven" % "maven-embedder" % mavenVersion, // provide org.apache.maven.cli.MavenCli + "org.apache.maven.wagon" % "wagon-http" % mavenWagonVersion, // HTTP connector for remore repositories + "org.apache.maven.wagon" % "wagon-file" % mavenWagonVersion, // File connector for local repositories + "org.eclipse.tycho" % "tycho-core" % tychoVersion, + "org.eclipse.tycho" % "tycho-p2-facade" % tychoVersion, + "org.osgi" % "org.osgi.core" % "5.0.0", + "org.osgi" % "org.osgi.enterprise" % "5.0.0", + "org.sonatype.aether" % "aether-connector-wagon" % aetherAPIVersion + ) +} + +scriptedBufferLog := false + +resolvers ++= Seq( + "sbt-osgi-mananger-digimead-maven" at "http://storage.googleapis.com/maven.repository.digimead.org/", + Resolver.url("sbt-osgi-manager-typesafe-ivy-releases", url("http://repo.typesafe.com/typesafe/ivy-releases/"))(Resolver.defaultIvyPatterns), + Resolver.url("sbt-osgi-manager-typesafe-ivy-snapshots", url("http://repo.typesafe.com/typesafe/ivy-snapshots/"))(Resolver.defaultIvyPatterns), + Resolver.url("sbt-osgi-manager-typesafe-repository", url("http://typesafe.artifactoryonline.com/typesafe/ivy-releases/"))(Resolver.defaultIvyPatterns), + Resolver.url("sbt-osgi-manager-typesafe-shapshots", url("http://typesafe.artifactoryonline.com/typesafe/ivy-snapshots/"))(Resolver.defaultIvyPatterns)) + +sourceGenerators in Compile <+= (sbtVersion, sourceDirectory in Compile, sourceManaged in Compile) map { (v, sourceDirectory, sourceManaged) => + val interface = v.split("""\.""").take(2).mkString(".") + val source = sourceDirectory / ".." / "patch" / interface + val generated = (PathFinder(source) ***) x Path.rebase(source, sourceManaged) + IO.copy(generated, true, false) + generated.map(_._2).filter(_.getName endsWith ".scala") +} + +//logLevel := Level.Debug diff --git a/main/src/test/resources/old-format/19.sbt.txt b/main/src/test/resources/old-format/19.sbt.txt new file mode 100644 index 000000000..700cdfcd0 --- /dev/null +++ b/main/src/test/resources/old-format/19.sbt.txt @@ -0,0 +1,2 @@ +//import sbt._ +// val a = 3 \ No newline at end of file diff --git a/main/src/test/resources/old-format/2.sbt.txt b/main/src/test/resources/old-format/2.sbt.txt new file mode 100644 index 000000000..9ac625267 --- /dev/null +++ b/main/src/test/resources/old-format/2.sbt.txt @@ -0,0 +1,39 @@ +import AssemblyKeys._ + +name := "s3_website" + +version := "0.0.1" + +scalaVersion := "2.11.2" + +scalacOptions += "-feature" + +scalacOptions += "-language:implicitConversions" + +scalacOptions += "-language:postfixOps" + +scalacOptions += "-target:jvm-1.6" + +libraryDependencies += "org.yaml" % "snakeyaml" % "1.13" + +libraryDependencies += "org.jruby" % "jruby" % "1.7.11" + +libraryDependencies += "com.amazonaws" % "aws-java-sdk" % "1.7.7" + +libraryDependencies += "log4j" % "log4j" % "1.2.17" + +libraryDependencies += "commons-codec" % "commons-codec" % "1.9" + +libraryDependencies += "commons-io" % "commons-io" % "2.4" + +libraryDependencies += "org.apache.tika" % "tika-core" % "1.4" + +libraryDependencies += "com.lexicalscope.jewelcli" % "jewelcli" % "0.8.9" + +libraryDependencies += "org.specs2" %% "specs2" % "2.3.11" % "test" + +resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots" + +jarName in assembly := "s3_website.jar" + +test in assembly := {} diff --git a/main/src/test/resources/old-format/20.sbt.txt b/main/src/test/resources/old-format/20.sbt.txt new file mode 100644 index 000000000..33a460719 --- /dev/null +++ b/main/src/test/resources/old-format/20.sbt.txt @@ -0,0 +1,18 @@ + +/* */ +/** Project */ +name := "signal-collect-yarn" +/* */ + +/* +AAA +*/ +val a = 3 +// OK + +libraryDependencies ++= Seq("org.scala-lang" % "scala-compiler" % "2.10.4") map { + (dependency) =>{ + dependency + } /* OK */ +} + diff --git a/main/src/test/resources/old-format/3.sbt.txt b/main/src/test/resources/old-format/3.sbt.txt new file mode 100644 index 000000000..3775200ff --- /dev/null +++ b/main/src/test/resources/old-format/3.sbt.txt @@ -0,0 +1,51 @@ +parallelExecution in Global := false + +val commonSettings = Seq( + organization := "com.github.germanosin", + version := "1.0", + javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6"), + scalaVersion := "2.11.1" +) + +lazy val foldermessages = project.settings(commonSettings: _*) + .settings( + name := "play-foldermessages", + crossScalaVersions := Seq("2.10.4", "2.11.1"), + libraryDependencies += "com.typesafe.play" %% "play" % "2.3.2", + resolvers += "Typesafe Releases" at "http://repo.typesafe.com/typesafe/releases/", + publishMavenStyle := true, + publishTo := { + val nexus = "https://oss.sonatype.org" + if (isSnapshot.value) Some("snapshots" at s"$nexus/content/repositories/snapshots") + else Some("releases" at s"$nexus/service/local/staging/deploy/maven2") + }, + pomExtra := ( + http://github.com/germanosin/play-foldermessages + + + MIT License + http://opensource.org/licenses/mit-license.php + + + + git@github.com:germanosin/play-foldermessages.git + scm:git:git@github.com:germanosin/play-foldermessages.git + + + + germanosin + German Osin + + + ), + useGpg := true + ) + +lazy val sampleApp = Project("sample-app", file("sample-app")) + .settings(commonSettings: _*) + .enablePlugins(PlayScala) + .dependsOn(foldermessages) + +lazy val playFolderMessages = project.in(file(".")) + .settings(commonSettings: _*) + .aggregate(foldermessages,sampleApp) \ No newline at end of file diff --git a/main/src/test/resources/old-format/4.sbt.txt b/main/src/test/resources/old-format/4.sbt.txt new file mode 100644 index 000000000..b3cae41bd --- /dev/null +++ b/main/src/test/resources/old-format/4.sbt.txt @@ -0,0 +1,74 @@ +import AssemblyKeys._ +assemblySettings + +/** Project */ +name := "signal-collect-yarn" + +version := "1.0-SNAPSHOT" + +organization := "com.signalcollect" + +scalaVersion := "2.11.1" + +val hadoopVersion = "2.3.0" + +net.virtualvoid.sbt.graph.Plugin.graphSettings + +scalacOptions ++= Seq("-optimize", "-Yinline-warnings", "-feature", "-deprecation", "-Xelide-below", "INFO" ) + +EclipseKeys.createSrc := EclipseCreateSrc.Default + EclipseCreateSrc.Resource + +EclipseKeys.withSource := true + +parallelExecution in Test := false + +test in assembly := {} + +mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) => + { + case PathList("org", "apache", "hadoop", xs @ _*) => MergeStrategy.last + case PathList("org", "apache", "commons", "collections", xs @ _*) => MergeStrategy.last + case PathList("org", "objectweb", "asm", xs @ _*) => MergeStrategy.last + case PathList("com", "thoughtworks", xs @ _*) => MergeStrategy.last + case PathList("META-INF", "maven", xs @ _*) => MergeStrategy.last + case PathList("log4j.properties") => MergeStrategy.last + case x => old(x) + } +} + +excludedJars in assembly <<= (fullClasspath in assembly) map { cp => + cp filter { entry => + (entry.data.getName == "asm-3.2.jar" || + entry.data.getName == "asm-3.1.jar" + )} +} + +/** Dependencies */ +libraryDependencies ++= Seq( + "org.scala-lang.modules" %% "scala-async" % "0.9.1", + "org.scala-lang" % "scala-library" % "2.11.1" % "compile", + ("org.apache.hadoop" % "hadoop-common" % hadoopVersion % "compile"). + exclude("commons-beanutils", "commons-beanutils-core"), + "org.apache.hadoop" % "hadoop-yarn-common" % hadoopVersion % "compile", + ("org.apache.hadoop" % "hadoop-yarn-client" % hadoopVersion % "compile"). + exclude("hadoop-yarn-api", "org.apache.hadoop"), + "org.apache.hadoop" % "hadoop-yarn-server-resourcemanager" % hadoopVersion % "compile", + "org.apache.hadoop" % "hadoop-yarn-server-nodemanager" % hadoopVersion % "compile", + "org.apache.hadoop" % "minicluster" % "2.2.0" , + "org.apache.hadoop" % "hadoop-hdfs" % hadoopVersion, + "com.github.romix.akka" %% "akka-kryo-serialization-custom" % "0.3.5" % "compile", + "com.amazonaws" % "aws-java-sdk" % "1.7.12" % "compile", + "com.jcraft" % "jsch" % "0.1.51" % "compile", + "org.apache.commons" % "commons-compress" % "1.5" % "compile", + "log4j" % "log4j" % "1.2.17" % "compile", + "com.typesafe.scala-logging" %% "scala-logging-slf4j" % "2.1.2", + "com.typesafe.akka" % "akka-slf4j_2.11" % "2.3.4", + "junit" % "junit" % "4.8.2" % "test", + "org.specs2" %% "specs2" % "2.3.11" % "test", + "org.scalacheck" %% "scalacheck" % "1.11.3" % "test", + "org.scalatest" %% "scalatest" % "2.1.3" % "test", + "org.easymock" % "easymock" % "3.2" % "test", + "com.typesafe.akka" %% "akka-remote" % "2.3.4" force() +) + +resolvers += "Ifi Public" at "https://maven.ifi.uzh.ch/maven2/content/groups/public/" diff --git a/main/src/test/resources/old-format/5.sbt.txt b/main/src/test/resources/old-format/5.sbt.txt new file mode 100644 index 000000000..938093ca7 --- /dev/null +++ b/main/src/test/resources/old-format/5.sbt.txt @@ -0,0 +1,41 @@ +sbtPlugin := true + +name := "sbt-docker" + +organization := "se.marcuslonnberg" + +organizationHomepage := Some(url("https://github.com/marcuslonnberg")) + +version := "0.6.0-SNAPSHOT" + +libraryDependencies += "org.scalatest" %% "scalatest" % "2.1.0" % "test" + +licenses := Seq("MIT License" -> url("https://github.com/marcuslonnberg/sbt-docker/blob/master/LICENSE")) + +homepage := Some(url("https://github.com/marcuslonnberg/sbt-docker")) + +scmInfo := Some(ScmInfo(url("https://github.com/marcuslonnberg/sbt-docker"), "scm:git:git://github.com:marcuslonnberg/sbt-docker.git")) + +scalacOptions := Seq("-deprecation", "-unchecked", "-feature") + +publishMavenStyle := true + +publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") +} + +pomIncludeRepository := { _ => false} + +pomExtra := ( + + + marcuslonnberg + Marcus Lönnberg + http://marcuslonnberg.se + + +) diff --git a/main/src/test/resources/old-format/6.sbt.txt b/main/src/test/resources/old-format/6.sbt.txt new file mode 100644 index 000000000..b90ea4af2 --- /dev/null +++ b/main/src/test/resources/old-format/6.sbt.txt @@ -0,0 +1,67 @@ +organization := "com.sksamuel.akka" + +name := "akka-patterns" + +version := "0.11.0" + +scalaVersion := "2.11.2" + +crossScalaVersions := Seq("2.11.2", "2.10.4") + +scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8") + +javacOptions ++= Seq("-source", "1.6", "-target", "1.6") + +publishTo <<= version { + (v: String) => + val nexus = "https://oss.sonatype.org/" + if (v.trim.endsWith("SNAPSHOT")) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") +} + +publishMavenStyle := true + +publishArtifact in Test := false + +parallelExecution in Test := false + +credentials += Credentials(Path.userHome / ".ivy2" / ".credentials") + +libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.3.3" + +libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % "2.3.3" + +libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.7" + +libraryDependencies += "org.slf4j" % "log4j-over-slf4j" % "1.7.7" % "test" + +libraryDependencies += "log4j" % "log4j" % "1.2.17" % "test" + +libraryDependencies += "commons-io" % "commons-io" % "2.4" + +libraryDependencies += "org.mockito" % "mockito-all" % "1.9.5" % "test" + +libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.0" % "test" + +pomExtra := ( + https://github.com/sksamuel/akka-patterns + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + git@github.com:sksamuel/akka-patterns.git + scm:git@github.com:sksamuel/akka-patterns.git + + + + sksamuel + sksamuel + http://github.com/akka-patterns + + ) diff --git a/main/src/test/resources/old-format/7.sbt.txt b/main/src/test/resources/old-format/7.sbt.txt new file mode 100644 index 000000000..58165b827 --- /dev/null +++ b/main/src/test/resources/old-format/7.sbt.txt @@ -0,0 +1,160 @@ +name <<= submitProjectName(pname => "progfun-"+ pname) + +version := "1.0.0" + +scalaVersion := "2.10.2" + +scalacOptions ++= Seq("-deprecation", "-feature") + +libraryDependencies += "org.scalatest" %% "scalatest" % "1.9.1" % "test" + +libraryDependencies += "junit" % "junit" % "4.10" % "test" + +libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.10.2" + +// This setting defines the project to which a solution is submitted. When creating a +// handout, the 'createHandout' task will make sure that its value is correct. +submitProjectName := "suggestions" + +libraryDependencies <++= (currentProject) { c => + if (c.isEmpty || c == "quickcheck") Seq( + "org.scalacheck" %% "scalacheck" % "1.10.1" + ) + else Seq.empty +} + +libraryDependencies <++= (currentProject) { c => + if (c.isEmpty || c == "nodescala" || c == "suggestions") Seq( + "com.netflix.rxjava" % "rxjava-scala" % "0.15.0", + "org.json4s" % "json4s-native_2.10" % "3.2.5", + "org.scala-lang" % "scala-swing" % "2.10.3", + "net.databinder.dispatch" % "dispatch-core_2.10" % "0.11.0", + "org.scala-lang" % "scala-reflect" % "2.10.3", + "org.slf4j" % "slf4j-api" % "1.7.5", + "org.slf4j" % "slf4j-simple" % "1.7.5", + "com.squareup.retrofit" % "retrofit" % "1.0.0", + "org.scala-lang.modules" %% "scala-async" % "0.9.0-M2" + ) + else Seq.empty +} + +libraryDependencies <++= (currentProject) { c => + if (c.isEmpty || c == "actorbintree") Seq( + "com.typesafe.akka" %% "akka-actor" % "2.2.3", + "com.typesafe.akka" %% "akka-testkit" % "2.2.3" + ) + else Seq.empty +} + +// See documentation in ProgFunBuild.scala +projectDetailsMap := { +val currentCourseId = "reactive-001" +Map( + "example" -> ProjectDetails( + packageName = "example", + assignmentPartId = "fTzFogNl", + maxScore = 10d, + styleScoreRatio = 0.2, + courseId=currentCourseId), + "recfun" -> ProjectDetails( + packageName = "recfun", + assignmentPartId = "3Rarn9Ki", + maxScore = 10d, + styleScoreRatio = 0.2, + courseId=currentCourseId), + "funsets" -> ProjectDetails( + packageName = "funsets", + assignmentPartId = "fBXOL6Rd", + maxScore = 10d, + styleScoreRatio = 0.2, + courseId=currentCourseId), + "objsets" -> ProjectDetails( + packageName = "objsets", + assignmentPartId = "05dMMEz7", + maxScore = 10d, + styleScoreRatio = 0.2, + courseId=currentCourseId), + "patmat" -> ProjectDetails( + packageName = "patmat", + assignmentPartId = "4gPmpcif", + maxScore = 10d, + styleScoreRatio = 0.2, + courseId=currentCourseId), + "forcomp" -> ProjectDetails( + packageName = "forcomp", + assignmentPartId = "fG2oZGIO", + maxScore = 10d, + styleScoreRatio = 0.2, + courseId=currentCourseId), + "streams" -> ProjectDetails( + packageName = "streams", + assignmentPartId = "DWKgCFCi", + maxScore = 10d, + styleScoreRatio = 0.2, + courseId=currentCourseId), + "quickcheck" -> ProjectDetails( + packageName = "quickcheck", + assignmentPartId = "02Vi5q7m", + maxScore = 10d, + styleScoreRatio = 0.0, + courseId=currentCourseId), + "simulations" -> ProjectDetails( + packageName = "simulations", + assignmentPartId = "pA3TAeu1", + maxScore = 10d, + styleScoreRatio = 0.0, + courseId=currentCourseId), + "nodescala" -> ProjectDetails( + packageName = "nodescala", + assignmentPartId = "RvoTAbRy", + maxScore = 10d, + styleScoreRatio = 0.0, + courseId=currentCourseId), + "suggestions" -> ProjectDetails( + packageName = "suggestions", + assignmentPartId = "rLLdQLGN", + maxScore = 10d, + styleScoreRatio = 0.0, + courseId=currentCourseId), + "actorbintree" -> ProjectDetails( + packageName = "actorbintree", + assignmentPartId = "VxIlIKoW", + maxScore = 10d, + styleScoreRatio = 0.0, + courseId=currentCourseId) +)} + +// Files that we hand out to the students +handoutFiles <<= (baseDirectory, projectDetailsMap, commonSourcePackages) map { (basedir, detailsMap, commonSrcs) => + (projectName: String) => { + val details = detailsMap.getOrElse(projectName, sys.error("Unknown project name: "+ projectName)) + val commonFiles = (PathFinder.empty /: commonSrcs)((files, pkg) => + files +++ (basedir / "src" / "main" / "scala" / pkg ** "*.scala") + ) + (basedir / "src" / "main" / "scala" / details.packageName ** "*.scala") +++ + commonFiles +++ + (basedir / "src" / "main" / "resources" / details.packageName ** "*") +++ + (basedir / "src" / "test" / "scala" / details.packageName ** "*.scala") +++ + (basedir / "build.sbt") +++ + (basedir / "project" / "build.properties") +++ + (basedir / "project" ** ("*.scala" || "*.sbt")) +++ + (basedir / "project" / "scalastyle_config.xml") +++ + (basedir / "project" / "scalastyle_config_reactive.xml") +++ + (basedir / "lib_managed" ** "*.jar") +++ + (basedir * (".classpath" || ".project")) +++ + (basedir / ".settings" / "org.scala-ide.sdt.core.prefs") + } +} + +// This setting allows to restrict the source files that are compiled and tested +// to one specific project. It should be either the empty string, in which case all +// projects are included, or one of the project names from the projectDetailsMap. +currentProject := "" + +// Packages in src/main/scala that are used in every project. Included in every +// handout, submission. +commonSourcePackages += "common" + +// Packages in src/test/scala that are used for grading projects. Always included +// compiling tests, grading a project. +gradingTestPackages += "grading" diff --git a/main/src/test/resources/old-format/8.sbt.txt b/main/src/test/resources/old-format/8.sbt.txt new file mode 100644 index 000000000..1b7b57283 --- /dev/null +++ b/main/src/test/resources/old-format/8.sbt.txt @@ -0,0 +1,55 @@ +name := "apidoc" + +scalaVersion in ThisBuild := "2.11.1" + +lazy val core = project + .in(file("core")) + .settings(commonSettings: _*) + .settings( + // play-json needs this to resolve correctly when not using Gilt's internal mirrors + resolvers += "Typesafe Maven Repository" at "http://repo.typesafe.com/typesafe/maven-releases/", + version := "1.0-SNAPSHOT", + libraryDependencies ++= Seq( + "com.typesafe.play" %% "play-json" % "2.3.0" + ) + ) + +lazy val api = project + .in(file("api")) + .dependsOn(core) + .aggregate(core) + .enablePlugins(PlayScala) + .settings(commonSettings: _*) + .settings( + version := "1.0-SNAPSHOT", + libraryDependencies ++= Seq( + jdbc, + anorm, + "org.postgresql" % "postgresql" % "9.3-1101-jdbc4", + "org.mindrot" % "jbcrypt" % "0.3m" + ) + ) + +lazy val www = project + .in(file("www")) + .dependsOn(core) + .aggregate(core) + .enablePlugins(PlayScala) + .settings(commonSettings: _*) + .settings( + version := "1.0-SNAPSHOT", + libraryDependencies ++= Seq( + ws + ) + ) + +lazy val commonSettings: Seq[Setting[_]] = Seq( + name <<= name("apidoc-" + _), + libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % "2.2.0" % "test" + ), + scalacOptions += "-feature" +) ++ instrumentSettings ++ Seq(ScoverageKeys.highlighting := true) + + + diff --git a/main/src/test/resources/old-format/9.sbt.txt b/main/src/test/resources/old-format/9.sbt.txt new file mode 100644 index 000000000..b54ccbf27 --- /dev/null +++ b/main/src/test/resources/old-format/9.sbt.txt @@ -0,0 +1,40 @@ +organization := "com.typesafe" + +name := "jse" + +version := "1.0.1-SNAPSHOT" + +scalaVersion := "2.10.4" + +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor" % "2.3.2", + "com.typesafe.akka" %% "akka-contrib" % "2.3.2", + "io.apigee.trireme" % "trireme-core" % "0.8.0", + "io.apigee.trireme" % "trireme-node10src" % "0.8.0", + "io.spray" %% "spray-json" % "1.2.6", + "org.slf4j" % "slf4j-simple" % "1.7.7", + "org.specs2" %% "specs2" % "2.3.11" % "test", + "junit" % "junit" % "4.11" % "test", + "com.typesafe.akka" %% "akka-testkit" % "2.3.2" % "test" +) + +resolvers ++= Seq( + Resolver.sonatypeRepo("snapshots"), + "Typesafe Releases Repository" at "http://repo.typesafe.com/typesafe/releases/" +) + +publishTo := { + val typesafe = "http://private-repo.typesafe.com/typesafe/" + val (name, url) = if (isSnapshot.value) + ("sbt-plugin-snapshots", typesafe + "maven-snapshots") + else + ("sbt-plugin-releases", typesafe + "maven-releases") + Some(Resolver.url(name, new URL(url))) +} + +lazy val root = project in file(".") + +lazy val `js-engine-tester` = project.dependsOn(root) + +// Somehow required to get a js engine in tests (https://github.com/sbt/sbt/issues/1214) +fork in Test := true diff --git a/main/src/test/scala/PluginCommandTest.scala b/main/src/test/scala/PluginCommandTest.scala index c118a5232..322bc08aa 100644 --- a/main/src/test/scala/PluginCommandTest.scala +++ b/main/src/test/scala/PluginCommandTest.scala @@ -8,7 +8,7 @@ object PluginCommandTestPlugin0 extends AutoPlugin package subpackage { -object PluginCommandTestPlugin1 extends AutoPlugin + object PluginCommandTestPlugin1 extends AutoPlugin } diff --git a/main/src/test/scala/sbt/AbstractSpec.scala b/main/src/test/scala/sbt/AbstractSpec.scala new file mode 100644 index 000000000..28551eb74 --- /dev/null +++ b/main/src/test/scala/sbt/AbstractSpec.scala @@ -0,0 +1,5 @@ +package sbt + +import org.specs2.mutable._ + +trait AbstractSpec extends Specification with SplitExpression \ No newline at end of file diff --git a/main/src/test/scala/sbt/CheckIfParsedSpec.scala b/main/src/test/scala/sbt/CheckIfParsedSpec.scala new file mode 100644 index 000000000..45f9c5c1a --- /dev/null +++ b/main/src/test/scala/sbt/CheckIfParsedSpec.scala @@ -0,0 +1,36 @@ +package sbt + +abstract class CheckIfParsedSpec(implicit val splitter: SplitExpressions.SplitExpression = EvaluateConfigurations.splitExpressions) extends AbstractSpec { + + this.getClass.getName should { + + "Parse sbt file " in { + foreach(files) { + case (content, description, nonEmptyImports, nonEmptyStatements) => + println(s"""${getClass.getSimpleName}: "$description" """) + val (imports, statements) = split(content) + + statements.nonEmpty must be_==(nonEmptyStatements) + // orPending(s"""$description + // |***${shouldContains(nonEmptyStatements)} statements*** + // |$content """.stripMargin) + + imports.nonEmpty must be_==(nonEmptyImports) + // orPending(s"""$description + // |***${shouldContains(nonEmptyImports)} imports*** + // |$content """.stripMargin) + } + } + } + + // private def shouldContains(b: Boolean) = s"""Should ${ + // if (b) { + // "contain" + // } else { + // "not contain" + // } + // }""" + + protected def files: Seq[(String, String, Boolean, Boolean)] + +} diff --git a/main/src/test/scala/sbt/CommentedXmlSpec.scala b/main/src/test/scala/sbt/CommentedXmlSpec.scala new file mode 100644 index 000000000..6ddb30ed6 --- /dev/null +++ b/main/src/test/scala/sbt/CommentedXmlSpec.scala @@ -0,0 +1,67 @@ +package sbt + +class CommentedXmlSpec extends CheckIfParsedSpec { + + override protected def files: Seq[(String, String, Boolean, Boolean)] = Seq( + ( + s"""| + |val pom = "" + | + |val aaa= git@github.com:mohiva/play.git + | ewrer + | + | + |val tra = "" + | + """.stripMargin, "Xml in string", false, true), + (""" + |val scmpom = taskKey[xml.NodeBuffer]("Node buffer") + | + |scmpom := + | git@github.com:mohiva/play-html-compressor.git + | scm:git:git@github.com:mohiva/play-html-compressor.git + | + | + | + | akkie + | Christian Kaps + | http://mohiva.com + | + | + | // + | + | + |publishMavenStyle := true + | + """.stripMargin, "Wrong Commented xml ", false, true), + (""" + |val scmpom = taskKey[xml.NodeBuffer]("Node buffer") + | + |scmpom := + | git@github.com:mohiva/play-html-compressor.git + | scm:git:git@github.com:mohiva/play-html-compressor.git + | + | + | + | akkie + | Christian Kaps + | http://mohiva.com + | + | + | // + | // + | + |publishMavenStyle := true + | + """.stripMargin, "Commented xml ", false, true), + (""" + |import sbt._ + | + |// + """.stripMargin, "Xml in comment2", false, false) + + ) +} diff --git a/main/src/test/scala/sbt/EmbeddedXmlSpec.scala b/main/src/test/scala/sbt/EmbeddedXmlSpec.scala new file mode 100644 index 000000000..4c207179f --- /dev/null +++ b/main/src/test/scala/sbt/EmbeddedXmlSpec.scala @@ -0,0 +1,143 @@ +package sbt + +class EmbeddedXmlSpec extends CheckIfParsedSpec { + + "File with xml content " should { + + "Handle last xml part" in { + val errorLine = """4.0""" + val buildSbt = s"""| + | + |name := "play-html-compressor" + | + |scalaVersion := "2.11.1" + | + |val pom = + |git@github.com:mhiva/play-html-compressor.git + |scm:git:git@github.com:mohiva/play-html-compressor.git + | + | + | + | akkie + | Christian Kaps + | http://mohiva.com + | + | + |$errorLine + | + |""".stripMargin + + split(buildSbt) must throwA[MessageOnlyException].like { + case exception => + val index = buildSbt.lines.indexWhere(line => line.contains(errorLine)) + 1 + val numberRegex = """(\d+)""".r + val message = exception.getMessage + val list = numberRegex.findAllIn(message).toList + list must contain(index.toString) + } + + } + + } + + protected val files = Seq( + (""" + |val p = + """.stripMargin, "Xml modified closing tag at end of file", false, true), + (""" + |val p = + """.stripMargin, "Xml at end of file", false, true), + ("""| + | + |name := "play-html-compressor" + | + |scalaVersion := "2.11.1" + | + |val lll = "" + | + |val not = "" + | + |val aaa = "ass/>" + | + |val pom = "" + | + |val aaa= git@github.com:mohiva/play-html-compressor.git + | scm:git:git@github.com:mohiva/play-html-compressor.git + | + | + | + | akkie + | Christian Kaps + | http://mohiva.com + | + | + | 4.0 + | + |publishMavenStyle := true + | + |val anotherXml = + | content + | + | + | + | + |val tra = "" + | + """.stripMargin, "Xml in string", false, true), + ("""| + | + |name := "play-html-compressor" + | + |scalaVersion := "2.11.1" + | + |val ok = + | + |val pom = + |git@github.com:mhiva/play-html-compressor.git + | scm:git:git@github.com:mohiva/play-html-compressor.git + | + | + | + |akkie + |Christian Kaps + |http://mohiva.com + | + | + |4.0 + | + |publishMavenStyle := true + | + |val anotherXml = + | content + | + | + | + | + | """.stripMargin, "Xml with attributes", false, true), + ( + """ + |scalaVersion := "2.10.2" + | + |libraryDependencies += "org.scala-sbt" %% "sbinary" % "0.4.1" withSources() withJavadoc() + | + |lazy val checkPom = taskKey[Unit]("check pom to ensure no sections are generated") + | + |checkPom := { + | val pomFile = makePom.value + | val pom = xml.XML.loadFile(pomFile) + | val tpe = pom \\ "type" + | if(!tpe.isEmpty) + | error("Expected no sections, got: " + tpe + " in \n\n" + pom) + |} + | + | + |val a = + | + | + | + | + | + """.stripMargin, "xml with blank line", false, true) + ) + +} diff --git a/main/src/test/scala/sbt/ErrorSpec.scala b/main/src/test/scala/sbt/ErrorSpec.scala new file mode 100644 index 000000000..15737f47c --- /dev/null +++ b/main/src/test/scala/sbt/ErrorSpec.scala @@ -0,0 +1,61 @@ +package sbt + +import java.io.File + +import org.scalacheck.Gen._ +import org.scalacheck.Prop._ +import org.specs2.ScalaCheck + +import scala.io.Source + +class ErrorSpec extends AbstractSpec with ScalaCheck { + implicit val splitter: SplitExpressions.SplitExpression = EvaluateConfigurations.splitExpressions + + "Parser " should { + + "contains file name and line number" in { + val rootPath = getClass.getResource("").getPath + "../error-format/" + println(s"Reading files from: $rootPath") + foreach(new File(rootPath).listFiles) { file => + print(s"Processing ${file.getName}: ") + val buildSbt = Source.fromFile(file).getLines().mkString("\n") + SplitExpressionsNoBlankies(file, buildSbt.lines.toSeq) must throwA[MessageOnlyException].like { + case exp => + val message = exp.getMessage + println(s"${exp.getMessage}") + message must contain(file.getName) + } + containsLineNumber(buildSbt) + } + } + + "handle wrong parsing " in { + val buildSbt = + """ + |libraryDependencies ++= Seq("a" % "b" % "2") map { + |(dependency) =>{ + | dependency + | } /* */ // + |} + """.stripMargin + BugInParser.tryWithNextStatement(buildSbt, buildSbt.length, 2, "fake.txt", new MessageOnlyException("fake")) must throwA[MessageOnlyException] + } + } + + private def containsLineNumber(buildSbt: String) = { + try { + split(buildSbt) + throw new IllegalStateException(s"${classOf[MessageOnlyException].getName} expected") + } catch { + case exception: MessageOnlyException => + val error = exception.getMessage + """(\d+)""".r.findFirstIn(error) match { + case Some(x) => + true + case None => + println(s"Number not found in $error") + false + } + } + } +} diff --git a/main/src/test/scala/sbt/EvaluateConfigurationsOriginal.scala b/main/src/test/scala/sbt/EvaluateConfigurationsOriginal.scala new file mode 100644 index 000000000..dfa3a2420 --- /dev/null +++ b/main/src/test/scala/sbt/EvaluateConfigurationsOriginal.scala @@ -0,0 +1,39 @@ +package sbt + +import java.io.File + +import scala.annotation.tailrec + +object EvaluateConfigurationsOriginal { + + private[this] def isSpace = (c: Char) => Character isWhitespace c + private[this] def fstS(f: String => Boolean): ((String, Int)) => Boolean = { case (s, i) => f(s) } + private[this] def firstNonSpaceIs(lit: String) = (_: String).view.dropWhile(isSpace).startsWith(lit) + private[this] def or[A](a: A => Boolean, b: A => Boolean): A => Boolean = in => a(in) || b(in) + + def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = + { + val blank = (_: String).forall(isSpace) + val isImport = firstNonSpaceIs("import ") + val comment = firstNonSpaceIs("//") + val blankOrComment = or(blank, comment) + val importOrBlank = fstS(or(blankOrComment, isImport)) + + val (imports, settings) = lines.zipWithIndex span importOrBlank + (imports filterNot fstS(blankOrComment), groupedLines(settings, blank, blankOrComment)) + } + + def groupedLines(lines: Seq[(String, Int)], delimiter: String => Boolean, skipInitial: String => Boolean): Seq[(String, LineRange)] = + { + val fdelim = fstS(delimiter) + @tailrec def group0(lines: Seq[(String, Int)], accum: Seq[(String, LineRange)]): Seq[(String, LineRange)] = + if (lines.isEmpty) accum.reverse + else { + val start = lines dropWhile fstS(skipInitial) + val (next, tail) = start.span { case (s, _) => !delimiter(s) } + val grouped = if (next.isEmpty) accum else (next.map(_._1).mkString("\n"), LineRange(next.head._2, next.last._2 + 1)) +: accum + group0(tail, grouped) + } + group0(lines, Nil) + } +} \ No newline at end of file diff --git a/main/src/test/scala/sbt/NewFormatSpec.scala b/main/src/test/scala/sbt/NewFormatSpec.scala new file mode 100644 index 000000000..f9f447b50 --- /dev/null +++ b/main/src/test/scala/sbt/NewFormatSpec.scala @@ -0,0 +1,27 @@ +package sbt + +import java.io.File +import scala.io.Source + +class NewFormatSpec extends AbstractSpec { + implicit val splitter: SplitExpressions.SplitExpression = EvaluateConfigurations.splitExpressions + + "New Format " should { + "Handle lines " in { + val rootPath = getClass.getResource("").getPath + "../new-format/" + println(s"Reading files from: $rootPath") + val allFiles = new File(rootPath).listFiles.toList + foreach(allFiles) { + path => + println(s"$path") + val lines = Source.fromFile(path).getLines().toList + val (_, statements) = splitter(path, lines) + statements.nonEmpty must be_==(true) + // orPending(s""" + // |***should contains statements*** + // |$lines """.stripMargin) + } + } + } + +} diff --git a/main/src/test/scala/sbt/SplitExpressions.scala b/main/src/test/scala/sbt/SplitExpressions.scala new file mode 100644 index 000000000..1c08ac9ab --- /dev/null +++ b/main/src/test/scala/sbt/SplitExpressions.scala @@ -0,0 +1,7 @@ +package sbt + +import java.io.File + +object SplitExpressions { + type SplitExpression = (File, Seq[String]) => (Seq[(String, Int)], Seq[(String, LineRange)]) +} diff --git a/main/src/test/scala/sbt/SplitExpressionsBehavior.scala b/main/src/test/scala/sbt/SplitExpressionsBehavior.scala new file mode 100644 index 000000000..c44f182e0 --- /dev/null +++ b/main/src/test/scala/sbt/SplitExpressionsBehavior.scala @@ -0,0 +1,91 @@ +package sbt + +import org.specs2.mutable.SpecificationLike + +trait SplitExpression { + def split(s: String)(implicit splitter: SplitExpressions.SplitExpression) = splitter(null, s.split("\n").toSeq) +} + +trait SplitExpressionsBehavior extends SplitExpression { + this: SpecificationLike => + + def oldExpressionsSplitter(implicit splitter: SplitExpressions.SplitExpression) { + + "parse a simple setting" in { + val (imports, settingsAndDefs) = split("""version := "1.0"""") + settingsAndDefs.head._1 === """version := "1.0"""" + + imports.isEmpty should beTrue + settingsAndDefs.isEmpty should beFalse + } + + "parse a config containing a single import" in { + val (imports, settingsAndDefs) = split("""import foo.Bar""") + imports.isEmpty should beFalse + settingsAndDefs.isEmpty should beTrue + } + + "parse a config containing two imports and a setting" in { + val (imports, settingsAndDefs) = split( + """import foo.Bar + import foo.Bar + + version := "1.0" + """.stripMargin) + imports.size === 2 + settingsAndDefs.size === 1 + } + + "parse a config containgn a def" in { + val (imports, settingsAndDefs) = split("""def foo(x: Int) = { + x + 1 +}""") + imports.isEmpty should beTrue + settingsAndDefs.isEmpty should beFalse + } + + "parse a config containgn a val" in { + val (imports, settingsAndDefs) = split("""val answer = 42""") + imports.isEmpty should beTrue + settingsAndDefs.isEmpty should beFalse + } + + "parse a config containgn a lazy val" in { + val (imports, settingsAndDefs) = split("""lazy val root = (project in file(".")).enablePlugins­(PlayScala)""") + imports.isEmpty should beTrue + settingsAndDefs.isEmpty should beFalse + } + + } + + def newExpressionsSplitter(implicit splitter: SplitExpressions.SplitExpression) { + + "parse a two settings without intervening blank line" in { + val (imports, settings) = split("""version := "1.0" +scalaVersion := "2.10.4"""") + + imports.isEmpty should beTrue + settings.size === 2 + } + + "parse a setting and val without intervening blank line" in { + val (imports, settings) = split("""version := "1.0" +lazy val root = (project in file(".")).enablePlugins­(PlayScala)""") + + imports.isEmpty should beTrue + settings.size === 2 + } + + "parse a config containing two imports and a setting with no blank line" in { + val (imports, settingsAndDefs) = split( + """import foo.Bar + import foo.Bar + version := "1.0" + """.stripMargin) + imports.size === 2 + settingsAndDefs.size === 1 + } + + } + +} \ No newline at end of file diff --git a/main/src/test/scala/sbt/SplitExpressionsFilesTest.scala b/main/src/test/scala/sbt/SplitExpressionsFilesTest.scala new file mode 100644 index 000000000..9116e4721 --- /dev/null +++ b/main/src/test/scala/sbt/SplitExpressionsFilesTest.scala @@ -0,0 +1,150 @@ +package sbt + +import java.io.File + +import org.specs2.mutable.Specification + +import scala.annotation.tailrec +import scala.io.Source +import scala.tools.reflect.ToolBoxError + +class SplitExpressionsFilesTest extends AbstractSplitExpressionsFilesTest("../old-format/") + +abstract class AbstractSplitExpressionsFilesTest(pathName: String) extends Specification { + + case class SplitterComparison(oldSplitterResult: util.Try[(Seq[(String, Int)], Seq[LineRange])], newSplitterResult: util.Try[(Seq[(String, Int)], Seq[LineRange])]) + + val oldSplitter: SplitExpressions.SplitExpression = EvaluateConfigurationsOriginal.splitExpressions + val newSplitter: SplitExpressions.SplitExpression = EvaluateConfigurations.splitExpressions + + final val REVERTED_LINES = true + final val START_COMMENT = "/*" + final val END_COMMENT = START_COMMENT.reverse + + s"$getClass " should { + "split whole sbt files" in { + val rootPath = getClass.getResource("").getPath + pathName + println(s"Reading files from: $rootPath") + val allFiles = new File(rootPath).listFiles.toList + + val results = for { + path <- allFiles + lines = Source.fromFile(path).getLines().toList + comparison = SplitterComparison(splitLines(path, oldSplitter, lines), splitLines(path, newSplitter, lines)) + } yield path -> comparison + + printResults(results) + + val validResults = results.collect { + case (path, SplitterComparison(util.Success(oldRes), util.Success(newRes))) if oldRes == newRes => path + } + + validResults.length must be_==(results.length) + } + } + + def removeCommentFromStatement(statement: String, lineRange: LineRange): Option[LineRange] = { + val lines = statement.lines.toList + val optionStatements = removeSlashAsterisk(lines, lineRange, !REVERTED_LINES) match { + case Some((st, lr)) => + removeDoubleSlash(st, lr) + case _ => None + } + optionStatements.map(t => t._2) + } + + @tailrec + private def removeSlashAsterisk(statements: Seq[String], lineRange: LineRange, reverted: Boolean): Option[(Seq[String], LineRange)] = + statements match { + case statement +: _ => + val openSlashAsteriskIndex = statement.indexOf(START_COMMENT, 0) + if (openSlashAsteriskIndex == -1 || statement.substring(0, openSlashAsteriskIndex).trim.nonEmpty) { + Some((statements, lineRange)) + } else { + val closeSlashAsteriskLine = statements.indexWhere(s => s.contains(END_COMMENT)) + if (closeSlashAsteriskLine == -1) { + Some((statements, lineRange)) + } else { + val newLineRange = if (reverted) { + lineRange.copy(end = lineRange.end - closeSlashAsteriskLine - 1) + } else { + lineRange.copy(start = lineRange.start + closeSlashAsteriskLine + 1) + } + removeSlashAsterisk(statements.drop(closeSlashAsteriskLine + 1), newLineRange, reverted) + } + } + case _ => + None + } + + /** + * Remove // and /* */ + * @param statements - lines + * @param lineRange - LineRange + * @return (lines,lineRange) without comments + */ + def removeDoubleSlash(statements: Seq[String], lineRange: LineRange): Option[(Seq[String], LineRange)] = { + + @tailrec + def removeDoubleSlashReversed(lines: Seq[String], lineRange: LineRange): Option[(Seq[String], LineRange)] = + lines match { + case statement +: _ => + val doubleSlashIndex = statement.indexOf("//") + if (doubleSlashIndex == -1 || statement.substring(0, doubleSlashIndex).trim.nonEmpty) { + removeSlashAsterisk(lines, lineRange, REVERTED_LINES) match { + case some @ Some((s, ln)) if ln == lineRange => + some + case Some((s, ln)) => + removeDoubleSlashReversed(s, ln) + case _ => None + } + + } else { + removeDoubleSlashReversed(lines.tail, lineRange.copy(end = lineRange.end - 1)) + } + case _ => + None + } + removeDoubleSlashReversed(statements.reverse, lineRange).map(t => (t._1.reverse, t._2)) + } + + def splitLines(file: File, splitter: SplitExpressions.SplitExpression, lines: List[String]): util.Try[(Seq[(String, Int)], Seq[LineRange])] = { + try { + val (imports, settingsAndDefs) = splitter(file, lines) + + //TODO: Return actual contents (after making both splitter... + //TODO: ...implementations return CharRanges instead of LineRanges) + val settingsAndDefWithoutComments = settingsAndDefs.flatMap(t => removeCommentFromStatement(t._1, t._2)) + util.Success((imports.map(imp => (imp._1.trim, imp._2)), settingsAndDefWithoutComments)) + } catch { + case e: ToolBoxError => + util.Failure(e) + case e: Throwable => + util.Failure(e) + } + } + + def printResults(results: List[(File, SplitterComparison)]) = { + for ((file, comparison) <- results) { + val fileName = file.getName + comparison match { + case SplitterComparison(util.Failure(ex), _) => + println(s"In file: $fileName, old splitter failed. ${ex.toString}") + case SplitterComparison(_, util.Failure(ex)) => + println(s"In file: $fileName, new splitter failed. ${ex.toString}") + case SplitterComparison(util.Success(resultOld), util.Success(resultNew)) => + if (resultOld == resultNew) { + println(s"In file: $fileName, same results (imports, settings): $resultOld") + } else { + println( + s"""In file: $fileName, results differ: + |resultOld: + |$resultOld + |resultNew: + |$resultNew""".stripMargin) + } + } + + } + } +} \ No newline at end of file diff --git a/main/src/test/scala/sbt/SplitExpressionsTest.scala b/main/src/test/scala/sbt/SplitExpressionsTest.scala new file mode 100644 index 000000000..bb172a2e4 --- /dev/null +++ b/main/src/test/scala/sbt/SplitExpressionsTest.scala @@ -0,0 +1,13 @@ +package sbt + +import org.specs2.mutable.Specification + +class SplitExpressionsTest extends Specification with SplitExpressionsBehavior { + + "EvaluateConfigurationsOriginal" should oldExpressionsSplitter(EvaluateConfigurationsOriginal.splitExpressions) + + "EvaluateConfigurations" should oldExpressionsSplitter(EvaluateConfigurations.splitExpressions) + + "EvaluateConfigurations" should newExpressionsSplitter(EvaluateConfigurations.splitExpressions) + +} \ No newline at end of file From eab7049479a5cab916169aebd21377ea762d44bc Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Sat, 6 Sep 2014 00:12:01 +0200 Subject: [PATCH 02/21] Comments fixed #1574 --- .../scala/sbt/EvaluateConfigurations.scala | 12 ++- .../sbt/SplitExpressionsNoBlankies.scala | 57 +++++++----- .../test/scala/sbt/CheckIfParsedSpec.scala | 32 +++---- .../src/test/scala/sbt/CommentedXmlSpec.scala | 90 +++++++++---------- main/src/test/scala/sbt/NewFormatSpec.scala | 12 ++- .../scala/sbt/SplitExpressionsBehavior.scala | 4 +- 6 files changed, 110 insertions(+), 97 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index 621a20d8f..f1c200a42 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -4,12 +4,10 @@ package sbt import java.io.File -import java.net.URI import compiler.{ Eval, EvalImports } import complete.DefaultParsers.validID -import Def.{ ScopedKey, Setting, SettingsDefinition } +import Def.{ ScopedKey, Setting } import Scope.GlobalScope -import scala.annotation.tailrec /** * This file is responsible for compiling the .sbt files used to configure sbt builds. @@ -61,7 +59,7 @@ object EvaluateConfigurations { * Parses a sequence of build.sbt lines into a [[ParsedFile]]. The result contains * a fragmentation of all imports, settings and definitions. * - * @param buildinImports The set of import statements to add to those parsed in the .sbt file. + * @param builtinImports The set of import statements to add to those parsed in the .sbt file. */ private[this] def parseConfiguration(file: File, lines: Seq[String], builtinImports: Seq[String], offset: Int): ParsedFile = { @@ -167,7 +165,7 @@ object EvaluateConfigurations { * @param expression The scala expression we're compiling * @param range The original position in source of the expression, for error messages. * - * @return A method that given an sbt classloader, can return the actual [[DslEntry]] defined by + * @return A method that given an sbt classloader, can return the actual [[internals.DslEntry]] defined by * the expression, and the sequence of .class files generated. */ private[sbt] def evaluateDslEntry(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): TrackedEvalResult[internals.DslEntry] = { @@ -216,7 +214,7 @@ object EvaluateConfigurations { */ def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { - val split = SplitExpressionsNoBlankies(null, lines) + val split = SplitExpressionsNoBlankies(file, lines) (split.imports, split.settings) } @@ -255,7 +253,7 @@ object Index { val multiMap = settings.groupBy(label) val duplicates = multiMap collect { case (k, xs) if xs.size > 1 => (k, xs.map(_.manifest)) } collect { case (k, xs) if xs.size > 1 => (k, xs) } if (duplicates.isEmpty) - multiMap.collect { case (k, v) if validID(k) => (k, v.head) } toMap; + multiMap.collect { case (k, v) if validID(k) => (k, v.head) } toMap else sys.error(duplicates map { case (k, tps) => "'" + k + "' (" + tps.mkString(", ") + ")" } mkString ("Some keys were defined with the same name but different types: ", ", ", "")) } diff --git a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala index 02ed24419..3dc971dfa 100644 --- a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala @@ -6,7 +6,8 @@ import scala.annotation.tailrec import SplitExpressionsNoBlankies._ object SplitExpressionsNoBlankies { - val END_OF_LINE = "\n" + val END_OF_LINE_CHAR = '\n' + val END_OF_LINE = String.valueOf(END_OF_LINE_CHAR) } case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { @@ -26,13 +27,14 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { val indexedLines = lines.toIndexedSeq val original = indexedLines.mkString(END_OF_LINE) val merged = handleXmlContent(original) - val fileName = if (file == null) "Here should be file name" else file.getAbsolutePath + val fileName = file.getAbsolutePath val parsed = try { toolbox.parse(merged) } catch { case e: ToolBoxError => + ConsoleLogger(System.err).trace(e) val seq = toolbox.frontEnd.infos.map { i => s"""[$fileName]:${i.pos.line}: ${i.msg}""" } @@ -54,17 +56,28 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { def convertImport(t: Tree): (String, Int) = (merged.substring(t.pos.start, t.pos.end), t.pos.line - 1) + /** + * See BugInParser + * @param t - tree + * @param originalStatement - original + * @return originalStatement or originalStatement with missing bracket + */ + def parseStatementAgain(t: Tree, originalStatement: String): String = { + val statement = util.Try(toolbox.parse(originalStatement)) match { + case util.Failure(th) => + val missingText = tryWithNextStatement(merged, t.pos.end, t.pos.line, fileName, th) + originalStatement + missingText + case _ => + originalStatement + } + statement + } + def convertStatement(t: Tree): Option[(String, LineRange)] = if (t.pos.isDefined) { val originalStatement = merged.substring(t.pos.start, t.pos.end) - val statement = util.Try(toolbox.parse(originalStatement)) match { - case util.Failure(th) => - val missingText = tryWithNextStatement(merged, t.pos.end, t.pos.line, fileName, th) - originalStatement + missingText - case _ => - originalStatement - } - val numberLines = statement.count(c => c == '\n') + val statement = parseStatementAgain(t, originalStatement) + val numberLines = statement.count(c => c == END_OF_LINE_CHAR) Some((statement, LineRange(t.pos.line - 1, t.pos.line + numberLines))) } else { None @@ -85,7 +98,7 @@ private[sbt] object BugInParser { * @param content - parsed file * @param positionEnd - from index * @param positionLine - number of start position line - * @param fileName - + * @param fileName - file name * @param th - original exception * @return */ @@ -156,7 +169,7 @@ private object XmlContent { /** * Cut file for normal text - xml - normal text - xml .... - * @param content - + * @param content - content * @param ts - import/statements * @return (text,true) - is xml (text,false) - if normal text */ @@ -181,7 +194,7 @@ private object XmlContent { /** * Cut potential xmls from content - * @param content - + * @param content - content * @return sorted by openIndex xml parts */ private def findXmlParts(content: String): Seq[(String, Int, Int)] = { @@ -283,9 +296,9 @@ private object XmlContent { /** * - * @param content - - * @param xmlParts - - * @return + * @param content - content + * @param xmlParts - xmlParts + * @return content with xml with brackets */ private def addExplicitXmlContent(content: String, xmlParts: Seq[(String, Int, Int)]): String = { val statements: Seq[(String, Boolean)] = splitFile(content, xmlParts) @@ -323,12 +336,12 @@ private object XmlContent { /** * Add to head if option is not empty - * @param acc - seq - * @param option - + * @param ts - seq + * @param option - option * @tparam T - type * @return original seq for None, add to head for Some[T] */ - private def addOptionToCollection[T](acc: Seq[T], option: Option[T]) = option.fold(acc)(el => el +: acc) + private def addOptionToCollection[T](ts: Seq[T], option: Option[T]) = option.fold(ts)(el => el +: ts) private def findNotModifiedOpeningTag(content: String, closeTagStartIndex: Int, closeTagEndIndex: Int): Option[(String, Int, Int)] = { @@ -348,8 +361,8 @@ private object XmlContent { /** * Check, if xmlPart is valid xml. If not - None is returned * @param content - file content - * @param openIndex - - * @param closeIndex - + * @param openIndex - open index + * @param closeIndex - close index * @return Some((String,Int,Int)) */ private def xmlFragmentOption(content: String, openIndex: Int, closeIndex: Int): Option[(String, Int, Int)] = { @@ -362,7 +375,7 @@ private object XmlContent { /** * If xml is in brackets - we do not need to add them - * @param statement - + * @param statement - statement * @return are brackets necessary? */ private def areBracketsNecessary(statement: String): Boolean = { diff --git a/main/src/test/scala/sbt/CheckIfParsedSpec.scala b/main/src/test/scala/sbt/CheckIfParsedSpec.scala index 45f9c5c1a..b1015f400 100644 --- a/main/src/test/scala/sbt/CheckIfParsedSpec.scala +++ b/main/src/test/scala/sbt/CheckIfParsedSpec.scala @@ -9,28 +9,24 @@ abstract class CheckIfParsedSpec(implicit val splitter: SplitExpressions.SplitEx case (content, description, nonEmptyImports, nonEmptyStatements) => println(s"""${getClass.getSimpleName}: "$description" """) val (imports, statements) = split(content) - - statements.nonEmpty must be_==(nonEmptyStatements) - // orPending(s"""$description - // |***${shouldContains(nonEmptyStatements)} statements*** - // |$content """.stripMargin) - - imports.nonEmpty must be_==(nonEmptyImports) - // orPending(s"""$description - // |***${shouldContains(nonEmptyImports)} imports*** - // |$content """.stripMargin) + statements.nonEmpty must be_==(nonEmptyStatements).setMessage(s"""$description + |***${shouldContains(nonEmptyStatements)} statements*** + |$content """.stripMargin) + imports.nonEmpty must be_==(nonEmptyImports).setMessage(s"""$description + |***${shouldContains(nonEmptyImports)} imports*** + |$content """.stripMargin) } } } - // private def shouldContains(b: Boolean) = s"""Should ${ - // if (b) { - // "contain" - // } else { - // "not contain" - // } - // }""" + private def shouldContains(b: Boolean) = s"""Should ${ + if (b) { + "contain" + } else { + "not contain" + } + }""" - protected def files: Seq[(String, String, Boolean, Boolean)] + protected val files: Seq[(String, String, Boolean, Boolean)] } diff --git a/main/src/test/scala/sbt/CommentedXmlSpec.scala b/main/src/test/scala/sbt/CommentedXmlSpec.scala index 6ddb30ed6..decf99b1c 100644 --- a/main/src/test/scala/sbt/CommentedXmlSpec.scala +++ b/main/src/test/scala/sbt/CommentedXmlSpec.scala @@ -2,7 +2,7 @@ package sbt class CommentedXmlSpec extends CheckIfParsedSpec { - override protected def files: Seq[(String, String, Boolean, Boolean)] = Seq( + override protected val files = Seq( ( s"""| |val pom = "" @@ -15,53 +15,53 @@ class CommentedXmlSpec extends CheckIfParsedSpec { | """.stripMargin, "Xml in string", false, true), (""" - |val scmpom = taskKey[xml.NodeBuffer]("Node buffer") - | - |scmpom := - | git@github.com:mohiva/play-html-compressor.git - | scm:git:git@github.com:mohiva/play-html-compressor.git - | - | - | - | akkie - | Christian Kaps - | http://mohiva.com - | - | - | // - | - | - |publishMavenStyle := true - | - """.stripMargin, "Wrong Commented xml ", false, true), + |val scmpom = taskKey[xml.NodeBuffer]("Node buffer") + | + |scmpom := + | git@github.com:mohiva/play-html-compressor.git + | scm:git:git@github.com:mohiva/play-html-compressor.git + | + | + | + | akkie + | Christian Kaps + | http://mohiva.com + | + | + | // + | + | + |publishMavenStyle := true + | + """.stripMargin, "Wrong Commented xml ", false, true), (""" - |val scmpom = taskKey[xml.NodeBuffer]("Node buffer") - | - |scmpom := - | git@github.com:mohiva/play-html-compressor.git - | scm:git:git@github.com:mohiva/play-html-compressor.git - | - | - | - | akkie - | Christian Kaps - | http://mohiva.com - | - | - | // - | // - | - |publishMavenStyle := true - | - """.stripMargin, "Commented xml ", false, true), + |val scmpom = taskKey[xml.NodeBuffer]("Node buffer") + | + |scmpom := + | git@github.com:mohiva/play-html-compressor.git + | scm:git:git@github.com:mohiva/play-html-compressor.git + | + | + | + | akkie + | Christian Kaps + | http://mohiva.com + | + | + | // + | // + | + |publishMavenStyle := true + | + """.stripMargin, "Commented xml ", false, true), (""" - |import sbt._ - | - |// - """.stripMargin, "Xml in comment2", false, false) + |// a/> + """.stripMargin, "Xml in comment2", false, false) ) } diff --git a/main/src/test/scala/sbt/NewFormatSpec.scala b/main/src/test/scala/sbt/NewFormatSpec.scala index f9f447b50..ee5a31493 100644 --- a/main/src/test/scala/sbt/NewFormatSpec.scala +++ b/main/src/test/scala/sbt/NewFormatSpec.scala @@ -1,8 +1,13 @@ package sbt import java.io.File + +import org.junit.runner.RunWith +import org.specs2.runner.JUnitRunner + import scala.io.Source +@RunWith(classOf[JUnitRunner]) class NewFormatSpec extends AbstractSpec { implicit val splitter: SplitExpressions.SplitExpression = EvaluateConfigurations.splitExpressions @@ -16,10 +21,9 @@ class NewFormatSpec extends AbstractSpec { println(s"$path") val lines = Source.fromFile(path).getLines().toList val (_, statements) = splitter(path, lines) - statements.nonEmpty must be_==(true) - // orPending(s""" - // |***should contains statements*** - // |$lines """.stripMargin) + statements.nonEmpty must be_==(true).setMessage(s""" + |***should contains statements*** + |$lines """.stripMargin) } } } diff --git a/main/src/test/scala/sbt/SplitExpressionsBehavior.scala b/main/src/test/scala/sbt/SplitExpressionsBehavior.scala index c44f182e0..246a29e11 100644 --- a/main/src/test/scala/sbt/SplitExpressionsBehavior.scala +++ b/main/src/test/scala/sbt/SplitExpressionsBehavior.scala @@ -1,9 +1,11 @@ package sbt +import java.io.File + import org.specs2.mutable.SpecificationLike trait SplitExpression { - def split(s: String)(implicit splitter: SplitExpressions.SplitExpression) = splitter(null, s.split("\n").toSeq) + def split(s: String, file: File = new File("noFile"))(implicit splitter: SplitExpressions.SplitExpression) = splitter(file, s.split("\n").toSeq) } trait SplitExpressionsBehavior extends SplitExpression { From 4a33fd22257adb0c45d90df78624a929cbf43eb8 Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Fri, 12 Sep 2014 23:35:07 +0200 Subject: [PATCH 03/21] Extract method - for new implementation. Not completed yet. --- main/src/main/scala/sbt/SessionSettings.scala | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/main/src/main/scala/sbt/SessionSettings.scala b/main/src/main/scala/sbt/SessionSettings.scala index 41bc651c1..b1984928f 100755 --- a/main/src/main/scala/sbt/SessionSettings.scala +++ b/main/src/main/scala/sbt/SessionSettings.scala @@ -113,15 +113,8 @@ object SessionSettings { } } val newSettings = settings diff replace - val (tmpLines, _) = ((List[String](), 1) /: IO.readLines(writeTo).zipWithIndex) { - case ((accLines, n), (line, m)) if n == m + 1 => - lineMap.get(n) match { - case Some(Pair(end, lines)) => (lines reverse_::: accLines, end) - case None => (line :: accLines, n + 1) - } - case (res, _) => res - } - val exist = tmpLines.reverse + val oldContentWithIndex = IO.readLines(writeTo).zipWithIndex + val exist: List[String] = toLines(oldContentWithIndex, lineMap) val adjusted = if (!newSettings.isEmpty && needsTrailingBlank(exist)) exist :+ "" else exist val lines = adjusted ++ newSettings.flatMap(_._2 ::: "" :: Nil) IO.writeLines(writeTo, lines) @@ -132,6 +125,19 @@ object SessionSettings { } (newWithPos.reverse, other ++ oldShifted) } + + private[sbt] def toLines(oldContentWithIndex: List[(String,Int)], lineMap: Map[Int, (Int, List[String])]): List[String] = { + val (tmpLines, _) = ((List[String](), 1) /: oldContentWithIndex) { + case ((accLines, n), (line, m)) if n == m + 1 => + lineMap.get(n) match { + case Some(Pair(end, lines)) => (lines reverse_::: accLines, end) + case None => (line :: accLines, n + 1) + } + case (res, _) => res + } + tmpLines.reverse + } + def needsTrailingBlank(lines: Seq[String]) = !lines.isEmpty && !lines.takeRight(1).exists(_.trim.isEmpty) def printAllSettings(s: State): State = withSettings(s) { session => From c720a973a636a1f12a0f2f20b07f0c6a2a521290 Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Mon, 15 Sep 2014 16:31:50 +0200 Subject: [PATCH 04/21] Corrections for "session save" --- main/src/main/scala/sbt/SessionSettings.scala | 24 ++---- .../scala/sbt/SessionSettingsNoBlankies.scala | 79 +++++++++++++++++++ .../sbt/SplitExpressionsNoBlankies.scala | 37 +++++---- .../test/resources/session-settings/1.sbt.txt | 27 +++++++ .../session-settings/1.sbt.txt_1/1.xml | 7 ++ .../session-settings/1.sbt.txt_1/1.xml.result | 27 +++++++ .../session-settings/1.sbt.txt_1/2.xml | 12 +++ .../session-settings/1.sbt.txt_1/2.xml.result | 27 +++++++ .../test/resources/session-settings/2.sbt.txt | 24 ++++++ .../session-settings/2.sbt.txt_1/1.xml | 7 ++ .../session-settings/2.sbt.txt_1/1.xml.result | 24 ++++++ .../test/scala/sbt/SessionSettingsSpec.scala | 76 ++++++++++++++++++ 12 files changed, 338 insertions(+), 33 deletions(-) create mode 100644 main/src/main/scala/sbt/SessionSettingsNoBlankies.scala create mode 100644 main/src/test/resources/session-settings/1.sbt.txt create mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/1.xml create mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/1.xml.result create mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/2.xml create mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result create mode 100644 main/src/test/resources/session-settings/2.sbt.txt create mode 100644 main/src/test/resources/session-settings/2.sbt.txt_1/1.xml create mode 100644 main/src/test/resources/session-settings/2.sbt.txt_1/1.xml.result create mode 100644 main/src/test/scala/sbt/SessionSettingsSpec.scala diff --git a/main/src/main/scala/sbt/SessionSettings.scala b/main/src/main/scala/sbt/SessionSettings.scala index b1984928f..921bd5db9 100755 --- a/main/src/main/scala/sbt/SessionSettings.scala +++ b/main/src/main/scala/sbt/SessionSettings.scala @@ -12,6 +12,8 @@ import compiler.Eval import SessionSettings._ +import scala.collection.immutable.SortedMap + final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, String], original: Seq[Setting[_]], append: SessionMap, rawAppend: Seq[Setting[_]], currentEval: () => Eval) { assert(currentProject contains currentBuild, "Current build (" + currentBuild + ") not associated with a current project.") def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings = copy(currentBuild = build, currentProject = currentProject.updated(build, project), currentEval = eval) @@ -100,21 +102,23 @@ object SessionSettings { } } - val (_, oldShifted, replace, lineMap) = ((0, List[Setting[_]](), List[SessionSetting](), Map.empty[Int, (Int, List[String])]) /: inFile) { + val (_, oldShifted, replace, lineMap) = ((0, List[Setting[_]](), List[SessionSetting](), SortedMap.empty[Int, List[(Int, List[String])]](Ordering[Int].reverse)) /: inFile) { case ((offs, olds, repl, lineMap), s) => val RangePosition(_, r @ LineRange(start, end)) = s.pos settings find (_._1.key == s.key) match { case Some(ss @ (ns, newLines)) if !ns.init.dependencies.contains(ns.key) => val shifted = ns withPos RangePosition(path, LineRange(start - offs, start - offs + newLines.size)) - (offs + end - start - newLines.size, shifted :: olds, ss :: repl, lineMap + (start -> (end, newLines))) + val head = (end, newLines) + val seq = lineMap.getOrElse(start, List()) + (offs + end - start - newLines.size, shifted :: olds, ss :: repl, lineMap + (start -> (head +: seq))) case _ => val shifted = s withPos RangePosition(path, r shift -offs) (offs, shifted :: olds, repl, lineMap) } } val newSettings = settings diff replace - val oldContentWithIndex = IO.readLines(writeTo).zipWithIndex - val exist: List[String] = toLines(oldContentWithIndex, lineMap) + val oldContent = IO.readLines(writeTo) + val exist: List[String] = SessionSettingsNoBlankies.oldLinesToNew(oldContent, lineMap) val adjusted = if (!newSettings.isEmpty && needsTrailingBlank(exist)) exist :+ "" else exist val lines = adjusted ++ newSettings.flatMap(_._2 ::: "" :: Nil) IO.writeLines(writeTo, lines) @@ -126,18 +130,6 @@ object SessionSettings { (newWithPos.reverse, other ++ oldShifted) } - private[sbt] def toLines(oldContentWithIndex: List[(String,Int)], lineMap: Map[Int, (Int, List[String])]): List[String] = { - val (tmpLines, _) = ((List[String](), 1) /: oldContentWithIndex) { - case ((accLines, n), (line, m)) if n == m + 1 => - lineMap.get(n) match { - case Some(Pair(end, lines)) => (lines reverse_::: accLines, end) - case None => (line :: accLines, n + 1) - } - case (res, _) => res - } - tmpLines.reverse - } - def needsTrailingBlank(lines: Seq[String]) = !lines.isEmpty && !lines.takeRight(1).exists(_.trim.isEmpty) def printAllSettings(s: State): State = withSettings(s) { session => diff --git a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala new file mode 100644 index 000000000..806332def --- /dev/null +++ b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala @@ -0,0 +1,79 @@ +package sbt + +import java.io.File + +import scala.collection.immutable.SortedMap +import scala.reflect.runtime.universe._ + +object SessionSettingsNoBlankies { + + val REVERSE_ORDERING_INT = Ordering[Int].reverse + + def oldLinesToNew(content: List[String], lineMap: SortedMap[Int, List[(Int, List[String])]]): List[String] = + if (lineMap.isEmpty) { + content + } else { + val head = lineMap.head + val newContent = toNewContent(content, head) + oldLinesToNew(newContent, lineMap.tail) + } + + private def toNewContent(content: List[String], tuple: (Int, List[(Int, List[String])])): List[String] = { + val (from, newSettingSeq) = tuple + + val newTreeStringSeqMap = newSettingSeq.seq.map { + case (_, lines) => toTreeStringMap(lines) + } + val to = newSettingSeq.map(_._1).max + val originalLine = content.slice(from - 1, to - 1) + + val operations = newTreeStringSeqMap.flatMap { + map => + map.flatMap { + case (name, (startIndex, statement)) => + val validLines = cutExpression(originalLine, name) + val treeStringMap = toTreeStringMap(validLines) + treeStringMap.get(name).map { + case (t, oldContent) => + (startIndex, oldContent, statement) + } + } + } + val statements = originalLine.mkString("\n") + val sortedOperations = operations.sortBy(_._1)(REVERSE_ORDERING_INT) + val newContent = sortedOperations.foldLeft(statements) { + case (acc, (startIndex, old, newStatement)) => + acc.replace(old, newStatement) + } + val newLines = newContent.lines.toList + content.take(from - 1) ++ newLines ++ content.drop(to - 1) + } + + private def cutExpression(l: List[String], name: String): List[String] = l match { + case h +: t => + val array = h.split(";").filter(_.contains(name)) + array.mkString(";") +: t + case _ => + l + } + + private def toTreeStringMap(lines: List[String]) = { + + val trees = SplitExpressionsNoBlankies(new File("fake"), lines).settingsTrees + val seq = trees.map { + case (statement, tree) => + (extractSettingName(tree), (tree.pos.start, statement)) + } + seq.toMap + } + + private def extractSettingName(tree: Tree): String = { + tree.children match { + case h :: _ => + extractSettingName(h) + case _ => + tree.toString() + } + } + +} diff --git a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala index 3dc971dfa..14fa6285d 100644 --- a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala @@ -4,6 +4,7 @@ import java.io.File import scala.annotation.tailrec import SplitExpressionsNoBlankies._ +import scala.reflect.runtime.universe._ object SplitExpressionsNoBlankies { val END_OF_LINE_CHAR = '\n' @@ -11,11 +12,12 @@ object SplitExpressionsNoBlankies { } case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { - val (imports, settings) = splitExpressions(file, lines) + //settingsTrees needed for "session save" + val (imports, settings, settingsTrees) = splitExpressions(file, lines) - private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { + private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)]) = { import scala.reflect.runtime._ - import scala.reflect.runtime.universe._ + import scala.tools.reflect.ToolBoxError import scala.tools.reflect.ToolBox import scala.compat.Platform.EOL @@ -34,12 +36,14 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { toolbox.parse(merged) } catch { case e: ToolBoxError => - ConsoleLogger(System.err).trace(e) val seq = toolbox.frontEnd.infos.map { i => s"""[$fileName]:${i.pos.line}: ${i.msg}""" } throw new MessageOnlyException( - s"""${seq.mkString(EOL)}""".stripMargin) + s"""====== + |$merged + |====== + |${seq.mkString(EOL)}""".stripMargin) } val parsedTrees = parsed match { case Block(stmt, expr) => @@ -73,19 +77,18 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { statement } - def convertStatement(t: Tree): Option[(String, LineRange)] = + def convertStatement(t: Tree): Option[(String, Tree, LineRange)] = if (t.pos.isDefined) { val originalStatement = merged.substring(t.pos.start, t.pos.end) val statement = parseStatementAgain(t, originalStatement) val numberLines = statement.count(c => c == END_OF_LINE_CHAR) - Some((statement, LineRange(t.pos.line - 1, t.pos.line + numberLines))) + Some((statement, t, LineRange(t.pos.line - 1, t.pos.line + numberLines))) } else { None } - - (imports map convertImport, statements flatMap convertStatement) + val statementsTreeLineRange = statements flatMap convertStatement + (imports map convertImport, statementsTreeLineRange.map(t => (t._1, t._3)), statementsTreeLineRange.map(t => (t._1, t._2))) } - } /** @@ -143,14 +146,14 @@ private[sbt] object BugInParser { /** * #ToolBox#parse(String) will fail for xml sequence: *
- *   val xml = 
txt
- * rr - *
- * At least brackets have to be added + * val xml =
txt
+ * rr + * + * At least brackets have to be added *
- *   val xml = (
txt
- * rr) - *
+ * val xml = (
txt
+ * rr) + * */ private object XmlContent { /** diff --git a/main/src/test/resources/session-settings/1.sbt.txt b/main/src/test/resources/session-settings/1.sbt.txt new file mode 100644 index 000000000..0e6f696cc --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt @@ -0,0 +1,27 @@ +name := "newName" +libraryDependencies := Seq("org.scala-sbt" %% "sbinary" % "0.4.1") + +lazy val checkPom = taskKey[Unit]("check pom to ensure no sections are generated"); checkPom := { + val pomFile = makePom.value + val pom = xml.XML.loadFile(pomFile) + val tpe = pom \\ "type" + if(!tpe.isEmpty) + sys.error("Expected no sections, got: " + tpe + " in \n\n" + pom) +};scalacOptions := Seq("-deprecation") + +val b = ( ) +val a = + + +/* + +*/ + + +organization := "jozwikr" // OK + +scalaVersion := "2.9.2" + +organization := "ololol" + + diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml b/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml new file mode 100644 index 000000000..752deca9b --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml @@ -0,0 +1,7 @@ + + + 1 + 2 + name := "alaMaKota" + + \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml.result b/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml.result new file mode 100644 index 000000000..b7b3c0f89 --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml.result @@ -0,0 +1,27 @@ +name := "alaMaKota" +libraryDependencies := Seq("org.scala-sbt" %% "sbinary" % "0.4.1") + +lazy val checkPom = taskKey[Unit]("check pom to ensure no sections are generated"); checkPom := { + val pomFile = makePom.value + val pom = xml.XML.loadFile(pomFile) + val tpe = pom \\ "type" + if(!tpe.isEmpty) + sys.error("Expected no sections, got: " + tpe + " in \n\n" + pom) +};scalacOptions := Seq("-deprecation") + +val b = ( ) +val a = + + +/* + +*/ + + +organization := "jozwikr" // OK + +scalaVersion := "2.9.2" + +organization := "ololol" + + diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml new file mode 100644 index 000000000..7aebf36a6 --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml @@ -0,0 +1,12 @@ + + + 1 + 2 + name := "alaMaKota" + + + 25 + 26 + organization := "scalania" + + \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result new file mode 100644 index 000000000..e1b25cd91 --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result @@ -0,0 +1,27 @@ +name := "alaMaKota" +libraryDependencies := Seq("org.scala-sbt" %% "sbinary" % "0.4.1") + +lazy val checkPom = taskKey[Unit]("check pom to ensure no sections are generated"); checkPom := { + val pomFile = makePom.value + val pom = xml.XML.loadFile(pomFile) + val tpe = pom \\ "type" + if(!tpe.isEmpty) + sys.error("Expected no sections, got: " + tpe + " in \n\n" + pom) +};scalacOptions := Seq("-deprecation") + +val b = ( ) +val a = + + +/* + +*/ + + +organization := "jozwikr" // OK + +scalaVersion := "2.9.2" + +organization := "scalania" + + diff --git a/main/src/test/resources/session-settings/2.sbt.txt b/main/src/test/resources/session-settings/2.sbt.txt new file mode 100644 index 000000000..f3fe3a61f --- /dev/null +++ b/main/src/test/resources/session-settings/2.sbt.txt @@ -0,0 +1,24 @@ +name := "newName";scalaVersion := "2.9.2"; organization := "jozwikr" +libraryDependencies := Seq("org.scala-sbt" %% "sbinary" % "0.4.1") + +lazy val checkPom = taskKey[Unit]("check pom to ensure no sections are generated"); checkPom := { + val pomFile = makePom.value + val pom = xml.XML.loadFile(pomFile) + val tpe = pom \\ "type" + if(!tpe.isEmpty) + sys.error("Expected no sections, got: " + tpe + " in \n\n" + pom) +};scalacOptions := Seq("-deprecation") + +val b = ( ) +val a = + + +/* + +*/ + + + + + + diff --git a/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml b/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml new file mode 100644 index 000000000..752deca9b --- /dev/null +++ b/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml @@ -0,0 +1,7 @@ + + + 1 + 2 + name := "alaMaKota" + + \ No newline at end of file diff --git a/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml.result b/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml.result new file mode 100644 index 000000000..86c7140cf --- /dev/null +++ b/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml.result @@ -0,0 +1,24 @@ +name := "alaMaKota";scalaVersion := "2.9.2"; organization := "jozwikr" +libraryDependencies := Seq("org.scala-sbt" %% "sbinary" % "0.4.1") + +lazy val checkPom = taskKey[Unit]("check pom to ensure no sections are generated"); checkPom := { + val pomFile = makePom.value + val pom = xml.XML.loadFile(pomFile) + val tpe = pom \\ "type" + if(!tpe.isEmpty) + sys.error("Expected no sections, got: " + tpe + " in \n\n" + pom) +};scalacOptions := Seq("-deprecation") + +val b = ( ) +val a = + + +/* + +*/ + + + + + + diff --git a/main/src/test/scala/sbt/SessionSettingsSpec.scala b/main/src/test/scala/sbt/SessionSettingsSpec.scala new file mode 100644 index 000000000..7cdbdcf58 --- /dev/null +++ b/main/src/test/scala/sbt/SessionSettingsSpec.scala @@ -0,0 +1,76 @@ +package sbt + +import java.io.{ File, FilenameFilter } + +import org.specs2.matcher.MatchResult + +import scala.collection.GenTraversableOnce +import scala.collection.immutable.{ SortedMap, TreeMap } +import scala.io.Source +import scala.xml.XML + +abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec { + protected val rootPath = getClass.getResource("").getPath + folder + println(s"Reading files from: $rootPath") + protected val rootDir = new File(rootPath) + + "SessionSettings " should { + "Be identical for empty map " in { + def unit(f: File) = Seq((Source.fromFile(f).getLines().toSeq, SortedMap.empty[Int, List[(Int, List[String])]])) + runTestOnFiles(unit) + } + + "Replace statements " in { + runTestOnFiles(replace) + } + } + + private def runTestOnFiles(expectedResultAndMap: File => Seq[(Seq[String], SortedMap[Int, List[(Int, List[String])]])]): MatchResult[GenTraversableOnce[File]] = { + + val allFiles = rootDir.listFiles(new FilenameFilter() { + def accept(dir: File, name: String) = name.endsWith(".sbt.txt") + }).toList + foreach(allFiles) { + file => + val originalLines = Source.fromFile(file).getLines().toList + foreach(expectedResultAndMap(file)) { + case (expectedResult, map) => + val result = SessionSettingsNoBlankies.oldLinesToNew(originalLines, map) + expectedResult === result + } + } + } + + protected def replace(f: File) = { + val dirs = rootDir.listFiles(new FilenameFilter() { + def accept(dir: File, name: String) = { + val startsWith = f.getName + "_" + name.startsWith(startsWith) + } + }).toList + dirs.flatMap { + dir => + val files = dir.listFiles(new FilenameFilter { + override def accept(dir: File, name: String) = name.endsWith(".xml") + }) + files.map { xmlFile => + val xml = XML.loadFile(xmlFile) + val result = Source.fromFile(xmlFile.getAbsolutePath + ".result").getLines().toSeq + val tupleCollection = (xml \\ "settings" \\ "setting").map { + node => + val set = (node \\ "set").text + val start = (node \\ "start").text.toInt + val end = (node \\ "end").text.toInt + (start, (end, List(set))) + }.toList + val map = tupleCollection.groupBy(el => el._1).map { + case (k, seq) => (k, seq.map(el => el._2)) + } + (result, TreeMap(map.toArray: _*)(SessionSettingsNoBlankies.REVERSE_ORDERING_INT)) + } + } + } + +} + +class SessionSettingsSpec extends AbstractSessionSettingsSpec("../session-settings") From 232c28ecd13bffe8289b51049fc719989afb217e Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Tue, 16 Sep 2014 11:25:33 +0200 Subject: [PATCH 05/21] Handle xml content in session save --- .gitignore | 2 ++ .../scala/sbt/SessionSettingsNoBlankies.scala | 16 +++++++------- .../sbt/SplitExpressionsNoBlankies.scala | 12 +++++------ .../session-settings/1.sbt.txt_1/1.xml | 2 +- .../session-settings/1.sbt.txt_1/2.xml | 4 ++-- .../session-settings/1.sbt.txt_1/3.xml | 7 +++++++ .../session-settings/1.sbt.txt_1/3.xml.result | 21 +++++++++++++++++++ .../session-settings/2.sbt.txt_1/1.xml | 2 +- .../test/resources/session-settings/3.sbt.txt | 16 ++++++++++++++ .../session-settings/3.sbt.txt_1/1.xml | 7 +++++++ .../session-settings/3.sbt.txt_1/1.xml.result | 5 +++++ .../test/scala/sbt/SessionSettingsSpec.scala | 18 +++++++++------- 12 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/3.xml create mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/3.xml.result create mode 100644 main/src/test/resources/session-settings/3.sbt.txt create mode 100644 main/src/test/resources/session-settings/3.sbt.txt_1/1.xml create mode 100644 main/src/test/resources/session-settings/3.sbt.txt_1/1.xml.result diff --git a/.gitignore b/.gitignore index e762de7f9..4d856c4e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ target/ __pycache__ +.idea +.idea_modules diff --git a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala index 806332def..762e8085b 100644 --- a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala +++ b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala @@ -18,16 +18,16 @@ object SessionSettingsNoBlankies { oldLinesToNew(newContent, lineMap.tail) } - private def toNewContent(content: List[String], tuple: (Int, List[(Int, List[String])])): List[String] = { - val (from, newSettingSeq) = tuple + private def toNewContent(content: List[String], setCommands: (Int, List[(Int, List[String])])): List[String] = { + val (from, newSettings) = setCommands - val newTreeStringSeqMap = newSettingSeq.seq.map { + val newTreeStringsMap = newSettings.map { case (_, lines) => toTreeStringMap(lines) } - val to = newSettingSeq.map(_._1).max + val to = newSettings.map(_._1).max val originalLine = content.slice(from - 1, to - 1) - val operations = newTreeStringSeqMap.flatMap { + val operations = newTreeStringsMap.flatMap { map => map.flatMap { case (name, (startIndex, statement)) => @@ -39,7 +39,7 @@ object SessionSettingsNoBlankies { } } } - val statements = originalLine.mkString("\n") + val statements = XmlContent.handleXmlContent(originalLine.mkString("\n")) val sortedOperations = operations.sortBy(_._1)(REVERSE_ORDERING_INT) val newContent = sortedOperations.foldLeft(statements) { case (acc, (startIndex, old, newStatement)) => @@ -58,8 +58,8 @@ object SessionSettingsNoBlankies { } private def toTreeStringMap(lines: List[String]) = { - - val trees = SplitExpressionsNoBlankies(new File("fake"), lines).settingsTrees + val split = SplitExpressionsNoBlankies(new File("fake"), lines) + val trees = split.settingsTrees val seq = trees.map { case (statement, tree) => (extractSettingName(tree), (tree.pos.start, statement)) diff --git a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala index 14fa6285d..82bff4074 100644 --- a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala @@ -28,12 +28,12 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { val toolbox = mirror.mkToolBox(options = "-Yrangepos") val indexedLines = lines.toIndexedSeq val original = indexedLines.mkString(END_OF_LINE) - val merged = handleXmlContent(original) + val modifiedContent = handleXmlContent(original) val fileName = file.getAbsolutePath val parsed = try { - toolbox.parse(merged) + toolbox.parse(modifiedContent) } catch { case e: ToolBoxError => val seq = toolbox.frontEnd.infos.map { i => @@ -41,7 +41,7 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { } throw new MessageOnlyException( s"""====== - |$merged + |$modifiedContent |====== |${seq.mkString(EOL)}""".stripMargin) } @@ -58,7 +58,7 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { } def convertImport(t: Tree): (String, Int) = - (merged.substring(t.pos.start, t.pos.end), t.pos.line - 1) + (modifiedContent.substring(t.pos.start, t.pos.end), t.pos.line - 1) /** * See BugInParser @@ -69,7 +69,7 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { def parseStatementAgain(t: Tree, originalStatement: String): String = { val statement = util.Try(toolbox.parse(originalStatement)) match { case util.Failure(th) => - val missingText = tryWithNextStatement(merged, t.pos.end, t.pos.line, fileName, th) + val missingText = tryWithNextStatement(modifiedContent, t.pos.end, t.pos.line, fileName, th) originalStatement + missingText case _ => originalStatement @@ -79,7 +79,7 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { def convertStatement(t: Tree): Option[(String, Tree, LineRange)] = if (t.pos.isDefined) { - val originalStatement = merged.substring(t.pos.start, t.pos.end) + val originalStatement = modifiedContent.substring(t.pos.start, t.pos.end) val statement = parseStatementAgain(t, originalStatement) val numberLines = statement.count(c => c == END_OF_LINE_CHAR) Some((statement, t, LineRange(t.pos.line - 1, t.pos.line + numberLines))) diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml b/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml index 752deca9b..a67818d62 100644 --- a/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml @@ -2,6 +2,6 @@ 1 2 - name := "alaMaKota" + \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml index 7aebf36a6..20e5c790f 100644 --- a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml @@ -2,11 +2,11 @@ 1 2 - name := "alaMaKota" + 25 26 - organization := "scalania" + \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml b/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml new file mode 100644 index 000000000..efdba1fe1 --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml @@ -0,0 +1,7 @@ + + + 4 + 11 + + + \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml.result b/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml.result new file mode 100644 index 000000000..0120a790a --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml.result @@ -0,0 +1,21 @@ +name := "newName" +libraryDependencies := Seq("org.scala-sbt" %% "sbinary" % "0.4.1") + +lazy val checkPom = taskKey[Unit]("check pom to ensure no sections are generated"); checkPom := "OK";scalacOptions := Seq("-deprecation") + +val b = ( ) +val a = + + +/* + +*/ + + +organization := "jozwikr" // OK + +scalaVersion := "2.9.2" + +organization := "ololol" + + diff --git a/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml b/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml index 752deca9b..a67818d62 100644 --- a/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml +++ b/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml @@ -2,6 +2,6 @@ 1 2 - name := "alaMaKota" + \ No newline at end of file diff --git a/main/src/test/resources/session-settings/3.sbt.txt b/main/src/test/resources/session-settings/3.sbt.txt new file mode 100644 index 000000000..6db83e79c --- /dev/null +++ b/main/src/test/resources/session-settings/3.sbt.txt @@ -0,0 +1,16 @@ +import sbt._ + +val scmpom = taskKey[xml.NodeBuffer]("Node buffer") + +scmpom := + git@github.com:mohiva/play-html-compressor.git + scm:git:git@github.com:mohiva/play-html-compressor.git + + + + akkie + Christian Kaps + http://mohiva.com + + + \ No newline at end of file diff --git a/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml b/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml new file mode 100644 index 000000000..9f7ba5288 --- /dev/null +++ b/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml @@ -0,0 +1,7 @@ + + + 5 + 18 + OK]]> + + \ No newline at end of file diff --git a/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml.result b/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml.result new file mode 100644 index 000000000..6b1956fb1 --- /dev/null +++ b/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml.result @@ -0,0 +1,5 @@ +import sbt._ + +val scmpom = taskKey[xml.NodeBuffer]("Node buffer") + +scmpom := ( OK ) \ No newline at end of file diff --git a/main/src/test/scala/sbt/SessionSettingsSpec.scala b/main/src/test/scala/sbt/SessionSettingsSpec.scala index 7cdbdcf58..0f5097478 100644 --- a/main/src/test/scala/sbt/SessionSettingsSpec.scala +++ b/main/src/test/scala/sbt/SessionSettingsSpec.scala @@ -9,14 +9,14 @@ import scala.collection.immutable.{ SortedMap, TreeMap } import scala.io.Source import scala.xml.XML -abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec { +abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean = false) extends AbstractSpec { protected val rootPath = getClass.getResource("").getPath + folder println(s"Reading files from: $rootPath") protected val rootDir = new File(rootPath) "SessionSettings " should { "Be identical for empty map " in { - def unit(f: File) = Seq((Source.fromFile(f).getLines().toSeq, SortedMap.empty[Int, List[(Int, List[String])]])) + def unit(f: File) = Seq((Source.fromFile(f).getLines().toList, SortedMap.empty[Int, List[(Int, List[String])]])) runTestOnFiles(unit) } @@ -25,7 +25,7 @@ abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec } } - private def runTestOnFiles(expectedResultAndMap: File => Seq[(Seq[String], SortedMap[Int, List[(Int, List[String])]])]): MatchResult[GenTraversableOnce[File]] = { + private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], SortedMap[Int, List[(Int, List[String])]])]): MatchResult[GenTraversableOnce[File]] = { val allFiles = rootDir.listFiles(new FilenameFilter() { def accept(dir: File, name: String) = name.endsWith(".sbt.txt") @@ -34,9 +34,11 @@ abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec file => val originalLines = Source.fromFile(file).getLines().toList foreach(expectedResultAndMap(file)) { - case (expectedResult, map) => - val result = SessionSettingsNoBlankies.oldLinesToNew(originalLines, map) - expectedResult === result + case (expectedResultList, map) => + val resultList = SessionSettingsNoBlankies.oldLinesToNew(originalLines, map) + val expected = SplitExpressionsNoBlankies(file, expectedResultList) + val result = SplitExpressionsNoBlankies(file, resultList) + result.settings must_== expected.settings } } } @@ -55,7 +57,7 @@ abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec }) files.map { xmlFile => val xml = XML.loadFile(xmlFile) - val result = Source.fromFile(xmlFile.getAbsolutePath + ".result").getLines().toSeq + val result = Source.fromFile(xmlFile.getAbsolutePath + ".result").getLines().toList val tupleCollection = (xml \\ "settings" \\ "setting").map { node => val set = (node \\ "set").text @@ -73,4 +75,4 @@ abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec } -class SessionSettingsSpec extends AbstractSessionSettingsSpec("../session-settings") +class SessionSettingsSpec extends AbstractSessionSettingsSpec("../session-settings") \ No newline at end of file From 742188393bdb03d56755c0a2160368d9c428992d Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Fri, 26 Sep 2014 23:58:56 +0200 Subject: [PATCH 06/21] Cut valid text corrected --- .../scala/sbt/SessionSettingsNoBlankies.scala | 18 ++++++++++++++---- .../scala/sbt/SplitExpressionsNoBlankies.scala | 15 ++++++++------- main/src/test/scala/sbt/ErrorSpec.scala | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala index 762e8085b..4f18b7080 100644 --- a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala +++ b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala @@ -7,6 +7,7 @@ import scala.reflect.runtime.universe._ object SessionSettingsNoBlankies { + private val FAKE_FILE = new File("fake") val REVERSE_ORDERING_INT = Ordering[Int].reverse def oldLinesToNew(content: List[String], lineMap: SortedMap[Int, List[(Int, List[String])]]): List[String] = @@ -49,16 +50,25 @@ object SessionSettingsNoBlankies { content.take(from - 1) ++ newLines ++ content.drop(to - 1) } - private def cutExpression(l: List[String], name: String): List[String] = l match { + private[sbt] def cutExpression(l: List[String], name: String): List[String] = l match { case h +: t => - val array = h.split(";").filter(_.contains(name)) - array.mkString(";") +: t + val statements = SplitExpressionsNoBlankies(FAKE_FILE, l).settingsTrees + val lastIndex = statements.lastIndexWhere { + tuple => extractSettingName(tuple._2) == name + } + val (statement, tree) = statements(lastIndex) + + if (tree.pos.end >= h.length) { + l + } else { + statement +: t + } case _ => l } private def toTreeStringMap(lines: List[String]) = { - val split = SplitExpressionsNoBlankies(new File("fake"), lines) + val split = SplitExpressionsNoBlankies(FAKE_FILE, lines) val trees = split.settingsTrees val seq = trees.map { case (statement, tree) => diff --git a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala index 82bff4074..00a86f73d 100644 --- a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala @@ -69,7 +69,7 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { def parseStatementAgain(t: Tree, originalStatement: String): String = { val statement = util.Try(toolbox.parse(originalStatement)) match { case util.Failure(th) => - val missingText = tryWithNextStatement(modifiedContent, t.pos.end, t.pos.line, fileName, th) + val missingText = findMissingText(modifiedContent, t.pos.end, t.pos.line, fileName, th) originalStatement + missingText case _ => originalStatement @@ -105,8 +105,9 @@ private[sbt] object BugInParser { * @param th - original exception * @return */ - private[sbt] def tryWithNextStatement(content: String, positionEnd: Int, positionLine: Int, fileName: String, th: Throwable): String = { - findFirstNotBlankNotCommentedIndex(content, positionEnd) match { + private[sbt] def findMissingText(content: String, positionEnd: Int, positionLine: Int, fileName: String, th: Throwable): String = { + // val scanner = new syntaxAnalyzer.UnitScanner(new CompilationUnit(source)) + findClosingBracketIndex(content, positionEnd) match { case Some(index) => content.substring(positionEnd, index + 1) case _ => @@ -120,8 +121,8 @@ private[sbt] object BugInParser { * @param from - start index * @return first not commented index or None */ - private def findFirstNotBlankNotCommentedIndex(content: String, from: Int): Option[Int] = { - val index = content.indexWhere(c => !c.isWhitespace, from) + private[sbt] def findClosingBracketIndex(content: String, from: Int): Option[Int] = { + val index = content.indexWhere(c => c == '}' || c == ')', from) if (index == -1) { None } else { @@ -130,11 +131,11 @@ private[sbt] object BugInParser { val nextChar = content.charAt(index + 1) if (nextChar == '/') { val endOfLine = content.indexOf('\n', index) - findFirstNotBlankNotCommentedIndex(content, endOfLine) + findClosingBracketIndex(content, endOfLine) } else { //if (nextChar == '*') val endOfCommented = content.indexOf("*/", index + 1) - findFirstNotBlankNotCommentedIndex(content, endOfCommented + 2) + findClosingBracketIndex(content, endOfCommented + 2) } } else { Some(index) diff --git a/main/src/test/scala/sbt/ErrorSpec.scala b/main/src/test/scala/sbt/ErrorSpec.scala index 15737f47c..8cc456df9 100644 --- a/main/src/test/scala/sbt/ErrorSpec.scala +++ b/main/src/test/scala/sbt/ErrorSpec.scala @@ -38,7 +38,7 @@ class ErrorSpec extends AbstractSpec with ScalaCheck { | } /* */ // |} """.stripMargin - BugInParser.tryWithNextStatement(buildSbt, buildSbt.length, 2, "fake.txt", new MessageOnlyException("fake")) must throwA[MessageOnlyException] + BugInParser.findMissingText(buildSbt, buildSbt.length, 2, "fake.txt", new MessageOnlyException("fake")) must throwA[MessageOnlyException] } } From 2019c6da62b7e248c440226aabd231915fa3a3ba Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Sat, 27 Sep 2014 10:48:58 +0200 Subject: [PATCH 07/21] findMissingText now searchs recursive --- .../scala/sbt/SessionSettingsNoBlankies.scala | 5 ++- .../sbt/SplitExpressionsNoBlankies.scala | 32 ++++++++----------- .../src/test/scala/sbt/CommentedXmlSpec.scala | 4 +-- .../SessionSettingsCutExpressionSpec.scala | 21 ++++++++++++ 4 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala diff --git a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala index 4f18b7080..8fe3e589c 100644 --- a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala +++ b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala @@ -1,13 +1,12 @@ package sbt -import java.io.File - import scala.collection.immutable.SortedMap import scala.reflect.runtime.universe._ object SessionSettingsNoBlankies { - private val FAKE_FILE = new File("fake") + import SplitExpressionsNoBlankies.FAKE_FILE + val REVERSE_ORDERING_INT = Ordering[Int].reverse def oldLinesToNew(content: List[String], lineMap: SortedMap[Int, List[(Int, List[String])]]): List[String] = diff --git a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala index 00a86f73d..fc91d12fc 100644 --- a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala @@ -9,6 +9,7 @@ import scala.reflect.runtime.universe._ object SplitExpressionsNoBlankies { val END_OF_LINE_CHAR = '\n' val END_OF_LINE = String.valueOf(END_OF_LINE_CHAR) + private[sbt] val FAKE_FILE = new File("fake") } case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { @@ -102,16 +103,22 @@ private[sbt] object BugInParser { * @param positionEnd - from index * @param positionLine - number of start position line * @param fileName - file name - * @param th - original exception + * @param originalException - original exception * @return */ - private[sbt] def findMissingText(content: String, positionEnd: Int, positionLine: Int, fileName: String, th: Throwable): String = { - // val scanner = new syntaxAnalyzer.UnitScanner(new CompilationUnit(source)) + private[sbt] def findMissingText(content: String, positionEnd: Int, positionLine: Int, fileName: String, originalException: Throwable): String = { findClosingBracketIndex(content, positionEnd) match { case Some(index) => - content.substring(positionEnd, index + 1) + val text = content.substring(positionEnd, index + 1) + val textWithoutBracket = text.substring(0, text.length - 1) + util.Try(SplitExpressionsNoBlankies(FAKE_FILE, textWithoutBracket.lines.toSeq)) match { + case util.Success(_) => + text + case util.Failure(th) => + findMissingText(content, index + 1, positionLine, fileName, originalException) + } case _ => - throw new MessageOnlyException(s"""[$fileName]:$positionLine: ${th.getMessage}""".stripMargin) + throw new MessageOnlyException(s"""[$fileName]:$positionLine: ${originalException.getMessage}""".stripMargin) } } @@ -126,20 +133,7 @@ private[sbt] object BugInParser { if (index == -1) { None } else { - val c = content.charAt(index) - if (c == '/' && content.size > index + 1) { - val nextChar = content.charAt(index + 1) - if (nextChar == '/') { - val endOfLine = content.indexOf('\n', index) - findClosingBracketIndex(content, endOfLine) - } else { - //if (nextChar == '*') - val endOfCommented = content.indexOf("*/", index + 1) - findClosingBracketIndex(content, endOfCommented + 2) - } - } else { - Some(index) - } + Some(index) } } } diff --git a/main/src/test/scala/sbt/CommentedXmlSpec.scala b/main/src/test/scala/sbt/CommentedXmlSpec.scala index decf99b1c..18e8b3bf3 100644 --- a/main/src/test/scala/sbt/CommentedXmlSpec.scala +++ b/main/src/test/scala/sbt/CommentedXmlSpec.scala @@ -7,8 +7,8 @@ class CommentedXmlSpec extends CheckIfParsedSpec { s"""| |val pom = "" | - |val aaa= git@github.com:mohiva/play.git - | ewrer + |val aaa= git@a.com:a/a.git + | e | | |val tra = "" diff --git a/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala b/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala new file mode 100644 index 000000000..ebdb9fbd8 --- /dev/null +++ b/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala @@ -0,0 +1,21 @@ +package sbt + +class SessionSettingsCutExpressionSpec extends AbstractSpec { + + "Cut expression " should { + + "Cut only statement which we are interesting " in { + val name = "scalaVersion" + val expression = s"""$name := "2.9.2"""" + val line = s"""name := "newName";$expression; organization := "jozwikr"""" + SessionSettingsNoBlankies.cutExpression(List(line), name) must_== List(expression) + } + + "Do not cut not valid expression " in { + val name = "k4" + val line = s"$name := { val x = $name.value; () }" + SessionSettingsNoBlankies.cutExpression(List(line), name) must_== List(line) + + } + } +} From ff466c5cd3591a888300a23ae2a18fd45ea9b771 Mon Sep 17 00:00:00 2001 From: Jean-Remi Desjardins Date: Mon, 15 Sep 2014 22:38:44 -0700 Subject: [PATCH 08/21] Change additional constructor to what it should probably be I am at a total loss for why this is not compiling. --- ivy/src/main/scala/sbt/IvyInterface.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ivy/src/main/scala/sbt/IvyInterface.scala b/ivy/src/main/scala/sbt/IvyInterface.scala index c5d4db1ea..b42ba89c4 100644 --- a/ivy/src/main/scala/sbt/IvyInterface.scala +++ b/ivy/src/main/scala/sbt/IvyInterface.scala @@ -11,8 +11,8 @@ import org.apache.ivy.util.url.CredentialsStore /** Additional information about a project module */ final case class ModuleInfo(nameFormal: String, description: String = "", homepage: Option[URL] = None, startYear: Option[Int] = None, licenses: Seq[(String, URL)] = Nil, organizationName: String = "", organizationHomepage: Option[URL] = None, scmInfo: Option[ScmInfo] = None, developers: Seq[Developer] = Seq()) { - def this(nameFormal: String, description: String, homepage: Option[URL], startYear: Option[Int], licenses: Seq[(String, URL)], organizationName: String, organizationHomepage: Option[URL], scmInfo: Option[ScmInfo]) = - this(nameFormal = nameFormal, description = description, homepage = homepage, startYear = startYear, licenses = licenses, organizationName = organizationName, organizationHomepage = organizationHomepage, scmInfo = scmInfo, developers = Seq()) + def this(nameFormal: String, description: String = "", homepage: Option[URL] = None, startYear: Option[Int] = None, licenses: Seq[(String, URL)] = Nil, organizationName: String = "", organizationHomepage: Option[URL] = None, scmInfo: Option[ScmInfo] = None) = + this(nameFormal, description, homepage, startYear, licenses, organizationName, organizationHomepage, scmInfo, Seq()) def formally(name: String) = copy(nameFormal = name) def describing(desc: String, home: Option[URL]) = copy(description = desc, homepage = home) def licensed(lics: (String, URL)*) = copy(licenses = lics) @@ -43,4 +43,4 @@ object ConflictManager { val latestCompatible = ConflictManager("latest-compatible") val strict = ConflictManager("strict") val default = latestRevision -} \ No newline at end of file +} From a8370880e03885e61edab43cc7ea7e4b9d11df1e Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 26 Sep 2014 13:26:48 -0400 Subject: [PATCH 09/21] Remove binary compatibility breakages and migrate new parser features into an internal package. --- ivy/src/main/scala/sbt/IvyInterface.scala | 2 +- .../scala/sbt/EvaluateConfigurations.scala | 32 ++++++++++++++++++- .../scala/sbt/SessionSettingsNoBlankies.scala | 3 ++ .../parser}/SplitExpressionsNoBlankies.scala | 18 ++++++----- main/src/test/scala/sbt/AbstractSpec.scala | 5 --- .../SessionSettingsCutExpressionSpec.scala | 2 ++ .../test/scala/sbt/SessionSettingsSpec.scala | 1 + .../sbt/internals/parser/AbstractSpec.scala | 5 +++ .../parser}/CheckIfParsedSpec.scala | 4 ++- .../parser}/CommentedXmlSpec.scala | 2 +- .../parser}/EmbeddedXmlSpec.scala | 4 ++- .../{ => internals/parser}/ErrorSpec.scala | 7 ++-- .../EvaluateConfigurationsOriginal.scala | 4 ++- .../parser}/NewFormatSpec.scala | 5 +-- .../parser}/SplitExpressions.scala | 4 ++- .../parser}/SplitExpressionsBehavior.scala | 2 +- .../parser}/SplitExpressionsFilesTest.scala | 6 ++-- .../parser}/SplitExpressionsTest.scala | 3 +- .../session-update-from-cmd/build.check.2 | 0 19 files changed, 79 insertions(+), 30 deletions(-) rename main/src/main/scala/sbt/{ => internals/parser}/SplitExpressionsNoBlankies.scala (96%) delete mode 100644 main/src/test/scala/sbt/AbstractSpec.scala create mode 100644 main/src/test/scala/sbt/internals/parser/AbstractSpec.scala rename main/src/test/scala/sbt/{ => internals/parser}/CheckIfParsedSpec.scala (94%) rename main/src/test/scala/sbt/{ => internals/parser}/CommentedXmlSpec.scala (98%) rename main/src/test/scala/sbt/{ => internals/parser}/EmbeddedXmlSpec.scala (98%) rename main/src/test/scala/sbt/{ => internals/parser}/ErrorSpec.scala (90%) rename main/src/test/scala/sbt/{ => internals/parser}/EvaluateConfigurationsOriginal.scala (96%) rename main/src/test/scala/sbt/{ => internals/parser}/NewFormatSpec.scala (84%) rename main/src/test/scala/sbt/{ => internals/parser}/SplitExpressions.scala (73%) rename main/src/test/scala/sbt/{ => internals/parser}/SplitExpressionsBehavior.scala (98%) rename main/src/test/scala/sbt/{ => internals/parser}/SplitExpressionsFilesTest.scala (97%) rename main/src/test/scala/sbt/{ => internals/parser}/SplitExpressionsTest.scala (87%) create mode 100644 sbt/src/sbt-test/project/session-update-from-cmd/build.check.2 diff --git a/ivy/src/main/scala/sbt/IvyInterface.scala b/ivy/src/main/scala/sbt/IvyInterface.scala index b42ba89c4..e4c2e8e46 100644 --- a/ivy/src/main/scala/sbt/IvyInterface.scala +++ b/ivy/src/main/scala/sbt/IvyInterface.scala @@ -11,7 +11,7 @@ import org.apache.ivy.util.url.CredentialsStore /** Additional information about a project module */ final case class ModuleInfo(nameFormal: String, description: String = "", homepage: Option[URL] = None, startYear: Option[Int] = None, licenses: Seq[(String, URL)] = Nil, organizationName: String = "", organizationHomepage: Option[URL] = None, scmInfo: Option[ScmInfo] = None, developers: Seq[Developer] = Seq()) { - def this(nameFormal: String, description: String = "", homepage: Option[URL] = None, startYear: Option[Int] = None, licenses: Seq[(String, URL)] = Nil, organizationName: String = "", organizationHomepage: Option[URL] = None, scmInfo: Option[ScmInfo] = None) = + def this(nameFormal: String, description: String, homepage: Option[URL], startYear: Option[Int], licenses: Seq[(String, URL)], organizationName: String, organizationHomepage: Option[URL], scmInfo: Option[ScmInfo]) = this(nameFormal, description, homepage, startYear, licenses, organizationName, organizationHomepage, scmInfo, Seq()) def formally(name: String) = copy(nameFormal = name) def describing(desc: String, home: Option[URL]) = copy(description = desc, homepage = home) diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index f1c200a42..44fdcf29f 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -8,6 +8,9 @@ import compiler.{ Eval, EvalImports } import complete.DefaultParsers.validID import Def.{ ScopedKey, Setting } import Scope.GlobalScope +import sbt.internals.parser.SplitExpressionsNoBlankies + +import scala.annotation.tailrec /** * This file is responsible for compiling the .sbt files used to configure sbt builds. @@ -206,18 +209,45 @@ object EvaluateConfigurations { } } private[this] def isSpace = (c: Char) => Character isWhitespace c + private[this] def fstS(f: String => Boolean): ((String, Int)) => Boolean = { case (s, i) => f(s) } private[this] def firstNonSpaceIs(lit: String) = (_: String).view.dropWhile(isSpace).startsWith(lit) private[this] def or[A](a: A => Boolean, b: A => Boolean): A => Boolean = in => a(in) || b(in) /** * Splits a set of lines into (imports, expressions). That is, * anything on the right of the tuple is a scala expression (definition or setting). */ - def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = + private[sbt] def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { val split = SplitExpressionsNoBlankies(file, lines) (split.imports, split.settings) } + @deprecated("This method is no longer part of the public API.", "0.13.7") + def splitExpressions(lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { + val blank = (_: String).forall(isSpace) + val isImport = firstNonSpaceIs("import ") + val comment = firstNonSpaceIs("//") + val blankOrComment = or(blank, comment) + val importOrBlank = fstS(or(blankOrComment, isImport)) + + val (imports, settings) = lines.zipWithIndex span importOrBlank + (imports filterNot fstS(blankOrComment), groupedLines(settings, blank, blankOrComment)) + } + @deprecated("This method is deprecated and no longer used.", "0.13.7") + def groupedLines(lines: Seq[(String, Int)], delimiter: String => Boolean, skipInitial: String => Boolean): Seq[(String, LineRange)] = + { + val fdelim = fstS(delimiter) + @tailrec def group0(lines: Seq[(String, Int)], accum: Seq[(String, LineRange)]): Seq[(String, LineRange)] = + if (lines.isEmpty) accum.reverse + else { + val start = lines dropWhile fstS(skipInitial) + val (next, tail) = start.span { case (s, _) => !delimiter(s) } + val grouped = if (next.isEmpty) accum else (next.map(_._1).mkString("\n"), LineRange(next.head._2, next.last._2 + 1)) +: accum + group0(tail, grouped) + } + group0(lines, Nil) + } + private[this] def splitSettingsDefinitions(lines: Seq[(String, LineRange)]): (Seq[(String, LineRange)], Seq[(String, LineRange)]) = lines partition { case (line, range) => isDefinition(line) } private[this] def isDefinition(line: String): Boolean = diff --git a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala index 8fe3e589c..823696fce 100644 --- a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala +++ b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala @@ -1,5 +1,8 @@ package sbt +import java.io.File + +import sbt.internals.parser.{ XmlContent, SplitExpressionsNoBlankies } import scala.collection.immutable.SortedMap import scala.reflect.runtime.universe._ diff --git a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala similarity index 96% rename from main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala rename to main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala index fc91d12fc..754a654cd 100644 --- a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala @@ -1,29 +1,31 @@ package sbt +package internals +package parser import java.io.File +import sbt.internals.parser.SplitExpressionsNoBlankies._ import scala.annotation.tailrec -import SplitExpressionsNoBlankies._ +import scala.reflect.runtime.universe import scala.reflect.runtime.universe._ -object SplitExpressionsNoBlankies { +private[sbt] object SplitExpressionsNoBlankies { val END_OF_LINE_CHAR = '\n' val END_OF_LINE = String.valueOf(END_OF_LINE_CHAR) private[sbt] val FAKE_FILE = new File("fake") } -case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { +private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { //settingsTrees needed for "session save" val (imports, settings, settingsTrees) = splitExpressions(file, lines) private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)]) = { - import scala.reflect.runtime._ + import sbt.internals.parser.BugInParser._ + import sbt.internals.parser.XmlContent._ - import scala.tools.reflect.ToolBoxError - import scala.tools.reflect.ToolBox import scala.compat.Platform.EOL import BugInParser._ - import XmlContent._ + import scala.tools.reflect.{ ToolBox, ToolBoxError } val mirror = universe.runtimeMirror(this.getClass.getClassLoader) val toolbox = mirror.mkToolBox(options = "-Yrangepos") @@ -150,7 +152,7 @@ private[sbt] object BugInParser { * rr) * */ -private object XmlContent { +private[sbt] object XmlContent { /** * * @param original - file content diff --git a/main/src/test/scala/sbt/AbstractSpec.scala b/main/src/test/scala/sbt/AbstractSpec.scala deleted file mode 100644 index 28551eb74..000000000 --- a/main/src/test/scala/sbt/AbstractSpec.scala +++ /dev/null @@ -1,5 +0,0 @@ -package sbt - -import org.specs2.mutable._ - -trait AbstractSpec extends Specification with SplitExpression \ No newline at end of file diff --git a/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala b/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala index ebdb9fbd8..17dfa3036 100644 --- a/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala +++ b/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala @@ -1,5 +1,7 @@ package sbt +import sbt.internals.parser.AbstractSpec + class SessionSettingsCutExpressionSpec extends AbstractSpec { "Cut expression " should { diff --git a/main/src/test/scala/sbt/SessionSettingsSpec.scala b/main/src/test/scala/sbt/SessionSettingsSpec.scala index 0f5097478..cc742897f 100644 --- a/main/src/test/scala/sbt/SessionSettingsSpec.scala +++ b/main/src/test/scala/sbt/SessionSettingsSpec.scala @@ -3,6 +3,7 @@ package sbt import java.io.{ File, FilenameFilter } import org.specs2.matcher.MatchResult +import sbt.internals.parser.{ AbstractSpec, SplitExpressionsNoBlankies } import scala.collection.GenTraversableOnce import scala.collection.immutable.{ SortedMap, TreeMap } diff --git a/main/src/test/scala/sbt/internals/parser/AbstractSpec.scala b/main/src/test/scala/sbt/internals/parser/AbstractSpec.scala new file mode 100644 index 000000000..6068bed9b --- /dev/null +++ b/main/src/test/scala/sbt/internals/parser/AbstractSpec.scala @@ -0,0 +1,5 @@ +package sbt.internals.parser + +import org.specs2.mutable._ + +trait AbstractSpec extends Specification with SplitExpression \ No newline at end of file diff --git a/main/src/test/scala/sbt/CheckIfParsedSpec.scala b/main/src/test/scala/sbt/internals/parser/CheckIfParsedSpec.scala similarity index 94% rename from main/src/test/scala/sbt/CheckIfParsedSpec.scala rename to main/src/test/scala/sbt/internals/parser/CheckIfParsedSpec.scala index b1015f400..2a9fdc71e 100644 --- a/main/src/test/scala/sbt/CheckIfParsedSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/CheckIfParsedSpec.scala @@ -1,4 +1,6 @@ -package sbt +package sbt.internals.parser + +import sbt.EvaluateConfigurations abstract class CheckIfParsedSpec(implicit val splitter: SplitExpressions.SplitExpression = EvaluateConfigurations.splitExpressions) extends AbstractSpec { diff --git a/main/src/test/scala/sbt/CommentedXmlSpec.scala b/main/src/test/scala/sbt/internals/parser/CommentedXmlSpec.scala similarity index 98% rename from main/src/test/scala/sbt/CommentedXmlSpec.scala rename to main/src/test/scala/sbt/internals/parser/CommentedXmlSpec.scala index 18e8b3bf3..22a91de10 100644 --- a/main/src/test/scala/sbt/CommentedXmlSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/CommentedXmlSpec.scala @@ -1,4 +1,4 @@ -package sbt +package sbt.internals.parser class CommentedXmlSpec extends CheckIfParsedSpec { diff --git a/main/src/test/scala/sbt/EmbeddedXmlSpec.scala b/main/src/test/scala/sbt/internals/parser/EmbeddedXmlSpec.scala similarity index 98% rename from main/src/test/scala/sbt/EmbeddedXmlSpec.scala rename to main/src/test/scala/sbt/internals/parser/EmbeddedXmlSpec.scala index 4c207179f..200a13e7d 100644 --- a/main/src/test/scala/sbt/EmbeddedXmlSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/EmbeddedXmlSpec.scala @@ -1,4 +1,6 @@ -package sbt +package sbt.internals.parser + +import sbt.MessageOnlyException class EmbeddedXmlSpec extends CheckIfParsedSpec { diff --git a/main/src/test/scala/sbt/ErrorSpec.scala b/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala similarity index 90% rename from main/src/test/scala/sbt/ErrorSpec.scala rename to main/src/test/scala/sbt/internals/parser/ErrorSpec.scala index 8cc456df9..198139fd5 100644 --- a/main/src/test/scala/sbt/ErrorSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala @@ -1,10 +1,9 @@ -package sbt +package sbt.internals.parser import java.io.File -import org.scalacheck.Gen._ -import org.scalacheck.Prop._ import org.specs2.ScalaCheck +import sbt.{ EvaluateConfigurations, MessageOnlyException } import scala.io.Source @@ -14,7 +13,7 @@ class ErrorSpec extends AbstractSpec with ScalaCheck { "Parser " should { "contains file name and line number" in { - val rootPath = getClass.getResource("").getPath + "../error-format/" + val rootPath = getClass.getClassLoader.getResource("").getPath + "/error-format/" println(s"Reading files from: $rootPath") foreach(new File(rootPath).listFiles) { file => print(s"Processing ${file.getName}: ") diff --git a/main/src/test/scala/sbt/EvaluateConfigurationsOriginal.scala b/main/src/test/scala/sbt/internals/parser/EvaluateConfigurationsOriginal.scala similarity index 96% rename from main/src/test/scala/sbt/EvaluateConfigurationsOriginal.scala rename to main/src/test/scala/sbt/internals/parser/EvaluateConfigurationsOriginal.scala index dfa3a2420..b7c53c7c5 100644 --- a/main/src/test/scala/sbt/EvaluateConfigurationsOriginal.scala +++ b/main/src/test/scala/sbt/internals/parser/EvaluateConfigurationsOriginal.scala @@ -1,7 +1,9 @@ -package sbt +package sbt.internals.parser import java.io.File +import sbt.LineRange + import scala.annotation.tailrec object EvaluateConfigurationsOriginal { diff --git a/main/src/test/scala/sbt/NewFormatSpec.scala b/main/src/test/scala/sbt/internals/parser/NewFormatSpec.scala similarity index 84% rename from main/src/test/scala/sbt/NewFormatSpec.scala rename to main/src/test/scala/sbt/internals/parser/NewFormatSpec.scala index ee5a31493..a540c318b 100644 --- a/main/src/test/scala/sbt/NewFormatSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/NewFormatSpec.scala @@ -1,9 +1,10 @@ -package sbt +package sbt.internals.parser import java.io.File import org.junit.runner.RunWith import org.specs2.runner.JUnitRunner +import sbt.EvaluateConfigurations import scala.io.Source @@ -13,7 +14,7 @@ class NewFormatSpec extends AbstractSpec { "New Format " should { "Handle lines " in { - val rootPath = getClass.getResource("").getPath + "../new-format/" + val rootPath = getClass.getClassLoader.getResource("").getPath + "/new-format/" println(s"Reading files from: $rootPath") val allFiles = new File(rootPath).listFiles.toList foreach(allFiles) { diff --git a/main/src/test/scala/sbt/SplitExpressions.scala b/main/src/test/scala/sbt/internals/parser/SplitExpressions.scala similarity index 73% rename from main/src/test/scala/sbt/SplitExpressions.scala rename to main/src/test/scala/sbt/internals/parser/SplitExpressions.scala index 1c08ac9ab..a93dde00d 100644 --- a/main/src/test/scala/sbt/SplitExpressions.scala +++ b/main/src/test/scala/sbt/internals/parser/SplitExpressions.scala @@ -1,7 +1,9 @@ -package sbt +package sbt.internals.parser import java.io.File +import sbt.LineRange + object SplitExpressions { type SplitExpression = (File, Seq[String]) => (Seq[(String, Int)], Seq[(String, LineRange)]) } diff --git a/main/src/test/scala/sbt/SplitExpressionsBehavior.scala b/main/src/test/scala/sbt/internals/parser/SplitExpressionsBehavior.scala similarity index 98% rename from main/src/test/scala/sbt/SplitExpressionsBehavior.scala rename to main/src/test/scala/sbt/internals/parser/SplitExpressionsBehavior.scala index 246a29e11..fd44478a5 100644 --- a/main/src/test/scala/sbt/SplitExpressionsBehavior.scala +++ b/main/src/test/scala/sbt/internals/parser/SplitExpressionsBehavior.scala @@ -1,4 +1,4 @@ -package sbt +package sbt.internals.parser import java.io.File diff --git a/main/src/test/scala/sbt/SplitExpressionsFilesTest.scala b/main/src/test/scala/sbt/internals/parser/SplitExpressionsFilesTest.scala similarity index 97% rename from main/src/test/scala/sbt/SplitExpressionsFilesTest.scala rename to main/src/test/scala/sbt/internals/parser/SplitExpressionsFilesTest.scala index 9116e4721..704e12366 100644 --- a/main/src/test/scala/sbt/SplitExpressionsFilesTest.scala +++ b/main/src/test/scala/sbt/internals/parser/SplitExpressionsFilesTest.scala @@ -1,4 +1,6 @@ package sbt +package internals +package parser import java.io.File @@ -8,7 +10,7 @@ import scala.annotation.tailrec import scala.io.Source import scala.tools.reflect.ToolBoxError -class SplitExpressionsFilesTest extends AbstractSplitExpressionsFilesTest("../old-format/") +class SplitExpressionsFilesTest extends AbstractSplitExpressionsFilesTest("/old-format/") abstract class AbstractSplitExpressionsFilesTest(pathName: String) extends Specification { @@ -23,7 +25,7 @@ abstract class AbstractSplitExpressionsFilesTest(pathName: String) extends Speci s"$getClass " should { "split whole sbt files" in { - val rootPath = getClass.getResource("").getPath + pathName + val rootPath = getClass.getClassLoader.getResource("").getPath + pathName println(s"Reading files from: $rootPath") val allFiles = new File(rootPath).listFiles.toList diff --git a/main/src/test/scala/sbt/SplitExpressionsTest.scala b/main/src/test/scala/sbt/internals/parser/SplitExpressionsTest.scala similarity index 87% rename from main/src/test/scala/sbt/SplitExpressionsTest.scala rename to main/src/test/scala/sbt/internals/parser/SplitExpressionsTest.scala index bb172a2e4..82dfdf0b2 100644 --- a/main/src/test/scala/sbt/SplitExpressionsTest.scala +++ b/main/src/test/scala/sbt/internals/parser/SplitExpressionsTest.scala @@ -1,6 +1,7 @@ -package sbt +package sbt.internals.parser import org.specs2.mutable.Specification +import sbt.EvaluateConfigurations class SplitExpressionsTest extends Specification with SplitExpressionsBehavior { diff --git a/sbt/src/sbt-test/project/session-update-from-cmd/build.check.2 b/sbt/src/sbt-test/project/session-update-from-cmd/build.check.2 new file mode 100644 index 000000000..e69de29bb From 1559b67c24c8f996e1a7ac348c380ea5bd117181 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 26 Sep 2014 13:48:40 -0400 Subject: [PATCH 10/21] Acceptance tests for and new .sbt parser. --- .../sbt-test/project/session-save/build.check.1 | 8 ++++++++ .../sbt-test/project/session-save/build.check.2 | 8 ++++++++ .../sbt-test/project/session-save/build.check.3 | 9 +++++++++ .../sbt-test/project/session-save/build.check.4 | 17 +++++++++++++++++ .../sbt-test/project/session-save/build.check.5 | 15 +++++++++++++++ sbt/src/sbt-test/project/session-save/build.sbt | 8 ++++++++ .../project/session-save/project/Build.scala | 3 +++ sbt/src/sbt-test/project/session-save/test | 10 +++++++++- .../session-update-from-cmd/build.check.1 | 2 ++ .../session-update-from-cmd/build.check.2 | 15 +++++++++++++++ .../project/session-update-from-cmd/build.sbt | 2 ++ .../session-update-from-cmd/project/build.scala | 17 ++++++++++++++++- .../project/session-update-from-cmd/test | 2 ++ 13 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 sbt/src/sbt-test/project/session-save/build.check.4 create mode 100644 sbt/src/sbt-test/project/session-save/build.check.5 diff --git a/sbt/src/sbt-test/project/session-save/build.check.1 b/sbt/src/sbt-test/project/session-save/build.check.1 index f1dd6431d..6a74daa67 100755 --- a/sbt/src/sbt-test/project/session-save/build.check.1 +++ b/sbt/src/sbt-test/project/session-save/build.check.1 @@ -2,3 +2,11 @@ k1 := {error("k1")} k2 <<= k1 map identity +k3 := { + + + val x = "hi" + () +} + +k4 := { }; k5 := () \ No newline at end of file diff --git a/sbt/src/sbt-test/project/session-save/build.check.2 b/sbt/src/sbt-test/project/session-save/build.check.2 index 2bb4dc49d..99b769797 100755 --- a/sbt/src/sbt-test/project/session-save/build.check.2 +++ b/sbt/src/sbt-test/project/session-save/build.check.2 @@ -2,3 +2,11 @@ k1 := {} k2 <<= k1 map identity +k3 := { + + + val x = "hi" + () +} + +k4 := { }; k5 := () diff --git a/sbt/src/sbt-test/project/session-save/build.check.3 b/sbt/src/sbt-test/project/session-save/build.check.3 index 0c0b932c5..0acc048b2 100755 --- a/sbt/src/sbt-test/project/session-save/build.check.3 +++ b/sbt/src/sbt-test/project/session-save/build.check.3 @@ -2,5 +2,14 @@ k1 := {} k2 := {} +k3 := { + + + val x = "hi" + () +} + +k4 := { }; k5 := () + k1 <<= k1 map {_ => error("k1")} diff --git a/sbt/src/sbt-test/project/session-save/build.check.4 b/sbt/src/sbt-test/project/session-save/build.check.4 new file mode 100644 index 000000000..693c6cef6 --- /dev/null +++ b/sbt/src/sbt-test/project/session-save/build.check.4 @@ -0,0 +1,17 @@ +k1 := {} + +k2 := {} + +k3 := { + + + val x = "hi" + () +} + +k4 := { }; k5 := () + +k1 <<= k1 map {_ => error("k1")} + +k4 := { val x = k4.value; () } + diff --git a/sbt/src/sbt-test/project/session-save/build.check.5 b/sbt/src/sbt-test/project/session-save/build.check.5 new file mode 100644 index 000000000..11a7d317b --- /dev/null +++ b/sbt/src/sbt-test/project/session-save/build.check.5 @@ -0,0 +1,15 @@ +k1 := {} + +k2 := {} + +k3 := { + + + val x = "hi" + () +} + +k4 := (); k5 := () + +k1 <<= k1 map {_ => error("k1")} + diff --git a/sbt/src/sbt-test/project/session-save/build.sbt b/sbt/src/sbt-test/project/session-save/build.sbt index d0208fa47..b0d216327 100755 --- a/sbt/src/sbt-test/project/session-save/build.sbt +++ b/sbt/src/sbt-test/project/session-save/build.sbt @@ -4,3 +4,11 @@ k1 := { k2 := { } +k3 := { + + + val x = "hi" + () +} + +k4 := { }; k5 := () \ No newline at end of file diff --git a/sbt/src/sbt-test/project/session-save/project/Build.scala b/sbt/src/sbt-test/project/session-save/project/Build.scala index b7ba35d2f..2454b14e8 100755 --- a/sbt/src/sbt-test/project/session-save/project/Build.scala +++ b/sbt/src/sbt-test/project/session-save/project/Build.scala @@ -3,6 +3,9 @@ import sbt._ object TestBuild extends Build { val k1 = TaskKey[Unit]("k1") val k2 = TaskKey[Unit]("k2") + val k3 = TaskKey[Unit]("k3") + val k4 = TaskKey[Unit]("k4") + val k5 = TaskKey[Unit]("k4") lazy val root = Project("root", file(".")) } diff --git a/sbt/src/sbt-test/project/session-save/test b/sbt/src/sbt-test/project/session-save/test index 58f8a7b46..48f9dc28f 100755 --- a/sbt/src/sbt-test/project/session-save/test +++ b/sbt/src/sbt-test/project/session-save/test @@ -22,4 +22,12 @@ $ must-mirror build.sbt build.check.2 > reload -> k1 > k2 -$ must-mirror build.sbt build.check.3 \ No newline at end of file +$ must-mirror build.sbt build.check.3 + +> set k4 := { val x = k4.value; () } +> session save +$ must-mirror build.sbt exbuild.check.4 + +> set k4 := () +> session save +$ must-mirror build.sbt build.check.5 \ No newline at end of file diff --git a/sbt/src/sbt-test/project/session-update-from-cmd/build.check.1 b/sbt/src/sbt-test/project/session-update-from-cmd/build.check.1 index 6363b1678..019984510 100644 --- a/sbt/src/sbt-test/project/session-update-from-cmd/build.check.1 +++ b/sbt/src/sbt-test/project/session-update-from-cmd/build.check.1 @@ -8,3 +8,5 @@ k1 := { k2 := { println("This is k2") } + +val x = 5; k3 := {}; k4 := {} \ No newline at end of file diff --git a/sbt/src/sbt-test/project/session-update-from-cmd/build.check.2 b/sbt/src/sbt-test/project/session-update-from-cmd/build.check.2 index e69de29bb..f9128e0be 100644 --- a/sbt/src/sbt-test/project/session-update-from-cmd/build.check.2 +++ b/sbt/src/sbt-test/project/session-update-from-cmd/build.check.2 @@ -0,0 +1,15 @@ +name := "projectName" + +k1 := { +// +// +} + +k2 := { + println("This is k2") +} + +val x = 5; k3 := { +// +// +}; k4 := {} \ No newline at end of file diff --git a/sbt/src/sbt-test/project/session-update-from-cmd/build.sbt b/sbt/src/sbt-test/project/session-update-from-cmd/build.sbt index 8bd18ad5c..fdd333a83 100644 --- a/sbt/src/sbt-test/project/session-update-from-cmd/build.sbt +++ b/sbt/src/sbt-test/project/session-update-from-cmd/build.sbt @@ -5,3 +5,5 @@ k1 := {} k2 := { println("This is k2") } + +val x = 5; k3 := {}; k4 := {} \ No newline at end of file diff --git a/sbt/src/sbt-test/project/session-update-from-cmd/project/build.scala b/sbt/src/sbt-test/project/session-update-from-cmd/project/build.scala index c7c6c8238..06c554f22 100644 --- a/sbt/src/sbt-test/project/session-update-from-cmd/project/build.scala +++ b/sbt/src/sbt-test/project/session-update-from-cmd/project/build.scala @@ -4,6 +4,8 @@ import Keys._ object build extends Build { lazy val k1 = taskKey[Unit]("") lazy val k2 = taskKey[Unit]("") + lazy val k3 = taskKey[Unit]("") + lazy val k4 = taskKey[Unit]("") val UpdateK1 = Command.command("UpdateK1") { st: State => val ex = Project extract st @@ -17,8 +19,21 @@ object build extends Build { SessionSettings.saveAllSettings(st1) } + + val UpdateK3 = Command.command("UpdateK3") { st: State => + val ex = Project extract st + import ex._ + val session2 = BuiltinCommands.setThis(st, ex, Seq(k3 := {}), """k3 := { + |// + |// + |}""".stripMargin).session + val st1 = BuiltinCommands.reapply(session2, structure, st) + // SessionSettings.writeSettings(ex.currentRef, session2, ex.session.original, ex.structure) + SessionSettings.saveAllSettings(st1) + } + lazy val root = Project("root", file(".")) settings( - commands += UpdateK1 + commands ++= Seq(UpdateK1, UpdateK3) ) } diff --git a/sbt/src/sbt-test/project/session-update-from-cmd/test b/sbt/src/sbt-test/project/session-update-from-cmd/test index d29ba8270..428b462ba 100644 --- a/sbt/src/sbt-test/project/session-update-from-cmd/test +++ b/sbt/src/sbt-test/project/session-update-from-cmd/test @@ -2,3 +2,5 @@ $ must-mirror build.sbt build.check.1 > UpdateK1 $ must-mirror build.sbt build.check.1 +> UpdateK3 +$ must-mirror build.sbt build.check.2 From 48be806dd43bd11d30b95b287510b6fa2c92d96c Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 26 Sep 2014 14:20:43 -0400 Subject: [PATCH 11/21] Adding scaladoc commnets for SessionSettings, and fixing type in check file. --- main/src/main/scala/sbt/SessionSettings.scala | 85 +++++++++++++++++++ sbt/src/sbt-test/project/session-save/test | 2 +- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/SessionSettings.scala b/main/src/main/scala/sbt/SessionSettings.scala index 921bd5db9..33ff92966 100755 --- a/main/src/main/scala/sbt/SessionSettings.scala +++ b/main/src/main/scala/sbt/SessionSettings.scala @@ -14,13 +14,63 @@ import SessionSettings._ import scala.collection.immutable.SortedMap + +/** + * Represents (potentially) transient settings added into a build via commands/user. + * + * @param currentBuild + * The current sbt build with which we scope new settings + * @param currentProject + * The current project with which we scope new settings. + * @param original + * The original list of settings for this build. + * @param append + * Settings which have been defined and appended that may ALSO be saved to disk. + * @param rawAppend + * Settings which have been defined and appended which CANNOT be saved to disk + * @param currentEval + * A compiler we can use to compile new setting strings. + */ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, String], original: Seq[Setting[_]], append: SessionMap, rawAppend: Seq[Setting[_]], currentEval: () => Eval) { assert(currentProject contains currentBuild, "Current build (" + currentBuild + ") not associated with a current project.") + + /** + * Modifiy the current state. + * + * @param build The buid with which we scope new settings. + * @param project The project reference with which we scope new settings. + * @param eval The mechanism to compile new settings. + * @return A new SessionSettings object + */ def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings = copy(currentBuild = build, currentProject = currentProject.updated(build, project), currentEval = eval) + + /** + * @return The current ProjectRef with which we scope settings. + */ def current: ProjectRef = ProjectRef(currentBuild, currentProject(currentBuild)) + + /** + * Appends a set of settings which can be persisted to disk + * @param s A sequence of SessionSetting objects, which contain a Setting[_] and a string. + * @return A new SessionSettings which contains this new sequence. + */ def appendSettings(s: Seq[SessionSetting]): SessionSettings = copy(append = modify(append, _ ++ s)) + + /** + * Appends a set of raw Setting[_] objects to the current session. + * @param ss The raw settings to include + * @return A new SessionSettings with the appeneded settings. + */ def appendRaw(ss: Seq[Setting[_]]): SessionSettings = copy(rawAppend = rawAppend ++ ss) + + /** + * @return A combined list of all Setting[_] objects for the current session, in priority order. + */ def mergeSettings: Seq[Setting[_]] = original ++ merge(append) ++ rawAppend + + /** + * @return A new SessionSettings object where additional transient settings are removed. + */ def clearExtraSettings: SessionSettings = copy(append = Map.empty, rawAppend = Nil) private[this] def merge(map: SessionMap): Seq[Setting[_]] = map.values.toSeq.flatten[SessionSetting].map(_._1) @@ -31,17 +81,33 @@ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, Str } } object SessionSettings { + /** A session setting is simply a tuple of a Setting[_] and the strings which define it. */ type SessionSetting = (Setting[_], List[String]) type SessionMap = Map[ProjectRef, Seq[SessionSetting]] + /** This will re-evaluate all Setting[_]'s on this session against the current build state and + * return the new build state. + */ def reapply(session: SessionSettings, s: State): State = BuiltinCommands.reapply(session, Project.structure(s), s) + /** This will clear any user-added session settings for a given build state and return the new build state. + * + * Note: Does not clear `rawAppend` settings + */ def clearSettings(s: State): State = withSettings(s)(session => reapply(session.copy(append = session.append - session.current), s)) + /** This will clear ALL transient session settings in a given build state, returning the new build state. */ def clearAllSettings(s: State): State = withSettings(s)(session => reapply(session.clearExtraSettings, s)) + /** + * A convenience method to alter the current build state using the current SessionSettings. + * + * @param s The current build state + * @param f A function which takes the current SessionSettings and returns the new build state. + * @return The new build state + */ def withSettings(s: State)(f: SessionSettings => State): State = { val extracted = Project extract s @@ -53,12 +119,18 @@ object SessionSettings { f(session) } + /** Adds `s` to a strings when needed. Maybe one day we'll care about non-english languages. */ def pluralize(size: Int, of: String) = size.toString + (if (size == 1) of else (of + "s")) + + /** Checks to see if any session settings are being discarded and issues a warning. */ def checkSession(newSession: SessionSettings, oldState: State) { val oldSettings = (oldState get Keys.sessionSettings).toList.flatMap(_.append).flatMap(_._2) if (newSession.append.isEmpty && !oldSettings.isEmpty) oldState.log.warn("Discarding " + pluralize(oldSettings.size, " session setting") + ". Use 'session save' to persist session settings.") } + + + def removeRanges[T](in: Seq[T], ranges: Seq[(Int, Int)]): Seq[T] = { val asSet = (Set.empty[Int] /: ranges) { case (s, (hi, lo)) => s ++ (hi to lo) } @@ -70,12 +142,20 @@ object SessionSettings { val newAppend = session.append.updated(current, removeRanges(session.append.getOrElse(current, Nil), ranges)) reapply(session.copy(append = newAppend), s) } + /** Saves *all* session settings to disk for all projects. */ def saveAllSettings(s: State): State = saveSomeSettings(s)(_ => true) + /** Saves the session settings to disk for the current project. */ def saveSettings(s: State): State = { val current = Project.session(s).current saveSomeSettings(s)(_ == current) } + + /** Saves session settings to disk if they match the filter. + * @param s The build state + * @param include A filter function to determine which project's settings to persist. + * @return The new build state. + */ def saveSomeSettings(s: State)(include: ProjectRef => Boolean): State = withSettings(s) { session => val newSettings = @@ -131,6 +211,8 @@ object SessionSettings { } def needsTrailingBlank(lines: Seq[String]) = !lines.isEmpty && !lines.takeRight(1).exists(_.trim.isEmpty) + + /** Prints all the user-defined SessionSettings (not raw) to System.out. */ def printAllSettings(s: State): State = withSettings(s) { session => for ((ref, settings) <- session.append if !settings.isEmpty) { @@ -181,6 +263,7 @@ save, save-all The session settings defined for a project are appended to the first '.sbt' configuration file in that project. If no '.sbt' configuration file exists, the settings are written to 'build.sbt' in the project's base directory.""" + /** AST for the syntax of the session command. Each subclass is an action that can be performed. */ sealed trait SessionCommand final class Print(val all: Boolean) extends SessionCommand final class Clear(val all: Boolean) extends SessionCommand @@ -190,6 +273,7 @@ save, save-all import complete._ import DefaultParsers._ + /** Parser for the session command. */ lazy val parser = token(Space) ~> (token("list-all" ^^^ new Print(true)) | token("list" ^^^ new Print(false)) | token("clear" ^^^ new Clear(false)) | @@ -200,6 +284,7 @@ save, save-all def natSelect = rep1sep(token(range, ""), ',') def range: Parser[(Int, Int)] = (NatBasic ~ ('-' ~> NatBasic).?).map { case lo ~ hi => (lo, hi getOrElse lo) } + /** The raw implementation of the sessoin command. */ def command(s: State) = Command.applyEffect(parser) { case p: Print => if (p.all) printAllSettings(s) else printSettings(s) case v: Save => if (v.all) saveAllSettings(s) else saveSettings(s) diff --git a/sbt/src/sbt-test/project/session-save/test b/sbt/src/sbt-test/project/session-save/test index 48f9dc28f..f5617fc98 100755 --- a/sbt/src/sbt-test/project/session-save/test +++ b/sbt/src/sbt-test/project/session-save/test @@ -26,7 +26,7 @@ $ must-mirror build.sbt build.check.3 > set k4 := { val x = k4.value; () } > session save -$ must-mirror build.sbt exbuild.check.4 +$ must-mirror build.sbt build.check.4 > set k4 := () > session save From 9294351e24db8980b8fe6e50faa1f3739343c224 Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Mon, 29 Sep 2014 22:40:44 +0200 Subject: [PATCH 12/21] #1628 --- main/src/main/scala/sbt/SessionSettings.scala | 46 +++++----- .../scala/sbt/SessionSettingsNoBlankies.scala | 87 +++++++------------ .../parser/SplitExpressionsNoBlankies.scala | 56 ++++++------ .../session-settings/1.sbt.txt_1/2.xml.result | 4 +- .../SessionSettingsCutExpressionSpec.scala | 23 ----- .../test/scala/sbt/SessionSettingsSpec.scala | 13 ++- 6 files changed, 86 insertions(+), 143 deletions(-) delete mode 100644 main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala diff --git a/main/src/main/scala/sbt/SessionSettings.scala b/main/src/main/scala/sbt/SessionSettings.scala index 33ff92966..24789a3c2 100755 --- a/main/src/main/scala/sbt/SessionSettings.scala +++ b/main/src/main/scala/sbt/SessionSettings.scala @@ -6,15 +6,11 @@ package sbt import java.io.File import java.net.URI import Def.{ ScopedKey, Setting } -import Project._ import Types.Endo import compiler.Eval import SessionSettings._ -import scala.collection.immutable.SortedMap - - /** * Represents (potentially) transient settings added into a build via commands/user. * @@ -85,16 +81,18 @@ object SessionSettings { type SessionSetting = (Setting[_], List[String]) type SessionMap = Map[ProjectRef, Seq[SessionSetting]] - /** This will re-evaluate all Setting[_]'s on this session against the current build state and - * return the new build state. - */ + /** + * This will re-evaluate all Setting[_]'s on this session against the current build state and + * return the new build state. + */ def reapply(session: SessionSettings, s: State): State = BuiltinCommands.reapply(session, Project.structure(s), s) - /** This will clear any user-added session settings for a given build state and return the new build state. - * - * Note: Does not clear `rawAppend` settings - */ + /** + * This will clear any user-added session settings for a given build state and return the new build state. + * + * Note: Does not clear `rawAppend` settings + */ def clearSettings(s: State): State = withSettings(s)(session => reapply(session.copy(append = session.append - session.current), s)) /** This will clear ALL transient session settings in a given build state, returning the new build state. */ @@ -128,9 +126,6 @@ object SessionSettings { if (newSession.append.isEmpty && !oldSettings.isEmpty) oldState.log.warn("Discarding " + pluralize(oldSettings.size, " session setting") + ". Use 'session save' to persist session settings.") } - - - def removeRanges[T](in: Seq[T], ranges: Seq[(Int, Int)]): Seq[T] = { val asSet = (Set.empty[Int] /: ranges) { case (s, (hi, lo)) => s ++ (hi to lo) } @@ -151,11 +146,12 @@ object SessionSettings { saveSomeSettings(s)(_ == current) } - /** Saves session settings to disk if they match the filter. - * @param s The build state - * @param include A filter function to determine which project's settings to persist. - * @return The new build state. - */ + /** + * Saves session settings to disk if they match the filter. + * @param s The build state + * @param include A filter function to determine which project's settings to persist. + * @return The new build state. + */ def saveSomeSettings(s: State)(include: ProjectRef => Boolean): State = withSettings(s) { session => val newSettings = @@ -182,23 +178,21 @@ object SessionSettings { } } - val (_, oldShifted, replace, lineMap) = ((0, List[Setting[_]](), List[SessionSetting](), SortedMap.empty[Int, List[(Int, List[String])]](Ordering[Int].reverse)) /: inFile) { - case ((offs, olds, repl, lineMap), s) => + val (_, oldShifted, replace, statements) = ((0, List[Setting[_]](), List[SessionSetting](), List[List[String]]()) /: inFile) { + case ((offs, olds, repl, statements), s) => val RangePosition(_, r @ LineRange(start, end)) = s.pos settings find (_._1.key == s.key) match { case Some(ss @ (ns, newLines)) if !ns.init.dependencies.contains(ns.key) => val shifted = ns withPos RangePosition(path, LineRange(start - offs, start - offs + newLines.size)) - val head = (end, newLines) - val seq = lineMap.getOrElse(start, List()) - (offs + end - start - newLines.size, shifted :: olds, ss :: repl, lineMap + (start -> (head +: seq))) + (offs + end - start - newLines.size, shifted :: olds, ss :: repl, newLines +: statements) case _ => val shifted = s withPos RangePosition(path, r shift -offs) - (offs, shifted :: olds, repl, lineMap) + (offs, shifted :: olds, repl, statements) } } val newSettings = settings diff replace val oldContent = IO.readLines(writeTo) - val exist: List[String] = SessionSettingsNoBlankies.oldLinesToNew(oldContent, lineMap) + val exist: List[String] = SessionSettingsNoBlankies.oldLinesToNew(oldContent, statements) val adjusted = if (!newSettings.isEmpty && needsTrailingBlank(exist)) exist :+ "" else exist val lines = adjusted ++ newSettings.flatMap(_._2 ::: "" :: Nil) IO.writeLines(writeTo, lines) diff --git a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala index 823696fce..191793b17 100644 --- a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala +++ b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala @@ -1,10 +1,7 @@ package sbt -import java.io.File - -import sbt.internals.parser.{ XmlContent, SplitExpressionsNoBlankies } -import scala.collection.immutable.SortedMap import scala.reflect.runtime.universe._ +import sbt.internals.parser.{ XmlContent, SplitExpressionsNoBlankies } object SessionSettingsNoBlankies { @@ -12,65 +9,43 @@ object SessionSettingsNoBlankies { val REVERSE_ORDERING_INT = Ordering[Int].reverse - def oldLinesToNew(content: List[String], lineMap: SortedMap[Int, List[(Int, List[String])]]): List[String] = - if (lineMap.isEmpty) { - content - } else { - val head = lineMap.head - val newContent = toNewContent(content, head) - oldLinesToNew(newContent, lineMap.tail) - } - - private def toNewContent(content: List[String], setCommands: (Int, List[(Int, List[String])])): List[String] = { - val (from, newSettings) = setCommands - - val newTreeStringsMap = newSettings.map { - case (_, lines) => toTreeStringMap(lines) - } - val to = newSettings.map(_._1).max - val originalLine = content.slice(from - 1, to - 1) - - val operations = newTreeStringsMap.flatMap { - map => + def oldLinesToNew(lines: List[String], setCommands: List[List[String]]): List[String] = { + val split = SplitExpressionsNoBlankies(FAKE_FILE, lines) + val recordedCommand = setCommands.flatMap { + command => + val map = toTreeStringMap(command) map.flatMap { - case (name, (startIndex, statement)) => - val validLines = cutExpression(originalLine, name) - val treeStringMap = toTreeStringMap(validLines) - treeStringMap.get(name).map { - case (t, oldContent) => - (startIndex, oldContent, statement) + case (name, (startPos, statement)) => + split.settingsTrees.foldLeft(Seq.empty[(Int, String, String)]) { + case (acc, (statement, tree)) => + val treeName = extractSettingName(tree) + if (name == treeName) { + val replacement = if (acc.isEmpty) { + command.mkString("\n") + } else { + "" + } + (tree.pos.start, statement, replacement) +: acc + } else { + acc + } } } } - val statements = XmlContent.handleXmlContent(originalLine.mkString("\n")) - val sortedOperations = operations.sortBy(_._1)(REVERSE_ORDERING_INT) - val newContent = sortedOperations.foldLeft(statements) { - case (acc, (startIndex, old, newStatement)) => - acc.replace(old, newStatement) + val sortedRecordedCommand = recordedCommand.sortBy(_._1)(REVERSE_ORDERING_INT) + + val newContent = sortedRecordedCommand.foldLeft(split.modifiedContent) { + case (acc, (from, old, replacement)) => + val before = acc.substring(0, from) + val after = acc.substring(from + old.length, acc.length) + before + replacement + after + // acc.replace(old, replacement) } - val newLines = newContent.lines.toList - content.take(from - 1) ++ newLines ++ content.drop(to - 1) + newContent.lines.toList } - private[sbt] def cutExpression(l: List[String], name: String): List[String] = l match { - case h +: t => - val statements = SplitExpressionsNoBlankies(FAKE_FILE, l).settingsTrees - val lastIndex = statements.lastIndexWhere { - tuple => extractSettingName(tuple._2) == name - } - val (statement, tree) = statements(lastIndex) - - if (tree.pos.end >= h.length) { - l - } else { - statement +: t - } - case _ => - l - } - - private def toTreeStringMap(lines: List[String]) = { - val split = SplitExpressionsNoBlankies(FAKE_FILE, lines) + private def toTreeStringMap(command: List[String]) = { + val split = SplitExpressionsNoBlankies(FAKE_FILE, command) val trees = split.settingsTrees val seq = trees.map { case (statement, tree) => diff --git a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala index 754a654cd..b975f5226 100644 --- a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala @@ -4,9 +4,8 @@ package parser import java.io.File -import sbt.internals.parser.SplitExpressionsNoBlankies._ +import SplitExpressionsNoBlankies._ import scala.annotation.tailrec -import scala.reflect.runtime.universe import scala.reflect.runtime.universe._ private[sbt] object SplitExpressionsNoBlankies { @@ -17,14 +16,14 @@ private[sbt] object SplitExpressionsNoBlankies { private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { //settingsTrees needed for "session save" - val (imports, settings, settingsTrees) = splitExpressions(file, lines) + val (imports, settings, settingsTrees, modifiedContent) = splitExpressions(file, lines) - private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)]) = { - import sbt.internals.parser.BugInParser._ - import sbt.internals.parser.XmlContent._ + private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)], String) = { + import scala.reflect.runtime._ import scala.compat.Platform.EOL import BugInParser._ + import XmlContent._ import scala.tools.reflect.{ ToolBox, ToolBoxError } val mirror = universe.runtimeMirror(this.getClass.getClassLoader) @@ -90,7 +89,7 @@ private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String None } val statementsTreeLineRange = statements flatMap convertStatement - (imports map convertImport, statementsTreeLineRange.map(t => (t._1, t._3)), statementsTreeLineRange.map(t => (t._1, t._2))) + (imports map convertImport, statementsTreeLineRange.map(t => (t._1, t._3)), statementsTreeLineRange.map(t => (t._1, t._2)), modifiedContent) } } @@ -302,31 +301,32 @@ private[sbt] object XmlContent { */ private def addExplicitXmlContent(content: String, xmlParts: Seq[(String, Int, Int)]): String = { val statements: Seq[(String, Boolean)] = splitFile(content, xmlParts) - val (builder, wasPreviousXml, wasXml, _) = statements.foldLeft((Seq.empty[String], false, false, "")) { - (acc, el) => - val (bAcc, wasXml, _, previous) = acc - val (content, isXml) = el - val contentEmpty = content.trim.isEmpty - val (isNotCommentedXml, newAcc) = if (isXml) { - if (!wasXml) { - if (areBracketsNecessary(previous)) { - (true, " ( " +: bAcc) - } else { - (false, bAcc) - } - } else { - (true, bAcc) - } - } else if (wasXml && !contentEmpty) { - (false, " ) " +: bAcc) + val (builder, addCloseBrackets, wasXml, _) = statements.foldLeft((Seq.empty[String], false, false, "")) { + case ((bAcc, addCloseBrackets, wasXml, previous), (content, isXml)) => + if (content.trim.isEmpty) { + (content +: bAcc, addCloseBrackets, wasXml, content) } else { - (false, bAcc) - } + val (newAddCloseBrackets, newAcc) = if (isXml) { + if (wasXml) { + (addCloseBrackets, bAcc) + } else { + if (areBracketsNecessary(previous)) { + (true, " ( " +: bAcc) + } else { + (false, bAcc) + } + } + } else if (addCloseBrackets) { + (false, " ) " +: bAcc) + } else { + (false, bAcc) + } - (content +: newAcc, isNotCommentedXml || (wasXml && contentEmpty), isXml, content) + (content +: newAcc, newAddCloseBrackets, isXml, content) + } } val closeIfNecessaryBuilder = - if (wasPreviousXml && !wasXml) { + if (addCloseBrackets && wasXml) { builder.head +: " ) " +: builder.tail } else { builder diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result index e1b25cd91..9ef907fc3 100644 --- a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result @@ -18,10 +18,10 @@ val a = */ -organization := "jozwikr" // OK +organization := "scalania" // OK scalaVersion := "2.9.2" -organization := "scalania" + diff --git a/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala b/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala deleted file mode 100644 index 17dfa3036..000000000 --- a/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala +++ /dev/null @@ -1,23 +0,0 @@ -package sbt - -import sbt.internals.parser.AbstractSpec - -class SessionSettingsCutExpressionSpec extends AbstractSpec { - - "Cut expression " should { - - "Cut only statement which we are interesting " in { - val name = "scalaVersion" - val expression = s"""$name := "2.9.2"""" - val line = s"""name := "newName";$expression; organization := "jozwikr"""" - SessionSettingsNoBlankies.cutExpression(List(line), name) must_== List(expression) - } - - "Do not cut not valid expression " in { - val name = "k4" - val line = s"$name := { val x = $name.value; () }" - SessionSettingsNoBlankies.cutExpression(List(line), name) must_== List(line) - - } - } -} diff --git a/main/src/test/scala/sbt/SessionSettingsSpec.scala b/main/src/test/scala/sbt/SessionSettingsSpec.scala index cc742897f..4c6e79100 100644 --- a/main/src/test/scala/sbt/SessionSettingsSpec.scala +++ b/main/src/test/scala/sbt/SessionSettingsSpec.scala @@ -6,7 +6,6 @@ import org.specs2.matcher.MatchResult import sbt.internals.parser.{ AbstractSpec, SplitExpressionsNoBlankies } import scala.collection.GenTraversableOnce -import scala.collection.immutable.{ SortedMap, TreeMap } import scala.io.Source import scala.xml.XML @@ -17,7 +16,7 @@ abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean "SessionSettings " should { "Be identical for empty map " in { - def unit(f: File) = Seq((Source.fromFile(f).getLines().toList, SortedMap.empty[Int, List[(Int, List[String])]])) + def unit(f: File) = Seq((Source.fromFile(f).getLines().toList, Nil)) runTestOnFiles(unit) } @@ -26,7 +25,7 @@ abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean } } - private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], SortedMap[Int, List[(Int, List[String])]])]): MatchResult[GenTraversableOnce[File]] = { + private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], List[List[String]])]): MatchResult[GenTraversableOnce[File]] = { val allFiles = rootDir.listFiles(new FilenameFilter() { def accept(dir: File, name: String) = name.endsWith(".sbt.txt") @@ -64,12 +63,10 @@ abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean val set = (node \\ "set").text val start = (node \\ "start").text.toInt val end = (node \\ "end").text.toInt - (start, (end, List(set))) + List(set) }.toList - val map = tupleCollection.groupBy(el => el._1).map { - case (k, seq) => (k, seq.map(el => el._2)) - } - (result, TreeMap(map.toArray: _*)(SessionSettingsNoBlankies.REVERSE_ORDERING_INT)) + + (result, tupleCollection) } } } From 2a603da0a86b15a35b28cb927218ec22b053d836 Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Tue, 30 Sep 2014 14:54:33 +0200 Subject: [PATCH 13/21] Moved tests to internal package --- main/src/main/scala/sbt/SessionSettings.scala | 3 +- .../parser/SbtRefactorings.scala} | 16 ++++--- .../parser/SplitExpressionsNoBlankies.scala | 4 +- .../session-settings-quick/4.sbt.txt | 17 ++++++++ .../session-settings-quick/4.sbt.txt_1/1.set | 1 + .../4.sbt.txt_1/1.set.result | 15 +++++++ .../session-settings/1.sbt.txt_1/1.set | 1 + .../{1.xml.result => 1.set.result} | 0 .../session-settings/1.sbt.txt_1/1.xml | 7 ---- .../session-settings/1.sbt.txt_1/2.set | 2 + .../{2.xml.result => 2.set.result} | 0 .../session-settings/1.sbt.txt_1/2.xml | 12 ------ .../session-settings/1.sbt.txt_1/3.set | 1 + .../{3.xml.result => 3.set.result} | 0 .../session-settings/1.sbt.txt_1/3.xml | 7 ---- .../session-settings/2.sbt.txt_1/1.set | 1 + .../{1.xml.result => 1.set.result} | 0 .../session-settings/2.sbt.txt_1/1.xml | 7 ---- .../session-settings/3.sbt.txt_1/1.set | 1 + .../{1.xml.result => 1.set.result} | 0 .../session-settings/3.sbt.txt_1/1.xml | 7 ---- .../test/resources/session-settings/4.sbt.txt | 17 ++++++++ .../session-settings/4.sbt.txt_1/1.set | 1 + .../session-settings/4.sbt.txt_1/1.set.result | 15 +++++++ .../sbt/internals/parser/ErrorSpec.scala | 2 +- .../parser}/SessionSettingsSpec.scala | 42 +++++++++---------- 26 files changed, 103 insertions(+), 76 deletions(-) rename main/src/main/scala/sbt/{SessionSettingsNoBlankies.scala => internals/parser/SbtRefactorings.scala} (82%) create mode 100644 main/src/test/resources/session-settings-quick/4.sbt.txt create mode 100644 main/src/test/resources/session-settings-quick/4.sbt.txt_1/1.set create mode 100644 main/src/test/resources/session-settings-quick/4.sbt.txt_1/1.set.result create mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/1.set rename main/src/test/resources/session-settings/1.sbt.txt_1/{1.xml.result => 1.set.result} (100%) delete mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/1.xml create mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/2.set rename main/src/test/resources/session-settings/1.sbt.txt_1/{2.xml.result => 2.set.result} (100%) delete mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/2.xml create mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/3.set rename main/src/test/resources/session-settings/1.sbt.txt_1/{3.xml.result => 3.set.result} (100%) delete mode 100644 main/src/test/resources/session-settings/1.sbt.txt_1/3.xml create mode 100644 main/src/test/resources/session-settings/2.sbt.txt_1/1.set rename main/src/test/resources/session-settings/2.sbt.txt_1/{1.xml.result => 1.set.result} (100%) delete mode 100644 main/src/test/resources/session-settings/2.sbt.txt_1/1.xml create mode 100644 main/src/test/resources/session-settings/3.sbt.txt_1/1.set rename main/src/test/resources/session-settings/3.sbt.txt_1/{1.xml.result => 1.set.result} (100%) delete mode 100644 main/src/test/resources/session-settings/3.sbt.txt_1/1.xml create mode 100644 main/src/test/resources/session-settings/4.sbt.txt create mode 100644 main/src/test/resources/session-settings/4.sbt.txt_1/1.set create mode 100644 main/src/test/resources/session-settings/4.sbt.txt_1/1.set.result rename main/src/test/scala/sbt/{ => internals/parser}/SessionSettingsSpec.scala (57%) diff --git a/main/src/main/scala/sbt/SessionSettings.scala b/main/src/main/scala/sbt/SessionSettings.scala index 24789a3c2..109b17442 100755 --- a/main/src/main/scala/sbt/SessionSettings.scala +++ b/main/src/main/scala/sbt/SessionSettings.scala @@ -10,6 +10,7 @@ import Types.Endo import compiler.Eval import SessionSettings._ +import sbt.internals.parser.SbtRefactorings /** * Represents (potentially) transient settings added into a build via commands/user. @@ -192,7 +193,7 @@ object SessionSettings { } val newSettings = settings diff replace val oldContent = IO.readLines(writeTo) - val exist: List[String] = SessionSettingsNoBlankies.oldLinesToNew(oldContent, statements) + val exist: List[String] = SbtRefactorings.applyStatements(oldContent, statements) val adjusted = if (!newSettings.isEmpty && needsTrailingBlank(exist)) exist :+ "" else exist val lines = adjusted ++ newSettings.flatMap(_._2 ::: "" :: Nil) IO.writeLines(writeTo, lines) diff --git a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala similarity index 82% rename from main/src/main/scala/sbt/SessionSettingsNoBlankies.scala rename to main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala index 191793b17..df2f00f44 100644 --- a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala +++ b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala @@ -1,15 +1,14 @@ -package sbt +package sbt.internals.parser import scala.reflect.runtime.universe._ -import sbt.internals.parser.{ XmlContent, SplitExpressionsNoBlankies } -object SessionSettingsNoBlankies { - - import SplitExpressionsNoBlankies.FAKE_FILE +private[sbt] object SbtRefactorings { + import sbt.internals.parser.SplitExpressionsNoBlankies.{ END_OF_LINE, FAKE_FILE } + val EMPTY_STRING = "" val REVERSE_ORDERING_INT = Ordering[Int].reverse - def oldLinesToNew(lines: List[String], setCommands: List[List[String]]): List[String] = { + def applyStatements(lines: List[String], setCommands: List[List[String]]): List[String] = { val split = SplitExpressionsNoBlankies(FAKE_FILE, lines) val recordedCommand = setCommands.flatMap { command => @@ -21,9 +20,9 @@ object SessionSettingsNoBlankies { val treeName = extractSettingName(tree) if (name == treeName) { val replacement = if (acc.isEmpty) { - command.mkString("\n") + command.mkString(END_OF_LINE) } else { - "" + EMPTY_STRING } (tree.pos.start, statement, replacement) +: acc } else { @@ -39,7 +38,6 @@ object SessionSettingsNoBlankies { val before = acc.substring(0, from) val after = acc.substring(from + old.length, acc.length) before + replacement + after - // acc.replace(old, replacement) } newContent.lines.toList } diff --git a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala index b975f5226..381efc9d2 100644 --- a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala @@ -22,7 +22,7 @@ private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String import scala.reflect.runtime._ import scala.compat.Platform.EOL - import BugInParser._ + import MissingBracketHandler._ import XmlContent._ import scala.tools.reflect.{ ToolBox, ToolBoxError } @@ -97,7 +97,7 @@ private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String * Scala parser cuts last bracket - * @see http://stackoverflow.com/questions/25547149/scala-parser-cuts-last-bracket */ -private[sbt] object BugInParser { +private[sbt] object MissingBracketHandler { /** * * @param content - parsed file diff --git a/main/src/test/resources/session-settings-quick/4.sbt.txt b/main/src/test/resources/session-settings-quick/4.sbt.txt new file mode 100644 index 000000000..693c6cef6 --- /dev/null +++ b/main/src/test/resources/session-settings-quick/4.sbt.txt @@ -0,0 +1,17 @@ +k1 := {} + +k2 := {} + +k3 := { + + + val x = "hi" + () +} + +k4 := { }; k5 := () + +k1 <<= k1 map {_ => error("k1")} + +k4 := { val x = k4.value; () } + diff --git a/main/src/test/resources/session-settings-quick/4.sbt.txt_1/1.set b/main/src/test/resources/session-settings-quick/4.sbt.txt_1/1.set new file mode 100644 index 000000000..2a3525578 --- /dev/null +++ b/main/src/test/resources/session-settings-quick/4.sbt.txt_1/1.set @@ -0,0 +1 @@ +k4 := () diff --git a/main/src/test/resources/session-settings-quick/4.sbt.txt_1/1.set.result b/main/src/test/resources/session-settings-quick/4.sbt.txt_1/1.set.result new file mode 100644 index 000000000..11a7d317b --- /dev/null +++ b/main/src/test/resources/session-settings-quick/4.sbt.txt_1/1.set.result @@ -0,0 +1,15 @@ +k1 := {} + +k2 := {} + +k3 := { + + + val x = "hi" + () +} + +k4 := (); k5 := () + +k1 <<= k1 map {_ => error("k1")} + diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/1.set b/main/src/test/resources/session-settings/1.sbt.txt_1/1.set new file mode 100644 index 000000000..bd735d1d1 --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/1.set @@ -0,0 +1 @@ +name := "alaMaKota" \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml.result b/main/src/test/resources/session-settings/1.sbt.txt_1/1.set.result similarity index 100% rename from main/src/test/resources/session-settings/1.sbt.txt_1/1.xml.result rename to main/src/test/resources/session-settings/1.sbt.txt_1/1.set.result diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml b/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml deleted file mode 100644 index a67818d62..000000000 --- a/main/src/test/resources/session-settings/1.sbt.txt_1/1.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - 1 - 2 - - - \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/2.set b/main/src/test/resources/session-settings/1.sbt.txt_1/2.set new file mode 100644 index 000000000..3a10fa9f2 --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/2.set @@ -0,0 +1,2 @@ +name := "alaMaKota" +organization := "scalania" \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result b/main/src/test/resources/session-settings/1.sbt.txt_1/2.set.result similarity index 100% rename from main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result rename to main/src/test/resources/session-settings/1.sbt.txt_1/2.set.result diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml deleted file mode 100644 index 20e5c790f..000000000 --- a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - 1 - 2 - - - - 25 - 26 - - - \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/3.set b/main/src/test/resources/session-settings/1.sbt.txt_1/3.set new file mode 100644 index 000000000..8dc207f57 --- /dev/null +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/3.set @@ -0,0 +1 @@ +checkPom := "OK" \ No newline at end of file diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml.result b/main/src/test/resources/session-settings/1.sbt.txt_1/3.set.result similarity index 100% rename from main/src/test/resources/session-settings/1.sbt.txt_1/3.xml.result rename to main/src/test/resources/session-settings/1.sbt.txt_1/3.set.result diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml b/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml deleted file mode 100644 index efdba1fe1..000000000 --- a/main/src/test/resources/session-settings/1.sbt.txt_1/3.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - 4 - 11 - - - \ No newline at end of file diff --git a/main/src/test/resources/session-settings/2.sbt.txt_1/1.set b/main/src/test/resources/session-settings/2.sbt.txt_1/1.set new file mode 100644 index 000000000..bd735d1d1 --- /dev/null +++ b/main/src/test/resources/session-settings/2.sbt.txt_1/1.set @@ -0,0 +1 @@ +name := "alaMaKota" \ No newline at end of file diff --git a/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml.result b/main/src/test/resources/session-settings/2.sbt.txt_1/1.set.result similarity index 100% rename from main/src/test/resources/session-settings/2.sbt.txt_1/1.xml.result rename to main/src/test/resources/session-settings/2.sbt.txt_1/1.set.result diff --git a/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml b/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml deleted file mode 100644 index a67818d62..000000000 --- a/main/src/test/resources/session-settings/2.sbt.txt_1/1.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - 1 - 2 - - - \ No newline at end of file diff --git a/main/src/test/resources/session-settings/3.sbt.txt_1/1.set b/main/src/test/resources/session-settings/3.sbt.txt_1/1.set new file mode 100644 index 000000000..3e2aa8d48 --- /dev/null +++ b/main/src/test/resources/session-settings/3.sbt.txt_1/1.set @@ -0,0 +1 @@ +scmpom := OK \ No newline at end of file diff --git a/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml.result b/main/src/test/resources/session-settings/3.sbt.txt_1/1.set.result similarity index 100% rename from main/src/test/resources/session-settings/3.sbt.txt_1/1.xml.result rename to main/src/test/resources/session-settings/3.sbt.txt_1/1.set.result diff --git a/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml b/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml deleted file mode 100644 index 9f7ba5288..000000000 --- a/main/src/test/resources/session-settings/3.sbt.txt_1/1.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - 5 - 18 - OK]]> - - \ No newline at end of file diff --git a/main/src/test/resources/session-settings/4.sbt.txt b/main/src/test/resources/session-settings/4.sbt.txt new file mode 100644 index 000000000..693c6cef6 --- /dev/null +++ b/main/src/test/resources/session-settings/4.sbt.txt @@ -0,0 +1,17 @@ +k1 := {} + +k2 := {} + +k3 := { + + + val x = "hi" + () +} + +k4 := { }; k5 := () + +k1 <<= k1 map {_ => error("k1")} + +k4 := { val x = k4.value; () } + diff --git a/main/src/test/resources/session-settings/4.sbt.txt_1/1.set b/main/src/test/resources/session-settings/4.sbt.txt_1/1.set new file mode 100644 index 000000000..2a3525578 --- /dev/null +++ b/main/src/test/resources/session-settings/4.sbt.txt_1/1.set @@ -0,0 +1 @@ +k4 := () diff --git a/main/src/test/resources/session-settings/4.sbt.txt_1/1.set.result b/main/src/test/resources/session-settings/4.sbt.txt_1/1.set.result new file mode 100644 index 000000000..11a7d317b --- /dev/null +++ b/main/src/test/resources/session-settings/4.sbt.txt_1/1.set.result @@ -0,0 +1,15 @@ +k1 := {} + +k2 := {} + +k3 := { + + + val x = "hi" + () +} + +k4 := (); k5 := () + +k1 <<= k1 map {_ => error("k1")} + diff --git a/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala b/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala index 198139fd5..1ac265050 100644 --- a/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala @@ -37,7 +37,7 @@ class ErrorSpec extends AbstractSpec with ScalaCheck { | } /* */ // |} """.stripMargin - BugInParser.findMissingText(buildSbt, buildSbt.length, 2, "fake.txt", new MessageOnlyException("fake")) must throwA[MessageOnlyException] + MissingBracketHandler.findMissingText(buildSbt, buildSbt.length, 2, "fake.txt", new MessageOnlyException("fake")) must throwA[MessageOnlyException] } } diff --git a/main/src/test/scala/sbt/SessionSettingsSpec.scala b/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala similarity index 57% rename from main/src/test/scala/sbt/SessionSettingsSpec.scala rename to main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala index 4c6e79100..64bf370ca 100644 --- a/main/src/test/scala/sbt/SessionSettingsSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala @@ -1,16 +1,14 @@ -package sbt +package sbt.internals.parser import java.io.{ File, FilenameFilter } import org.specs2.matcher.MatchResult -import sbt.internals.parser.{ AbstractSpec, SplitExpressionsNoBlankies } import scala.collection.GenTraversableOnce import scala.io.Source -import scala.xml.XML -abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean = false) extends AbstractSpec { - protected val rootPath = getClass.getResource("").getPath + folder +abstract class AbstractSessionSettingsSpec(folder: String, deepCompare: Boolean = false) extends AbstractSpec { + protected val rootPath = getClass.getClassLoader.getResource("").getPath + folder println(s"Reading files from: $rootPath") protected val rootDir = new File(rootPath) @@ -25,7 +23,7 @@ abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean } } - private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], List[List[String]])]): MatchResult[GenTraversableOnce[File]] = { + private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], List[String])]): MatchResult[GenTraversableOnce[File]] = { val allFiles = rootDir.listFiles(new FilenameFilter() { def accept(dir: File, name: String) = name.endsWith(".sbt.txt") @@ -34,11 +32,15 @@ abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean file => val originalLines = Source.fromFile(file).getLines().toList foreach(expectedResultAndMap(file)) { - case (expectedResultList, map) => - val resultList = SessionSettingsNoBlankies.oldLinesToNew(originalLines, map) + case (expectedResultList, commands) => + val resultList = SbtRefactorings.applyStatements(originalLines, commands.map(List(_))) val expected = SplitExpressionsNoBlankies(file, expectedResultList) val result = SplitExpressionsNoBlankies(file, resultList) + if (deepCompare) { + expectedResultList must_== resultList + } result.settings must_== expected.settings + } } } @@ -49,28 +51,22 @@ abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean val startsWith = f.getName + "_" name.startsWith(startsWith) } - }).toList + }).toSeq dirs.flatMap { dir => val files = dir.listFiles(new FilenameFilter { - override def accept(dir: File, name: String) = name.endsWith(".xml") + override def accept(dir: File, name: String) = name.endsWith(".set") }) - files.map { xmlFile => - val xml = XML.loadFile(xmlFile) - val result = Source.fromFile(xmlFile.getAbsolutePath + ".result").getLines().toList - val tupleCollection = (xml \\ "settings" \\ "setting").map { - node => - val set = (node \\ "set").text - val start = (node \\ "start").text.toInt - val end = (node \\ "end").text.toInt - List(set) - }.toList - - (result, tupleCollection) + files.map { file => + val list = Source.fromFile(file).getLines().toList + val result = Source.fromFile(file.getAbsolutePath + ".result").getLines().toList + (result, list) } } } } -class SessionSettingsSpec extends AbstractSessionSettingsSpec("../session-settings") \ No newline at end of file +class SessionSettingsSpec extends AbstractSessionSettingsSpec("session-settings") + +//class SessionSettingsQuickSpec extends AbstractSessionSettingsSpec("session-settings-quick", true) \ No newline at end of file From 963e75d182e565dc06cc6fb8e48f5f3dabadd58a Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Tue, 30 Sep 2014 16:35:57 +0200 Subject: [PATCH 14/21] Fixed #1630. Remove last line if last statement is being removed --- .../scala/sbt/internals/parser/SbtRefactorings.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala index df2f00f44..93bd577bf 100644 --- a/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala +++ b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala @@ -14,9 +14,9 @@ private[sbt] object SbtRefactorings { command => val map = toTreeStringMap(command) map.flatMap { - case (name, (startPos, statement)) => + case (name, statement) => split.settingsTrees.foldLeft(Seq.empty[(Int, String, String)]) { - case (acc, (statement, tree)) => + case (acc, (st, tree)) => val treeName = extractSettingName(tree) if (name == treeName) { val replacement = if (acc.isEmpty) { @@ -24,7 +24,7 @@ private[sbt] object SbtRefactorings { } else { EMPTY_STRING } - (tree.pos.start, statement, replacement) +: acc + (tree.pos.start, st, replacement) +: acc } else { acc } @@ -37,7 +37,8 @@ private[sbt] object SbtRefactorings { case (acc, (from, old, replacement)) => val before = acc.substring(0, from) val after = acc.substring(from + old.length, acc.length) - before + replacement + after + val afterLast = if (after.trim.isEmpty) after.trim else after + before + replacement + afterLast } newContent.lines.toList } @@ -47,7 +48,7 @@ private[sbt] object SbtRefactorings { val trees = split.settingsTrees val seq = trees.map { case (statement, tree) => - (extractSettingName(tree), (tree.pos.start, statement)) + (extractSettingName(tree), statement) } seq.toMap } From dac3edb54654291c6f6bf3b20c4d84416f2b9fd0 Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Tue, 30 Sep 2014 18:04:30 +0200 Subject: [PATCH 15/21] Split to small methods --- .../internals/parser/SbtRefactorings.scala | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala index 93bd577bf..1acd3ae4f 100644 --- a/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala +++ b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala @@ -1,48 +1,60 @@ -package sbt.internals.parser +package sbt +package internals +package parser import scala.reflect.runtime.universe._ private[sbt] object SbtRefactorings { import sbt.internals.parser.SplitExpressionsNoBlankies.{ END_OF_LINE, FAKE_FILE } + val EMPTY_STRING = "" val REVERSE_ORDERING_INT = Ordering[Int].reverse - def applyStatements(lines: List[String], setCommands: List[List[String]]): List[String] = { + def applyStatements(lines: List[String], commands: List[List[String]]): List[String] = { val split = SplitExpressionsNoBlankies(FAKE_FILE, lines) - val recordedCommand = setCommands.flatMap { - command => - val map = toTreeStringMap(command) - map.flatMap { - case (name, statement) => - split.settingsTrees.foldLeft(Seq.empty[(Int, String, String)]) { - case (acc, (st, tree)) => - val treeName = extractSettingName(tree) - if (name == treeName) { - val replacement = if (acc.isEmpty) { - command.mkString(END_OF_LINE) - } else { - EMPTY_STRING - } - (tree.pos.start, st, replacement) +: acc - } else { - acc - } - } - } - } + val recordedCommand = recordCommands(commands, split) val sortedRecordedCommand = recordedCommand.sortBy(_._1)(REVERSE_ORDERING_INT) val newContent = sortedRecordedCommand.foldLeft(split.modifiedContent) { case (acc, (from, old, replacement)) => val before = acc.substring(0, from) val after = acc.substring(from + old.length, acc.length) - val afterLast = if (after.trim.isEmpty) after.trim else after + val afterLast = emptyStringForEmptyStatement(after) before + replacement + afterLast } newContent.lines.toList } + def emptyStringForEmptyStatement(after: String) = + if (after.trim.isEmpty) EMPTY_STRING else after + + private def recordCommands(commands: List[List[String]], split: SplitExpressionsNoBlankies) = + commands.flatMap { + command => + val map = toTreeStringMap(command) + map.flatMap { + case (name, statement) => + treesToReplacements(split, name, command) + } + } + + private def treesToReplacements(split: SplitExpressionsNoBlankies, name: String, command: List[String]) = + split.settingsTrees.foldLeft(Seq.empty[(Int, String, String)]) { + case (acc, (st, tree)) => + val treeName = extractSettingName(tree) + if (name == treeName) { + val replacement = if (acc.isEmpty) { + command.mkString(END_OF_LINE) + } else { + EMPTY_STRING + } + (tree.pos.start, st, replacement) +: acc + } else { + acc + } + } + private def toTreeStringMap(command: List[String]) = { val split = SplitExpressionsNoBlankies(FAKE_FILE, command) val trees = split.settingsTrees @@ -53,13 +65,12 @@ private[sbt] object SbtRefactorings { seq.toMap } - private def extractSettingName(tree: Tree): String = { + private def extractSettingName(tree: Tree): String = tree.children match { case h :: _ => extractSettingName(h) case _ => tree.toString() } - } } From 97a96d5bf8412ed19bfb97b394be65bee2189385 Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Tue, 30 Sep 2014 19:53:51 +0200 Subject: [PATCH 16/21] Split to small methods --- .../parser/SplitExpressionsNoBlankies.scala | 114 +++++++++++------- 1 file changed, 68 insertions(+), 46 deletions(-) diff --git a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala index 381efc9d2..d89ffd8b4 100644 --- a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala @@ -4,26 +4,28 @@ package parser import java.io.File -import SplitExpressionsNoBlankies._ +import sbt.internals.parser.SplitExpressionsNoBlankies._ + import scala.annotation.tailrec import scala.reflect.runtime.universe._ private[sbt] object SplitExpressionsNoBlankies { val END_OF_LINE_CHAR = '\n' val END_OF_LINE = String.valueOf(END_OF_LINE_CHAR) + private[parser] val NOT_FOUND_INDEX = -1 private[sbt] val FAKE_FILE = new File("fake") } private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { - //settingsTrees needed for "session save" + //settingsTrees,modifiedContent needed for "session save" val (imports, settings, settingsTrees, modifiedContent) = splitExpressions(file, lines) private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)], String) = { - import scala.reflect.runtime._ + import sbt.internals.parser.MissingBracketHandler._ + import sbt.internals.parser.XmlContent._ import scala.compat.Platform.EOL - import MissingBracketHandler._ - import XmlContent._ + import scala.reflect.runtime._ import scala.tools.reflect.{ ToolBox, ToolBoxError } val mirror = universe.runtimeMirror(this.getClass.getClassLoader) @@ -83,7 +85,7 @@ private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String if (t.pos.isDefined) { val originalStatement = modifiedContent.substring(t.pos.start, t.pos.end) val statement = parseStatementAgain(t, originalStatement) - val numberLines = statement.count(c => c == END_OF_LINE_CHAR) + val numberLines = countLines(statement) Some((statement, t, LineRange(t.pos.line - 1, t.pos.line + numberLines))) } else { None @@ -91,11 +93,13 @@ private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String val statementsTreeLineRange = statements flatMap convertStatement (imports map convertImport, statementsTreeLineRange.map(t => (t._1, t._3)), statementsTreeLineRange.map(t => (t._1, t._2)), modifiedContent) } + + private def countLines(statement: String) = statement.count(c => c == END_OF_LINE_CHAR) } /** * Scala parser cuts last bracket - - * @see http://stackoverflow.com/questions/25547149/scala-parser-cuts-last-bracket + * @see https://github.com/scala/scala/pull/3991 */ private[sbt] object MissingBracketHandler { /** @@ -105,7 +109,7 @@ private[sbt] object MissingBracketHandler { * @param positionLine - number of start position line * @param fileName - file name * @param originalException - original exception - * @return + * @return missing text */ private[sbt] def findMissingText(content: String, positionEnd: Int, positionLine: Int, fileName: String, originalException: Throwable): String = { findClosingBracketIndex(content, positionEnd) match { @@ -131,7 +135,7 @@ private[sbt] object MissingBracketHandler { */ private[sbt] def findClosingBracketIndex(content: String, from: Int): Option[Int] = { val index = content.indexWhere(c => c == '}' || c == ')', from) - if (index == -1) { + if (index == NOT_FOUND_INDEX) { None } else { Some(index) @@ -152,6 +156,17 @@ private[sbt] object MissingBracketHandler { * */ private[sbt] object XmlContent { + + private val OPEN_PARENTHESIS = '{' + + private val OPEN_CURLY_BRACKET = '(' + + private val DOUBLE_SLASH = "//" + + private val OPEN_BRACKET = s" $OPEN_CURLY_BRACKET " + + private val CLOSE_BRACKET = " ) " + /** * * @param original - file content @@ -206,7 +221,7 @@ private[sbt] object XmlContent { private def searchForTagName(text: String, startIndex: Int, endIndex: Int) = { val subs = text.substring(startIndex, endIndex) val spaceIndex = subs.indexWhere(c => c.isWhitespace, 1) - if (spaceIndex == -1) { + if (spaceIndex == NOT_FOUND_INDEX) { subs } else { subs.substring(0, spaceIndex) @@ -222,7 +237,7 @@ private[sbt] object XmlContent { @tailrec private def findModifiedOpeningTags(content: String, offsetIndex: Int, acc: Seq[(String, Int, Int)]): Seq[(String, Int, Int)] = { val endIndex = content.indexOf("/>", offsetIndex) - if (endIndex == -1) { + if (endIndex == NOT_FOUND_INDEX) { acc } else { val xmlFragment = findModifiedOpeningTag(content, offsetIndex, endIndex) @@ -233,7 +248,7 @@ private[sbt] object XmlContent { private def findModifiedOpeningTag(content: String, offsetIndex: Int, endIndex: Int): Option[(String, Int, Int)] = { val startIndex = content.substring(offsetIndex, endIndex).lastIndexOf("<") - if (startIndex == -1) { + if (startIndex == NOT_FOUND_INDEX) { None } else { val tagName = searchForTagName(content, startIndex + 1 + offsetIndex, endIndex) @@ -249,7 +264,7 @@ private[sbt] object XmlContent { private def searchForOpeningIndex(text: String, closeTagStartIndex: Int, tagName: String) = { val subs = text.substring(0, closeTagStartIndex) val index = subs.lastIndexOf(s"<$tagName>") - if (index == -1) { + if (index == NOT_FOUND_INDEX) { subs.lastIndexOf(s"<$tagName ") } else { index @@ -265,11 +280,11 @@ private[sbt] object XmlContent { @tailrec private def findNotModifiedOpeningTags(content: String, current: Int, acc: Seq[(String, Int, Int)]): Seq[(String, Int, Int)] = { val closeTagStartIndex = content.indexOf("", closeTagStartIndex) - if (closeTagEndIndex == -1) { + if (closeTagEndIndex == NOT_FOUND_INDEX) { findNotModifiedOpeningTags(content, closeTagStartIndex + 2, acc) } else { val xmlFragment = findNotModifiedOpeningTag(content, closeTagStartIndex, closeTagEndIndex) @@ -301,39 +316,46 @@ private[sbt] object XmlContent { */ private def addExplicitXmlContent(content: String, xmlParts: Seq[(String, Int, Int)]): String = { val statements: Seq[(String, Boolean)] = splitFile(content, xmlParts) - val (builder, addCloseBrackets, wasXml, _) = statements.foldLeft((Seq.empty[String], false, false, "")) { - case ((bAcc, addCloseBrackets, wasXml, previous), (content, isXml)) => - if (content.trim.isEmpty) { - (content +: bAcc, addCloseBrackets, wasXml, content) + val (correctedStmt, shouldAddCloseBrackets, wasXml, _) = addBracketsIfNecessary(statements) + val closeIfNecessaryCorrectedStmt = + if (shouldAddCloseBrackets && wasXml) { + correctedStmt.head +: CLOSE_BRACKET +: correctedStmt.tail + } else { + correctedStmt + } + closeIfNecessaryCorrectedStmt.reverse.mkString + } + + def addBracketsIfNecessary(statements: Seq[(String, Boolean)]): (Seq[String], Boolean, Boolean, String) = { + statements.foldLeft((Seq.empty[String], false, false, "")) { + case ((accStmt, shouldAddCloseBracket, prvWasXml, prvStmt), (stmt, isXml)) => + if (stmt.trim.isEmpty) { + (stmt +: accStmt, shouldAddCloseBracket, prvWasXml, stmt) } else { - val (newAddCloseBrackets, newAcc) = if (isXml) { - if (wasXml) { - (addCloseBrackets, bAcc) - } else { - if (areBracketsNecessary(previous)) { - (true, " ( " +: bAcc) - } else { - (false, bAcc) - } - } - } else if (addCloseBrackets) { - (false, " ) " +: bAcc) + val (newShouldAddCloseBracket, newStmtAcc) = if (isXml) { + addOpenBracketIfNecessary(accStmt, shouldAddCloseBracket, prvWasXml, prvStmt) + } else if (shouldAddCloseBracket) { + (false, CLOSE_BRACKET +: accStmt) } else { - (false, bAcc) + (false, accStmt) } - (content +: newAcc, newAddCloseBrackets, isXml, content) + (stmt +: newStmtAcc, newShouldAddCloseBracket, isXml, stmt) } } - val closeIfNecessaryBuilder = - if (addCloseBrackets && wasXml) { - builder.head +: " ) " +: builder.tail - } else { - builder - } - closeIfNecessaryBuilder.reverse.mkString } + private def addOpenBracketIfNecessary(stmtAcc: Seq[String], shouldAddCloseBracket: Boolean, prvWasXml: Boolean, prvStatement: String) = + if (prvWasXml) { + (shouldAddCloseBracket, stmtAcc) + } else { + if (areBracketsNecessary(prvStatement)) { + (true, OPEN_BRACKET +: stmtAcc) + } else { + (false, stmtAcc) + } + } + /** * Add to head if option is not empty * @param ts - seq @@ -348,7 +370,7 @@ private[sbt] object XmlContent { val tagName = content.substring(closeTagStartIndex + 2, closeTagEndIndex) if (xml.Utility.isName(tagName)) { val openTagIndex = searchForOpeningIndex(content, closeTagStartIndex, tagName) - if (openTagIndex == -1) { + if (openTagIndex == NOT_FOUND_INDEX) { None } else { xmlFragmentOption(content, openTagIndex, closeTagEndIndex + 1) @@ -379,13 +401,13 @@ private[sbt] object XmlContent { * @return are brackets necessary? */ private def areBracketsNecessary(statement: String): Boolean = { - val doubleSlash = statement.indexOf("//") + val doubleSlash = statement.indexOf(DOUBLE_SLASH) val endOfLine = statement.indexOf(END_OF_LINE) - if (doubleSlash == -1 || (doubleSlash < endOfLine)) { - val roundBrackets = statement.lastIndexOf("(") - val braces = statement.lastIndexOf("{") + if (doubleSlash == NOT_FOUND_INDEX || (doubleSlash < endOfLine)) { + val roundBrackets = statement.lastIndexOf(OPEN_CURLY_BRACKET) + val braces = statement.lastIndexOf(OPEN_PARENTHESIS) val max = roundBrackets.max(braces) - if (max == -1) { + if (max == NOT_FOUND_INDEX) { true } else { val trimmed = statement.substring(max + 1).trim From a67c5fd1874d26b83c23ad648113366f359f7826 Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Tue, 30 Sep 2014 23:58:05 +0200 Subject: [PATCH 17/21] Comments #1630 --- main/src/main/scala/sbt/SessionSettings.scala | 150 ++++++++++-------- .../internals/parser/SbtRefactorings.scala | 41 +++-- .../parser/SessionSettingsSpec.scala | 23 +-- 3 files changed, 122 insertions(+), 92 deletions(-) diff --git a/main/src/main/scala/sbt/SessionSettings.scala b/main/src/main/scala/sbt/SessionSettings.scala index 109b17442..2ca37ffac 100755 --- a/main/src/main/scala/sbt/SessionSettings.scala +++ b/main/src/main/scala/sbt/SessionSettings.scala @@ -16,17 +16,17 @@ import sbt.internals.parser.SbtRefactorings * Represents (potentially) transient settings added into a build via commands/user. * * @param currentBuild - * The current sbt build with which we scope new settings + * The current sbt build with which we scope new settings * @param currentProject - * The current project with which we scope new settings. + * The current project with which we scope new settings. * @param original - * The original list of settings for this build. + * The original list of settings for this build. * @param append - * Settings which have been defined and appended that may ALSO be saved to disk. + * Settings which have been defined and appended that may ALSO be saved to disk. * @param rawAppend - * Settings which have been defined and appended which CANNOT be saved to disk + * Settings which have been defined and appended which CANNOT be saved to disk * @param currentEval - * A compiler we can use to compile new setting strings. + * A compiler we can use to compile new setting strings. */ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, String], original: Seq[Setting[_]], append: SessionMap, rawAppend: Seq[Setting[_]], currentEval: () => Eval) { assert(currentProject contains currentBuild, "Current build (" + currentBuild + ") not associated with a current project.") @@ -71,16 +71,18 @@ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, Str def clearExtraSettings: SessionSettings = copy(append = Map.empty, rawAppend = Nil) private[this] def merge(map: SessionMap): Seq[Setting[_]] = map.values.toSeq.flatten[SessionSetting].map(_._1) - private[this] def modify(map: SessionMap, onSeq: Endo[Seq[SessionSetting]]): SessionMap = - { - val cur = current - map.updated(cur, onSeq(map.getOrElse(cur, Nil))) - } + + private[this] def modify(map: SessionMap, onSeq: Endo[Seq[SessionSetting]]): SessionMap = { + val cur = current + map.updated(cur, onSeq(map.getOrElse(cur, Nil))) + } } + object SessionSettings { /** A session setting is simply a tuple of a Setting[_] and the strings which define it. */ - type SessionSetting = (Setting[_], List[String]) + type SessionSetting = (Setting[_], Seq[String]) type SessionMap = Map[ProjectRef, Seq[SessionSetting]] + type SbtConfigFile = (File, Seq[String]) /** * This will re-evaluate all Setting[_]'s on this session against the current build state and @@ -96,6 +98,7 @@ object SessionSettings { */ def clearSettings(s: State): State = withSettings(s)(session => reapply(session.copy(append = session.append - session.current), s)) + /** This will clear ALL transient session settings in a given build state, returning the new build state. */ def clearAllSettings(s: State): State = withSettings(s)(session => reapply(session.clearExtraSettings, s)) @@ -107,16 +110,15 @@ object SessionSettings { * @param f A function which takes the current SessionSettings and returns the new build state. * @return The new build state */ - def withSettings(s: State)(f: SessionSettings => State): State = - { - val extracted = Project extract s - import extracted._ - if (session.append.isEmpty) { - s.log.info("No session settings defined.") - s - } else - f(session) - } + def withSettings(s: State)(f: SessionSettings => State): State = { + val extracted = Project extract s + import extracted._ + if (session.append.isEmpty) { + s.log.info("No session settings defined.") + s + } else + f(session) + } /** Adds `s` to a strings when needed. Maybe one day we'll care about non-english languages. */ def pluralize(size: Int, of: String) = size.toString + (if (size == 1) of else (of + "s")) @@ -127,25 +129,27 @@ object SessionSettings { if (newSession.append.isEmpty && !oldSettings.isEmpty) oldState.log.warn("Discarding " + pluralize(oldSettings.size, " session setting") + ". Use 'session save' to persist session settings.") } - def removeRanges[T](in: Seq[T], ranges: Seq[(Int, Int)]): Seq[T] = - { - val asSet = (Set.empty[Int] /: ranges) { case (s, (hi, lo)) => s ++ (hi to lo) } - in.zipWithIndex.flatMap { case (t, index) => if (asSet(index + 1)) Nil else t :: Nil } - } + + def removeRanges[T](in: Seq[T], ranges: Seq[(Int, Int)]): Seq[T] = { + val asSet = (Set.empty[Int] /: ranges) { case (s, (hi, lo)) => s ++ (hi to lo) } + in.zipWithIndex.flatMap { case (t, index) => if (asSet(index + 1)) Nil else t :: Nil } + } + def removeSettings(s: State, ranges: Seq[(Int, Int)]): State = withSettings(s) { session => val current = session.current val newAppend = session.append.updated(current, removeRanges(session.append.getOrElse(current, Nil), ranges)) reapply(session.copy(append = newAppend), s) } + /** Saves *all* session settings to disk for all projects. */ def saveAllSettings(s: State): State = saveSomeSettings(s)(_ => true) + /** Saves the session settings to disk for the current project. */ - def saveSettings(s: State): State = - { - val current = Project.session(s).current - saveSomeSettings(s)(_ == current) - } + def saveSettings(s: State): State = { + val current = Project.session(s).current + saveSomeSettings(s)(_ == current) + } /** * Saves session settings to disk if they match the filter. @@ -164,47 +168,47 @@ object SessionSettings { val newSession = session.copy(append = newAppend.toMap, original = newOriginal.flatten.toSeq) reapply(newSession.copy(original = newSession.mergeSettings, append = Map.empty), s) } - def writeSettings(pref: ProjectRef, settings: List[SessionSetting], original: Seq[Setting[_]], structure: BuildStructure): (Seq[SessionSetting], Seq[Setting[_]]) = - { - val project = Project.getProject(pref, structure).getOrElse(sys.error("Invalid project reference " + pref)) - val writeTo: File = BuildPaths.configurationSources(project.base).headOption.getOrElse(new File(project.base, "build.sbt")) - writeTo.createNewFile() - val path = writeTo.getAbsolutePath - val (inFile, other, _) = ((List[Setting[_]](), List[Setting[_]](), Set.empty[ScopedKey[_]]) /: original.reverse) { - case ((in, oth, keys), s) => - s.pos match { - case RangePosition(`path`, _) if !keys.contains(s.key) => (s :: in, oth, keys + s.key) - case _ => (in, s :: oth, keys) - } - } + def writeSettings(pref: ProjectRef, settings: List[SessionSetting], original: Seq[Setting[_]], structure: BuildStructure): (Seq[SessionSetting], Seq[Setting[_]]) = { + val project = Project.getProject(pref, structure).getOrElse(sys.error("Invalid project reference " + pref)) + val writeTo: File = BuildPaths.configurationSources(project.base).headOption.getOrElse(new File(project.base, "build.sbt")) + writeTo.createNewFile() - val (_, oldShifted, replace, statements) = ((0, List[Setting[_]](), List[SessionSetting](), List[List[String]]()) /: inFile) { - case ((offs, olds, repl, statements), s) => - val RangePosition(_, r @ LineRange(start, end)) = s.pos - settings find (_._1.key == s.key) match { - case Some(ss @ (ns, newLines)) if !ns.init.dependencies.contains(ns.key) => - val shifted = ns withPos RangePosition(path, LineRange(start - offs, start - offs + newLines.size)) - (offs + end - start - newLines.size, shifted :: olds, ss :: repl, newLines +: statements) - case _ => - val shifted = s withPos RangePosition(path, r shift -offs) - (offs, shifted :: olds, repl, statements) - } - } - val newSettings = settings diff replace - val oldContent = IO.readLines(writeTo) - val exist: List[String] = SbtRefactorings.applyStatements(oldContent, statements) - val adjusted = if (!newSettings.isEmpty && needsTrailingBlank(exist)) exist :+ "" else exist - val lines = adjusted ++ newSettings.flatMap(_._2 ::: "" :: Nil) - IO.writeLines(writeTo, lines) - val (newWithPos, _) = ((List[SessionSetting](), adjusted.size + 1) /: newSettings) { - case ((acc, line), (s, newLines)) => - val endLine = line + newLines.size - ((s withPos RangePosition(path, LineRange(line, endLine)), newLines) :: acc, endLine + 1) - } - (newWithPos.reverse, other ++ oldShifted) + val path = writeTo.getAbsolutePath + val (inFile, other, _) = ((List[Setting[_]](), List[Setting[_]](), Set.empty[ScopedKey[_]]) /: original.reverse) { + case ((in, oth, keys), s) => + s.pos match { + case RangePosition(`path`, _) if !keys.contains(s.key) => (s :: in, oth, keys + s.key) + case _ => (in, s :: oth, keys) + } } + val (_, oldShifted, replace) = ((0, List[Setting[_]](), Seq[SessionSetting]()) /: inFile) { + case ((offs, olds, repl), s) => + val RangePosition(_, r @ LineRange(start, end)) = s.pos + settings find (_._1.key == s.key) match { + case Some(ss @ (ns, newLines)) if !ns.init.dependencies.contains(ns.key) => + val shifted = ns withPos RangePosition(path, LineRange(start - offs, start - offs + newLines.size)) + (offs + end - start - newLines.size, shifted :: olds, ss +: repl) + case _ => + val shifted = s withPos RangePosition(path, r shift -offs) + (offs, shifted :: olds, repl) + } + } + val newSettings = settings diff replace + val oldContent = IO.readLines(writeTo) + val (_, exist) = SbtRefactorings.applySessionSettings((writeTo, oldContent), replace) + val adjusted = if (!newSettings.isEmpty && needsTrailingBlank(exist)) exist :+ "" else exist + val lines = adjusted ++ newSettings.flatMap(x => x._2 :+ "") + IO.writeLines(writeTo, lines) + val (newWithPos, _) = ((List[SessionSetting](), adjusted.size + 1) /: newSettings) { + case ((acc, line), (s, newLines)) => + val endLine = line + newLines.size + ((s withPos RangePosition(path, LineRange(line, endLine)), newLines) :: acc, endLine + 1) + } + (newWithPos.reverse, other ++ oldShifted) + } + def needsTrailingBlank(lines: Seq[String]) = !lines.isEmpty && !lines.takeRight(1).exists(_.trim.isEmpty) /** Prints all the user-defined SessionSettings (not raw) to System.out. */ @@ -216,11 +220,13 @@ object SessionSettings { } s } + def printSettings(s: State): State = withSettings(s) { session => printSettings(session.append.getOrElse(session.current, Nil)) s } + def printSettings(settings: Seq[SessionSetting]): Unit = for (((_, stringRep), index) <- settings.zipWithIndex) println(" " + (index + 1) + ". " + stringRep.mkString("\n")) @@ -260,9 +266,13 @@ save, save-all /** AST for the syntax of the session command. Each subclass is an action that can be performed. */ sealed trait SessionCommand + final class Print(val all: Boolean) extends SessionCommand + final class Clear(val all: Boolean) extends SessionCommand + final class Save(val all: Boolean) extends SessionCommand + final class Remove(val ranges: Seq[(Int, Int)]) extends SessionCommand import complete._ @@ -276,7 +286,9 @@ save, save-all remove) lazy val remove = token("remove") ~> token(Space) ~> natSelect.map(ranges => new Remove(ranges)) + def natSelect = rep1sep(token(range, ""), ',') + def range: Parser[(Int, Int)] = (NatBasic ~ ('-' ~> NatBasic).?).map { case lo ~ hi => (lo, hi getOrElse lo) } /** The raw implementation of the sessoin command. */ diff --git a/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala index 1acd3ae4f..6074d8611 100644 --- a/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala +++ b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala @@ -7,31 +7,48 @@ import scala.reflect.runtime.universe._ private[sbt] object SbtRefactorings { import sbt.internals.parser.SplitExpressionsNoBlankies.{ END_OF_LINE, FAKE_FILE } + import sbt.SessionSettings.{ SessionSetting, SbtConfigFile } val EMPTY_STRING = "" val REVERSE_ORDERING_INT = Ordering[Int].reverse - def applyStatements(lines: List[String], commands: List[List[String]]): List[String] = { + /** + * Refactoring a `.sbt` file so that the new settings are used instead of any existing settings. + * @param configFile SbtConfigFile with the lines of an sbt file as a List[String] where each string is one line + * @param commands A List of settings (space separate) that should be inserted into the current file. + * If the settings replaces a value, it will replace the original line in the .sbt file. + * If in the `.sbt` file we have multiply value for one settings - + * the first will be replaced and the other will be removed. + * @return a SbtConfigFile with new lines which represent the contents of the refactored .sbt file. + */ + def applySessionSettings(configFile: SbtConfigFile, commands: Seq[SessionSetting]): SbtConfigFile = { + val (file, lines) = configFile val split = SplitExpressionsNoBlankies(FAKE_FILE, lines) - val recordedCommand = recordCommands(commands, split) - val sortedRecordedCommand = recordedCommand.sortBy(_._1)(REVERSE_ORDERING_INT) + val recordedCommands = recordCommands(commands, split) + val sortedRecordedCommands = recordedCommands.sortBy(_._1)(REVERSE_ORDERING_INT) - val newContent = sortedRecordedCommand.foldLeft(split.modifiedContent) { + val newContent = replaceFromBottomToTop(split.modifiedContent, sortedRecordedCommands) + (file, newContent.lines.toList) + } + + private def replaceFromBottomToTop(modifiedContent: String, sortedRecordedCommands: Seq[(Int, String, String)]) = { + sortedRecordedCommands.foldLeft(modifiedContent) { case (acc, (from, old, replacement)) => val before = acc.substring(0, from) val after = acc.substring(from + old.length, acc.length) - val afterLast = emptyStringForEmptyStatement(after) + val afterLast = emptyStringForEmptyString(after) before + replacement + afterLast } - newContent.lines.toList } - def emptyStringForEmptyStatement(after: String) = - if (after.trim.isEmpty) EMPTY_STRING else after + private def emptyStringForEmptyString(text: String) = { + val trimmed = text.trim + if (trimmed.isEmpty) trimmed else text + } - private def recordCommands(commands: List[List[String]], split: SplitExpressionsNoBlankies) = + private def recordCommands(commands: Seq[SessionSetting], split: SplitExpressionsNoBlankies) = commands.flatMap { - command => + case (_, command) => val map = toTreeStringMap(command) map.flatMap { case (name, statement) => @@ -39,7 +56,7 @@ private[sbt] object SbtRefactorings { } } - private def treesToReplacements(split: SplitExpressionsNoBlankies, name: String, command: List[String]) = + private def treesToReplacements(split: SplitExpressionsNoBlankies, name: String, command: Seq[String]) = split.settingsTrees.foldLeft(Seq.empty[(Int, String, String)]) { case (acc, (st, tree)) => val treeName = extractSettingName(tree) @@ -55,7 +72,7 @@ private[sbt] object SbtRefactorings { } } - private def toTreeStringMap(command: List[String]) = { + private def toTreeStringMap(command: Seq[String]) = { val split = SplitExpressionsNoBlankies(FAKE_FILE, command) val trees = split.settingsTrees val seq = trees.map { diff --git a/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala b/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala index 64bf370ca..51a75edd8 100644 --- a/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala @@ -1,4 +1,6 @@ -package sbt.internals.parser +package sbt +package internals +package parser import java.io.{ File, FilenameFilter } @@ -6,15 +8,16 @@ import org.specs2.matcher.MatchResult import scala.collection.GenTraversableOnce import scala.io.Source +import SessionSettings.SessionSetting -abstract class AbstractSessionSettingsSpec(folder: String, deepCompare: Boolean = false) extends AbstractSpec { +abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec { protected val rootPath = getClass.getClassLoader.getResource("").getPath + folder println(s"Reading files from: $rootPath") protected val rootDir = new File(rootPath) "SessionSettings " should { "Be identical for empty map " in { - def unit(f: File) = Seq((Source.fromFile(f).getLines().toList, Nil)) + def unit(f: File) = Seq((Source.fromFile(f).getLines().toList, Seq())) runTestOnFiles(unit) } @@ -23,7 +26,7 @@ abstract class AbstractSessionSettingsSpec(folder: String, deepCompare: Boolean } } - private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], List[String])]): MatchResult[GenTraversableOnce[File]] = { + private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], Seq[SessionSetting])]): MatchResult[GenTraversableOnce[File]] = { val allFiles = rootDir.listFiles(new FilenameFilter() { def accept(dir: File, name: String) = name.endsWith(".sbt.txt") @@ -33,12 +36,9 @@ abstract class AbstractSessionSettingsSpec(folder: String, deepCompare: Boolean val originalLines = Source.fromFile(file).getLines().toList foreach(expectedResultAndMap(file)) { case (expectedResultList, commands) => - val resultList = SbtRefactorings.applyStatements(originalLines, commands.map(List(_))) + val resultList = SbtRefactorings.applySessionSettings((file, originalLines), commands) val expected = SplitExpressionsNoBlankies(file, expectedResultList) - val result = SplitExpressionsNoBlankies(file, resultList) - if (deepCompare) { - expectedResultList must_== resultList - } + val result = SplitExpressionsNoBlankies(file, resultList._2) result.settings must_== expected.settings } @@ -58,9 +58,10 @@ abstract class AbstractSessionSettingsSpec(folder: String, deepCompare: Boolean override def accept(dir: File, name: String) = name.endsWith(".set") }) files.map { file => - val list = Source.fromFile(file).getLines().toList + val seq = Source.fromFile(file).getLines().toSeq val result = Source.fromFile(file.getAbsolutePath + ".result").getLines().toList - (result, list) + val sessionSettings = seq.map(line => (null, Seq(line))) + (result, sessionSettings) } } } From e230a17d3b5bcf3a724e1053cd8f90c70fc620af Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Wed, 1 Oct 2014 08:56:43 +0200 Subject: [PATCH 18/21] @deprecated("Removed from the public API", "2.11.0") def isDefined: Boolean --- .../parser/SplitExpressionsNoBlankies.scala | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala index d89ffd8b4..66992e31d 100644 --- a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala @@ -82,16 +82,17 @@ private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String } def convertStatement(t: Tree): Option[(String, Tree, LineRange)] = - if (t.pos.isDefined) { - val originalStatement = modifiedContent.substring(t.pos.start, t.pos.end) - val statement = parseStatementAgain(t, originalStatement) - val numberLines = countLines(statement) - Some((statement, t, LineRange(t.pos.line - 1, t.pos.line + numberLines))) - } else { - None + t.pos match { + case NoPosition => + None + case position => + val originalStatement = modifiedContent.substring(position.start, position.end) + val statement = parseStatementAgain(t, originalStatement) + val numberLines = countLines(statement) + Some((statement, t, LineRange(position.line - 1, position.line + numberLines))) } - val statementsTreeLineRange = statements flatMap convertStatement - (imports map convertImport, statementsTreeLineRange.map(t => (t._1, t._3)), statementsTreeLineRange.map(t => (t._1, t._2)), modifiedContent) + val stmtTreeLineRange = statements flatMap convertStatement + (imports map convertImport, stmtTreeLineRange.map { case (stmt, _, lr) => (stmt, lr) }, stmtTreeLineRange.map { case (stmt, tree, _) => (stmt, tree) }, modifiedContent) } private def countLines(statement: String) = statement.count(c => c == END_OF_LINE_CHAR) From 009426d896aaa78f1eeb5ebbe1132c9f36add546 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 1 Oct 2014 13:15:20 -0400 Subject: [PATCH 19/21] Documentation and renaming of "blankies" into somethign a bit easier to find. * Rename SPlitExpression* to `SbtParser` denoting that is parses .sbt files * Adds a few todos * Document APIs for internal usage. --- .../scala/sbt/EvaluateConfigurations.scala | 6 ++- main/src/main/scala/sbt/SessionSettings.scala | 13 +++++- ...ssionsNoBlankies.scala => SbtParser.scala} | 44 +++++++++++++++++-- .../internals/parser/SbtRefactorings.scala | 10 ++--- .../sbt/internals/parser/ErrorSpec.scala | 2 +- .../parser/SessionSettingsSpec.scala | 4 +- 6 files changed, 64 insertions(+), 15 deletions(-) rename main/src/main/scala/sbt/internals/parser/{SplitExpressionsNoBlankies.scala => SbtParser.scala} (89%) diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index 44fdcf29f..3763e315c 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -8,7 +8,7 @@ import compiler.{ Eval, EvalImports } import complete.DefaultParsers.validID import Def.{ ScopedKey, Setting } import Scope.GlobalScope -import sbt.internals.parser.SplitExpressionsNoBlankies +import sbt.internals.parser.SbtParser import scala.annotation.tailrec @@ -218,7 +218,9 @@ object EvaluateConfigurations { */ private[sbt] def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { - val split = SplitExpressionsNoBlankies(file, lines) + val split = SbtParser(file, lines) + // TODO - Look at pulling the parsed expression trees from the SbtParser and stitch them back into a different + // scala compiler rather than re-parsing. (split.imports, split.settings) } diff --git a/main/src/main/scala/sbt/SessionSettings.scala b/main/src/main/scala/sbt/SessionSettings.scala index 2ca37ffac..ff70359a5 100755 --- a/main/src/main/scala/sbt/SessionSettings.scala +++ b/main/src/main/scala/sbt/SessionSettings.scala @@ -130,11 +130,18 @@ object SessionSettings { oldState.log.warn("Discarding " + pluralize(oldSettings.size, " session setting") + ". Use 'session save' to persist session settings.") } + @deprecated("This method will no longer be public", "0.13.7") def removeRanges[T](in: Seq[T], ranges: Seq[(Int, Int)]): Seq[T] = { val asSet = (Set.empty[Int] /: ranges) { case (s, (hi, lo)) => s ++ (hi to lo) } in.zipWithIndex.flatMap { case (t, index) => if (asSet(index + 1)) Nil else t :: Nil } } + /** + * Removes settings from the current session, by range. + * @param s The current build state. + * @param ranges A set of Low->High tuples for which settings to remove. + * @return The new build state with settings removed. + */ def removeSettings(s: State, ranges: Seq[(Int, Int)]): State = withSettings(s) { session => val current = session.current @@ -169,6 +176,7 @@ object SessionSettings { reapply(newSession.copy(original = newSession.mergeSettings, append = Map.empty), s) } + @deprecated("This method will no longer be publlic", "0.13.7") def writeSettings(pref: ProjectRef, settings: List[SessionSetting], original: Seq[Setting[_]], structure: BuildStructure): (Seq[SessionSetting], Seq[Setting[_]]) = { val project = Project.getProject(pref, structure).getOrElse(sys.error("Invalid project reference " + pref)) val writeTo: File = BuildPaths.configurationSources(project.base).headOption.getOrElse(new File(project.base, "build.sbt")) @@ -209,6 +217,7 @@ object SessionSettings { (newWithPos.reverse, other ++ oldShifted) } + @deprecated("This method will no longer be publlic", "0.13.7") def needsTrailingBlank(lines: Seq[String]) = !lines.isEmpty && !lines.takeRight(1).exists(_.trim.isEmpty) /** Prints all the user-defined SessionSettings (not raw) to System.out. */ @@ -221,12 +230,14 @@ object SessionSettings { s } + /** Prints all the defined session settings for the current project in the given build state. */ def printSettings(s: State): State = withSettings(s) { session => printSettings(session.append.getOrElse(session.current, Nil)) s } + /** Prints all the passed in session settings */ def printSettings(settings: Seq[SessionSetting]): Unit = for (((_, stringRep), index) <- settings.zipWithIndex) println(" " + (index + 1) + ". " + stringRep.mkString("\n")) @@ -291,7 +302,7 @@ save, save-all def range: Parser[(Int, Int)] = (NatBasic ~ ('-' ~> NatBasic).?).map { case lo ~ hi => (lo, hi getOrElse lo) } - /** The raw implementation of the sessoin command. */ + /** The raw implementation of the session command. */ def command(s: State) = Command.applyEffect(parser) { case p: Print => if (p.all) printAllSettings(s) else printSettings(s) case v: Save => if (v.all) saveAllSettings(s) else saveSettings(s) diff --git a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/internals/parser/SbtParser.scala similarity index 89% rename from main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala rename to main/src/main/scala/sbt/internals/parser/SbtParser.scala index 66992e31d..785492e7b 100644 --- a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/internals/parser/SbtParser.scala @@ -4,20 +4,56 @@ package parser import java.io.File -import sbt.internals.parser.SplitExpressionsNoBlankies._ +import sbt.internals.parser.SbtParser._ import scala.annotation.tailrec import scala.reflect.runtime.universe._ -private[sbt] object SplitExpressionsNoBlankies { +private[sbt] object SbtParser { val END_OF_LINE_CHAR = '\n' val END_OF_LINE = String.valueOf(END_OF_LINE_CHAR) private[parser] val NOT_FOUND_INDEX = -1 private[sbt] val FAKE_FILE = new File("fake") } -private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { +/** + * This method soley exists to add scaladoc to members in SbtParser which + * are defined using pattern matching. + */ +sealed trait ParsedSbtFileExpressions { + /** The set of parsed import expressions. */ + def imports: Seq[(String, Int)] + /** The set of parsed defintions and/or sbt build settings. */ + def settings: Seq[(String, LineRange)] + /** The set of scala tree's for parsed definitions/settings and the underlying string representation.. */ + def settingsTrees: Seq[(String, Tree)] + /** Represents the changes we had to perform to the sbt file so that XML will parse correctly. */ + def modifiedContent: String +} + +/** + * An initial parser/splitter of .sbt files. + * + * This class is responsible for chunking a `.sbt` file into expression ranges + * which we can then compile using the Scala compiler. + * + * Example: + * + * {{{ + * val parser = SbtParser(myFile, IO.readLines(myFile)) + * // All import statements + * val imports = parser.imports + * // All other statements (val x =, or raw settings) + * val settings = parser.settings + * }}} + * + * @param file The file we're parsing (may be a dummy file) + * @param lines The parsed "lines" of the file, where each string is a line. + */ +private[sbt] case class SbtParser(file: File, lines: Seq[String]) extends ParsedSbtFileExpressions { //settingsTrees,modifiedContent needed for "session save" + // TODO - We should look into splitting out "defintiions" vs. "settings" here instead of further string lookups, since we have the + // parsed trees. val (imports, settings, settingsTrees, modifiedContent) = splitExpressions(file, lines) private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)], String) = { @@ -117,7 +153,7 @@ private[sbt] object MissingBracketHandler { case Some(index) => val text = content.substring(positionEnd, index + 1) val textWithoutBracket = text.substring(0, text.length - 1) - util.Try(SplitExpressionsNoBlankies(FAKE_FILE, textWithoutBracket.lines.toSeq)) match { + util.Try(SbtParser(FAKE_FILE, textWithoutBracket.lines.toSeq)) match { case util.Success(_) => text case util.Failure(th) => diff --git a/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala index 6074d8611..dde9643cc 100644 --- a/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala +++ b/main/src/main/scala/sbt/internals/parser/SbtRefactorings.scala @@ -6,7 +6,7 @@ import scala.reflect.runtime.universe._ private[sbt] object SbtRefactorings { - import sbt.internals.parser.SplitExpressionsNoBlankies.{ END_OF_LINE, FAKE_FILE } + import sbt.internals.parser.SbtParser.{ END_OF_LINE, FAKE_FILE } import sbt.SessionSettings.{ SessionSetting, SbtConfigFile } val EMPTY_STRING = "" @@ -23,7 +23,7 @@ private[sbt] object SbtRefactorings { */ def applySessionSettings(configFile: SbtConfigFile, commands: Seq[SessionSetting]): SbtConfigFile = { val (file, lines) = configFile - val split = SplitExpressionsNoBlankies(FAKE_FILE, lines) + val split = SbtParser(FAKE_FILE, lines) val recordedCommands = recordCommands(commands, split) val sortedRecordedCommands = recordedCommands.sortBy(_._1)(REVERSE_ORDERING_INT) @@ -46,7 +46,7 @@ private[sbt] object SbtRefactorings { if (trimmed.isEmpty) trimmed else text } - private def recordCommands(commands: Seq[SessionSetting], split: SplitExpressionsNoBlankies) = + private def recordCommands(commands: Seq[SessionSetting], split: SbtParser) = commands.flatMap { case (_, command) => val map = toTreeStringMap(command) @@ -56,7 +56,7 @@ private[sbt] object SbtRefactorings { } } - private def treesToReplacements(split: SplitExpressionsNoBlankies, name: String, command: Seq[String]) = + private def treesToReplacements(split: SbtParser, name: String, command: Seq[String]) = split.settingsTrees.foldLeft(Seq.empty[(Int, String, String)]) { case (acc, (st, tree)) => val treeName = extractSettingName(tree) @@ -73,7 +73,7 @@ private[sbt] object SbtRefactorings { } private def toTreeStringMap(command: Seq[String]) = { - val split = SplitExpressionsNoBlankies(FAKE_FILE, command) + val split = SbtParser(FAKE_FILE, command) val trees = split.settingsTrees val seq = trees.map { case (statement, tree) => diff --git a/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala b/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala index 1ac265050..25cd348f4 100644 --- a/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/ErrorSpec.scala @@ -18,7 +18,7 @@ class ErrorSpec extends AbstractSpec with ScalaCheck { foreach(new File(rootPath).listFiles) { file => print(s"Processing ${file.getName}: ") val buildSbt = Source.fromFile(file).getLines().mkString("\n") - SplitExpressionsNoBlankies(file, buildSbt.lines.toSeq) must throwA[MessageOnlyException].like { + SbtParser(file, buildSbt.lines.toSeq) must throwA[MessageOnlyException].like { case exp => val message = exp.getMessage println(s"${exp.getMessage}") diff --git a/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala b/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala index 51a75edd8..db64a065a 100644 --- a/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala +++ b/main/src/test/scala/sbt/internals/parser/SessionSettingsSpec.scala @@ -37,8 +37,8 @@ abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec foreach(expectedResultAndMap(file)) { case (expectedResultList, commands) => val resultList = SbtRefactorings.applySessionSettings((file, originalLines), commands) - val expected = SplitExpressionsNoBlankies(file, expectedResultList) - val result = SplitExpressionsNoBlankies(file, resultList._2) + val expected = SbtParser(file, expectedResultList) + val result = SbtParser(file, resultList._2) result.settings must_== expected.settings } From f2496fa05086bb7b1dce53508b7b39653db75ad4 Mon Sep 17 00:00:00 2001 From: "andrzej.jozwik@gmail.com" Date: Fri, 3 Oct 2014 22:57:17 +0200 Subject: [PATCH 20/21] .gitignore cleaned --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4d856c4e5..e762de7f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ target/ __pycache__ -.idea -.idea_modules From 608c499ad2299f50da2870c666ade27845af5619 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 6 Oct 2014 14:25:29 -0400 Subject: [PATCH 21/21] notes --- notes/0.13.7/no-blankies.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 notes/0.13.7/no-blankies.md diff --git a/notes/0.13.7/no-blankies.md b/notes/0.13.7/no-blankies.md new file mode 100644 index 000000000..62a984eab --- /dev/null +++ b/notes/0.13.7/no-blankies.md @@ -0,0 +1,8 @@ + [1606]: https://github.com/sbt/sbt/issues/1606 + [1645]: https://github.com/sbt/sbt/pull/1645 + +### whitespace handling improvements + +Starting sbt 0.13.7, build.sbt will be parsed using a customized Scala parser. This eliminates the requirement to use blank line as the delimiter between each settings, and also allows blank lines to be inserted at arbitrary position within a block. + +This feature was contributed by [Andrzej Jozwik @ajozwik](https://github.com/ajozwik) inspired by Typesafe's @gkossakowski organizing multiple meetups with Warszaw Scala on [how to patch sbt specifically with the focus on blank line issue](http://blog.japila.pl/2014/07/gkossakowski-on-warszawscala-about-how-to-patch-scalasbt/). [#1606][1606]/[#1645][1645]