func graphID(g graph.Graph, n graph.Node) string { switch g := g.(type) { case Node: return g.DOTID() default: return nodeID(n) } }
func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool) error { nodes := g.Nodes() sort.Sort(ordered.ByID(nodes)) p.buf.WriteString(p.prefix) if needsIndent { for i := 0; i < p.depth; i++ { p.buf.WriteString(p.indent) } } _, isDirected := g.(graph.Directed) if isSubgraph { p.buf.WriteString("sub") } else if isDirected { p.buf.WriteString("di") } p.buf.WriteString("graph") if name == "" { if g, ok := g.(Graph); ok { name = g.DOTID() } } if name != "" { p.buf.WriteByte(' ') p.buf.WriteString(name) } p.openBlock(" {") if a, ok := g.(Attributers); ok { p.writeAttributeComplex(a) } if s, ok := g.(Structurer); ok { for _, g := range s.Structure() { _, subIsDirected := g.(graph.Directed) if subIsDirected != isDirected { return errors.New("dot: mismatched graph type") } p.buf.WriteByte('\n') p.print(g, g.DOTID(), true, true) } } havePrintedNodeHeader := false for _, n := range nodes { if s, ok := n.(Subgrapher); ok { // If the node is not linked to any other node // the graph needs to be written now. if len(g.From(n)) == 0 { g := s.Subgraph() _, subIsDirected := g.(graph.Directed) if subIsDirected != isDirected { return errors.New("dot: mismatched graph type") } if !havePrintedNodeHeader { p.newline() p.buf.WriteString("// Node definitions.") havePrintedNodeHeader = true } p.newline() p.print(g, graphID(g, n), false, true) } continue } if !havePrintedNodeHeader { p.newline() p.buf.WriteString("// Node definitions.") havePrintedNodeHeader = true } p.newline() p.writeNode(n) if a, ok := n.(Attributer); ok { p.writeAttributeList(a) } p.buf.WriteByte(';') } havePrintedEdgeHeader := false for _, n := range nodes { to := g.From(n) sort.Sort(ordered.ByID(to)) for _, t := range to { if isDirected { if p.visited[edge{inGraph: name, from: n.ID(), to: t.ID()}] { continue } p.visited[edge{inGraph: name, from: n.ID(), to: t.ID()}] = true } else { if p.visited[edge{inGraph: name, from: n.ID(), to: t.ID()}] { continue } p.visited[edge{inGraph: name, from: n.ID(), to: t.ID()}] = true p.visited[edge{inGraph: name, from: t.ID(), to: n.ID()}] = true } if !havePrintedEdgeHeader { p.buf.WriteByte('\n') p.buf.WriteString(strings.TrimRight(p.prefix, " \t\xa0")) // Trim whitespace suffix. p.newline() p.buf.WriteString("// Edge definitions.") havePrintedEdgeHeader = true } p.newline() if s, ok := n.(Subgrapher); ok { g := s.Subgraph() _, subIsDirected := g.(graph.Directed) if subIsDirected != isDirected { return errors.New("dot: mismatched graph type") } p.print(g, graphID(g, n), false, true) } else { p.writeNode(n) } e, edgeIsPorter := g.Edge(n, t).(Porter) if edgeIsPorter { p.writePorts(e.FromPort()) } if isDirected { p.buf.WriteString(" -> ") } else { p.buf.WriteString(" -- ") } if s, ok := t.(Subgrapher); ok { g := s.Subgraph() _, subIsDirected := g.(graph.Directed) if subIsDirected != isDirected { return errors.New("dot: mismatched graph type") } p.print(g, graphID(g, t), false, true) } else { p.writeNode(t) } if edgeIsPorter { p.writePorts(e.ToPort()) } if a, ok := g.Edge(n, t).(Attributer); ok { p.writeAttributeList(a) } p.buf.WriteByte(';') } } p.closeBlock("}") return nil }