mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
dag: new Graph API
This commit is contained in:
parent
87f4c3aae1
commit
2a910585a2
27
dag/edge.go
Normal file
27
dag/edge.go
Normal file
@ -0,0 +1,27 @@
|
||||
package dag
|
||||
|
||||
// Edge represents an edge in the graph, with a source and target vertex.
|
||||
type Edge interface {
|
||||
Source() Vertex
|
||||
Target() Vertex
|
||||
}
|
||||
|
||||
// BasicEdge returns an Edge implementation that simply tracks the source
|
||||
// and target given as-is.
|
||||
func BasicEdge(source, target Vertex) Edge {
|
||||
return &basicEdge{S: source, T: target}
|
||||
}
|
||||
|
||||
// basicEdge is a basic implementation of Edge that has the source and
|
||||
// target vertex.
|
||||
type basicEdge struct {
|
||||
S, T Vertex
|
||||
}
|
||||
|
||||
func (e *basicEdge) Source() Vertex {
|
||||
return e.S
|
||||
}
|
||||
|
||||
func (e *basicEdge) Target() Vertex {
|
||||
return e.T
|
||||
}
|
113
dag/graph.go
113
dag/graph.go
@ -4,50 +4,106 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Graph is used to represent a dependency graph.
|
||||
type Graph struct {
|
||||
Nodes []Node
|
||||
vertices []Vertex
|
||||
edges []Edge
|
||||
downEdges map[Vertex]*set
|
||||
upEdges map[Vertex]*set
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Node is an element of the graph that has other dependencies.
|
||||
type Node interface {
|
||||
Deps() []Node
|
||||
}
|
||||
// Vertex of the graph.
|
||||
type Vertex interface{}
|
||||
|
||||
// NamedNode is an optional interface implementation of a Node that
|
||||
// can have a name. If this is implemented, this will be used for various
|
||||
// output.
|
||||
type NamedNode interface {
|
||||
Node
|
||||
// NamedVertex is an optional interface that can be implemented by Vertex
|
||||
// to give it a human-friendly name that is used for outputting the graph.
|
||||
type NamedVertex interface {
|
||||
Vertex
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Vertices returns the list of all the vertices in the graph.
|
||||
func (g *Graph) Vertices() []Vertex {
|
||||
return g.vertices
|
||||
}
|
||||
|
||||
// Edges returns the list of all the edges in the graph.
|
||||
func (g *Graph) Edges() []Edge {
|
||||
return g.edges
|
||||
}
|
||||
|
||||
// Add adds a vertex to the graph. This is safe to call multiple time with
|
||||
// the same Vertex.
|
||||
func (g *Graph) Add(v Vertex) {
|
||||
g.once.Do(g.init)
|
||||
g.vertices = append(g.vertices, v)
|
||||
}
|
||||
|
||||
// Connect adds an edge with the given source and target. This is safe to
|
||||
// call multiple times with the same value. Note that the same value is
|
||||
// verified through pointer equality of the vertices, not through the
|
||||
// value of the edge itself.
|
||||
func (g *Graph) Connect(edge Edge) {
|
||||
g.once.Do(g.init)
|
||||
|
||||
source := edge.Source()
|
||||
target := edge.Target()
|
||||
|
||||
// Do we have this already? If so, don't add it again.
|
||||
if s, ok := g.downEdges[source]; ok && s.Include(target) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: add all edges
|
||||
g.edges = append(g.edges, edge)
|
||||
|
||||
// Add the down edge
|
||||
s, ok := g.downEdges[source]
|
||||
if !ok {
|
||||
s = new(set)
|
||||
g.downEdges[source] = s
|
||||
}
|
||||
s.Add(target)
|
||||
|
||||
// Add the up edge
|
||||
s, ok = g.upEdges[target]
|
||||
if !ok {
|
||||
s = new(set)
|
||||
g.upEdges[target] = s
|
||||
}
|
||||
s.Add(source)
|
||||
}
|
||||
|
||||
// String outputs some human-friendly output for the graph structure.
|
||||
func (g *Graph) String() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Build the list of node names and a mapping so that we can more
|
||||
// easily alphabetize the output to remain deterministic.
|
||||
names := make([]string, 0, len(g.Nodes))
|
||||
mapping := make(map[string]Node, len(g.Nodes))
|
||||
for _, n := range g.Nodes {
|
||||
name := nodeName(n)
|
||||
names := make([]string, 0, len(g.vertices))
|
||||
mapping := make(map[string]Vertex, len(g.vertices))
|
||||
for _, v := range g.vertices {
|
||||
name := vertName(v)
|
||||
names = append(names, name)
|
||||
mapping[name] = n
|
||||
mapping[name] = v
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
// Write each node in order...
|
||||
for _, name := range names {
|
||||
n := mapping[name]
|
||||
v := mapping[name]
|
||||
targets := g.downEdges[v]
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s\n", name))
|
||||
|
||||
// Alphabetize dependencies
|
||||
depsRaw := n.Deps()
|
||||
deps := make([]string, 0, len(depsRaw))
|
||||
for _, d := range depsRaw {
|
||||
deps = append(deps, nodeName(d))
|
||||
deps := make([]string, 0, targets.Len())
|
||||
for _, target := range targets.List() {
|
||||
deps = append(deps, vertName(target))
|
||||
}
|
||||
sort.Strings(deps)
|
||||
|
||||
@ -60,11 +116,20 @@ func (g *Graph) String() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func nodeName(n Node) string {
|
||||
switch v := n.(type) {
|
||||
case NamedNode:
|
||||
func (g *Graph) init() {
|
||||
g.vertices = make([]Vertex, 0, 5)
|
||||
g.edges = make([]Edge, 0, 2)
|
||||
g.downEdges = make(map[Vertex]*set)
|
||||
g.upEdges = make(map[Vertex]*set)
|
||||
}
|
||||
|
||||
func vertName(raw Vertex) string {
|
||||
switch v := raw.(type) {
|
||||
case NamedVertex:
|
||||
return v.Name()
|
||||
default:
|
||||
case fmt.Stringer:
|
||||
return fmt.Sprintf("%s", v)
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
45
dag/graph_test.go
Normal file
45
dag/graph_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package dag
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGraph_empty(t *testing.T) {
|
||||
var g Graph
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphEmptyStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraph_basic(t *testing.T) {
|
||||
var g Graph
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Connect(BasicEdge(1, 3))
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testGraphBasicStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
const testGraphBasicStr = `
|
||||
1
|
||||
3
|
||||
2
|
||||
3
|
||||
`
|
||||
const testGraphEmptyStr = `
|
||||
1
|
||||
2
|
||||
3
|
||||
`
|
58
dag/set.go
Normal file
58
dag/set.go
Normal file
@ -0,0 +1,58 @@
|
||||
package dag
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// set is an internal Set data structure that is based on simply using
|
||||
// pointers as the hash key into a map.
|
||||
type set struct {
|
||||
m map[interface{}]struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Add adds an item to the set
|
||||
func (s *set) Add(v interface{}) {
|
||||
s.once.Do(s.init)
|
||||
s.m[v] = struct{}{}
|
||||
}
|
||||
|
||||
// Delete removes an item from the set.
|
||||
func (s *set) Delete(v interface{}) {
|
||||
s.once.Do(s.init)
|
||||
delete(s.m, v)
|
||||
}
|
||||
|
||||
// Include returns true/false of whether a value is in the set.
|
||||
func (s *set) Include(v interface{}) bool {
|
||||
s.once.Do(s.init)
|
||||
_, ok := s.m[v]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Len is the number of items in the set.
|
||||
func (s *set) Len() int {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(s.m)
|
||||
}
|
||||
|
||||
// List returns the list of set elements.
|
||||
func (s *set) List() []interface{} {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := make([]interface{}, 0, len(s.m))
|
||||
for k, _ := range s.m {
|
||||
r = append(r, k)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (s *set) init() {
|
||||
s.m = make(map[interface{}]struct{})
|
||||
}
|
Loading…
Reference in New Issue
Block a user