diff --git a/graph/encoding/dot/decode.go b/graph/encoding/dot/decode.go index 46477bdd..319e0ab5 100644 --- a/graph/encoding/dot/decode.go +++ b/graph/encoding/dot/decode.go @@ -25,6 +25,13 @@ type Builder interface { NewEdge(from, to graph.Node) graph.Edge } +// UnmashalerAttrs is implemented by graph values that can unmarshal global +// DOT attributes. +type UnmarshalerAttrs interface { + // DOTUnmarshalerAttrs returns the global attribute unmarshalers. + DOTUnmarshalerAttrs() (graph, node, edge UnmarshalerAttr) +} + // UnmarshalerAttr is implemented by types that can unmarshal a DOT // attribute description of themselves. type UnmarshalerAttr interface { @@ -60,6 +67,9 @@ func copyGraph(dst Builder, src *ast.Graph) (err error) { directed: src.Directed, ids: make(map[string]graph.Node), } + if a, ok := dst.(UnmarshalerAttrs); ok { + gen.graphAttr, gen.nodeAttr, gen.edgeAttr = a.DOTUnmarshalerAttrs() + } for _, stmt := range src.Stmts { gen.addStmt(dst, stmt) } @@ -79,6 +89,8 @@ type generator struct { // Stack of start indices into the subgraph node slice. The top element // corresponds to the start index of the active (or inner-most) subgraph. subStart []int + // graphAttr, nodeAttr and edgeAttr are global graph attributes. + graphAttr, nodeAttr, edgeAttr UnmarshalerAttr } // node returns the gonum node corresponding to the given dot AST node ID, @@ -119,7 +131,39 @@ func (gen *generator) addStmt(dst Builder, stmt ast.Stmt) { case *ast.EdgeStmt: gen.addEdgeStmt(dst, stmt) case *ast.AttrStmt: - // ignore. + var n UnmarshalerAttr + var dst string + switch stmt.Kind { + case ast.KindGraph: + if gen.graphAttr == nil { + return + } + n = gen.graphAttr + dst = "graph" + case ast.KindNode: + if gen.nodeAttr == nil { + return + } + n = gen.nodeAttr + dst = "node" + case ast.KindEdge: + if gen.edgeAttr == nil { + return + } + n = gen.edgeAttr + dst = "edge" + default: + panic("unreachable") + } + for _, attr := range stmt.Attrs { + a := Attribute{ + Key: attr.Key, + Value: attr.Val, + } + if err := n.UnmarshalDOTAttr(a); err != nil { + panic(fmt.Errorf("unable to unmarshal global %s DOT attribute (%s=%s)", dst, a.Key, a.Value)) + } + } case *ast.Attr: // ignore. case *ast.Subgraph: diff --git a/graph/encoding/dot/decode_test.go b/graph/encoding/dot/decode_test.go index 7860d6c3..d99fdc44 100644 --- a/graph/encoding/dot/decode_test.go +++ b/graph/encoding/dot/decode_test.go @@ -45,13 +45,25 @@ func TestRoundTrip(t *testing.T) { } got := string(buf) if got != g.want { - t.Errorf("i=%d: graph content mismatch; expected `%s`, got `%s`", i, g.want, got) + t.Errorf("i=%d: graph content mismatch; want:\n%s\n\ngot:\n%s", i, g.want, got) continue } } } const directed = `digraph { + graph [ + outputorder=edgesfirst + ]; + node [ + shape=circle + style=filled + ]; + edge [ + penwidth=5 + color=gray + ]; + // Node definitions. 0 [label="foo 2"]; 1 [label="bar 2"]; @@ -61,6 +73,18 @@ const directed = `digraph { }` const undirected = `graph { + graph [ + outputorder=edgesfirst + ]; + node [ + shape=circle + style=filled + ]; + edge [ + penwidth=5 + color=gray + ]; + // Node definitions. 0 [label="foo 2"]; 1 [label="bar 2"]; @@ -79,6 +103,7 @@ const undirected = `graph { // dotDirectedGraph implements the dot.Builder interface. type dotDirectedGraph struct { *simple.DirectedGraph + graph, node, edge attributes } // newDotDirectedGraph returns a new directed capable of creating user-defined @@ -105,12 +130,23 @@ func (g *dotDirectedGraph) NewEdge(from, to graph.Node) graph.Edge { return e } +// DOTAttributers implements the dot.Attributers interface. +func (g *dotDirectedGraph) DOTAttributers() (graph, node, edge Attributer) { + return g.graph, g.node, g.edge +} + +// DOTUnmarshalerAttrs implements the dot.UnmarshalerAttrs interface. +func (g *dotDirectedGraph) DOTUnmarshalerAttrs() (graph, node, edge UnmarshalerAttr) { + return &g.graph, &g.node, &g.edge +} + // dotUndirectedGraph extends simple.UndirectedGraph to add NewNode and NewEdge // methods for creating user-defined nodes and edges. // // dotUndirectedGraph implements the dot.Builder interface. type dotUndirectedGraph struct { *simple.UndirectedGraph + graph, node, edge attributes } // newDotUndirectedGraph returns a new undirected capable of creating user- @@ -137,6 +173,16 @@ func (g *dotUndirectedGraph) NewEdge(from, to graph.Node) graph.Edge { return e } +// DOTAttributers implements the dot.Attributers interface. +func (g *dotUndirectedGraph) DOTAttributers() (graph, node, edge Attributer) { + return g.graph, g.node, g.edge +} + +// DOTUnmarshalerAttrs implements the dot.UnmarshalerAttrs interface. +func (g *dotUndirectedGraph) DOTUnmarshalerAttrs() (graph, node, edge UnmarshalerAttr) { + return &g.graph, &g.node, &g.edge +} + // dotNode extends simple.Node with a label field to test round-trip encoding // and decoding of node DOT label attributes. type dotNode struct { @@ -194,3 +240,14 @@ func (e *dotEdge) DOTAttributes() []Attribute { } return []Attribute{attr} } + +// attributes is a helper for global attributes. +type attributes []Attribute + +func (a attributes) DOTAttributes() []Attribute { + return []Attribute(a) +} +func (a *attributes) UnmarshalDOTAttr(attr Attribute) error { + *a = append(*a, attr) + return nil +}