implement dag.Subgrapher interface

This allows the dag package to detect subgraphs, even when impelemnted
by types from other packages
This commit is contained in:
James Bardin 2016-11-09 16:52:22 -05:00
parent 28d406c040
commit 7b774f771b
10 changed files with 45 additions and 24 deletions

View File

@ -24,6 +24,10 @@ type WalkFunc func(Vertex) error
// walk as an argument // walk as an argument
type DepthWalkFunc func(Vertex, int) error type DepthWalkFunc func(Vertex, int) error
func (g *AcyclicGraph) DirectedGraph() Grapher {
return g
}
// Returns a Set that includes every Vertex yielded by walking down from the // Returns a Set that includes every Vertex yielded by walking down from the
// provided starting Vertex v. // provided starting Vertex v.
func (g *AcyclicGraph) Ancestors(v Vertex) (*Set, error) { func (g *AcyclicGraph) Ancestors(v Vertex) (*Set, error) {

View File

@ -17,6 +17,18 @@ type Graph struct {
once sync.Once once sync.Once
} }
// Subgrapher allows a Vertex to be a Graph itself, by returning a Grapher.
type Subgrapher interface {
Subgraph() Grapher
}
// A Grapher is any type that returns a Grapher, mainly used to identify
// dag.Graph and dag.AcyclicGraph. In the case of Graph and AcyclicGraph, they
// return themselves.
type Grapher interface {
DirectedGraph() Grapher
}
// Vertex of the graph. // Vertex of the graph.
type Vertex interface{} type Vertex interface{}
@ -27,6 +39,10 @@ type NamedVertex interface {
Name() string Name() string
} }
func (g *Graph) DirectedGraph() Grapher {
return g
}
// Vertices returns the list of all the vertices in the graph. // Vertices returns the list of all the vertices in the graph.
func (g *Graph) Vertices() []Vertex { func (g *Graph) Vertices() []Vertex {
list := g.vertices.List() list := g.vertices.List()

View File

@ -60,7 +60,7 @@ func newMarshalGraph(name string, g *Graph) *marshalGraph {
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
id := marshalVertexID(v) id := marshalVertexID(v)
if sg, ok := marshalSubgraph(v); ok { if sg, ok := marshalSubgrapher(v); ok {
sdg := newMarshalGraph(VertexName(v), sg) sdg := newMarshalGraph(VertexName(v), sg)
sdg.ID = id sdg.ID = id
@ -129,22 +129,19 @@ func marshalVertexID(v Vertex) string {
panic("unhashable value in graph") panic("unhashable value in graph")
} }
func debugSubgraph(v Vertex) (*Graph, bool) { // check for a Subgrapher, and return the underlying *Graph.
val := reflect.ValueOf(v) func marshalSubgrapher(v Vertex) (*Graph, bool) {
m, ok := val.Type().MethodByName("Subgraph") sg, ok := v.(Subgrapher)
if !ok { if !ok {
return nil, false return nil, false
} }
if m.Type.NumOut() != 1 { switch g := sg.Subgraph().DirectedGraph().(type) {
return nil, false case *Graph:
return g, true
case *AcyclicGraph:
return &g.Graph, true
} }
// can't check for the subgraph type, because we can't import terraform, so return nil, false
// we assume this is the correct method.
// TODO: create a dag interface type that we can satisfy
sg := val.MethodByName("Subgraph").Call(nil)[0]
ag := sg.Elem().FieldByName("AcyclicGraph").Interface().(AcyclicGraph)
return &ag.Graph, true
} }

View File

@ -43,6 +43,10 @@ type Graph struct {
once sync.Once once sync.Once
} }
func (g *Graph) DirectedGraph() dag.Grapher {
return &g.AcyclicGraph
}
// Annotations returns the annotations that are configured for the // Annotations returns the annotations that are configured for the
// given vertex. The map is guaranteed to be non-nil but may be empty. // given vertex. The map is guaranteed to be non-nil but may be empty.
// //
@ -317,7 +321,7 @@ func (g *Graph) walk(walker GraphWalker) error {
walker.Debug().Printf( walker.Debug().Printf(
"[DEBUG] vertex %T(%s.%s): subgraph\n", v, path, dag.VertexName(v)) "[DEBUG] vertex %T(%s.%s): subgraph\n", v, path, dag.VertexName(v))
if rerr = sn.Subgraph().walk(walker); rerr != nil { if rerr = sn.Subgraph().(*Graph).walk(walker); rerr != nil {
return return
} }
} }

View File

@ -156,7 +156,7 @@ func (n *graphNodeModuleExpanded) EvalTree() EvalNode {
// GraphNodeFlattenable impl. // GraphNodeFlattenable impl.
func (n *graphNodeModuleExpanded) FlattenGraph() *Graph { func (n *graphNodeModuleExpanded) FlattenGraph() *Graph {
graph := n.Subgraph() graph := n.Subgraph().(*Graph)
input := n.Original.Module.RawConfig input := n.Original.Module.RawConfig
// Go over each vertex and do some modifications to the graph for // Go over each vertex and do some modifications to the graph for
@ -189,7 +189,7 @@ func (n *graphNodeModuleExpanded) FlattenGraph() *Graph {
} }
// GraphNodeSubgraph impl. // GraphNodeSubgraph impl.
func (n *graphNodeModuleExpanded) Subgraph() *Graph { func (n *graphNodeModuleExpanded) Subgraph() dag.Grapher {
return n.Graph return n.Graph
} }

View File

@ -33,7 +33,7 @@ func TestGraphNodeConfigModuleExpand(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
actual := strings.TrimSpace(g.Subgraph().String()) actual := strings.TrimSpace(g.Subgraph().(*Graph).String())
expected := strings.TrimSpace(testGraphNodeModuleExpandStr) expected := strings.TrimSpace(testGraphNodeModuleExpandStr)
if actual != expected { if actual != expected {
t.Fatalf("bad:\n\n%s", actual) t.Fatalf("bad:\n\n%s", actual)

View File

@ -184,7 +184,7 @@ func (dg *DebugGraph) buildSubgraph(modName string, g *Graph, modDepth int) erro
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
if sn, ok := v.(GraphNodeSubgraph); ok { if sn, ok := v.(GraphNodeSubgraph); ok {
subgraphVertices[v] = sn.Subgraph() subgraphVertices[v] = sn.Subgraph().(*Graph)
} }
} }
@ -200,7 +200,7 @@ func (dg *DebugGraph) buildSubgraph(modName string, g *Graph, modDepth int) erro
toDraw = append(toDraw, v) toDraw = append(toDraw, v)
if sn, ok := v.(GraphNodeSubgraph); ok { if sn, ok := v.(GraphNodeSubgraph); ok {
subgraphVertices[v] = sn.Subgraph() subgraphVertices[v] = sn.Subgraph().(*Graph)
} }
return nil return nil
} }

View File

@ -299,7 +299,7 @@ type testDrawableSubgraph struct {
func (node *testDrawableSubgraph) Name() string { func (node *testDrawableSubgraph) Name() string {
return node.VertexName return node.VertexName
} }
func (node *testDrawableSubgraph) Subgraph() *Graph { func (node *testDrawableSubgraph) Subgraph() dag.Grapher {
return node.SubgraphMock return node.SubgraphMock
} }
func (node *testDrawableSubgraph) DotNode(n string, opts *dag.DotOpts) *dot.Node { func (node *testDrawableSubgraph) DotNode(n string, opts *dag.DotOpts) *dot.Node {

View File

@ -24,7 +24,7 @@ type GraphNodeDynamicExpandable interface {
// GraphNodeSubgraph is an interface a node can implement if it has // GraphNodeSubgraph is an interface a node can implement if it has
// a larger subgraph that should be walked. // a larger subgraph that should be walked.
type GraphNodeSubgraph interface { type GraphNodeSubgraph interface {
Subgraph() *Graph Subgraph() dag.Grapher
} }
// ExpandTransform is a transformer that does a subgraph expansion // ExpandTransform is a transformer that does a subgraph expansion
@ -56,7 +56,7 @@ func (n *GraphNodeBasicSubgraph) Name() string {
return n.NameValue return n.NameValue
} }
func (n *GraphNodeBasicSubgraph) Subgraph() *Graph { func (n *GraphNodeBasicSubgraph) Subgraph() dag.Grapher {
return n.Graph return n.Graph
} }

View File

@ -30,7 +30,7 @@ func TestExpandTransform(t *testing.T) {
t.Fatalf("not subgraph: %#v", out) t.Fatalf("not subgraph: %#v", out)
} }
actual := strings.TrimSpace(sn.Subgraph().String()) actual := strings.TrimSpace(sn.Subgraph().(*Graph).String())
expected := strings.TrimSpace(testExpandTransformStr) expected := strings.TrimSpace(testExpandTransformStr)
if actual != expected { if actual != expected {
t.Fatalf("bad: %s", actual) t.Fatalf("bad: %s", actual)
@ -66,7 +66,7 @@ type testSubgraph struct {
Graph *Graph Graph *Graph
} }
func (n *testSubgraph) Subgraph() *Graph { func (n *testSubgraph) Subgraph() dag.Grapher {
return n.Graph return n.Graph
} }