1 | package main |
---|---|
2 | |
3 | import ( |
4 | "bytes" |
5 | "fmt" |
6 | "io" |
7 | "log" |
8 | "os" |
9 | "os/exec" |
10 | "path/filepath" |
11 | "strings" |
12 | "text/template" |
13 | ) |
14 | |
15 | var ( |
16 | minlen uint |
17 | nodesep float64 |
18 | nodeshape string |
19 | nodestyle string |
20 | rankdir string |
21 | ) |
22 | |
23 | const tmplCluster = `{{define "cluster" -}} |
24 | {{printf "subgraph %q {" .}} |
25 | {{printf "%s" .Attrs.Lines}} |
26 | {{range .Nodes}} |
27 | {{template "node" .}} |
28 | {{- end}} |
29 | {{range .Clusters}} |
30 | {{template "cluster" .}} |
31 | {{- end}} |
32 | {{println "}" }} |
33 | {{- end}}` |
34 | |
35 | const tmplNode = `{{define "edge" -}} |
36 | {{printf "%q -> %q [ %s ]" .From .To .Attrs}} |
37 | {{- end}}` |
38 | |
39 | const tmplEdge = `{{define "node" -}} |
40 | {{printf "%q [ %s ]" .ID .Attrs}} |
41 | {{- end}}` |
42 | |
43 | const tmplGraph = `digraph gocallvis { |
44 | label="{{.Title}}"; |
45 | labeljust="l"; |
46 | fontname="Arial"; |
47 | fontsize="14"; |
48 | rankdir="{{.Options.rankdir}}"; |
49 | bgcolor="lightgray"; |
50 | style="solid"; |
51 | penwidth="0.5"; |
52 | pad="0.0"; |
53 | nodesep="{{.Options.nodesep}}"; |
54 | |
55 | node [shape="{{.Options.nodeshape}}" style="{{.Options.nodestyle}}" fillcolor="honeydew" fontname="Verdana" penwidth="1.0" margin="0.05,0.0"]; |
56 | edge [minlen="{{.Options.minlen}}"] |
57 | |
58 | {{template "cluster" .Cluster}} |
59 | |
60 | {{- range .Edges}} |
61 | {{template "edge" .}} |
62 | {{- end}} |
63 | } |
64 | ` |
65 | |
66 | //==[ type def/func: dotCluster ]=============================================== |
67 | type dotCluster struct { |
68 | ID string |
69 | Clusters map[string]*dotCluster |
70 | Nodes []*dotNode |
71 | Attrs dotAttrs |
72 | } |
73 | |
74 | func NewDotCluster(id string) *dotCluster { |
75 | return &dotCluster{ |
76 | ID: id, |
77 | Clusters: make(map[string]*dotCluster), |
78 | Attrs: make(dotAttrs), |
79 | } |
80 | } |
81 | |
82 | func (c *dotCluster) String() string { |
83 | return fmt.Sprintf("cluster_%s", c.ID) |
84 | } |
85 | |
86 | //==[ type def/func: dotNode ]=============================================== |
87 | type dotNode struct { |
88 | ID string |
89 | Attrs dotAttrs |
90 | } |
91 | |
92 | func (n *dotNode) String() string { |
93 | return n.ID |
94 | } |
95 | |
96 | //==[ type def/func: dotEdge ]=============================================== |
97 | type dotEdge struct { |
98 | From *dotNode |
99 | To *dotNode |
100 | Attrs dotAttrs |
101 | } |
102 | |
103 | //==[ type def/func: dotAttrs ]=============================================== |
104 | type dotAttrs map[string]string |
105 | |
106 | func (p dotAttrs) List() []string { |
107 | l := []string{} |
108 | for k, v := range p { |
109 | l = append(l, fmt.Sprintf("%s=%q", k, v)) |
110 | } |
111 | return l |
112 | } |
113 | |
114 | func (p dotAttrs) String() string { |
115 | return strings.Join(p.List(), " ") |
116 | } |
117 | |
118 | func (p dotAttrs) Lines() string { |
119 | return fmt.Sprintf("%s;", strings.Join(p.List(), ";\n")) |
120 | } |
121 | |
122 | //==[ type def/func: dotGraph ]=============================================== |
123 | type dotGraph struct { |
124 | Title string |
125 | Minlen uint |
126 | Attrs dotAttrs |
127 | Cluster *dotCluster |
128 | Nodes []*dotNode |
129 | Edges []*dotEdge |
130 | Options map[string]string |
131 | } |
132 | |
133 | func (g *dotGraph) WriteDot(w io.Writer) error { |
134 | t := template.New("dot") |
135 | for _, s := range []string{tmplCluster, tmplNode, tmplEdge, tmplGraph} { |
136 | if _, err := t.Parse(s); err != nil { |
137 | return err |
138 | } |
139 | } |
140 | var buf bytes.Buffer |
141 | if err := t.Execute(&buf, g); err != nil { |
142 | return err |
143 | } |
144 | _, err := buf.WriteTo(w) |
145 | return err |
146 | } |
147 | |
148 | func dotToImage(outfname string, format string, dot []byte) (string, error) { |
149 | if *graphvizFlag { |
150 | return runDotToImageCallSystemGraphviz(outfname, format, dot) |
151 | } |
152 | |
153 | return runDotToImage(outfname, format, dot) |
154 | } |
155 | |
156 | // location of dot executable for converting from .dot to .svg |
157 | // it's usually at: /usr/bin/dot |
158 | var dotSystemBinary string |
159 | |
160 | // runDotToImageCallSystemGraphviz generates a SVG using the 'dot' utility, returning the filepath |
161 | func runDotToImageCallSystemGraphviz(outfname string, format string, dot []byte) (string, error) { |
162 | if dotSystemBinary == "" { |
163 | dot, err := exec.LookPath("dot") |
164 | if err != nil { |
165 | log.Fatalln("unable to find program 'dot', please install it or check your PATH") |
166 | } |
167 | dotSystemBinary = dot |
168 | } |
169 | |
170 | var img string |
171 | if outfname == "" { |
172 | img = filepath.Join(os.TempDir(), fmt.Sprintf("go-callvis_export.%s", format)) |
173 | } else { |
174 | img = fmt.Sprintf("%s.%s", outfname, format) |
175 | } |
176 | cmd := exec.Command(dotSystemBinary, fmt.Sprintf("-T%s", format), "-o", img) |
177 | cmd.Stdin = bytes.NewReader(dot) |
178 | var stderr bytes.Buffer |
179 | cmd.Stderr = &stderr |
180 | if err := cmd.Run(); err != nil { |
181 | return "", fmt.Errorf("command '%v': %v\n%v", cmd, err, stderr.String()) |
182 | } |
183 | return img, nil |
184 | } |
185 |
Members