graph/encoding/dot: allow unmarshaling of global attributes
MODIFY
graph/encoding/dot/decode.go
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:

MODIFY
graph/encoding/dot/decode_test.go
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
+}

graph/encoding/dot: allow unmarshaling of global attributes