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  }