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)))
+ }
+}