|
/* sbt -- Simple Build Tool |
|
* Copyright 2011 Mark Harrah, Eugene Yokota |
|
*/ |
|
package sbt |
|
package myinspect |
|
|
|
import java.io.File |
|
|
|
import Def.{ScopedKey, compiled, flattenLocals} |
|
|
|
import Predef.{any2stringadd => _, _} |
|
|
|
object SettingGraph { |
|
def myInspect = |
|
Command("inspect2", ("",""), "")(Inspect.parser) { |
|
case (s, (option, sk)) => |
|
s.log.info(Inspect.output(s, option, sk)) |
|
s |
|
} |
|
|
|
def apply(structure: BuildStructure, basedir: File, scoped: ScopedKey[_], generation: Int)(implicit display: Show[ScopedKey[_]]): SettingGraph = |
|
{ |
|
val comp = compiled(structure.settings, actual = true /* <--- The only thing I actually changed */)(structure.delegates, structure.scopeLocal, display) |
|
val cMap = flattenLocals(comp) |
|
|
|
|
|
def loop(scoped: ScopedKey[_], generation: Int): SettingGraph = |
|
{ |
|
val key = scoped.key |
|
val scope = scoped.scope |
|
|
|
val definedIn = structure.data.definingScope(scope, key) map { sc => display(ScopedKey(sc, key)) } |
|
val depends = cMap.get(scoped) match { case Some(c) => c.dependencies.toSet; case None => Set.empty } |
|
|
|
SettingGraph(display(scoped), definedIn, |
|
Project.scopedKeyData(structure, scope, key), |
|
key.description, basedir, |
|
depends map { (x: ScopedKey[_]) => loop(x, generation + 1) }) |
|
} |
|
loop(scoped, generation) |
|
} |
|
|
|
trait Graph[N] { |
|
def successors(node: N): Seq[N] |
|
} |
|
def findPaths[N](from: N, to: N)(implicit graph: Graph[N]): Seq[List[N]] = { |
|
def find(from: N, visited: Set[N]): (Set[N], Seq[List[N]]) = { |
|
if (visited.contains(from)) (visited, Seq.empty) // been here... |
|
else if (from == to) (visited, Seq(to :: Nil)) |
|
else { |
|
graph.successors(from) |
|
.foldLeft((visited + from, Seq.empty[List[N]])) { (state, succ) => |
|
val (visited, found) = state |
|
val (newVisited, newlyFound) = find(succ, visited) |
|
(newVisited, found ++ newlyFound.map(from :: _)) |
|
} |
|
} |
|
} |
|
find(from, Set.empty)._2 |
|
} |
|
|
|
def findChains(structure: BuildStructure, basedir: File, from: ScopedKey[_], to: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String = { |
|
val comp = compiled(structure.settings, actual = true /* <--- The only thing I actually changed */)(structure.delegates, structure.scopeLocal, display) |
|
val cMap = flattenLocals(comp) |
|
|
|
implicit val keyGraph = new Graph[ScopedKey[_]] { |
|
def successors(node: ScopedKey[_]): Seq[ScopedKey[_]] = |
|
cMap.get(node) match { case Some(c) => c.dependencies.toSeq; case None => Seq.empty } |
|
} |
|
|
|
findPaths[ScopedKey[_]](from, to)(keyGraph).map { path => |
|
path.map(display(_)).mkString(" -> ") |
|
}.mkString("\n") |
|
} |
|
} |
|
|
|
case class SettingGraph(name: String, |
|
definedIn: Option[String], |
|
data: Option[ScopedKeyData[_]], |
|
description: Option[String], |
|
basedir: File, |
|
depends: Set[SettingGraph]) { |
|
def dataString: String = |
|
data map { d => |
|
d.settingValue map { |
|
case f: File => IO.relativize(basedir, f) getOrElse { f.toString } |
|
case x => x.toString |
|
} getOrElse { d.typeName } |
|
} getOrElse { "" } |
|
|
|
def dependsAscii(defaultWidth: Int) = Graph.toAscii(this, |
|
(x: SettingGraph) => x.depends.toSeq.sortBy(_.name), |
|
(x: SettingGraph) => "%s = %s" format (x.definedIn getOrElse { "" }, x.dataString), |
|
defaultWidth |
|
) |
|
} |
|
|
|
object Graph { |
|
// [info] foo |
|
// [info] +-bar |
|
// [info] | +-baz |
|
// [info] | |
|
// [info] +-quux |
|
def toAscii[A](top: A, children: A => Seq[A], display: A => String, defaultWidth: Int): String = { |
|
val maxColumn = math.max(JLine.usingTerminal(_.getWidth), defaultWidth) - 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 = |
|
if (at < s.length) |
|
s.slice(0, at) + |
|
(s(at).toString match { |
|
case " " => "|" |
|
case x => x |
|
}) + |
|
s.slice(at + 1, s.length) |
|
else s |
|
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") |
|
} |
|
} |
|
|
|
|
|
import complete.{ DefaultParsers, Parser } |
|
import DefaultParsers._ |
|
import Def.ScopedKey |
|
//import Types.idFun |
|
import java.io.File |
|
|
|
object Inspect { |
|
sealed trait Mode |
|
final case class Details(actual: Boolean) extends Mode |
|
private[this] final class Opt(override val toString: String) extends Mode |
|
val DependencyTree: Mode = new Opt("tree") |
|
val Uses: Mode = new Opt("inspect") |
|
val Definitions: Mode = new Opt("definitions") |
|
val How: Mode = new Opt("how") |
|
case class HowComplete(from: ScopedKey[_]) extends Mode |
|
|
|
def parser: State => Parser[(Inspect.Mode, ScopedKey[_])] = (s: State) => spacedModeParser(s) flatMap { |
|
case opt @ (Uses | Definitions) => allKeyParser(s).map(key => (opt, Def.ScopedKey(Global, key))) |
|
case opt @ (DependencyTree | Details(_)) => spacedKeyParser(s).map(key => (opt, key)) |
|
case opt @ How => (spacedKeyParser(s) ~ spacedKeyParser(s)).map { |
|
case (from, to) => (HowComplete(from), to) |
|
} |
|
} |
|
val spacedModeParser: (State => Parser[Mode]) = (s: State) => { |
|
val actual = "actual" ^^^ Details(true) |
|
val tree = "tree" ^^^ DependencyTree |
|
val uses = "uses" ^^^ Uses |
|
val definitions = "definitions" ^^^ Definitions |
|
val how = "how" ^^^ How |
|
token(Space ~> (tree | actual | uses | definitions | how)) ?? Details(false) |
|
} |
|
|
|
def allKeyParser(s: State): Parser[AttributeKey[_]] = |
|
{ |
|
val keyMap = Project.structure(s).index.keyMap |
|
token(Space ~> (ID !!! "Expected key" examples keyMap.keySet)) flatMap { key => Act.getKey(keyMap, key, idFun) } |
|
} |
|
val spacedKeyParser: State => Parser[ScopedKey[_]] = (s: State) => Act.requireSession(s, token(Space) ~> Act.scopedKeyParser(s)) |
|
|
|
def output(s: State, option: Mode, sk: Def.ScopedKey[_]): String = |
|
{ |
|
val extracted = Project.extract(s) |
|
import extracted._ |
|
option match { |
|
case Details(actual) => |
|
Project.details(structure, actual, sk.scope, sk.key) |
|
case DependencyTree => |
|
val basedir = new File(Project.session(s).current.build) |
|
SettingGraph(structure, basedir, sk, 0).dependsAscii(get(sbt.Keys.asciiGraphWidth)) |
|
case Uses => |
|
Project.showUses(Project.usedBy(structure, true, sk.key)) |
|
case Definitions => |
|
Project.showDefinitions(sk.key, Project.definitions(structure, true, sk.key)) |
|
case HowComplete(from) => |
|
val basedir = new File(Project.session(s).current.build) |
|
SettingGraph.findChains(structure, basedir, from, sk) |
|
} |
|
} |
|
|
|
} |