Merge pull request #274 from eed3si9n/inspecttree

"inspect tree <key>" command
This commit is contained in:
Mark Harrah 2011-11-23 05:07:32 -08:00
commit 2f52884df8
5 changed files with 127 additions and 7 deletions

View File

@ -142,6 +142,13 @@ object Command
( (c & opOrIDSpaced(name)) ~ c.+) map { case (f, rem) => (f +: rem).mkString }
}
sealed trait InspectOption
object InspectOption
{
final case class Details(actual: Boolean) extends InspectOption
case object DependencyTree extends InspectOption
}
trait Help
{
def detail: Map[String, String]

View File

@ -84,14 +84,17 @@ LastCommand + """ <key>
See also '""" + LastGrepCommand + "'."
val InspectCommand = "inspect"
val inspectBrief = (InspectCommand + " <key>", "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.")
val inspectBrief = (InspectCommand + " [tree] <key>", "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.")
val inspectDetailed =
InspectCommand + """ <key>
InspectCommand + """ [tree] <key>
For a plain setting, the value bound to the key argument is displayed using its toString method.
Otherwise, the type of task ("Task" or "Input task") is displayed.
"Dependencies" shows the settings that this setting depends on.
If 'tree' is specified, the bound value as well as the settings that this setting depends on
(and their bound values) are displayed as a tree structure.
"Reverse dependencies" shows the settings that depend on this setting.
When a key is resolved to a value, it may not actually be defined in the requested scope.

View File

@ -382,10 +382,16 @@ object BuiltinCommands
val newSession = session.appendSettings( append map (a => (a, arg)))
reapply(newSession, structure, s)
}
def inspect = Command(InspectCommand, inspectBrief, inspectDetailed)(inspectParser) { case (s,(actual,sk)) =>
val detailString = Project.details(Project.structure(s), actual, sk.scope, sk.key)( Project.showContextKey(s) )
logger(s).info(detailString)
s
def inspect = Command(InspectCommand, inspectBrief, inspectDetailed)(inspectParser) {
case (s, (InspectOption.DependencyTree, sk)) =>
val basedir = new File(Project.session(s).current.build)
val treeString = Project.settingGraph(Project.structure(s), basedir, sk)( Project.showContextKey(s) ).dependsAscii
logger(s).info(treeString)
s
case (s, (InspectOption.Details(actual), sk)) =>
val detailString = Project.details(Project.structure(s), actual, sk.scope, sk.key)( Project.showContextKey(s) )
logger(s).info(detailString)
s
}
def lastGrep = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) {
case (s, (pattern,Some(sk))) =>
@ -401,7 +407,13 @@ object BuiltinCommands
val ext = Project.extract(s)
(ext.structure, Select(ext.currentRef), ext.showKey)
}
def inspectParser = (s: State) => token((Space ~> ("actual" ^^^ true)) ?? false) ~ spacedKeyParser(s)
def inspectParser = (s: State) => spacedInspectOptionParser(s) ~ spacedKeyParser(s)
val spacedInspectOptionParser: (State => Parser[InspectOption]) = (s: State) => {
import InspectOption._
val actual = "actual" ^^^ Details(true)
val tree = "tree" ^^^ DependencyTree
token(Space ~> (tree | actual)) ?? Details(false)
}
val spacedKeyParser = (s: State) => Act.requireSession(s, token(Space) ~> Act.scopedKeyParser(s))
val optSpacedKeyParser = (s: State) => spacedKeyParser(s).?
def lastGrepParser(s: State) = Act.requireSession(s, (token(Space) ~> token(NotSpace, "<pattern>")) ~ optSpacedKeyParser(s))

View File

@ -291,6 +291,8 @@ object Project extends Init[Scope] with ProjectExtra
printScopes("Delegates", delegates(structure, scope, key)) +
printScopes("Related", related)
}
def settingGraph(structure: BuildStructure, basedir: File, scoped: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): SettingGraph =
SettingGraph(structure, basedir, scoped, 0)
def graphSettings(structure: BuildStructure, basedir: File)(implicit display: Show[ScopedKey[_]])
{
def graph(actual: Boolean, name: String) = graphSettings(structure, actual, name, new File(basedir, name + ".dot"))

96
main/SettingGraph.scala Normal file
View File

@ -0,0 +1,96 @@
/* sbt -- Simple Build Tool
* Copyright 2011 Mark Harrah, Eugene Yokota
*/
package sbt
import java.net.URI
import java.io.File
import Project.{ScopedKey, flattenLocals, compiled}
import Load.BuildStructure
import Predef.{any2stringadd => _, _}
object SettingGraph
{
def apply(structure: BuildStructure, basedir: File, scoped: ScopedKey[_], generation: Int)
(implicit display: Show[ScopedKey[_]]): SettingGraph =
{
val key = scoped.key
val scope = scoped.scope
lazy val clazz = key.manifest.erasure
lazy val firstType = key.manifest.typeArguments.head
val (typeName: String, value: Option[_]) =
structure.data.get(scope, key) match {
case None => ("", None)
case Some(v) =>
if(clazz == classOf[Task[_]]) ("Task[" + firstType.toString + "]", None)
else if(clazz == classOf[InputTask[_]]) ("InputTask[" + firstType.toString + "]", None)
else (key.manifest.toString, Some(v))
}
val definedIn = structure.data.definingScope(scope, key) map { sc => display(ScopedKey(sc, key)) }
val cMap = flattenLocals(compiled(structure.settings, false)(structure.delegates, structure.scopeLocal, display))
// val related = cMap.keys.filter(k => k.key == key && k.scope != scope)
val depends = cMap.get(scoped) match { case Some(c) => c.dependencies.toSet; case None => Set.empty }
// val reverse = reverseDependencies(cMap, scoped)
SettingGraph(display(scoped), definedIn, typeName, value, key.description, basedir,
depends map { apply(structure, basedir, _, generation + 1) })
}
}
case class SettingGraph(name: String,
definedIn: Option[String],
typeName: String,
value: Option[Any],
description: Option[String],
basedir: File,
depends: Set[SettingGraph])
{
def valueString: String =
value map {
case f: File => IO.relativize(basedir, f) getOrElse {f.toString}
case x => x.toString
} getOrElse {typeName}
def dependsAscii: String = Graph.toAscii(this,
(x: SettingGraph) => x.depends.toSeq,
(x: SettingGraph) => "%s = %s" format(x.definedIn getOrElse {""}, x.valueString))
}
object Graph
{
// [info] foo
// [info] +-bar
// [info] | +-baz
// [info] |
// [info] +-quux
def toAscii[A](top: A, children: A => Seq[A], display: A => String): String = {
val maxColumn = jline.Terminal.getTerminal.getTerminalWidth - 8
val twoSpaces = " " + " " // prevent accidentally being converted into a tab
def limitLine(s: String): String =
if (s.length > maxColumn) s.slice(0, maxColumn - 2) + ".."
else s
def insertBar(s: String, at: Int): String =
s.slice(0, at) +
(s(at).toString match {
case " " => "|"
case x => x
}) +
s.slice(at + 1, s.length)
def toAsciiLines(node: A, level: Int): Vector[String] = {
val line = limitLine((twoSpaces * level) + (if (level == 0) "" else "+-") + display(node))
val cs = Vector(children(node): _*)
val childLines = cs map {toAsciiLines(_, level + 1)}
val withBar = childLines.zipWithIndex flatMap {
case (lines, pos) if pos < (cs.size - 1) => lines map {insertBar(_, 2 * (level + 1))}
case (lines, pos) =>
if (lines.last.trim != "") lines ++ Vector(twoSpaces * (level + 1))
else lines
}
line +: withBar
}
toAsciiLines(top, 0).mkString("\n")
}
}