Skip to content

Commit

Permalink
graph/encoding/dot: allow unmarshaling of global attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
kortschak committed Jun 8, 2017
1 parent d49f26e commit e4ba87b
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 2 deletions.
46 changes: 45 additions & 1 deletion graph/encoding/dot/decode.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
// graph, node and edge are top-level graph attributes.
graphAttr, nodeAttr, edgeAttr UnmarshalerAttr
}

// node returns the gonum node corresponding to the given dot AST node ID,
Expand Down Expand Up @@ -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:
Expand Down
59 changes: 58 additions & 1 deletion graph/encoding/dot/decode_test.go
Expand Up @@ -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"];
Expand All @@ -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"];
Expand All @@ -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
Expand All @@ -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-
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}

0 comments on commit e4ba87b

Please sign in to comment.