-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvisualizer_graphviz.go
110 lines (98 loc) · 2.78 KB
/
visualizer_graphviz.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
package fsm
import (
"bytes"
"fmt"
"strings"
"golang.org/x/exp/constraints"
)
// VisualizeGraphviz outputs a visualization of a Fsm in Graphviz format.
func VisualizeGraphviz[E constraints.Ordered, S constraints.Ordered](fsm Visualizer[E, S]) (string, error) {
v := newVisualizeGraphvizBuilder(fsm).
writeHeaderLine().
writeTransitions().
writeStates().
writeFooter()
if v.Err() != nil {
return "", v.Err()
}
return v.String(), nil
}
type visualizeGraphvizBuilder[E constraints.Ordered, S constraints.Ordered] struct {
fsm Visualizer[E, S]
sortedTriggerSources []TriggerSource[E, S] // we sort the key alphabetically to have a reproducible graph output
sortedStates []S
buf strings.Builder
err error
}
func newVisualizeGraphvizBuilder[E constraints.Ordered, S constraints.Ordered](fsm Visualizer[E, S]) *visualizeGraphvizBuilder[E, S] {
return &visualizeGraphvizBuilder[E, S]{
fsm: fsm,
sortedTriggerSources: fsm.SortedTriggerSource(),
sortedStates: fsm.SortedStates(),
}
}
func (v *visualizeGraphvizBuilder[E, S]) writeHeaderLine() *visualizeGraphvizBuilder[E, S] {
if v.err != nil {
return v
}
v.buf.WriteString("digraph fsm {")
v.buf.WriteString("\n")
if v.fsm.Name() != "" {
v.buf.WriteString(fmt.Sprintf(` label="%s"`, v.fsm.Name()))
v.buf.WriteString("\n")
}
return v
}
func (v *visualizeGraphvizBuilder[E, S]) writeTransitions() *visualizeGraphvizBuilder[E, S] {
if v.err != nil {
return v
}
b := bytes.Buffer{}
// make sure the current state is at top
for _, ts := range v.sortedTriggerSources {
dst, err := v.fsm.Transform(ts.State(), ts.Event())
if err != nil {
return v.setErr(err)
}
line := fmt.Sprintf(` "%s" -> "%s" [ label = "%s" ];`, v.fsm.StateName(ts.State()), v.fsm.StateName(dst), v.fsm.EventName(ts.Event()))
if ts.State() == v.fsm.Current() {
v.buf.WriteString(line)
v.buf.WriteString("\n")
} else {
b.WriteString(line)
b.WriteString("\n")
}
}
if b.Len() > 0 {
v.buf.Write(b.Bytes())
}
v.buf.WriteString("\n")
return v
}
func (v *visualizeGraphvizBuilder[E, S]) writeStates() *visualizeGraphvizBuilder[E, S] {
if v.err != nil {
return v
}
for _, state := range v.sortedStates {
v.buf.WriteString(fmt.Sprintf(` "%s";`, v.fsm.StateName(state)))
v.buf.WriteString("\n")
}
return v
}
func (v *visualizeGraphvizBuilder[E, S]) writeFooter() *visualizeGraphvizBuilder[E, S] {
if v.err != nil {
return v
}
v.buf.WriteString(fmt.Sprintln("}"))
return v
}
func (v *visualizeGraphvizBuilder[E, S]) setErr(err error) *visualizeGraphvizBuilder[E, S] {
v.err = err
return v
}
func (v *visualizeGraphvizBuilder[E, S]) Err() error {
return v.err
}
func (v *visualizeGraphvizBuilder[E, S]) String() string {
return v.buf.String()
}