Merge pull request #370 from vigdorchik/introduce_range_positions

Remember the range for settings read from .sbt files
This commit is contained in:
Mark Harrah 2012-02-17 05:04:59 -08:00
commit b6c9742060
5 changed files with 36 additions and 18 deletions

View File

@ -10,7 +10,7 @@ package sbt
import complete.DefaultParsers.validID
import Compiler.Compilers
import Keys.{globalBaseDirectory, globalPluginsDirectory, globalSettingsDirectory, stagingDirectory, Streams}
import Project.{ScopedKey, Setting, SourceCoord}
import Project.{ScopedKey, Setting}
import Keys.{globalBaseDirectory, Streams}
import Scope.GlobalScope
import scala.annotation.tailrec
@ -73,8 +73,8 @@ object EvaluateConfigurations
def evaluateConfiguration(eval: Eval, name: String, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => Seq[Setting[_]] =
{
val (importExpressions, settingExpressions) = splitExpressions(lines)
val settings = addOffset(offset, settingExpressions) map { case (settingExpression,line) =>
evaluateSetting(eval, name, (imports.map(s => (s, -1)) ++ addOffset(offset, importExpressions)), settingExpression, line)
val settings = addOffsetToRange(offset, settingExpressions) map { case (settingExpression,range) =>
evaluateSetting(eval, name, (imports.map(s => (s, -1)) ++ addOffset(offset, importExpressions)), settingExpression, range)
}
flatten(settings)
}
@ -82,24 +82,26 @@ object EvaluateConfigurations
loader => mksettings.flatMap(_ apply loader)
def addOffset(offset: Int, lines: Seq[(String,Int)]): Seq[(String,Int)] =
lines.map { case (s, i) => (s, i + offset) }
def addOffsetToRange(offset: Int, ranges: Seq[(String,LineRange)]): Seq[(String,LineRange)] =
ranges.map { case (s, r) => (s, r shift offset) }
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, line: Int): ClassLoader => Seq[Setting[_]] =
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, range: LineRange): ClassLoader => Seq[Setting[_]] =
{
val result = try {
eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some("sbt.Project.SettingsDefinition"), line = line)
eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some("sbt.Project.SettingsDefinition"), line = range.start)
} catch {
case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage)
}
loader => {
val coord = SourceCoord(name, line + 1)
result.getValue(loader).asInstanceOf[Project.SettingsDefinition].settings map (_ withPos coord)
val pos = RangePosition(name, range shift 1)
result.getValue(loader).asInstanceOf[Project.SettingsDefinition].settings map (_ withPos pos)
}
}
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(lines: Seq[String]): (Seq[(String,Int)], Seq[(String,Int)]) =
def splitExpressions(lines: Seq[String]): (Seq[(String,Int)], Seq[(String,LineRange)]) =
{
val blank = (_: String).forall(isSpace)
val isImport = firstNonSpaceIs("import ")
@ -110,16 +112,16 @@ object EvaluateConfigurations
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,Int)] =
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,Int)]): Seq[(String,Int)] =
@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"), next.head._2) +: accum
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)

View File

@ -166,7 +166,7 @@ object BuiltinCommands
def set = Command(SetCommand, setBrief, setDetailed)(setParser) { case (s, (all, arg)) =>
val extracted = Project extract s
import extracted._
val settings = EvaluateConfigurations.evaluateSetting(session.currentEval(), "<set>", imports(extracted), arg, 0)(currentLoader)
val settings = EvaluateConfigurations.evaluateSetting(session.currentEval(), "<set>", imports(extracted), arg, LineRange(0,0))(currentLoader)
val newSession = if(all) Project.setAll(extracted, settings) else setThis(s, extracted, settings, arg)
reapply(newSession, structure, s)
}

View File

@ -275,7 +275,7 @@ object Project extends Init[Scope] with ProjectExtra
val comp = compiled(structure.settings, actual)(structure.delegates, structure.scopeLocal, display)
val definedAt = comp get scoped map { c =>
def fmt(s: Setting[_]) = s.pos match {
case SourceCoord(fileName, line) => Some(fileName + ":" + line)
case pos: FilePosition => Some(pos.path + ":" + pos.startLine)
case NoPosition => None
}
val posDefined = c.settings.map(fmt).flatten

20
util/collection/Positions.scala Executable file
View File

@ -0,0 +1,20 @@
package sbt
sealed trait SourcePosition
sealed trait FilePosition {
def path: String
def startLine: Int
}
case object NoPosition extends SourcePosition
final case class LinePosition(path: String, startLine: Int) extends SourcePosition with FilePosition
final case class LineRange(start: Int, end: Int) {
def shift(n: Int) = new LineRange(start + n, end + n)
}
final case class RangePosition(path: String, range: LineRange) extends SourcePosition with FilePosition {
def startLine = range.start
}

View File

@ -255,14 +255,10 @@ trait Init[Scope]
def mapKey(g: MapScoped): Setting[T] = new Setting(g(key), init, pos)
def mapInit(f: (ScopedKey[T], T) => T): Setting[T] = new Setting(key, init(t => f(key,t)), pos)
def mapConstant(g: MapConstant): Setting[T] = new Setting(key, init mapConstant g, pos)
def withPos(pos: SourceCoord) = new Setting(key, init, pos)
def withPos(pos: SourcePosition) = new Setting(key, init, pos)
override def toString = "setting(" + key + ") at " + pos
}
sealed trait SourcePosition
case object NoPosition extends SourcePosition
final case class SourceCoord(fileName: String, line: Int) extends SourcePosition
// mainly for reducing generated class count
private[this] def validateReferencedT(g: ValidateRef) =
new (Initialize ~> ValidatedInit) { def apply[T](i: Initialize[T]) = i validateReferenced g }