github.com/jonasi/terraform@v0.6.10-0.20160125170522-e865c342cc1f/dot/graph.go (about) 1 // The dot package contains utilities for working with DOT graphs. 2 package dot 3 4 import ( 5 "bytes" 6 "fmt" 7 "sort" 8 "strings" 9 ) 10 11 // Graph is a representation of a drawable DOT graph. 12 type Graph struct { 13 // Whether this is a "digraph" or just a "graph" 14 Directed bool 15 16 // Used for K/V settings in the DOT 17 Attrs map[string]string 18 19 Nodes []*Node 20 Edges []*Edge 21 Subgraphs []*Subgraph 22 23 nodesByName map[string]*Node 24 } 25 26 // Subgraph is a Graph that lives inside a Parent graph, and contains some 27 // additional parameters to control how it is drawn. 28 type Subgraph struct { 29 Graph 30 Name string 31 Parent *Graph 32 Cluster bool 33 } 34 35 // An Edge in a DOT graph, as expressed by recording the Name of the Node at 36 // each end. 37 type Edge struct { 38 // Name of source node. 39 Source string 40 41 // Name of dest node. 42 Dest string 43 44 // List of K/V attributes for this edge. 45 Attrs map[string]string 46 } 47 48 // A Node in a DOT graph. 49 type Node struct { 50 Name string 51 Attrs map[string]string 52 } 53 54 // Creates a properly initialized DOT Graph. 55 func NewGraph(attrs map[string]string) *Graph { 56 return &Graph{ 57 Attrs: attrs, 58 nodesByName: make(map[string]*Node), 59 } 60 } 61 62 func NewEdge(src, dst string, attrs map[string]string) *Edge { 63 return &Edge{ 64 Source: src, 65 Dest: dst, 66 Attrs: attrs, 67 } 68 } 69 70 func NewNode(n string, attrs map[string]string) *Node { 71 return &Node{ 72 Name: n, 73 Attrs: attrs, 74 } 75 } 76 77 // Initializes a Subgraph with the provided name, attaches is to this Graph, 78 // and returns it. 79 func (g *Graph) AddSubgraph(name string) *Subgraph { 80 subgraph := &Subgraph{ 81 Graph: *NewGraph(map[string]string{}), 82 Parent: g, 83 Name: name, 84 } 85 g.Subgraphs = append(g.Subgraphs, subgraph) 86 return subgraph 87 } 88 89 func (g *Graph) AddAttr(k, v string) { 90 g.Attrs[k] = v 91 } 92 93 func (g *Graph) AddNode(n *Node) { 94 g.Nodes = append(g.Nodes, n) 95 g.nodesByName[n.Name] = n 96 } 97 98 func (g *Graph) AddEdge(e *Edge) { 99 g.Edges = append(g.Edges, e) 100 } 101 102 // Adds an edge between two Nodes. 103 // 104 // Note this does not do any verification of the existence of these nodes, 105 // which means that any strings you provide that are not existing nodes will 106 // result in extra auto-defined nodes in your resulting DOT. 107 func (g *Graph) AddEdgeBetween(src, dst string, attrs map[string]string) error { 108 g.AddEdge(NewEdge(src, dst, attrs)) 109 110 return nil 111 } 112 113 // Look up a node by name 114 func (g *Graph) GetNode(name string) (*Node, error) { 115 node, ok := g.nodesByName[name] 116 if !ok { 117 return nil, fmt.Errorf("Could not find node: %s", name) 118 } 119 return node, nil 120 } 121 122 // Returns the DOT representation of this Graph. 123 func (g *Graph) String() string { 124 w := newGraphWriter() 125 126 g.drawHeader(w) 127 w.Indent() 128 g.drawBody(w) 129 w.Unindent() 130 g.drawFooter(w) 131 132 return w.String() 133 } 134 135 func (g *Graph) drawHeader(w *graphWriter) { 136 if g.Directed { 137 w.Printf("digraph {\n") 138 } else { 139 w.Printf("graph {\n") 140 } 141 } 142 143 func (g *Graph) drawBody(w *graphWriter) { 144 for _, as := range attrStrings(g.Attrs) { 145 w.Printf("%s\n", as) 146 } 147 148 nodeStrings := make([]string, 0, len(g.Nodes)) 149 for _, n := range g.Nodes { 150 nodeStrings = append(nodeStrings, n.String()) 151 } 152 sort.Strings(nodeStrings) 153 for _, ns := range nodeStrings { 154 w.Printf(ns) 155 } 156 157 edgeStrings := make([]string, 0, len(g.Edges)) 158 for _, e := range g.Edges { 159 edgeStrings = append(edgeStrings, e.String()) 160 } 161 sort.Strings(edgeStrings) 162 for _, es := range edgeStrings { 163 w.Printf(es) 164 } 165 166 for _, s := range g.Subgraphs { 167 s.drawHeader(w) 168 w.Indent() 169 s.drawBody(w) 170 w.Unindent() 171 s.drawFooter(w) 172 } 173 } 174 175 func (g *Graph) drawFooter(w *graphWriter) { 176 w.Printf("}\n") 177 } 178 179 // Returns the DOT representation of this Edge. 180 func (e *Edge) String() string { 181 var buf bytes.Buffer 182 buf.WriteString( 183 fmt.Sprintf( 184 "%q -> %q", e.Source, e.Dest)) 185 writeAttrs(&buf, e.Attrs) 186 buf.WriteString("\n") 187 188 return buf.String() 189 } 190 191 func (s *Subgraph) drawHeader(w *graphWriter) { 192 name := s.Name 193 if s.Cluster { 194 name = fmt.Sprintf("cluster_%s", name) 195 } 196 w.Printf("subgraph %q {\n", name) 197 } 198 199 // Returns the DOT representation of this Node. 200 func (n *Node) String() string { 201 var buf bytes.Buffer 202 buf.WriteString(fmt.Sprintf("%q", n.Name)) 203 writeAttrs(&buf, n.Attrs) 204 buf.WriteString("\n") 205 206 return buf.String() 207 } 208 209 func writeAttrs(buf *bytes.Buffer, attrs map[string]string) { 210 if len(attrs) > 0 { 211 buf.WriteString(" [") 212 buf.WriteString(strings.Join(attrStrings(attrs), ", ")) 213 buf.WriteString("]") 214 } 215 } 216 217 func attrStrings(attrs map[string]string) []string { 218 strings := make([]string, 0, len(attrs)) 219 for k, v := range attrs { 220 strings = append(strings, fmt.Sprintf("%s = %q", k, v)) 221 } 222 sort.Strings(strings) 223 return strings 224 }