github.com/cmalfait/terraform@v0.11.12-beta1/dag/dot.go (about) 1 package dag 2 3 import ( 4 "bytes" 5 "fmt" 6 "sort" 7 "strings" 8 ) 9 10 // DotOpts are the options for generating a dot formatted Graph. 11 type DotOpts struct { 12 // Allows some nodes to decide to only show themselves when the user has 13 // requested the "verbose" graph. 14 Verbose bool 15 16 // Highlight Cycles 17 DrawCycles bool 18 19 // How many levels to expand modules as we draw 20 MaxDepth int 21 22 // use this to keep the cluster_ naming convention from the previous dot writer 23 cluster bool 24 } 25 26 // GraphNodeDotter can be implemented by a node to cause it to be included 27 // in the dot graph. The Dot method will be called which is expected to 28 // return a representation of this node. 29 type GraphNodeDotter interface { 30 // Dot is called to return the dot formatting for the node. 31 // The first parameter is the title of the node. 32 // The second parameter includes user-specified options that affect the dot 33 // graph. See GraphDotOpts below for details. 34 DotNode(string, *DotOpts) *DotNode 35 } 36 37 // DotNode provides a structure for Vertices to return in order to specify their 38 // dot format. 39 type DotNode struct { 40 Name string 41 Attrs map[string]string 42 } 43 44 // Returns the DOT representation of this Graph. 45 func (g *marshalGraph) Dot(opts *DotOpts) []byte { 46 if opts == nil { 47 opts = &DotOpts{ 48 DrawCycles: true, 49 MaxDepth: -1, 50 Verbose: true, 51 } 52 } 53 54 var w indentWriter 55 w.WriteString("digraph {\n") 56 w.Indent() 57 58 // some dot defaults 59 w.WriteString(`compound = "true"` + "\n") 60 w.WriteString(`newrank = "true"` + "\n") 61 62 // the top level graph is written as the first subgraph 63 w.WriteString(`subgraph "root" {` + "\n") 64 g.writeBody(opts, &w) 65 66 // cluster isn't really used other than for naming purposes in some graphs 67 opts.cluster = opts.MaxDepth != 0 68 maxDepth := opts.MaxDepth 69 if maxDepth == 0 { 70 maxDepth = -1 71 } 72 73 for _, s := range g.Subgraphs { 74 g.writeSubgraph(s, opts, maxDepth, &w) 75 } 76 77 w.Unindent() 78 w.WriteString("}\n") 79 return w.Bytes() 80 } 81 82 func (v *marshalVertex) dot(g *marshalGraph, opts *DotOpts) []byte { 83 var buf bytes.Buffer 84 graphName := g.Name 85 if graphName == "" { 86 graphName = "root" 87 } 88 89 name := v.Name 90 attrs := v.Attrs 91 if v.graphNodeDotter != nil { 92 node := v.graphNodeDotter.DotNode(name, opts) 93 if node == nil { 94 return []byte{} 95 } 96 97 newAttrs := make(map[string]string) 98 for k, v := range attrs { 99 newAttrs[k] = v 100 } 101 for k, v := range node.Attrs { 102 newAttrs[k] = v 103 } 104 105 name = node.Name 106 attrs = newAttrs 107 } 108 109 buf.WriteString(fmt.Sprintf(`"[%s] %s"`, graphName, name)) 110 writeAttrs(&buf, attrs) 111 buf.WriteByte('\n') 112 113 return buf.Bytes() 114 } 115 116 func (e *marshalEdge) dot(g *marshalGraph) string { 117 var buf bytes.Buffer 118 graphName := g.Name 119 if graphName == "" { 120 graphName = "root" 121 } 122 123 sourceName := g.vertexByID(e.Source).Name 124 targetName := g.vertexByID(e.Target).Name 125 s := fmt.Sprintf(`"[%s] %s" -> "[%s] %s"`, graphName, sourceName, graphName, targetName) 126 buf.WriteString(s) 127 writeAttrs(&buf, e.Attrs) 128 129 return buf.String() 130 } 131 132 func cycleDot(e *marshalEdge, g *marshalGraph) string { 133 return e.dot(g) + ` [color = "red", penwidth = "2.0"]` 134 } 135 136 // Write the subgraph body. The is recursive, and the depth argument is used to 137 // record the current depth of iteration. 138 func (g *marshalGraph) writeSubgraph(sg *marshalGraph, opts *DotOpts, depth int, w *indentWriter) { 139 if depth == 0 { 140 return 141 } 142 depth-- 143 144 name := sg.Name 145 if opts.cluster { 146 // we prefix with cluster_ to match the old dot output 147 name = "cluster_" + name 148 sg.Attrs["label"] = sg.Name 149 } 150 w.WriteString(fmt.Sprintf("subgraph %q {\n", name)) 151 sg.writeBody(opts, w) 152 153 for _, sg := range sg.Subgraphs { 154 g.writeSubgraph(sg, opts, depth, w) 155 } 156 } 157 158 func (g *marshalGraph) writeBody(opts *DotOpts, w *indentWriter) { 159 w.Indent() 160 161 for _, as := range attrStrings(g.Attrs) { 162 w.WriteString(as + "\n") 163 } 164 165 // list of Vertices that aren't to be included in the dot output 166 skip := map[string]bool{} 167 168 for _, v := range g.Vertices { 169 if v.graphNodeDotter == nil { 170 skip[v.ID] = true 171 continue 172 } 173 174 w.Write(v.dot(g, opts)) 175 } 176 177 var dotEdges []string 178 179 if opts.DrawCycles { 180 for _, c := range g.Cycles { 181 if len(c) < 2 { 182 continue 183 } 184 185 for i, j := 0, 1; i < len(c); i, j = i+1, j+1 { 186 if j >= len(c) { 187 j = 0 188 } 189 src := c[i] 190 tgt := c[j] 191 192 if skip[src.ID] || skip[tgt.ID] { 193 continue 194 } 195 196 e := &marshalEdge{ 197 Name: fmt.Sprintf("%s|%s", src.Name, tgt.Name), 198 Source: src.ID, 199 Target: tgt.ID, 200 Attrs: make(map[string]string), 201 } 202 203 dotEdges = append(dotEdges, cycleDot(e, g)) 204 src = tgt 205 } 206 } 207 } 208 209 for _, e := range g.Edges { 210 dotEdges = append(dotEdges, e.dot(g)) 211 } 212 213 // srot these again to match the old output 214 sort.Strings(dotEdges) 215 216 for _, e := range dotEdges { 217 w.WriteString(e + "\n") 218 } 219 220 w.Unindent() 221 w.WriteString("}\n") 222 } 223 224 func writeAttrs(buf *bytes.Buffer, attrs map[string]string) { 225 if len(attrs) > 0 { 226 buf.WriteString(" [") 227 buf.WriteString(strings.Join(attrStrings(attrs), ", ")) 228 buf.WriteString("]") 229 } 230 } 231 232 func attrStrings(attrs map[string]string) []string { 233 strings := make([]string, 0, len(attrs)) 234 for k, v := range attrs { 235 strings = append(strings, fmt.Sprintf("%s = %q", k, v)) 236 } 237 sort.Strings(strings) 238 return strings 239 } 240 241 // Provide a bytes.Buffer like structure, which will indent when starting a 242 // newline. 243 type indentWriter struct { 244 bytes.Buffer 245 level int 246 } 247 248 func (w *indentWriter) indent() { 249 newline := []byte("\n") 250 if !bytes.HasSuffix(w.Bytes(), newline) { 251 return 252 } 253 for i := 0; i < w.level; i++ { 254 w.Buffer.WriteString("\t") 255 } 256 } 257 258 // Indent increases indentation by 1 259 func (w *indentWriter) Indent() { w.level++ } 260 261 // Unindent decreases indentation by 1 262 func (w *indentWriter) Unindent() { w.level-- } 263 264 // the following methods intercecpt the byte.Buffer writes and insert the 265 // indentation when starting a new line. 266 func (w *indentWriter) Write(b []byte) (int, error) { 267 w.indent() 268 return w.Buffer.Write(b) 269 } 270 271 func (w *indentWriter) WriteString(s string) (int, error) { 272 w.indent() 273 return w.Buffer.WriteString(s) 274 } 275 func (w *indentWriter) WriteByte(b byte) error { 276 w.indent() 277 return w.Buffer.WriteByte(b) 278 } 279 func (w *indentWriter) WriteRune(r rune) (int, error) { 280 w.indent() 281 return w.Buffer.WriteRune(r) 282 }