-
Notifications
You must be signed in to change notification settings - Fork 0
/
subgraph_printer.go
241 lines (213 loc) · 7.21 KB
/
subgraph_printer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prettyprinters
import (
"bytes"
"errors"
"github.com/asteris-llc/converge/graph"
"github.com/asteris-llc/converge/graph/node"
"golang.org/x/net/context"
)
// Subgraph are treated as a semi-lattice with the root graph as the ⊥ (join)
// element. Subgraphs are partially ordered based on the rank of their root
// element in the graph.
// Subgraph is a type that represents a subgraph containing it's ID, it's root
// node, a list of it's terminal nodes, and any other included nodes.
type Subgraph struct {
StartNode *string
EndNodes []string
ID SubgraphID
Nodes []string
}
// SubgraphMap is a simple type alias for keeping track of SubgraphID ->
// Subgraph mappings
type SubgraphMap map[SubgraphID]Subgraph
// A SubgraphID is an opaque type that is handled by the DigraphPrettyPrinter.
// SubgraphID must adhere to the following conditions:
//
// * Unique per graph
// * Comparable
type SubgraphID interface{}
// A SubgraphMarker is a structure used to identify graph nodes that mark the
// beginning or end of a Subgraph. SubgraphMakers are returned by MarkNode().
// The SubgraphID field should be set to the name of a unique SubgraphID if
// start is true. When Start is false the SubgraphID field is ignored.
type SubgraphMarker struct {
SubgraphID SubgraphID // The ID for this subgraph (if Start is true)
Start bool // True if this is the start of a new subgraph
}
// SubgraphBottomID defines the SubgraphID for the bottom subgraph. "⊥" is a
// reserved SubgraphID and shouldn't be returned as the SubgraphID by any calls
// to MakeNode.
var SubgraphBottomID interface{}
var (
// This is the join element that contains all other subgraphs. subgraphBottom
// is the parent of all top-level subgraphs.
subgraphBottom = Subgraph{
StartNode: nil,
Nodes: make([]string, 0),
}
)
// makeSubgraphMap creates a new map to hold subgraph data and includes a ⊥
// element. Returns a tuple of the subgraph map and the identifier for the join
// element.
func makeSubgraphMap() SubgraphMap {
subgraph := make(SubgraphMap)
subgraph[SubgraphBottomID] = subgraphBottom
return subgraph
}
// loadSubgraphs takes a graph and a subgraph map and traverses the graph,
// calling MarkNode() on each node, creating and updating subgraphs as
// necessary.
func (p Printer) loadSubgraphs(ctx context.Context, g *graph.Graph, subgraphs SubgraphMap) {
printer, ok := p.pp.(SubgraphPrinter)
if !ok {
for _, node := range g.Vertices() {
addNodeToSubgraph(subgraphs, nil, node)
}
return
}
g.RootFirstWalk(ctx, func(meta *node.Node) error {
if sgMarker := printer.MarkNode(g, meta.ID); sgMarker != nil {
if sgMarker.Start {
addNodeToSubgraph(subgraphs, sgMarker.SubgraphID, meta.ID)
} else {
thisSubgraph := getSubgraphByID(subgraphs, graph.ParentID(meta.ID))
setSubgraphEndNode(subgraphs, thisSubgraph, meta.ID)
}
} else {
subgraphID := getSubgraphByID(subgraphs, graph.ParentID(meta.ID))
addNodeToSubgraph(subgraphs, subgraphID, meta.ID)
}
return nil
})
}
// getParentSubgraph will, given a SubgraphMap a SubgraphID, and a position in
// the graph, locate the parent subgraph, or return ⊥ if no other subgraph is
// found.
func getParentSubgraph(subgraphs SubgraphMap, thisSubgraph SubgraphID, id string) SubgraphID {
parent := graph.ParentID(id)
if thisSubgraph == SubgraphBottomID {
return SubgraphBottomID
}
if id == "." || parent == "." {
return SubgraphBottomID
}
for subgraphID, subgraph := range subgraphs {
for nodeIdx := range subgraph.Nodes {
if id == subgraph.Nodes[nodeIdx] {
if subgraphID == thisSubgraph {
return getParentSubgraph(subgraphs, thisSubgraph, graph.ParentID(id))
}
return subgraphID
}
}
}
return getParentSubgraph(subgraphs, thisSubgraph, graph.ParentID(id))
}
// setSubgraphEndNode appends the given node to the nodes and end nodes lists
// for the specified subgraph.
func setSubgraphEndNode(subgraphs SubgraphMap, subgraphID SubgraphID, node string) {
if subgraphID == SubgraphBottomID {
return
}
if sg, found := subgraphs[subgraphID]; found {
sg.EndNodes = append(sg.EndNodes, node)
sg.Nodes = append(sg.Nodes, node)
subgraphs[subgraphID] = sg
}
}
// isSubgraphEnd returns true if the subgraph exists and the node string is a
// member it's EndNodes list, and false otherwise.
func isSubgraphEnd(subgraphs SubgraphMap, id SubgraphID, node string) bool {
sg, found := subgraphs[id]
if !found {
return false
}
for endNodeIdx := range sg.EndNodes {
if node == sg.EndNodes[endNodeIdx] {
return true
}
}
return false
}
// getSubgraphByID returns the first subgraph that has the given node as an
// element, and ⊥ otherwise.
func getSubgraphByID(subgraphs SubgraphMap, id string) SubgraphID {
if id == "." {
return SubgraphBottomID
}
for subgraphID, subgraph := range subgraphs {
if isSubgraphEnd(subgraphs, subgraphID, id) {
return getParentSubgraph(subgraphs, subgraphID, id)
}
for nodeIdx := range subgraph.Nodes {
if id == subgraph.Nodes[nodeIdx] {
return subgraphID
}
}
}
return getSubgraphByID(subgraphs, graph.ParentID(id))
}
// addNodeToSubgraph appnds the given node to a subgraph, creating a new
// subgraph if necessary.
func addNodeToSubgraph(subgraphs SubgraphMap, subgraphID SubgraphID, vertexID string) {
oldGraph, found := subgraphs[subgraphID]
if !found {
oldGraph.StartNode = &vertexID
oldGraph.ID = subgraphID
}
oldGraph.StartNode = &vertexID
oldGraph.Nodes = append(oldGraph.Nodes, vertexID)
subgraphs[subgraphID] = oldGraph
}
// drawSubgraph call DrawNode for each element within the specified subgraph,
// calling StartSubgraph() and FinishSubgraph before and after to ensure that
// the returned string contains any prefix/postfix additions defined by the
// printer.
func (p Printer) drawSubgraph(g *graph.Graph, id SubgraphID, subgraph Subgraph) (Renderable, error) {
subgraphPrinter, spOK := p.pp.(SubgraphPrinter)
nodePrinter, npOK := p.pp.(NodePrinter)
if !spOK && !npOK {
return HiddenString(), nil
}
buffer := new(bytes.Buffer)
write := func(s Renderable) { buffer.WriteString(s.String()) }
subgraphNodes := subgraph.Nodes
if nil == subgraph.StartNode {
return nil, errors.New("Cannot draw subgraph starting at nil vertex")
}
if spOK {
if str, err := subgraphPrinter.StartSubgraph(g, *subgraph.StartNode, subgraph.ID); err == nil {
write(str)
} else {
return nil, err
}
}
if npOK {
for idx := range subgraphNodes {
if str, err := nodePrinter.DrawNode(g, subgraphNodes[idx]); err == nil {
write(str)
} else {
return nil, err
}
}
}
if spOK {
if str, err := subgraphPrinter.FinishSubgraph(g, id); err == nil {
write(str)
}
}
return buffer, nil
}