Skip to content

Commit 2ce2a99

Browse files
authored
Merge pull request #53 from jbreckmckye/jbmk-local-mermaid
2 parents 509944e + c657996 commit 2ce2a99

File tree

4 files changed

+177
-56
lines changed

4 files changed

+177
-56
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@ argument:
1515

1616
godepgraph github.com/kisielk/godepgraph
1717

18-
If you intent to graph a go mod project, your package should be passed as a relative path:
18+
If you intend to graph a go mod project, your package should be passed as a relative path:
1919

2020
godepgraph ./pkg/api
2121

22-
The output is a graph in [Graphviz][graphviz] dot format. If you have the
22+
The default output is a graph in [Graphviz][graphviz] dot format. If you have the
2323
graphviz tools installed you can render it by piping the output to dot:
2424

2525
godepgraph github.com/kisielk/godepgraph | dot -Tpng -o godepgraph.png
2626

27+
You can also generate [Mermaid](https://mermaid.js.org/) graphs:
28+
29+
godepgraph github.com/kisielk/godepgraph -format mermaid > graph.mmd
30+
2731
By default godepgraph will display packages in the standard library in the
2832
graph, though it will not delve in to their dependencies.
2933

graphviz_lang.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"go/build"
6+
)
7+
8+
// graphvizPrinter implements graphPrinter for the DOT / GraphViz diagramming language.
9+
type graphvizPrinter struct {
10+
ids map[string]string
11+
}
12+
13+
func newGraphvizPrinter() *graphvizPrinter {
14+
p := new(graphvizPrinter)
15+
p.ids = make(map[string]string)
16+
return p
17+
}
18+
19+
func (p *graphvizPrinter) writeHeader(hLayout bool) {
20+
fmt.Println("digraph godep {")
21+
if hLayout {
22+
fmt.Println(`rankdir="LR"`)
23+
}
24+
fmt.Println(`splines=ortho
25+
nodesep=0.4
26+
ranksep=0.8
27+
node [shape="box",style="rounded,filled"]
28+
edge [arrowsize="0.5"]`)
29+
}
30+
31+
func (p *graphvizPrinter) writeNode(pkgName string, attrs *build.Package) {
32+
id := p.getId(pkgName)
33+
34+
var color string
35+
switch {
36+
case attrs.Goroot:
37+
color = "palegreen"
38+
case len(attrs.CgoFiles) > 0:
39+
color = "darkgoldenrod1"
40+
case isVendored(attrs.ImportPath):
41+
color = "palegoldenrod"
42+
case hasBuildErrors(attrs):
43+
color = "red"
44+
default:
45+
color = "paleturquoise"
46+
}
47+
48+
fmt.Printf("%s [label=\"%s\" color=\"%s\" URL=\"%s\" target=\"_blank\"];\n", id, pkgName, color, pkgDocsURL(pkgName))
49+
}
50+
51+
func (p *graphvizPrinter) writeEdge(u string, v string) {
52+
uId := p.getId(u)
53+
vId := p.getId(v)
54+
fmt.Printf("%s -> %s;\n", uId, vId)
55+
}
56+
57+
func (p *graphvizPrinter) getId(pkgName string) string {
58+
id, ok := p.ids[pkgName]
59+
if !ok {
60+
id = "\"" + pkgName + "\""
61+
p.ids[pkgName] = id
62+
}
63+
return id
64+
}
65+
66+
func (p *graphvizPrinter) writeEnd() {
67+
fmt.Println("}")
68+
}

main.go

Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ import (
1010
"strings"
1111
)
1212

13+
type graphPrinter interface {
14+
writeHeader(hLayout bool)
15+
writeNode(pkgName string, attrs *build.Package)
16+
writeEdge(u string, v string)
17+
writeEnd()
18+
}
19+
1320
var (
1421
pkgs map[string]*build.Package
1522
erroredPkgs map[string]bool
16-
ids map[string]string
1723

1824
ignored = map[string]bool{
1925
"C": true,
@@ -32,6 +38,7 @@ var (
3238
horizontal = flag.Bool("horizontal", false, "lay out the dependency graph horizontally instead of vertically")
3339
withTests = flag.Bool("withtests", false, "include test packages")
3440
maxLevel = flag.Int("maxlevel", 256, "max level of go dependency graph")
41+
format = flag.String("format", "dot", "output format of graph (dot, mermaid)")
3542

3643
buildTags []string
3744
buildContext = build.Default
@@ -50,7 +57,6 @@ func init() {
5057
func main() {
5158
pkgs = make(map[string]*build.Package)
5259
erroredPkgs = make(map[string]bool)
53-
ids = make(map[string]string)
5460
flag.Parse()
5561

5662
args := flag.Args()
@@ -75,6 +81,11 @@ func main() {
7581
}
7682
buildContext.BuildTags = buildTags
7783

84+
printer, err := getPrinter()
85+
if err != nil {
86+
log.Fatal(err)
87+
}
88+
7889
cwd, err := os.Getwd()
7990
if err != nil {
8091
log.Fatalf("failed to get cwd: %s", err)
@@ -85,16 +96,7 @@ func main() {
8596
}
8697
}
8798

88-
fmt.Println("digraph godep {")
89-
if *horizontal {
90-
fmt.Println(`rankdir="LR"`)
91-
}
92-
fmt.Print(`splines=ortho
93-
nodesep=0.4
94-
ranksep=0.8
95-
node [shape="box",style="rounded,filled"]
96-
edge [arrowsize="0.5"]
97-
`)
99+
printer.writeHeader(*horizontal)
98100

99101
// sort packages
100102
pkgKeys := []string{}
@@ -105,27 +107,12 @@ edge [arrowsize="0.5"]
105107

106108
for _, pkgName := range pkgKeys {
107109
pkg := pkgs[pkgName]
108-
pkgId := getId(pkgName)
109110

110111
if isIgnored(pkg) {
111112
continue
112113
}
113114

114-
var color string
115-
switch {
116-
case pkg.Goroot:
117-
color = "palegreen"
118-
case len(pkg.CgoFiles) > 0:
119-
color = "darkgoldenrod1"
120-
case isVendored(pkg.ImportPath):
121-
color = "palegoldenrod"
122-
case hasBuildErrors(pkg):
123-
color = "red"
124-
default:
125-
color = "paleturquoise"
126-
}
127-
128-
fmt.Printf("%s [label=\"%s\" color=\"%s\" URL=\"%s\" target=\"_blank\"];\n", pkgId, pkgName, color, pkgDocsURL(pkgName))
115+
printer.writeNode(pkgName, pkg)
129116

130117
// Don't render imports from packages in Goroot
131118
if pkg.Goroot && !*withGoroot {
@@ -138,11 +125,11 @@ edge [arrowsize="0.5"]
138125
continue
139126
}
140127

141-
impId := getId(imp)
142-
fmt.Printf("%s -> %s;\n", pkgId, impId)
128+
printer.writeEdge(pkgName, imp)
143129
}
144130
}
145-
fmt.Println("}")
131+
132+
printer.writeEnd()
146133
}
147134

148135
func pkgDocsURL(pkgName string) string {
@@ -212,21 +199,6 @@ func getImports(pkg *build.Package) []string {
212199
return imports
213200
}
214201

215-
func deriveNodeID(packageName string) string {
216-
//TODO: improve implementation?
217-
id := "\"" + packageName + "\""
218-
return id
219-
}
220-
221-
func getId(name string) string {
222-
id, ok := ids[name]
223-
if !ok {
224-
id = deriveNodeID(name)
225-
ids[name] = id
226-
}
227-
return id
228-
}
229-
230202
func hasPrefixes(s string, prefixes []string) bool {
231203
for _, p := range prefixes {
232204
if strings.HasPrefix(s, p) {
@@ -259,14 +231,6 @@ func hasBuildErrors(pkg *build.Package) bool {
259231
return v
260232
}
261233

262-
func debug(args ...interface{}) {
263-
fmt.Fprintln(os.Stderr, args...)
264-
}
265-
266-
func debugf(s string, args ...interface{}) {
267-
fmt.Fprintf(os.Stderr, s, args...)
268-
}
269-
270234
func isVendored(path string) bool {
271235
return strings.Contains(path, "/vendor/")
272236
}
@@ -275,3 +239,14 @@ func normalizeVendor(path string) string {
275239
pieces := strings.Split(path, "vendor/")
276240
return pieces[len(pieces)-1]
277241
}
242+
243+
func getPrinter() (graphPrinter, error) {
244+
switch *format {
245+
case "dot":
246+
return newGraphvizPrinter(), nil
247+
case "mermaid":
248+
return newMermaidPrinter(), nil
249+
default:
250+
return nil, fmt.Errorf("invalid format flag %q, must be: dot, mermaid", *format)
251+
}
252+
}

mermaid_lang.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"go/build"
6+
)
7+
8+
// mermaidPrinter implements graphPrinter for the Mermaid diagramming language.
9+
type mermaidPrinter struct {
10+
ids map[string]string
11+
nextId int
12+
}
13+
14+
func newMermaidPrinter() *mermaidPrinter {
15+
p := new(mermaidPrinter)
16+
p.ids = make(map[string]string)
17+
return p
18+
}
19+
20+
func (p *mermaidPrinter) writeHeader(hLayout bool) {
21+
if hLayout {
22+
fmt.Println("flowchart LR")
23+
} else {
24+
fmt.Println("flowchart TD")
25+
}
26+
27+
fmt.Println()
28+
fmt.Println("classDef goroot fill:#1D4,color:white")
29+
fmt.Println("classDef cgofiles fill:#D52,color:white")
30+
fmt.Println("classDef vendored fill:#D90,color:white")
31+
fmt.Println("classDef buildErrs fill:#C10,color:white")
32+
}
33+
34+
func (p *mermaidPrinter) writeNode(pkgName string, attrs *build.Package) {
35+
id := p.getId(pkgName)
36+
37+
var classname string
38+
switch {
39+
case attrs.Goroot:
40+
classname = "goroot"
41+
case len(attrs.CgoFiles) > 0:
42+
classname = "cgofiles"
43+
case isVendored(attrs.ImportPath):
44+
classname = "vendored"
45+
case hasBuildErrors(attrs):
46+
classname = "buildErrs"
47+
}
48+
49+
fmt.Println()
50+
fmt.Printf("%s[%s]\n", id, pkgName)
51+
fmt.Printf("click %s href %q\n", id, pkgDocsURL(pkgName))
52+
53+
if classname != "" {
54+
fmt.Printf("class %s %s\n", id, classname)
55+
}
56+
}
57+
58+
func (p *mermaidPrinter) writeEdge(u string, v string) {
59+
uId := p.getId(u)
60+
vId := p.getId(v)
61+
fmt.Printf("%s --> %s\n", uId, vId)
62+
}
63+
64+
func (p *mermaidPrinter) getId(pkgName string) string {
65+
id, ok := p.ids[pkgName]
66+
if !ok {
67+
p.nextId = p.nextId + 1
68+
id = fmt.Sprintf("%d", p.nextId)
69+
p.ids[pkgName] = id
70+
}
71+
return id
72+
}
73+
74+
func (p *mermaidPrinter) writeEnd() {}

0 commit comments

Comments
 (0)