graph/encoding: move basic encoding API into new encoding package

This commit is contained in:
kortschak
2017-08-03 15:50:51 +09:30
committed by Dan Kortschak
parent 416ee8b88c
commit 8e3d6da27f
5 changed files with 126 additions and 114 deletions

View File

@@ -8,32 +8,17 @@ import (
"fmt" "fmt"
"gonum.org/v1/gonum/graph" "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"
"gonum.org/v1/gonum/graph/formats/dot/ast" "gonum.org/v1/gonum/graph/formats/dot/ast"
"gonum.org/v1/gonum/graph/internal/set" "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 // UnmashalerAttrs is implemented by graph values that can unmarshal global
// DOT attributes. // DOT attributes.
type UnmarshalerAttrs interface { type UnmarshalerAttrs interface {
// DOTUnmarshalerAttrs returns the global attribute unmarshalers. // DOTUnmarshalerAttrs returns the global attribute unmarshalers.
DOTUnmarshalerAttrs() (graph, node, edge UnmarshalerAttr) DOTUnmarshalerAttrs() (graph, node, edge encoding.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
} }
// UnmarshalerID is implemented by types that can unmarshal a DOT ID. // UnmarshalerID is implemented by types that can unmarshal a DOT ID.
@@ -43,7 +28,7 @@ type UnmarshalerID interface {
} }
// Unmarshal parses the Graphviz DOT-encoded data and stores the result in dst. // 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) file, err := dot.ParseBytes(data)
if err != nil { if err != nil {
return err return err
@@ -56,7 +41,7 @@ func Unmarshal(data []byte, dst Builder) error {
// copyGraph copies the nodes and edges from the Graphviz AST source graph to // copyGraph copies the nodes and edges from the Graphviz AST source graph to
// the destination graph. Edge direction is maintained if present. // 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() { defer func() {
switch e := recover().(type) { switch e := recover().(type) {
case nil: case nil:
@@ -93,12 +78,12 @@ type generator struct {
// corresponds to the start index of the active (or inner-most) subgraph. // corresponds to the start index of the active (or inner-most) subgraph.
subStart []int subStart []int
// graphAttr, nodeAttr and edgeAttr are global graph attributes. // 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, // node returns the gonum node corresponding to the given dot AST node ID,
// generating a new such node if none exist. // 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 { if n, ok := gen.ids[id]; ok {
return n return n
} }
@@ -119,26 +104,26 @@ func (gen *generator) node(dst Builder, id string) graph.Node {
} }
// addStmt adds the given statement to the graph. // 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) { switch stmt := stmt.(type) {
case *ast.NodeStmt: case *ast.NodeStmt:
n, ok := gen.node(dst, stmt.Node.ID).(UnmarshalerAttr) n, ok := gen.node(dst, stmt.Node.ID).(encoding.UnmarshalerAttr)
if !ok { if !ok {
return return
} }
for _, attr := range stmt.Attrs { for _, attr := range stmt.Attrs {
a := Attribute{ a := encoding.Attribute{
Key: attr.Key, Key: attr.Key,
Value: attr.Val, 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)) panic(fmt.Errorf("unable to unmarshal node DOT attribute (%s=%s)", a.Key, a.Value))
} }
} }
case *ast.EdgeStmt: case *ast.EdgeStmt:
gen.addEdgeStmt(dst, stmt) gen.addEdgeStmt(dst, stmt)
case *ast.AttrStmt: case *ast.AttrStmt:
var n UnmarshalerAttr var n encoding.UnmarshalerAttr
var dst string var dst string
switch stmt.Kind { switch stmt.Kind {
case ast.GraphKind: case ast.GraphKind:
@@ -163,11 +148,11 @@ func (gen *generator) addStmt(dst Builder, stmt ast.Stmt) {
panic("unreachable") panic("unreachable")
} }
for _, attr := range stmt.Attrs { for _, attr := range stmt.Attrs {
a := Attribute{ a := encoding.Attribute{
Key: attr.Key, Key: attr.Key,
Value: attr.Val, 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)) panic(fmt.Errorf("unable to unmarshal global %s DOT attribute (%s=%s)", dst, a.Key, a.Value))
} }
} }
@@ -183,21 +168,21 @@ func (gen *generator) addStmt(dst Builder, stmt ast.Stmt) {
} }
// addEdgeStmt adds the given edge statement to the graph. // 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) fs := gen.addVertex(dst, e.From)
ts := gen.addEdge(dst, e.To) ts := gen.addEdge(dst, e.To)
for _, f := range fs { for _, f := range fs {
for _, t := range ts { for _, t := range ts {
edge, ok := dst.NewEdge(f, t).(UnmarshalerAttr) edge, ok := dst.NewEdge(f, t).(encoding.UnmarshalerAttr)
if !ok { if !ok {
continue continue
} }
for _, attr := range e.Attrs { for _, attr := range e.Attrs {
a := Attribute{ a := encoding.Attribute{
Key: attr.Key, Key: attr.Key,
Value: attr.Val, 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)) panic(fmt.Errorf("unable to unmarshal edge DOT attribute (%s=%s)", a.Key, a.Value))
} }
} }
@@ -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. // 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) { switch v := v.(type) {
case *ast.Node: case *ast.Node:
n := gen.node(dst, v.ID) n := gen.node(dst, v.ID)
@@ -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. // 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 { if !gen.directed && to.Directed {
panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex)) panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex))
} }

View File

@@ -9,6 +9,7 @@ import (
"testing" "testing"
"gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/simple" "gonum.org/v1/gonum/graph/simple"
) )
@@ -27,7 +28,7 @@ func TestRoundTrip(t *testing.T) {
}, },
} }
for i, g := range golden { for i, g := range golden {
var dst Builder var dst encoding.Builder
if g.directed { if g.directed {
dst = newDotDirectedGraph() dst = newDotDirectedGraph()
} else { } else {
@@ -129,12 +130,12 @@ func (g *dotDirectedGraph) NewEdge(from, to graph.Node) graph.Edge {
} }
// DOTAttributers implements the dot.Attributers interface. // 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 return g.graph, g.node, g.edge
} }
// DOTUnmarshalerAttrs implements the dot.UnmarshalerAttrs interface. // 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 return &g.graph, &g.node, &g.edge
} }
@@ -170,12 +171,12 @@ func (g *dotUndirectedGraph) NewEdge(from, to graph.Node) graph.Edge {
} }
// DOTAttributers implements the dot.Attributers interface. // 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 return g.graph, g.node, g.edge
} }
// DOTUnmarshalerAttrs implements the dot.UnmarshalerAttrs interface. // 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 return &g.graph, &g.node, &g.edge
} }
@@ -198,8 +199,8 @@ func (n *dotNode) UnmarshalDOTID(id string) {
n.dotID = id n.dotID = id
} }
// UnmarshalDOTAttr decodes a single DOT attribute. // UnmarshalAttr decodes a single DOT attribute.
func (n *dotNode) UnmarshalDOTAttr(attr Attribute) error { func (n *dotNode) UnmarshalAttr(attr encoding.Attribute) error {
if attr.Key != "label" { if attr.Key != "label" {
return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key) return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key)
} }
@@ -207,16 +208,16 @@ func (n *dotNode) UnmarshalDOTAttr(attr Attribute) error {
return nil return nil
} }
// DOTAttributes returns the DOT attributes of the node. // Attributes returns the DOT attributes of the node.
func (n *dotNode) DOTAttributes() []Attribute { func (n *dotNode) Attributes() []encoding.Attribute {
if len(n.Label) == 0 { if len(n.Label) == 0 {
return nil return nil
} }
attr := Attribute{ attr := encoding.Attribute{
Key: "label", Key: "label",
Value: n.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 // dotEdge extends simple.Edge with a label field to test round-trip encoding and
@@ -227,8 +228,8 @@ type dotEdge struct {
Label string Label string
} }
// UnmarshalDOTAttr decodes a single DOT attribute. // UnmarshalAttr decodes a single DOT attribute.
func (e *dotEdge) UnmarshalDOTAttr(attr Attribute) error { func (e *dotEdge) UnmarshalAttr(attr encoding.Attribute) error {
if attr.Key != "label" { if attr.Key != "label" {
return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key) return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key)
} }
@@ -236,25 +237,25 @@ func (e *dotEdge) UnmarshalDOTAttr(attr Attribute) error {
return nil return nil
} }
// DOTAttributes returns the DOT attributes of the edge. // Attributes returns the DOT attributes of the edge.
func (e *dotEdge) DOTAttributes() []Attribute { func (e *dotEdge) Attributes() []encoding.Attribute {
if len(e.Label) == 0 { if len(e.Label) == 0 {
return nil return nil
} }
attr := Attribute{ attr := encoding.Attribute{
Key: "label", Key: "label",
Value: e.Label, Value: e.Label,
} }
return []Attribute{attr} return []encoding.Attribute{attr}
} }
// attributes is a helper for global attributes. // attributes is a helper for global attributes.
type attributes []Attribute type attributes []encoding.Attribute
func (a attributes) DOTAttributes() []Attribute { func (a attributes) Attributes() []encoding.Attribute {
return []Attribute(a) return []encoding.Attribute(a)
} }
func (a *attributes) UnmarshalDOTAttr(attr Attribute) error { func (a *attributes) UnmarshalAttr(attr encoding.Attribute) error {
*a = append(*a, attr) *a = append(*a, attr)
return nil return nil
} }

View File

@@ -12,6 +12,7 @@ import (
"strings" "strings"
"gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/internal/ordered" "gonum.org/v1/gonum/graph/internal/ordered"
) )
@@ -32,18 +33,7 @@ type Node interface {
// Attributers are graph.Graph values that specify top-level DOT // Attributers are graph.Graph values that specify top-level DOT
// attributes. // attributes.
type Attributers interface { type Attributers interface {
DOTAttributers() (graph, node, edge Attributer) DOTAttributers() (graph, node, edge encoding.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
} }
// Porter defines the behavior of graph.Edge values that can specify // Porter defines the behavior of graph.Edge values that can specify
@@ -184,7 +174,7 @@ func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool
} }
p.newline() p.newline()
p.writeNode(n) p.writeNode(n)
if a, ok := n.(Attributer); ok { if a, ok := n.(encoding.Attributer); ok {
p.writeAttributeList(a) p.writeAttributeList(a)
} }
p.buf.WriteByte(';') p.buf.WriteByte(';')
@@ -252,7 +242,7 @@ func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool
p.writePorts(e.ToPort()) 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) p.writeAttributeList(a)
} }
@@ -297,8 +287,8 @@ func graphID(g graph.Graph, n graph.Node) string {
} }
} }
func (p *printer) writeAttributeList(a Attributer) { func (p *printer) writeAttributeList(a encoding.Attributer) {
attributes := a.DOTAttributes() attributes := a.Attributes()
switch len(attributes) { switch len(attributes) {
case 0: case 0:
case 1: case 1:
@@ -324,8 +314,8 @@ var attType = []string{"graph", "node", "edge"}
func (p *printer) writeAttributeComplex(ca Attributers) { func (p *printer) writeAttributeComplex(ca Attributers) {
g, n, e := ca.DOTAttributers() g, n, e := ca.DOTAttributers()
haveWrittenBlock := false haveWrittenBlock := false
for i, a := range []Attributer{g, n, e} { for i, a := range []encoding.Attributer{g, n, e} {
attributes := a.DOTAttributes() attributes := a.Attributes()
if len(attributes) == 0 { if len(attributes) == 0 {
continue continue
} }

View File

@@ -9,6 +9,7 @@ import (
"testing" "testing"
"gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/simple" "gonum.org/v1/gonum/graph/simple"
) )
@@ -112,17 +113,17 @@ func undirectedNamedIDGraphFrom(g []intset) graph.Graph {
type attrNode struct { type attrNode struct {
id int64 id int64
name string name string
attr []Attribute attr []encoding.Attribute
} }
func (n attrNode) ID() int64 { return n.id } func (n attrNode) ID() int64 { return n.id }
func (n attrNode) DOTAttributes() []Attribute { return n.attr } func (n attrNode) Attributes() []encoding.Attribute { return n.attr }
func directedNodeAttrGraphFrom(g []intset, attr [][]Attribute) graph.Directed { func directedNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1)) dg := simple.NewDirectedGraph(0, math.Inf(1))
for u, e := range g { for u, e := range g {
u := int64(u) u := int64(u)
var at []Attribute var at []encoding.Attribute
if u < int64(len(attr)) { if u < int64(len(attr)) {
at = attr[u] at = attr[u]
} }
@@ -138,11 +139,11 @@ func directedNodeAttrGraphFrom(g []intset, attr [][]Attribute) graph.Directed {
return dg return dg
} }
func undirectedNodeAttrGraphFrom(g []intset, attr [][]Attribute) graph.Graph { func undirectedNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1)) dg := simple.NewUndirectedGraph(0, math.Inf(1))
for u, e := range g { for u, e := range g {
u := int64(u) u := int64(u)
var at []Attribute var at []encoding.Attribute
if u < int64(len(attr)) { if u < int64(len(attr)) {
at = attr[u] at = attr[u]
} }
@@ -161,18 +162,18 @@ func undirectedNodeAttrGraphFrom(g []intset, attr [][]Attribute) graph.Graph {
type namedAttrNode struct { type namedAttrNode struct {
id int64 id int64
name string name string
attr []Attribute attr []encoding.Attribute
} }
func (n namedAttrNode) ID() int64 { return n.id } func (n namedAttrNode) ID() int64 { return n.id }
func (n namedAttrNode) DOTID() string { return n.name } func (n namedAttrNode) DOTID() string { return n.name }
func (n namedAttrNode) DOTAttributes() []Attribute { return n.attr } func (n namedAttrNode) Attributes() []encoding.Attribute { return n.attr }
func directedNamedIDNodeAttrGraphFrom(g []intset, attr [][]Attribute) graph.Directed { func directedNamedIDNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1)) dg := simple.NewDirectedGraph(0, math.Inf(1))
for u, e := range g { for u, e := range g {
u := int64(u) u := int64(u)
var at []Attribute var at []encoding.Attribute
if u < int64(len(attr)) { if u < int64(len(attr)) {
at = attr[u] at = attr[u]
} }
@@ -188,11 +189,11 @@ func directedNamedIDNodeAttrGraphFrom(g []intset, attr [][]Attribute) graph.Dire
return dg return dg
} }
func undirectedNamedIDNodeAttrGraphFrom(g []intset, attr [][]Attribute) graph.Graph { func undirectedNamedIDNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1)) dg := simple.NewUndirectedGraph(0, math.Inf(1))
for u, e := range g { for u, e := range g {
u := int64(u) u := int64(u)
var at []Attribute var at []encoding.Attribute
if u < int64(len(attr)) { if u < int64(len(attr)) {
at = attr[u] at = attr[u]
} }
@@ -211,15 +212,15 @@ func undirectedNamedIDNodeAttrGraphFrom(g []intset, attr [][]Attribute) graph.Gr
type attrEdge struct { type attrEdge struct {
from, to graph.Node from, to graph.Node
attr []Attribute attr []encoding.Attribute
} }
func (e attrEdge) From() graph.Node { return e.from } func (e attrEdge) From() graph.Node { return e.from }
func (e attrEdge) To() graph.Node { return e.to } func (e attrEdge) To() graph.Node { return e.to }
func (e attrEdge) Weight() float64 { return 0 } func (e attrEdge) Weight() float64 { return 0 }
func (e attrEdge) DOTAttributes() []Attribute { return e.attr } func (e attrEdge) Attributes() []encoding.Attribute { return e.attr }
func directedEdgeAttrGraphFrom(g []intset, attr map[edge][]Attribute) graph.Directed { func directedEdgeAttrGraphFrom(g []intset, attr map[edge][]encoding.Attribute) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1)) dg := simple.NewDirectedGraph(0, math.Inf(1))
for u, e := range g { for u, e := range g {
u := int64(u) u := int64(u)
@@ -230,7 +231,7 @@ func directedEdgeAttrGraphFrom(g []intset, attr map[edge][]Attribute) graph.Dire
return dg return dg
} }
func undirectedEdgeAttrGraphFrom(g []intset, attr map[edge][]Attribute) graph.Graph { func undirectedEdgeAttrGraphFrom(g []intset, attr map[edge][]encoding.Attribute) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1)) dg := simple.NewUndirectedGraph(0, math.Inf(1))
for u, e := range g { for u, e := range g {
u := int64(u) u := int64(u)
@@ -271,11 +272,11 @@ func (e portedEdge) ToPort() (port, compass string) {
return e.toPort, e.toCompass return e.toPort, e.toCompass
} }
func directedPortedAttrGraphFrom(g []intset, attr [][]Attribute, ports map[edge]portedEdge) graph.Directed { func directedPortedAttrGraphFrom(g []intset, attr [][]encoding.Attribute, ports map[edge]portedEdge) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1)) dg := simple.NewDirectedGraph(0, math.Inf(1))
for u, e := range g { for u, e := range g {
u := int64(u) u := int64(u)
var at []Attribute var at []encoding.Attribute
if u < int64(len(attr)) { if u < int64(len(attr)) {
at = attr[u] at = attr[u]
} }
@@ -293,11 +294,11 @@ func directedPortedAttrGraphFrom(g []intset, attr [][]Attribute, ports map[edge]
return dg return dg
} }
func undirectedPortedAttrGraphFrom(g []intset, attr [][]Attribute, ports map[edge]portedEdge) graph.Graph { func undirectedPortedAttrGraphFrom(g []intset, attr [][]encoding.Attribute, ports map[edge]portedEdge) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1)) dg := simple.NewUndirectedGraph(0, math.Inf(1))
for u, e := range g { for u, e := range g {
u := int64(u) u := int64(u)
var at []Attribute var at []encoding.Attribute
if u < int64(len(attr)) { if u < int64(len(attr)) {
at = attr[u] at = attr[u]
} }
@@ -322,11 +323,11 @@ type graphAttributer struct {
edge attributer edge attributer
} }
type attributer []Attribute type attributer []encoding.Attribute
func (a attributer) DOTAttributes() []Attribute { return a } func (a attributer) Attributes() []encoding.Attribute { return a }
func (g graphAttributer) DOTAttributers() (graph, node, edge Attributer) { func (g graphAttributer) DOTAttributers() (graph, node, edge encoding.Attributer) {
return g.graph, g.node, g.edge return g.graph, g.node, g.edge
} }
@@ -748,7 +749,7 @@ var encodeTests = []struct {
}`, }`,
}, },
{ {
g: directedNodeAttrGraphFrom(powerMethodGraph, [][]Attribute{ g: directedNodeAttrGraphFrom(powerMethodGraph, [][]encoding.Attribute{
2: {{"fontsize", "16"}, {"shape", "ellipse"}}, 2: {{"fontsize", "16"}, {"shape", "ellipse"}},
4: {}, 4: {},
}), }),
@@ -775,7 +776,7 @@ var encodeTests = []struct {
}`, }`,
}, },
{ {
g: undirectedNodeAttrGraphFrom(powerMethodGraph, [][]Attribute{ g: undirectedNodeAttrGraphFrom(powerMethodGraph, [][]encoding.Attribute{
2: {{"fontsize", "16"}, {"shape", "ellipse"}}, 2: {{"fontsize", "16"}, {"shape", "ellipse"}},
4: {}, 4: {},
}), }),
@@ -802,7 +803,7 @@ var encodeTests = []struct {
}`, }`,
}, },
{ {
g: directedNamedIDNodeAttrGraphFrom(powerMethodGraph, [][]Attribute{ g: directedNamedIDNodeAttrGraphFrom(powerMethodGraph, [][]encoding.Attribute{
2: {{"fontsize", "16"}, {"shape", "ellipse"}}, 2: {{"fontsize", "16"}, {"shape", "ellipse"}},
4: {}, 4: {},
}), }),
@@ -829,7 +830,7 @@ var encodeTests = []struct {
}`, }`,
}, },
{ {
g: undirectedNamedIDNodeAttrGraphFrom(powerMethodGraph, [][]Attribute{ g: undirectedNamedIDNodeAttrGraphFrom(powerMethodGraph, [][]encoding.Attribute{
0: nil, 0: nil,
1: nil, 1: nil,
2: {{"fontsize", "16"}, {"shape", "ellipse"}}, 2: {{"fontsize", "16"}, {"shape", "ellipse"}},
@@ -903,7 +904,7 @@ var encodeTests = []struct {
}`, }`,
}, },
{ {
g: directedEdgeAttrGraphFrom(powerMethodGraph, map[edge][]Attribute{ g: directedEdgeAttrGraphFrom(powerMethodGraph, map[edge][]encoding.Attribute{
{from: 0, to: 2}: {{"label", `"???"`}, {"style", "dashed"}}, {from: 0, to: 2}: {{"label", `"???"`}, {"style", "dashed"}},
{from: 2, to: 4}: {}, {from: 2, to: 4}: {},
{from: 3, to: 4}: {{"color", "red"}}, {from: 3, to: 4}: {{"color", "red"}},
@@ -931,7 +932,7 @@ var encodeTests = []struct {
}`, }`,
}, },
{ {
g: undirectedEdgeAttrGraphFrom(powerMethodGraph, map[edge][]Attribute{ g: undirectedEdgeAttrGraphFrom(powerMethodGraph, map[edge][]encoding.Attribute{
{from: 0, to: 2}: {{"label", `"???"`}, {"style", "dashed"}}, {from: 0, to: 2}: {{"label", `"???"`}, {"style", "dashed"}},
{from: 2, to: 4}: {}, {from: 2, to: 4}: {},
{from: 3, to: 4}: {{"color", "red"}}, {from: 3, to: 4}: {{"color", "red"}},
@@ -1004,7 +1005,7 @@ var encodeTests = []struct {
}, },
{ {
g: directedPortedAttrGraphFrom(powerMethodGraph, g: directedPortedAttrGraphFrom(powerMethodGraph,
[][]Attribute{ [][]encoding.Attribute{
2: {{"shape", "record"}, {"label", `"<Two>English|<Zwei>German"`}}, 2: {{"shape", "record"}, {"label", `"<Two>English|<Zwei>German"`}},
4: {{"shape", "record"}, {"label", `"<Four>English|<Vier>German"`}}, 4: {{"shape", "record"}, {"label", `"<Four>English|<Vier>German"`}},
}, },
@@ -1044,7 +1045,7 @@ var encodeTests = []struct {
}, },
{ {
g: undirectedPortedAttrGraphFrom(powerMethodGraph, g: undirectedPortedAttrGraphFrom(powerMethodGraph,
[][]Attribute{ [][]encoding.Attribute{
2: {{"shape", "record"}, {"label", `"<Two>English|<Zwei>German"`}}, 2: {{"shape", "record"}, {"label", `"<Two>English|<Zwei>German"`}},
4: {{"shape", "record"}, {"label", `"<Four>English|<Vier>German"`}}, 4: {{"shape", "record"}, {"label", `"<Four>English|<Vier>German"`}},
}, },
@@ -1092,7 +1093,7 @@ var encodeTests = []struct {
// Handling graph attributes. // Handling graph attributes.
{ {
g: graphAttributer{Graph: undirectedEdgeAttrGraphFrom(powerMethodGraph, map[edge][]Attribute{ g: graphAttributer{Graph: undirectedEdgeAttrGraphFrom(powerMethodGraph, map[edge][]encoding.Attribute{
{from: 0, to: 2}: {{"label", `"???"`}, {"style", "dashed"}}, {from: 0, to: 2}: {{"label", `"???"`}, {"style", "dashed"}},
{from: 2, to: 4}: {}, {from: 2, to: 4}: {},
{from: 3, to: 4}: {{"color", "red"}}, {from: 3, to: 4}: {{"color", "red"}},
@@ -1120,13 +1121,13 @@ var encodeTests = []struct {
}`, }`,
}, },
{ {
g: graphAttributer{Graph: undirectedEdgeAttrGraphFrom(powerMethodGraph, map[edge][]Attribute{ g: graphAttributer{Graph: undirectedEdgeAttrGraphFrom(powerMethodGraph, map[edge][]encoding.Attribute{
{from: 0, to: 2}: {{"label", `"???"`}, {"style", "dashed"}}, {from: 0, to: 2}: {{"label", `"???"`}, {"style", "dashed"}},
{from: 2, to: 4}: {}, {from: 2, to: 4}: {},
{from: 3, to: 4}: {{"color", "red"}}, {from: 3, to: 4}: {{"color", "red"}},
}), }),
graph: []Attribute{{"rankdir", `"LR"`}}, graph: []encoding.Attribute{{"rankdir", `"LR"`}},
node: []Attribute{{"fontsize", "16"}, {"shape", "ellipse"}}, node: []encoding.Attribute{{"fontsize", "16"}, {"shape", "ellipse"}},
}, },
want: `graph { want: `graph {

View File

@@ -0,0 +1,35 @@
// Copyright ©2017 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package encoding provides a common graph encoding API.
package encoding // import "gonum.org/v1/gonum/graph/encoding"
import "gonum.org/v1/gonum/graph"
// 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
}
// UnmarshalerAttr is implemented by types that can unmarshal a graph
// attribute description of themselves.
type UnmarshalerAttr interface {
// UnmarshalAttr decodes a single attribute.
UnmarshalAttr(attr Attribute) error
}
// Attributer defines graph.Node or graph.Edge values that can
// specify graph attributes.
type Attributer interface {
Attributes() []Attribute
}
// Attribute is an encoded key value attribute pair use in graph encoding.
type Attribute struct {
Key, Value string
}