Skip to content

Commit a4ed05f

Browse files
committed
cmd/digraph: add "to dot" to emit Graphviz dot
This CL adds "digraph to dot", which prints the graph in Graphviz dot format, which can then be piped into dot. Change-Id: I8209d9038bb4e1df1ca2ced427ce14f6df8051db Reviewed-on: https://go-review.googlesource.com/c/tools/+/503437 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]> Run-TryBot: Austin Clements <[email protected]>
1 parent 7261b32 commit a4ed05f

File tree

2 files changed

+65
-5
lines changed

2 files changed

+65
-5
lines changed

cmd/digraph/digraph.go

+40-5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ The support commands are:
3737
the set of nodes strongly connected to the specified one
3838
focus <node>
3939
the subgraph containing all directed paths that pass through the specified node
40+
to dot
41+
print the graph in Graphviz dot format (other formats may be supported in the future)
4042
4143
Input format:
4244
@@ -81,6 +83,11 @@ Show which packages in x/tools depend, perhaps indirectly, on the callgraph pack
8183
$ go list -f '{{.ImportPath}} {{join .Imports " "}}' -deps golang.org/x/tools/... |
8284
digraph reverse golang.org/x/tools/go/callgraph
8385
86+
Visualize the package dependency graph of the current package:
87+
88+
$ go list -f '{{.ImportPath}} {{join .Imports " "}}' -deps |
89+
digraph to dot | dot -Tpng -o x.png
90+
8491
Using a module graph produced by go mod, show all dependencies of the current module:
8592
8693
$ go mod graph | digraph forward $(go list -m)
@@ -137,6 +144,8 @@ The support commands are:
137144
the set of nodes nodes strongly connected to the specified one
138145
focus <node>
139146
the subgraph containing all directed paths that pass through the specified node
147+
to dot
148+
print the graph in Graphviz dot format (other formats may be supported in the future)
140149
`)
141150
os.Exit(2)
142151
}
@@ -207,6 +216,14 @@ func (g graph) addEdges(from string, to ...string) {
207216
}
208217
}
209218

219+
func (g graph) nodelist() nodelist {
220+
nodes := make(nodeset)
221+
for node := range g {
222+
nodes[node] = true
223+
}
224+
return nodes.sort()
225+
}
226+
210227
func (g graph) reachableFrom(roots nodeset) nodeset {
211228
seen := make(nodeset)
212229
var visit func(node string)
@@ -356,6 +373,20 @@ func (g graph) somepath(from, to string) error {
356373
return nil
357374
}
358375

376+
func (g graph) toDot(w *bytes.Buffer) {
377+
fmt.Fprintln(w, "digraph {")
378+
for _, src := range g.nodelist() {
379+
for _, dst := range g[src].sort() {
380+
// Dot's quoting rules appear to align with Go's for escString,
381+
// which is the syntax of node IDs. Labels require significantly
382+
// more quoting, but that appears not to be necessary if the node ID
383+
// is implicitly used as the label.
384+
fmt.Fprintf(w, "\t%q -> %q;\n", src, dst)
385+
}
386+
}
387+
fmt.Fprintln(w, "}")
388+
}
389+
359390
func parse(rd io.Reader) (graph, error) {
360391
g := make(graph)
361392

@@ -404,11 +435,7 @@ func digraph(cmd string, args []string) error {
404435
if len(args) != 0 {
405436
return fmt.Errorf("usage: digraph nodes")
406437
}
407-
nodes := make(nodeset)
408-
for node := range g {
409-
nodes[node] = true
410-
}
411-
nodes.sort().println("\n")
438+
g.nodelist().println("\n")
412439

413440
case "degree":
414441
if len(args) != 0 {
@@ -563,6 +590,14 @@ func digraph(cmd string, args []string) error {
563590
sort.Strings(edgesSorted)
564591
fmt.Fprintln(stdout, strings.Join(edgesSorted, "\n"))
565592

593+
case "to":
594+
if len(args) != 1 || args[0] != "dot" {
595+
return fmt.Errorf("usage: digraph to dot")
596+
}
597+
var b bytes.Buffer
598+
g.toDot(&b)
599+
stdout.Write(b.Bytes())
600+
566601
default:
567602
return fmt.Errorf("no such command %q", cmd)
568603
}

cmd/digraph/digraph_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package main
66
import (
77
"bytes"
88
"fmt"
9+
"io"
910
"reflect"
1011
"sort"
1112
"strings"
@@ -346,3 +347,27 @@ func TestFocus(t *testing.T) {
346347
})
347348
}
348349
}
350+
351+
func TestToDot(t *testing.T) {
352+
in := `a b c
353+
b "d\"\\d"
354+
c "d\"\\d"`
355+
want := `digraph {
356+
"a" -> "b";
357+
"a" -> "c";
358+
"b" -> "d\"\\d";
359+
"c" -> "d\"\\d";
360+
}
361+
`
362+
defer func(in io.Reader, out io.Writer) { stdin, stdout = in, out }(stdin, stdout)
363+
stdin = strings.NewReader(in)
364+
stdout = new(bytes.Buffer)
365+
if err := digraph("to", []string{"dot"}); err != nil {
366+
t.Fatal(err)
367+
}
368+
got := stdout.(fmt.Stringer).String()
369+
if got != want {
370+
t.Errorf("digraph(to, dot) = got %q, want %q", got, want)
371+
}
372+
373+
}

0 commit comments

Comments
 (0)