diff --git a/README.md b/README.md index 0cf0b42..a045e3b 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,9 @@ at `localhost:18080`. This tool is a rewrite from scratch of a tool I wrote while at Google for exploring the dependency graphs used by the Blaze build system. The basic design could be easily be generalized to support any kind of -dependency graph, independent of language or domain. +dependency graph, independent of language or domain, or turned into a +secure multi-user web service that operates on graph data uploaded from +the client program that generates it. + +You can probably tell that web UIs are not my expertise. +PRs that provide cosmetic improvements are most welcome! diff --git a/code.js b/code.js index c3eb126..333b4de 100644 --- a/code.js +++ b/code.js @@ -5,8 +5,6 @@ var broken = null // array of 2-arrays of int, the node ids of broken edges. function onLoad() { // Grab data from server: package graph, "directory" tree, broken edges. - // TODO(adonovan): opt: put JSON data in the /index.html page? - // There's no need for a second HTTP request. jQuery.ajax({url: "/data", success: onData}) } @@ -76,10 +74,9 @@ function onData(data) { function selectPkg(json) { if (json.Package < 0) { // Non-package "directory" node: clear the fields. - $('#json').text("") $('#pkgname').text("none") $('#doc').text("") - $('#imports').text("") + $('#imports').html("") $('#path').text("") return } @@ -87,25 +84,34 @@ function selectPkg(json) { // A package node was selected. var pkg = packages[json.Package] - // Show JSON, for debugging. - $('#json').html("" + JSON.stringify(json) + "") - // Show selected package. $('#pkgname').text(pkg.PkgPath) - // Set link to package documentation. - $('#doc').html("doc") - - // TODO(adonovan): display imports as a set of links, - // with as ImportPath text and "select dir tree node" as action. - $('#imports').text(json.Imports.join(" ")) + // Set link to Go package documentation. + $('#doc').html("") + + // Show imports in a drop-down menu. + // Selecting an import acts like clicking on that package in the tree. + var imports = $('#imports') + imports.html("") + var option = document.createElement("option") + option.textContent = "..." + option.value = "-1" + imports.append(option) + for (var i in json.Imports) { + var imp = json.Imports[i] + option = document.createElement("option") + option.textContent = packages[imp].PkgPath + option.value = "" + imp // package index, used by onSelectImport + imports.append(option) + } // Show "break edges" buttons. var html = "" var path = [].concat(json.Path).reverse() // from root to selected package for (var i in path) { var p = packages[path[i]] - if (i >= 0) { + if (i > 0) { html += " " + " " + "⟶ " @@ -124,3 +130,11 @@ function unbreak(i, j) { // Must reload the page since the graph has changed. document.location = "/unbreak?from=" + i + "&to=" + j } + +// onSelectImport is called by the imports dropdown. +function onSelectImport(sel) { + if (sel.value >= 0) { + // Simulate a click on a tree node corresponding to the selected import. + $('#tree').jstree('select_node', 'node' + sel.value); + } +} diff --git a/go.mod b/go.mod index d22436d..57e26d6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/adonovan/spaghetti go 1.16 require ( - github.com/google/renameio v0.1.0 // indirect - golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399 // indirect + golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399 golang.org/x/tools/gopls v0.6.9 // indirect ) diff --git a/go.sum b/go.sum index 725b9c6..21a462b 100644 --- a/go.sum +++ b/go.sum @@ -5,7 +5,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/safehtml v0.0.2/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= github.com/jba/templatecheck v0.5.0/go.mod h1:/1k7EajoSErFI9GLHAsiIJEaNLt3ALKNw2TV7z2SYv4= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -26,7 +25,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= @@ -40,7 +38,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -49,7 +46,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399 h1:O5bm8buX/OaamnfcBrkjn0SPUIU30jFmaS8lP+ikkxs= golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= diff --git a/index.html b/index.html index b9271e8..e8e18ad 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - Spaghetti + Spaghetti: dependency analysis for Go packages @@ -10,34 +10,36 @@ -

Spaghetti

+

Spaghetti: dependency analysis for Go packages

This tool displays the complete dependencies of these initial packages:

...

- Click on a package in the tree view to display information about - it, including a path by which it is reached from one of the - initial packages. Use the break button to remove an - edge from the graph, so that you can assess what edges need to be - broken to remove an unwanted dependency. + Click on a package in the tree view to display information about it, + including a path by which it is reached from one of the initial packages. + + Use the break button to remove an edge from the graph, so + that you can assess what edges need to be broken to eliminate an + unwanted dependency.

Packages

+

This tree shows all dependencies of the initial packages, grouped hierarchically by import path and containing - module.
+ module.

Each package has a numeric weight, computed using network flow: this is the size of the graph rooted at the node, - divided by the node's in-degree.
+ divided by the node's in-degree.

- Click a package to show more information aboutit.
- + Click a package to show more information about it. + +

@@ -45,26 +47,25 @@

Packages

tree
-

Selected package: none

- -

- [...]
-

+

Selected package: none   

This list shows the packages directly imported by the selected package - - ... + +

- This section - displays an arbitrary path from one of the initial packages to - the selected package. Click the break button so see how - your dependencies would change if you were to remove a single - edge. Click break all to remove all inbound edges to a + + This section displays an arbitrary path from one of the + initial packages to the selected package. Click + the break button so see how your dependencies would + change if you were to remove a single edge.

+ + Click break all to remove all inbound edges to a package, removing it from the graph. This may be useful for removing distracting packages that you don't plan to - eliminate. + eliminate.

+ The bold nodes are dominators: nodes that are found on every path to the selected node. One way to break a dependency on a package is to break all dependencies on any of its dominators. diff --git a/spaghetti.go b/spaghetti.go index 26dd9f9..4785358 100644 --- a/spaghetti.go +++ b/spaghetti.go @@ -101,7 +101,7 @@ func main() { http.Handle("/", http.FileServer(http.FS(content))) const addr = "localhost:18080" - log.Printf("Listening on %s...", addr) + log.Printf("listening on http://%s", addr) if err := http.ListenAndServe(addr, nil); err != nil { log.Fatal(err) } @@ -256,7 +256,7 @@ func onData(w http.ResponseWriter, req *http.Request) { // Any additional fields will be accessible // in the jstree node's .original field. Package int // -1 for non-package nodes - Imports []string + Imports []int Dominators []int // path through dom tree, from package to root inclusive Path []int // path through package graph, from package to root inclusive } @@ -306,9 +306,9 @@ func onData(w http.ResponseWriter, req *http.Request) { // item.State = { 'opened' : true, 'selected' : true } item.Package = e.node.index - item.Imports = []string{} // avoid JSON null + item.Imports = []int{} // avoid JSON null for _, imp := range e.node.imports { - item.Imports = append(item.Imports, imp.Package.ID) + item.Imports = append(item.Imports, imp.index) } for n := e.node; n != nil; n = n.Idom() { item.Dominators = append(item.Dominators, n.index) diff --git a/style.css b/style.css index 4cfc81d..7f26294 100644 --- a/style.css +++ b/style.css @@ -1,9 +1,9 @@ -body { margin: 1em; font-family: "Minion Pro", serif; } +body { margin: 1em; font-family: "Minion Pro", serif; max-width: 1024px; } h1, h2, h3, h4, .jstree-default { font-family: Lato; } code, pre { font-family: Consolas, monospace; } code.dom { font-weight: bold; } -button { font-size: 50%; } -pre#imports { font-size: 50%; max-height: 3em; overflow: scroll; } +button { font-size: 90%; } +pre#initial { margin-left: 1em; } /* the ⓘ symbol */ .tooltip {