From 009426d896aaa78f1eeb5ebbe1132c9f36add546 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 1 Oct 2014 13:15:20 -0400 Subject: [PATCH] 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 }