-
Notifications
You must be signed in to change notification settings - Fork 19
/
record.go
137 lines (120 loc) · 2.74 KB
/
record.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
package dot
import (
"fmt"
"strings"
)
// recordBuilder can build the label of a node with shape "record" or "mrecord".
type recordBuilder struct {
target Node
shape string
nesting *stack
currentLabel recordLabel
}
// newRecordBuilder returns a new recordBuilder for constructing the label of the node.
func newRecordBuilder(n Node) *recordBuilder {
return &recordBuilder{
target: n,
shape: "record",
nesting: new(stack),
}
}
type recordLabel []recordField
func (r recordLabel) writeOn(buf *strings.Builder) {
for i, each := range r {
if i > 0 {
buf.WriteRune('|')
}
each.writeOn(buf)
}
}
type recordField struct {
id recordFieldId
// or
nestedLabel *recordLabel
}
func (r recordField) writeOn(buf *strings.Builder) {
if r.nestedLabel != nil {
buf.WriteRune('{')
r.nestedLabel.writeOn(buf)
buf.WriteRune('}')
return
}
r.id.writeOn(buf)
}
type recordFieldId struct {
id string
content string
}
func (r recordFieldId) writeOn(buf *strings.Builder) {
if r.id != "" {
fmt.Fprintf(buf, "<%s> ", r.id)
}
buf.WriteString(r.content)
}
// MRecord sets the shape of the node to "mrecord"
func (r *recordBuilder) MRecord() *recordBuilder {
r.shape = "mrecord"
return r
}
// Field adds a record field
func (r *recordBuilder) Field(content string) *recordBuilder {
rf := recordField{
id: recordFieldId{
content: content,
},
}
r.currentLabel = append(r.currentLabel, rf)
return r
}
// FieldWithId adds a record field with an identifier for connecting edges.
func (r *recordBuilder) FieldWithId(content, id string) *recordBuilder {
rf := recordField{
id: recordFieldId{
id: id,
content: content,
},
}
r.currentLabel = append(r.currentLabel, rf)
return r
}
// Nesting will create a nested (layout flipped) list of rlabel.
func (r *recordBuilder) Nesting(block func()) {
r.nesting.push(r.currentLabel)
r.currentLabel = recordLabel{}
block()
// currentLabel has fields added by block
// top of stack has label before block
top := r.nesting.pop()
cpy := r.currentLabel[:]
top = append(top, recordField{
nestedLabel: &cpy,
})
r.currentLabel = top
}
// Build sets the computed label and shape
func (r *recordBuilder) Build() error {
r.target.Attr("shape", r.shape)
r.target.Attr("label", r.Label())
return nil
}
// Label returns the computed label
func (r *recordBuilder) Label() string {
buf := new(strings.Builder)
for i, each := range r.currentLabel {
if i > 0 {
buf.WriteString("|")
}
each.writeOn(buf)
}
return buf.String()
}
// stack implements a lifo queue for recordLabel instances.
type stack []recordLabel
func (s *stack) push(r recordLabel) {
*s = append(*s, r)
}
func (s *stack) pop() recordLabel {
top := (*s)[len(*s)-1]
*s = (*s)[0 : len(*s)-1]
return top
}