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")