diff --git a/README.md b/README.md index 255a12b38..3a97598aa 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ the notes of version [0.8.2](https://github.com/jrudolph/sbt-dependency-graph/tr ## Main Tasks + * `dependencyBrowseTree`: Opens a browser window with a visualization of the dependency tree (courtesy of jstree). * `dependencyTree`: Shows an ASCII tree representation of the project's dependencies * `dependencyBrowseGraph`: Opens a browser window with a visualization of the dependency graph (courtesy of graphlib-dot + dagre-d3). * `dependencyList`: Shows a flat list of all transitive dependencies on the sbt console (sorted by organization and name) diff --git a/src/main/resources/tree.html b/src/main/resources/tree.html new file mode 100644 index 000000000..909a9b82e --- /dev/null +++ b/src/main/resources/tree.html @@ -0,0 +1,49 @@ + + + + + dependencyBrowseTree + + + + + + + + + + + +

Dependencies

+Search: +
+ + + + \ No newline at end of file diff --git a/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphKeys.scala b/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphKeys.scala index fbc6ae440..0ca497312 100644 --- a/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphKeys.scala +++ b/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphKeys.scala @@ -49,6 +49,15 @@ trait DependencyGraphKeys { val dependencyBrowseGraph = TaskKey[URI]( "dependency-browse-graph", "Opens an HTML page that can be used to view the graph.") + val dependencyBrowseTreeTarget = SettingKey[File]( + "dependency-browse-tree-target", + "The location dependency browse tree files should be put.") + val dependencyBrowseTreeHTML = TaskKey[URI]( + "dependency-browse-tree-html", + "Creates an HTML page that can be used to view the dependency tree") + val dependencyBrowseTree = TaskKey[URI]( + "dependency-browse-tree", + "Opens an HTML page that can be used to view the dependency tree") val moduleGraph = TaskKey[ModuleGraph]( "module-graph", "The dependency graph for a project") @@ -97,4 +106,4 @@ trait DependencyGraphKeys { private[graph] val crossProjectId = SettingKey[ModuleID]("dependency-graph-cross-project-id") } -object DependencyGraphKeys extends DependencyGraphKeys \ No newline at end of file +object DependencyGraphKeys extends DependencyGraphKeys diff --git a/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphSettings.scala b/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphSettings.scala index 2924fa368..d4b6907e6 100644 --- a/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphSettings.scala +++ b/src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphSettings.scala @@ -22,7 +22,7 @@ import sbt._ import Keys._ import sbt.complete.Parser import net.virtualvoid.sbt.graph.backend.{ IvyReport, SbtUpdateReport } -import net.virtualvoid.sbt.graph.rendering.{ AsciiGraph, DagreHTML } +import net.virtualvoid.sbt.graph.rendering.{ AsciiGraph, DagreHTML, TreeView } import net.virtualvoid.sbt.graph.util.IOUtil import internal.librarymanagement._ import librarymanagement._ @@ -74,12 +74,10 @@ object DependencyGraphSettings { dependencyDot := writeToFile(dependencyDotString, dependencyDotFile).value, dependencyBrowseGraphTarget := { target.value / "browse-dependency-graph" }, dependencyBrowseGraphHTML := browseGraphHTMLTask.value, - dependencyBrowseGraph := { - val uri = dependencyBrowseGraphHTML.value - streams.value.log.info("Opening in browser...") - java.awt.Desktop.getDesktop.browse(uri) - uri - }, + dependencyBrowseGraph := openBrowser(dependencyBrowseGraphHTML).value, + dependencyBrowseTreeTarget := { target.value / "browse-dependency-tree" }, + dependencyBrowseTreeHTML := browseTreeHTMLTask.value, + dependencyBrowseTree := openBrowser(dependencyBrowseTreeHTML).value, dependencyList := printFromGraph(rendering.FlatList.render(_, _.id.idString)).value, dependencyStats := printFromGraph(rendering.Statistics.renderModuleStatsList).value, dependencyDotHeader := @@ -139,6 +137,14 @@ object DependencyGraphSettings { link } + def browseTreeHTMLTask = + Def.task { + val renderedTree = TreeView.createJson(moduleGraph.value) + val link = TreeView.createLink(renderedTree, target.value) + streams.value.log.info(s"HTML tree written to $link") + link + } + def writeToFile(dataTask: TaskKey[String], fileTask: SettingKey[File]) = Def.task { val outFile = fileTask.value @@ -156,6 +162,14 @@ object DependencyGraphSettings { def printFromGraph(f: ModuleGraph ⇒ String) = Def.task { streams.value.log.info(f(moduleGraph.value)) } + def openBrowser(uriKey: TaskKey[URI]) = + Def.task { + val uri = uriKey.value + streams.value.log.info("Opening in browser...") + java.awt.Desktop.getDesktop.browse(uri) + uri + } + def showLicenseInfo(graph: ModuleGraph, streams: TaskStreams): Unit = { val output = graph.nodes.filter(_.isUsed).groupBy(_.license).toSeq.sortBy(_._1).map { diff --git a/src/main/scala/net/virtualvoid/sbt/graph/rendering/TreeView.scala b/src/main/scala/net/virtualvoid/sbt/graph/rendering/TreeView.scala new file mode 100644 index 000000000..582202def --- /dev/null +++ b/src/main/scala/net/virtualvoid/sbt/graph/rendering/TreeView.scala @@ -0,0 +1,42 @@ +package net.virtualvoid.sbt.graph.rendering + +import java.io.File +import java.net.URI + +import net.virtualvoid.sbt.graph.util.IOUtil +import net.virtualvoid.sbt.graph.{ Module, ModuleGraph } + +import scala.util.parsing.json.{ JSONArray, JSONObject } + +object TreeView { + def createJson(graph: ModuleGraph): String = { + val trees = graph.roots + .map(module ⇒ processSubtree(graph, module)) + .toList + JSONArray(trees).toString + } + + def createLink(graphJson: String, targetDirectory: File): URI = { + targetDirectory.mkdirs() + val graphHTML = new File(targetDirectory, "tree.html") + IOUtil.saveResource("tree.html", graphHTML) + IOUtil.writeToFile(graphJson, new File(targetDirectory, "tree.json")) + IOUtil.writeToFile(s"tree_data = $graphJson;", new File(targetDirectory, "tree.data.js")) + new URI(graphHTML.toURI.toString) + } + + private def processSubtree(graph: ModuleGraph, module: Module): JSONObject = { + val children = graph.dependencyMap + .getOrElse(module.id, List()) + .map(module ⇒ processSubtree(graph, module)) + .toList + moduleAsJson(module, children) + } + + private def moduleAsJson(module: Module, children: List[JSONObject]): JSONObject = { + val eviction = module.evictedByVersion.map(version ⇒ s" (evicted by $version)").getOrElse("") + val error = module.error.map(err ⇒ s" (errors: $err)").getOrElse("") + val text = module.id.idString + eviction + error + JSONObject(Map("text" -> text, "children" -> JSONArray(children))) + } +}