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  }