github.com/rhenning/terraform@v0.8.0-beta2/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) []byte { 83 var buf bytes.Buffer 84 graphName := g.Name 85 if graphName == "" { 86 graphName = "root" 87 } 88 buf.WriteString(fmt.Sprintf(`"[%s] %s"`, graphName, v.Name)) 89 writeAttrs(&buf, v.Attrs) 90 buf.WriteByte('\n') 91 92 return buf.Bytes() 93 } 94 95 func (e *marshalEdge) dot(g *marshalGraph) string { 96 var buf bytes.Buffer 97 graphName := g.Name 98 if graphName == "" { 99 graphName = "root" 100 } 101 102 sourceName := g.vertexByID(e.Source).Name 103 targetName := g.vertexByID(e.Target).Name 104 s := fmt.Sprintf(`"[%s] %s" -> "[%s] %s"`, graphName, sourceName, graphName, targetName) 105 buf.WriteString(s) 106 writeAttrs(&buf, e.Attrs) 107 108 return buf.String() 109 } 110 111 func cycleDot(e *marshalEdge, g *marshalGraph) string { 112 return e.dot(g) + ` [color = "red", penwidth = "2.0"]` 113 } 114 115 // Write the subgraph body. The is recursive, and the depth argument is used to 116 // record the current depth of iteration. 117 func (g *marshalGraph) writeSubgraph(sg *marshalGraph, opts *DotOpts, depth int, w *indentWriter) { 118 if depth == 0 { 119 return 120 } 121 depth-- 122 123 name := sg.Name 124 if opts.cluster { 125 // we prefix with cluster_ to match the old dot output 126 name = "cluster_" + name 127 sg.Attrs["label"] = sg.Name 128 } 129 w.WriteString(fmt.Sprintf("subgraph %q {\n", name)) 130 sg.writeBody(opts, w) 131 132 for _, sg := range sg.Subgraphs { 133 g.writeSubgraph(sg, opts, depth, w) 134 } 135 } 136 137 func (g *marshalGraph) writeBody(opts *DotOpts, w *indentWriter) { 138 w.Indent() 139 140 for _, as := range attrStrings(g.Attrs) { 141 w.WriteString(as + "\n") 142 } 143 144 // list of Vertices that aren't to be included in the dot output 145 skip := map[string]bool{} 146 147 for _, v := range g.Vertices { 148 if !v.graphNodeDotter { 149 skip[v.ID] = true 150 continue 151 } 152 153 w.Write(v.dot(g)) 154 } 155 156 var dotEdges []string 157 158 if opts.DrawCycles { 159 for _, c := range g.Cycles { 160 if len(c) < 2 { 161 continue 162 } 163 164 for i, j := 0, 1; i < len(c); i, j = i+1, j+1 { 165 if j >= len(c) { 166 j = 0 167 } 168 src := c[i] 169 tgt := c[j] 170 171 if skip[src.ID] || skip[tgt.ID] { 172 continue 173 } 174 175 e := &marshalEdge{ 176 Name: fmt.Sprintf("%s|%s", src.Name, tgt.Name), 177 Source: src.ID, 178 Target: tgt.ID, 179 Attrs: make(map[string]string), 180 } 181 182 dotEdges = append(dotEdges, cycleDot(e, g)) 183 src = tgt 184 } 185 } 186 } 187 188 for _, e := range g.Edges { 189 dotEdges = append(dotEdges, e.dot(g)) 190 } 191 192 // srot these again to match the old output 193 sort.Strings(dotEdges) 194 195 for _, e := range dotEdges { 196 w.WriteString(e + "\n") 197 } 198 199 w.Unindent() 200 w.WriteString("}\n") 201 } 202 203 func writeAttrs(buf *bytes.Buffer, attrs map[string]string) { 204 if len(attrs) > 0 { 205 buf.WriteString(" [") 206 buf.WriteString(strings.Join(attrStrings(attrs), ", ")) 207 buf.WriteString("]") 208 } 209 } 210 211 func attrStrings(attrs map[string]string) []string { 212 strings := make([]string, 0, len(attrs)) 213 for k, v := range attrs { 214 strings = append(strings, fmt.Sprintf("%s = %q", k, v)) 215 } 216 sort.Strings(strings) 217 return strings 218 } 219 220 // Provide a bytes.Buffer like structure, which will indent when starting a 221 // newline. 222 type indentWriter struct { 223 bytes.Buffer 224 level int 225 } 226 227 func (w *indentWriter) indent() { 228 newline := []byte("\n") 229 if !bytes.HasSuffix(w.Bytes(), newline) { 230 return 231 } 232 for i := 0; i < w.level; i++ { 233 w.Buffer.WriteString("\t") 234 } 235 } 236 237 // Indent increases indentation by 1 238 func (w *indentWriter) Indent() { w.level++ } 239 240 // Unindent decreases indentation by 1 241 func (w *indentWriter) Unindent() { w.level-- } 242 243 // the following methods intercecpt the byte.Buffer writes and insert the 244 // indentation when starting a new line. 245 func (w *indentWriter) Write(b []byte) (int, error) { 246 w.indent() 247 return w.Buffer.Write(b) 248 } 249 250 func (w *indentWriter) WriteString(s string) (int, error) { 251 w.indent() 252 return w.Buffer.WriteString(s) 253 } 254 func (w *indentWriter) WriteByte(b byte) error { 255 w.indent() 256 return w.Buffer.WriteByte(b) 257 } 258 func (w *indentWriter) WriteRune(r rune) (int, error) { 259 w.indent() 260 return w.Buffer.WriteRune(r) 261 }