Skip to content

Commit

Permalink
encoding: move basic encoding API into new encoding package
Browse files Browse the repository at this point in the history
  • Loading branch information
kortschak committed Aug 3, 2017
1 parent fc1f440 commit 6f6a2c7
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 112 deletions.
53 changes: 19 additions & 34 deletions graph/encoding/dot/decode.go
Expand Up @@ -8,32 +8,17 @@ import (
"fmt"

"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/formats/dot"
"gonum.org/v1/gonum/graph/formats/dot/ast"
"gonum.org/v1/gonum/graph/internal/set"
)

// Builder is a graph that can have user-defined nodes and edges added.
type Builder interface {
graph.Graph
graph.Builder
// NewEdge adds a new edge from the source to the destination node to the
// graph, or returns the existing edge if already present.
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 {
// UnmarshalDOTAttr decodes a single DOT attribute.
UnmarshalDOTAttr(attr Attribute) error
DOTUnmarshalerAttrs() (graph, node, edge encoding.UnmarshalerAttr)
}

// UnmarshalerID is implemented by types that can unmarshal a DOT ID.
Expand All @@ -43,7 +28,7 @@ type UnmarshalerID interface {
}

// Unmarshal parses the Graphviz DOT-encoded data and stores the result in dst.
func Unmarshal(data []byte, dst Builder) error {
func Unmarshal(data []byte, dst encoding.Builder) error {
file, err := dot.ParseBytes(data)
if err != nil {
return err
Expand All @@ -56,7 +41,7 @@ func Unmarshal(data []byte, dst Builder) error {

// copyGraph copies the nodes and edges from the Graphviz AST source graph to
// the destination graph. Edge direction is maintained if present.
func copyGraph(dst Builder, src *ast.Graph) (err error) {
func copyGraph(dst encoding.Builder, src *ast.Graph) (err error) {
defer func() {
switch e := recover().(type) {
case nil:
Expand Down Expand Up @@ -93,12 +78,12 @@ type generator struct {
// 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
graphAttr, nodeAttr, edgeAttr encoding.UnmarshalerAttr
}

// node returns the gonum node corresponding to the given dot AST node ID,
// generating a new such node if none exist.
func (gen *generator) node(dst Builder, id string) graph.Node {
func (gen *generator) node(dst encoding.Builder, id string) graph.Node {
if n, ok := gen.ids[id]; ok {
return n
}
Expand All @@ -119,26 +104,26 @@ func (gen *generator) node(dst Builder, id string) graph.Node {
}

// addStmt adds the given statement to the graph.
func (gen *generator) addStmt(dst Builder, stmt ast.Stmt) {
func (gen *generator) addStmt(dst encoding.Builder, stmt ast.Stmt) {
switch stmt := stmt.(type) {
case *ast.NodeStmt:
n, ok := gen.node(dst, stmt.Node.ID).(UnmarshalerAttr)
n, ok := gen.node(dst, stmt.Node.ID).(encoding.UnmarshalerAttr)
if !ok {
return
}
for _, attr := range stmt.Attrs {
a := Attribute{
a := encoding.Attribute{
Key: attr.Key,
Value: attr.Val,
}
if err := n.UnmarshalDOTAttr(a); err != nil {
if err := n.UnmarshalAttr(a); err != nil {
panic(fmt.Errorf("unable to unmarshal node DOT attribute (%s=%s)", a.Key, a.Value))
}
}
case *ast.EdgeStmt:
gen.addEdgeStmt(dst, stmt)
case *ast.AttrStmt:
var n UnmarshalerAttr
var n encoding.UnmarshalerAttr
var dst string
switch stmt.Kind {
case ast.GraphKind:
Expand All @@ -163,11 +148,11 @@ func (gen *generator) addStmt(dst Builder, stmt ast.Stmt) {
panic("unreachable")
}
for _, attr := range stmt.Attrs {
a := Attribute{
a := encoding.Attribute{
Key: attr.Key,
Value: attr.Val,
}
if err := n.UnmarshalDOTAttr(a); err != nil {
if err := n.UnmarshalAttr(a); err != nil {
panic(fmt.Errorf("unable to unmarshal global %s DOT attribute (%s=%s)", dst, a.Key, a.Value))
}
}
Expand All @@ -183,21 +168,21 @@ func (gen *generator) addStmt(dst Builder, stmt ast.Stmt) {
}

// addEdgeStmt adds the given edge statement to the graph.
func (gen *generator) addEdgeStmt(dst Builder, e *ast.EdgeStmt) {
func (gen *generator) addEdgeStmt(dst encoding.Builder, e *ast.EdgeStmt) {
fs := gen.addVertex(dst, e.From)
ts := gen.addEdge(dst, e.To)
for _, f := range fs {
for _, t := range ts {
edge, ok := dst.NewEdge(f, t).(UnmarshalerAttr)
edge, ok := dst.NewEdge(f, t).(encoding.UnmarshalerAttr)
if !ok {
continue
}
for _, attr := range e.Attrs {
a := Attribute{
a := encoding.Attribute{
Key: attr.Key,
Value: attr.Val,
}
if err := edge.UnmarshalDOTAttr(a); err != nil {
if err := edge.UnmarshalAttr(a); err != nil {
panic(fmt.Errorf("unable to unmarshal edge DOT attribute (%s=%s)", a.Key, a.Value))
}
}
Expand All @@ -206,7 +191,7 @@ func (gen *generator) addEdgeStmt(dst Builder, e *ast.EdgeStmt) {
}

// addVertex adds the given vertex to the graph, and returns its set of nodes.
func (gen *generator) addVertex(dst Builder, v ast.Vertex) []graph.Node {
func (gen *generator) addVertex(dst encoding.Builder, v ast.Vertex) []graph.Node {
switch v := v.(type) {
case *ast.Node:
n := gen.node(dst, v.ID)
Expand All @@ -223,7 +208,7 @@ func (gen *generator) addVertex(dst Builder, v ast.Vertex) []graph.Node {
}

// addEdge adds the given edge to the graph, and returns its set of nodes.
func (gen *generator) addEdge(dst Builder, to *ast.Edge) []graph.Node {
func (gen *generator) addEdge(dst encoding.Builder, to *ast.Edge) []graph.Node {
if !gen.directed && to.Directed {
panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex))
}
Expand Down
39 changes: 20 additions & 19 deletions graph/encoding/dot/decode_test.go
Expand Up @@ -9,6 +9,7 @@ import (
"testing"

"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/simple"
)

Expand All @@ -27,7 +28,7 @@ func TestRoundTrip(t *testing.T) {
},
}
for i, g := range golden {
var dst Builder
var dst encoding.Builder
if g.directed {
dst = newDotDirectedGraph()
} else {
Expand Down Expand Up @@ -129,12 +130,12 @@ func (g *dotDirectedGraph) NewEdge(from, to graph.Node) graph.Edge {
}

// DOTAttributers implements the dot.Attributers interface.
func (g *dotDirectedGraph) DOTAttributers() (graph, node, edge Attributer) {
func (g *dotDirectedGraph) DOTAttributers() (graph, node, edge encoding.Attributer) {
return g.graph, g.node, g.edge
}

// DOTUnmarshalerAttrs implements the dot.UnmarshalerAttrs interface.
func (g *dotDirectedGraph) DOTUnmarshalerAttrs() (graph, node, edge UnmarshalerAttr) {
func (g *dotDirectedGraph) DOTUnmarshalerAttrs() (graph, node, edge encoding.UnmarshalerAttr) {
return &g.graph, &g.node, &g.edge
}

Expand Down Expand Up @@ -170,12 +171,12 @@ func (g *dotUndirectedGraph) NewEdge(from, to graph.Node) graph.Edge {
}

// DOTAttributers implements the dot.Attributers interface.
func (g *dotUndirectedGraph) DOTAttributers() (graph, node, edge Attributer) {
func (g *dotUndirectedGraph) DOTAttributers() (graph, node, edge encoding.Attributer) {
return g.graph, g.node, g.edge
}

// DOTUnmarshalerAttrs implements the dot.UnmarshalerAttrs interface.
func (g *dotUndirectedGraph) DOTUnmarshalerAttrs() (graph, node, edge UnmarshalerAttr) {
func (g *dotUndirectedGraph) DOTUnmarshalerAttrs() (graph, node, edge encoding.UnmarshalerAttr) {
return &g.graph, &g.node, &g.edge
}

Expand All @@ -199,24 +200,24 @@ func (n *dotNode) UnmarshalDOTID(id string) {
}

// UnmarshalDOTAttr decodes a single DOT attribute.
func (n *dotNode) UnmarshalDOTAttr(attr Attribute) error {
func (n *dotNode) UnmarshalDOTAttr(attr encoding.Attribute) error {
if attr.Key != "label" {
return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key)
}
n.Label = attr.Value
return nil
}

// DOTAttributes returns the DOT attributes of the node.
func (n *dotNode) DOTAttributes() []Attribute {
// Attributes returns the DOT attributes of the node.
func (n *dotNode) Attributes() []encoding.Attribute {
if len(n.Label) == 0 {
return nil
}
attr := Attribute{
attr := encoding.Attribute{
Key: "label",
Value: n.Label,
}
return []Attribute{attr}
return []encoding.Attribute{attr}
}

// dotEdge extends simple.Edge with a label field to test round-trip encoding and
Expand All @@ -228,33 +229,33 @@ type dotEdge struct {
}

// UnmarshalDOTAttr decodes a single DOT attribute.
func (e *dotEdge) UnmarshalDOTAttr(attr Attribute) error {
func (e *dotEdge) UnmarshalDOTAttr(attr encoding.Attribute) error {
if attr.Key != "label" {
return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key)
}
e.Label = attr.Value
return nil
}

// DOTAttributes returns the DOT attributes of the edge.
func (e *dotEdge) DOTAttributes() []Attribute {
// Attributes returns the DOT attributes of the edge.
func (e *dotEdge) Attributes() []encoding.Attribute {
if len(e.Label) == 0 {
return nil
}
attr := Attribute{
attr := encoding.Attribute{
Key: "label",
Value: e.Label,
}
return []Attribute{attr}
return []encoding.Attribute{attr}
}

// attributes is a helper for global attributes.
type attributes []Attribute
type attributes []encoding.Attribute

func (a attributes) DOTAttributes() []Attribute {
return []Attribute(a)
func (a attributes) Attributes() []encoding.Attribute {
return []encoding.Attribute(a)
}
func (a *attributes) UnmarshalDOTAttr(attr Attribute) error {
func (a *attributes) UnmarshalDOTAttr(attr encoding.Attribute) error {
*a = append(*a, attr)
return nil
}
26 changes: 8 additions & 18 deletions graph/encoding/dot/encode.go
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/internal/ordered"
)

Expand All @@ -32,18 +33,7 @@ type Node interface {
// Attributers are graph.Graph values that specify top-level DOT
// attributes.
type Attributers interface {
DOTAttributers() (graph, node, edge Attributer)
}

// Attributer defines graph.Node or graph.Edge values that can
// specify DOT attributes.
type Attributer interface {
DOTAttributes() []Attribute
}

// Attribute is a DOT language key value attribute pair.
type Attribute struct {
Key, Value string
DOTAttributers() (graph, node, edge encoding.Attributer)
}

// Porter defines the behavior of graph.Edge values that can specify
Expand Down Expand Up @@ -184,7 +174,7 @@ func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool
}
p.newline()
p.writeNode(n)
if a, ok := n.(Attributer); ok {
if a, ok := n.(encoding.Attributer); ok {
p.writeAttributeList(a)
}
p.buf.WriteByte(';')
Expand Down Expand Up @@ -252,7 +242,7 @@ func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool
p.writePorts(e.ToPort())
}

if a, ok := g.Edge(n, t).(Attributer); ok {
if a, ok := g.Edge(n, t).(encoding.Attributer); ok {
p.writeAttributeList(a)
}

Expand Down Expand Up @@ -297,8 +287,8 @@ func graphID(g graph.Graph, n graph.Node) string {
}
}

func (p *printer) writeAttributeList(a Attributer) {
attributes := a.DOTAttributes()
func (p *printer) writeAttributeList(a encoding.Attributer) {
attributes := a.Attributes()
switch len(attributes) {
case 0:
case 1:
Expand All @@ -324,8 +314,8 @@ var attType = []string{"graph", "node", "edge"}
func (p *printer) writeAttributeComplex(ca Attributers) {
g, n, e := ca.DOTAttributers()
haveWrittenBlock := false
for i, a := range []Attributer{g, n, e} {
attributes := a.DOTAttributes()
for i, a := range []encoding.Attributer{g, n, e} {
attributes := a.Attributes()
if len(attributes) == 0 {
continue
}
Expand Down

0 comments on commit 6f6a2c7

Please sign in to comment.