From 2f0ce6c17d774ae50d5fe254cc2637355a537cec Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Mon, 24 Oct 2022 22:51:38 -0600 Subject: [PATCH 1/7] Add ability to document FSM visually. --- README.md | 27 +++-- cmd/doc.go | 61 +++++++++++ cmd/doc_fsm_string.go | 41 +++++++ doc.go | 54 --------- fsm.go | 77 ++++++++----- fsm_doc.go | 247 ++++++++++++++++++++++++++++++++++++++++++ fsm_nodoc.go | 65 +++++++++++ 7 files changed, 482 insertions(+), 90 deletions(-) create mode 100644 cmd/doc.go create mode 100644 cmd/doc_fsm_string.go delete mode 100644 doc.go create mode 100644 fsm_doc.go create mode 100644 fsm_nodoc.go diff --git a/README.md b/README.md index 670c352..496299d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,24 @@ f.Transition( ) ``` +Transitions can be triggered the second time an event occurs: + +```go +f.Transition( + fsm.On(EventFoo), fsm.Src(StateFoo), fsm.Times(2), + fsm.Dst(StateBar), +) +``` + +_Visual example_: +```mermaid +flowchart LR + id0(StateBar) + id1(StateFoo) + id1--> |EventFoo| id0 + id1--> |2 x EventFoo| id0 +``` + You can have custom checks or actions: ```go @@ -35,15 +53,6 @@ f.Transition( ) ``` -Transitions can be triggered the second time an event occurs: - -```go -f.Transition( - fsm.On(EventFoo), fsm.Src(StateFoo), fsm.Times(2), - fsm.Dst(StateBar), -) -``` - Functions can be called when entering or leaving a state: ```go diff --git a/cmd/doc.go b/cmd/doc.go new file mode 100644 index 0000000..4088ad6 --- /dev/null +++ b/cmd/doc.go @@ -0,0 +1,61 @@ +//go:generate stringer -type=State,Event --output=doc_fsm_string.go +//go:generate go run -tags doc doc.go doc_fsm_string.go ../README.md + +package main + +import ( + "fmt" + "os" + + "github.com/cocoonspace/fsm" +) + +type State fsm.State +type Event fsm.Event + +func (s State) State() fsm.State { + return fsm.State(s) +} + +func (e Event) Event() fsm.Event { + return fsm.Event(e) +} + +var _ fsm.ExtendedState = (*State)(nil) +var _ fsm.ExtendedEvent = (*Event)(nil) + +const ( + StateFoo State = iota + StateBar +) + +const ( + EventFoo Event = iota +) + +func example() *fsm.FSM { + f := fsm.New(StateFoo.State()) + f.Transition( + fsm.On(EventFoo), + fsm.Src(StateFoo), + fsm.Dst(StateBar), + ) + + f.Transition( + fsm.On(EventFoo), + fsm.Times(2), + fsm.Src(StateFoo), + fsm.Dst(StateBar), + ) + + return f +} + +func main() { + if len(os.Args) != 2 { + fmt.Println("use go generate") + return + } + f := example() + f.GenerateDoc("Visual example", os.Args[1]) +} diff --git a/cmd/doc_fsm_string.go b/cmd/doc_fsm_string.go new file mode 100644 index 0000000..b34dec9 --- /dev/null +++ b/cmd/doc_fsm_string.go @@ -0,0 +1,41 @@ +// Code generated by "stringer -type=State,Event --output=doc_fsm_string.go"; DO NOT EDIT. + +package main + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[StateFoo-0] + _ = x[StateBar-1] +} + +const _State_name = "StateFooStateBar" + +var _State_index = [...]uint8{0, 8, 16} + +func (i State) String() string { + if i < 0 || i >= State(len(_State_index)-1) { + return "State(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _State_name[_State_index[i]:_State_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[EventFoo-0] +} + +const _Event_name = "EventFoo" + +var _Event_index = [...]uint8{0, 8} + +func (i Event) String() string { + if i < 0 || i >= Event(len(_Event_index)-1) { + return "Event(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Event_name[_Event_index[i]:_Event_index[i+1]] +} diff --git a/doc.go b/doc.go deleted file mode 100644 index e4cda39..0000000 --- a/doc.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Package fsm allows you to add Finite State Machines to your code. - - const ( - StateFoo fsm.State = iota - StateBar - ) - - const ( - EventFoo fsm.Event = iota - ) - - f := fsm.New(StateFoo) - f.Transition( - fsm.On(EventFoo), fsm.Src(StateFoo), - fsm.Dst(StateBar), - ) - -You can have custom checks or actions: - - f.Transition( - fsm.Src(StateFoo), fsm.Check(func() bool { - // check something - }), - fsm.Call(func() { - // do something - }), - ) - - -Transitions can be triggered the second time an event occurs: - - f.Transition( - fsm.On(EventFoo), fsm.Src(StateFoo), fsm.Times(2), - fsm.Dst(StateBar), - ) - -Functions can be called when entering or leaving a state: - - f.EnterState(StateFoo, func() { - // do something - }) - f.Enter(func(state fsm.State) { - // do something - }) - f.ExitState(StateFoo, func() { - // do something - }) - f.Exit(func(state fsm.State) { - // do something - }) - -*/ -package fsm diff --git a/fsm.go b/fsm.go index ad130e7..1cfeadd 100644 --- a/fsm.go +++ b/fsm.go @@ -1,7 +1,13 @@ package fsm +import ( + "fmt" + "strconv" +) + // Event is the event type. // You can define your own values as +// // const ( // EventFoo fsm.Event = iota // EventBar @@ -10,17 +16,28 @@ type Event int // State is the state type. // You can define your own values as +// // const ( // StateFoo fsm.State = iota // StateBar // ) type State int -type transition struct { - conditions []optionCondition - actions []optionAction +// ExtendedState allow for pretty printing the FSM state by providing a String() interface +type ExtendedState interface { + State() State + fmt.Stringer +} + +// ExtendedEvent allow for pretty printing the FSM event by providing a String() interface +type ExtendedEvent interface { + Event() Event + fmt.Stringer } +var _ ExtendedState = (*State)(nil) +var _ ExtendedEvent = (*Event)(nil) + func (t *transition) match(e Event, times int, fsm *FSM) result { var res result for _, fn := range t.conditions { @@ -88,12 +105,11 @@ func (f *FSM) Transition(opts ...Option) { f.transitions = append(f.transitions, t) } -// Src defines the source States for a Transition. -func Src(s ...State) Option { +func srcInternal(s ...ExtendedState) Option { return func(t *transition) { t.conditions = append(t.conditions, func(e Event, times int, fsm *FSM) result { for _, src := range s { - if fsm.current == src { + if fsm.current == src.State() { return resultOK } } @@ -102,11 +118,10 @@ func Src(s ...State) Option { } } -// On defines the Event that triggers a Transition. -func On(e Event) Option { +func onInternal(e ExtendedEvent) Option { return func(t *transition) { t.conditions = append(t.conditions, func(evt Event, times int, fsm *FSM) result { - if e == evt { + if e.Event() == evt { return resultOK } return resultNOK @@ -114,8 +129,7 @@ func On(e Event) Option { } } -// Dst defines the new State the machine switches to after a Transition. -func Dst(s State) Option { +func dstInternal(s ExtendedState) Option { return func(t *transition) { t.actions = append(t.actions, func(fsm *FSM) { if fsm.current == s { @@ -127,7 +141,7 @@ func Dst(s State) Option { if fsm.exit != nil { fsm.exit(fsm.current) } - fsm.current = s + fsm.current = s.State() if fn, ok := fsm.enterState[fsm.current]; ok { fn() } @@ -138,8 +152,7 @@ func Dst(s State) Option { } } -// NotCheck is an external condition that allows a Transition only if fn returns false. -func NotCheck(fn func() bool) Option { +func notCheckInternal(fn func() bool) Option { return func(t *transition) { t.conditions = append(t.conditions, func(e Event, times int, fsm *FSM) result { if !fn() { @@ -150,8 +163,7 @@ func NotCheck(fn func() bool) Option { } } -// Check is an external condition that allows a Transition only if fn returns true. -func Check(fn func() bool) Option { +func checkInternal(fn func() bool) Option { return func(t *transition) { t.conditions = append(t.conditions, func(e Event, times int, fsm *FSM) result { if fn() { @@ -162,8 +174,7 @@ func Check(fn func() bool) Option { } } -// Call defines a function that is called when a Transition occurs. -func Call(fn func()) Option { +func callInternal(fn func()) Option { return func(t *transition) { t.actions = append(t.actions, func(fsm *FSM) { fn() @@ -171,9 +182,7 @@ func Call(fn func()) Option { } } -// Times defines the number of consecutive times conditions must be valid before a Transition occurs. -// Times will not work if multiple Transitions are possible at the same time. -func Times(n int) Option { +func timesInternal(n int) Option { return func(t *transition) { t.conditions = append(t.conditions, func(e Event, times int, fsm *FSM) result { if times == n { @@ -207,14 +216,12 @@ func (f *FSM) Exit(fn func(state State)) { f.exit = fn } -// EnterState sets a func that will be called when entering a specific state. -func (f *FSM) EnterState(state State, fn func()) { - f.enterState[state] = fn +func (f *FSM) enterStateInternal(state ExtendedState, fn func()) { + f.enterState[state.State()] = fn } -// ExitState sets a func that will be called when exiting a specific state. -func (f *FSM) ExitState(state State, fn func()) { - f.exitState[state] = fn +func (f *FSM) exitStateInternal(state ExtendedState, fn func()) { + f.exitState[state.State()] = fn } // Event send an Event to a machine, applying at most one transition. @@ -240,3 +247,19 @@ func (f *FSM) Event(e Event) bool { } return false } + +func (f State) State() State { + return f +} + +func (f State) String() string { + return strconv.Itoa(int(f)) +} + +func (e Event) Event() Event { + return e +} + +func (e Event) String() string { + return strconv.Itoa(int(e)) +} diff --git a/fsm_doc.go b/fsm_doc.go new file mode 100644 index 0000000..3717a25 --- /dev/null +++ b/fsm_doc.go @@ -0,0 +1,247 @@ +//go:build doc +// +build doc + +package fsm + +import ( + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "runtime" +) + +type transition struct { + conditions []optionCondition + actions []optionAction + + srcs []string + on string + dst string + calls []string + times int +} + +// Src defines the source States for a Transition. +func Src(s ...ExtendedState) Option { + return func(t *transition) { + srcInternal(s...)(t) + + for _, src := range s { + t.srcs = append(t.srcs, fmt.Sprintf("%v", src)) + } + } +} + +// On defines the Event that triggers a Transition. +func On(e ExtendedEvent) Option { + return func(t *transition) { + onInternal(e)(t) + + t.on = fmt.Sprintf("%v", e) + } +} + +// Dst defines the new State the machine switches to after a Transition. +func Dst(s ExtendedState) Option { + return func(t *transition) { + dstInternal(s)(t) + + t.dst = fmt.Sprintf("%v", s) + } +} + +// NotCheck is an external condition that allows a Transition only if fn returns false. +func NotCheck(fn func() bool) Option { + return notCheckInternal(fn) +} + +// Check is an external condition that allows a Transition only if fn returns true. +func Check(fn func() bool) Option { + return checkInternal(fn) +} + +// Call defines a function that is called when a Transition occurs. +func Call(fn func()) Option { + _, file, line, _ := runtime.Caller(1) + + return func(t *transition) { + callInternal(fn)(t) + + t.calls = append(t.calls, fmt.Sprintf("%s:%d", file, line)) + } +} + +// Times defines the number of consecutive times conditions must be valid before a Transition occurs. +// Times will not work if multiple Transitions are possible at the same time. +func Times(n int) Option { + return func(t *transition) { + timesInternal(n) + + t.times = n + } +} + +// EnterState sets a func that will be called when entering a specific state. +func (f *FSM) EnterState(state ExtendedState, fn func()) { + f.enterStateInternal(state, fn) +} + +// ExitState sets a func that will be called when exiting a specific state. +func (f *FSM) ExitState(state ExtendedState, fn func()) { + f.exitStateInternal(state, fn) +} + +// GenerateDoc will find if it exist the mermaid block in the markdown file with the right title +// and update it with the content describing this FSM. If it can not find the mermaid block, it will +// append it at the end of the file. The FSM package need to be compile with the tag `doc` for this +// to work. +func (f *FSM) GenerateDoc(title string, file string) error { + lookupTitle := []byte("_" + title + "_:") + + generated, err := f.insertMermaidGraphInPlace(lookupTitle, file) + if err == nil { + os.Remove(file) + return os.Rename(generated, file) + } + + if !os.IsNotExist(err) { + return err + } + + c, err := os.Create(file) + if err != nil { + return err + } + defer c.Close() + + w := bufio.NewWriter(c) + defer w.Flush() + + f.insertMermaidBlock(lookupTitle, w) + + return nil +} + +var uniqueNameCounter int + +func uniqueName(w *bufio.Writer, state string, uniqueNameMapping map[string]string) string { + unique, ok := uniqueNameMapping[state] + if ok { + return unique + } + + // Generate a unique ID for this state + unique = fmt.Sprintf("id%d", uniqueNameCounter) + uniqueNameCounter++ + w.WriteString("\t" + unique + "(" + state + ")\n") + uniqueNameMapping[state] = unique + + return unique +} + +var ( + lookupMermaid = []byte("```mermaid") + lookupEnd = []byte("```") +) + +func (f *FSM) insertMermaidGraph(w *bufio.Writer) { + uniqueNameMapping := make(map[string]string) + + w.WriteString("flowchart LR\n") + + for _, t := range f.transitions { + dstID := uniqueName(w, t.dst, uniqueNameMapping) + + if t.on == "" { + continue + } + on := t.on + if t.times > 1 { + on = fmt.Sprintf("%d x %s", t.times, on) + } + for _, call := range t.calls { + on = on + "
" + call + } + + for _, src := range t.srcs { + srcID := uniqueName(w, src, uniqueNameMapping) + + w.WriteString("\t" + srcID + "--> |" + on + "| " + dstID + "\n") + } + } +} + +func (f *FSM) insertMermaidBlock(lookupTitle []byte, w *bufio.Writer) { + w.Write(lookupTitle) + w.WriteString("\n") + w.Write(lookupMermaid) + w.WriteString("\n") + + f.insertMermaidGraph(w) + + w.Write(lookupEnd) + w.WriteString("\n") +} + +func (f *FSM) insertMermaidGraphInPlace(lookupTitle []byte, file string) (string, error) { + out, err := ioutil.TempFile(".", "fsm-doc-") + if err != nil { + return "", err + } + defer out.Close() + + w := bufio.NewWriter(out) + defer w.Flush() + + in, err := os.Open(file) + if err != nil { + os.Remove(out.Name()) + return "", err + } + defer in.Close() + + mermaidNext := false + searchEnd := false + found := false + + reader := bufio.NewReader(in) + for { + line, _, err := reader.ReadLine() + + if err == io.EOF { + break + } else if err != nil { + return "", err + } + + if bytes.Equal(line, lookupTitle) { + mermaidNext = true + } else if mermaidNext && bytes.Equal(line, lookupMermaid) { + mermaidNext = false + searchEnd = true + found = true + + w.Write(lookupMermaid) + w.WriteString("\n") + } else if searchEnd && bytes.Equal(line, lookupEnd) { + f.insertMermaidGraph(w) + searchEnd = false + } else { + mermaidNext = false + } + + if !searchEnd { + w.Write(line) + w.WriteString("\n") + } + } + + if !found { + f.insertMermaidBlock(lookupTitle, w) + } + + return out.Name(), nil +} diff --git a/fsm_nodoc.go b/fsm_nodoc.go new file mode 100644 index 0000000..8441e03 --- /dev/null +++ b/fsm_nodoc.go @@ -0,0 +1,65 @@ +//go:build !doc +// +build !doc + +package fsm + +import "errors" + +type transition struct { + conditions []optionCondition + actions []optionAction +} + +// Src defines the source States for a Transition. +func Src(s ...ExtendedState) Option { + return srcInternal(s...) +} + +// On defines the Event that triggers a Transition. +func On(e ExtendedEvent) Option { + return onInternal(e) +} + +// Dst defines the new State the machine switches to after a Transition. +func Dst(s ExtendedState) Option { + return dstInternal(s) +} + +// NotCheck is an external condition that allows a Transition only if fn returns false. +func NotCheck(fn func() bool) Option { + return notCheckInternal(fn) +} + +// Check is an external condition that allows a Transition only if fn returns true. +func Check(fn func() bool) Option { + return checkInternal(fn) +} + +// Call defines a function that is called when a Transition occurs. +func Call(fn func()) Option { + return callInternal(fn) +} + +// Times defines the number of consecutive times conditions must be valid before a Transition occurs. +// Times will not work if multiple Transitions are possible at the same time. +func Times(n int) Option { + return timesInternal(n) +} + +// EnterState sets a func that will be called when entering a specific state. +func (f *FSM) EnterState(state ExtendedState, fn func()) { + f.enterStateInternal(state, fn) +} + +// ExitState sets a func that will be called when exiting a specific state. +func (f *FSM) ExitState(state ExtendedState, fn func()) { + f.exitStateInternal(state, fn) +} + +// GenerateDoc will find if it exist the mermaid block in the markdown file with the right title +// and update it with the content describing this FSM. If it can not find the mermaid block, it will +// append it at the end of the file. The FSM package need to be compile with the tag `doc` for this +// to work. +func (f *FSM) GenerateDoc(_ string, _ string) error { + return errors.New("not compiled with doc tag") +} From e76ad60b0940613750fa4baa08aff3587609c140 Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Tue, 25 Oct 2022 11:21:01 -0600 Subject: [PATCH 2/7] Better documentation in README.md --- README.md | 17 ++++++++++++----- cmd/doc.go | 13 ++++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 496299d..b69d168 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,14 @@ f.Transition( ) ``` +_Visual simple event_: +```mermaid +flowchart LR + id0(StateBar) + id1(StateFoo) + id1--> |EventFoo| id0 +``` + Transitions can be triggered the second time an event occurs: ```go @@ -31,13 +39,12 @@ f.Transition( ) ``` -_Visual example_: +_Visual repeated event_: ```mermaid flowchart LR - id0(StateBar) - id1(StateFoo) - id1--> |EventFoo| id0 - id1--> |2 x EventFoo| id0 + id2(StateBar) + id3(StateFoo) + id3--> |2 x EventFoo| id2 ``` You can have custom checks or actions: diff --git a/cmd/doc.go b/cmd/doc.go index 4088ad6..9fe812e 100644 --- a/cmd/doc.go +++ b/cmd/doc.go @@ -33,14 +33,21 @@ const ( EventFoo Event = iota ) -func example() *fsm.FSM { +func example1() *fsm.FSM { f := fsm.New(StateFoo.State()) + f.Transition( fsm.On(EventFoo), fsm.Src(StateFoo), fsm.Dst(StateBar), ) + return f +} + +func example2() *fsm.FSM { + f := fsm.New(StateFoo.State()) + f.Transition( fsm.On(EventFoo), fsm.Times(2), @@ -56,6 +63,6 @@ func main() { fmt.Println("use go generate") return } - f := example() - f.GenerateDoc("Visual example", os.Args[1]) + example1().GenerateDoc("Visual simple event", os.Args[1]) + example2().GenerateDoc("Visual repeated event", os.Args[1]) } From 7917e37431435e5e6ec7432123ed4f295632f4f3 Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Tue, 25 Oct 2022 11:37:51 -0600 Subject: [PATCH 3/7] Forgotten addition of ExtendedState to fsm.New() --- fsm.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fsm.go b/fsm.go index 1cfeadd..8edc217 100644 --- a/fsm.go +++ b/fsm.go @@ -72,12 +72,12 @@ type FSM struct { } // New creates a new finite state machine having the specified initial state. -func New(initial State) *FSM { +func New(initial ExtendedState) *FSM { return &FSM{ enterState: map[State]func(){}, exitState: map[State]func(){}, - current: initial, - initial: initial, + current: initial.State(), + initial: initial.State(), } } From bcd0ac28a49e32e035be05b4122691870e5f51be Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Tue, 25 Oct 2022 12:00:57 -0600 Subject: [PATCH 4/7] Add fsm.Event() support for ExtendedEvent. --- fsm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fsm.go b/fsm.go index 8edc217..118de1e 100644 --- a/fsm.go +++ b/fsm.go @@ -226,13 +226,13 @@ func (f *FSM) exitStateInternal(state ExtendedState, fn func()) { // Event send an Event to a machine, applying at most one transition. // true is returned if a transition has been applied, false otherwise. -func (f *FSM) Event(e Event) bool { +func (f *FSM) Event(e ExtendedEvent) bool { for i := range f.transitions { times := f.times if i != f.previous { times = 0 } - if res := f.transitions[i].match(e, times+1, f); res != resultNOK { + if res := f.transitions[i].match(e.Event(), times+1, f); res != resultNOK { if res == resultOK { f.transitions[i].apply(f) } From 100eacce470e18f63bfb1458f3c7663ea775404b Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Tue, 25 Oct 2022 13:01:13 -0600 Subject: [PATCH 5/7] Add ability to discard base part of the file path for function location. --- fsm.go | 11 +++++++++++ fsm_doc.go | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/fsm.go b/fsm.go index 118de1e..78aff39 100644 --- a/fsm.go +++ b/fsm.go @@ -248,18 +248,29 @@ func (f *FSM) Event(e ExtendedEvent) bool { return false } +var documentationPathToIgnore []string + +// AddDocumentationPathToIgnore add base path to ignore when displaying file path in generated documentation +func AddDocumentationPathToIgnore(path string) { + documentationPathToIgnore = append(documentationPathToIgnore, path) +} + +// State return the value of a State to be compliant with ExtendedState func (f State) State() State { return f } +// String return the value as a string of a State to be compliant with ExtendedState func (f State) String() string { return strconv.Itoa(int(f)) } +// Event return the value of an Event to be compliant with ExtendedEvent func (e Event) Event() Event { return e } +// String return the value as a string of an Event to be compliant with ExtendedEvent func (e Event) String() string { return strconv.Itoa(int(e)) } diff --git a/fsm_doc.go b/fsm_doc.go index 3717a25..c9187d8 100644 --- a/fsm_doc.go +++ b/fsm_doc.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "os" "runtime" + "strings" ) type transition struct { @@ -163,7 +164,14 @@ func (f *FSM) insertMermaidGraph(w *bufio.Writer) { on = fmt.Sprintf("%d x %s", t.times, on) } for _, call := range t.calls { - on = on + "
" + call + prettyCall := call + for _, path := range documentationPathToIgnore { + if strings.HasPrefix(prettyCall, path) { + prettyCall = prettyCall[len(path):] + break + } + } + on = on + "
" + prettyCall } for _, src := range t.srcs { From 1f192529ab292a6d50ddd89d7f7cf92ace86025b Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Tue, 25 Oct 2022 15:36:27 -0600 Subject: [PATCH 6/7] Forgot to reset counter between two generation of graph. --- README.md | 6 +++--- fsm_doc.go | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b69d168..d98f12b 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ f.Transition( _Visual repeated event_: ```mermaid flowchart LR - id2(StateBar) - id3(StateFoo) - id3--> |2 x EventFoo| id2 + id0(StateBar) + id1(StateFoo) + id1--> |2 x EventFoo| id0 ``` You can have custom checks or actions: diff --git a/fsm_doc.go b/fsm_doc.go index c9187d8..0bcd94b 100644 --- a/fsm_doc.go +++ b/fsm_doc.go @@ -149,6 +149,7 @@ var ( ) func (f *FSM) insertMermaidGraph(w *bufio.Writer) { + uniqueNameCounter = 0 uniqueNameMapping := make(map[string]string) w.WriteString("flowchart LR\n") From 845b29ed457174ce058bd3546f3f08ce2b7623ab Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Fri, 28 Oct 2022 15:20:59 -0600 Subject: [PATCH 7/7] Rename ExtendedState->NamedState and ExtendedEvent->NamedEvent. --- cmd/doc.go | 4 ++-- fsm.go | 34 +++++++++++++++++----------------- fsm_doc.go | 10 +++++----- fsm_nodoc.go | 10 +++++----- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/cmd/doc.go b/cmd/doc.go index 9fe812e..ca90c2f 100644 --- a/cmd/doc.go +++ b/cmd/doc.go @@ -21,8 +21,8 @@ func (e Event) Event() fsm.Event { return fsm.Event(e) } -var _ fsm.ExtendedState = (*State)(nil) -var _ fsm.ExtendedEvent = (*Event)(nil) +var _ fsm.NamedState = (*State)(nil) +var _ fsm.NamedEvent = (*Event)(nil) const ( StateFoo State = iota diff --git a/fsm.go b/fsm.go index 78aff39..b243cb0 100644 --- a/fsm.go +++ b/fsm.go @@ -23,20 +23,20 @@ type Event int // ) type State int -// ExtendedState allow for pretty printing the FSM state by providing a String() interface -type ExtendedState interface { +// NamedState allow for pretty printing the FSM state by providing a String() interface +type NamedState interface { State() State fmt.Stringer } -// ExtendedEvent allow for pretty printing the FSM event by providing a String() interface -type ExtendedEvent interface { +// NamedEvent allow for pretty printing the FSM event by providing a String() interface +type NamedEvent interface { Event() Event fmt.Stringer } -var _ ExtendedState = (*State)(nil) -var _ ExtendedEvent = (*Event)(nil) +var _ NamedState = (*State)(nil) +var _ NamedEvent = (*Event)(nil) func (t *transition) match(e Event, times int, fsm *FSM) result { var res result @@ -72,7 +72,7 @@ type FSM struct { } // New creates a new finite state machine having the specified initial state. -func New(initial ExtendedState) *FSM { +func New(initial NamedState) *FSM { return &FSM{ enterState: map[State]func(){}, exitState: map[State]func(){}, @@ -105,7 +105,7 @@ func (f *FSM) Transition(opts ...Option) { f.transitions = append(f.transitions, t) } -func srcInternal(s ...ExtendedState) Option { +func srcInternal(s ...NamedState) Option { return func(t *transition) { t.conditions = append(t.conditions, func(e Event, times int, fsm *FSM) result { for _, src := range s { @@ -118,7 +118,7 @@ func srcInternal(s ...ExtendedState) Option { } } -func onInternal(e ExtendedEvent) Option { +func onInternal(e NamedEvent) Option { return func(t *transition) { t.conditions = append(t.conditions, func(evt Event, times int, fsm *FSM) result { if e.Event() == evt { @@ -129,7 +129,7 @@ func onInternal(e ExtendedEvent) Option { } } -func dstInternal(s ExtendedState) Option { +func dstInternal(s NamedState) Option { return func(t *transition) { t.actions = append(t.actions, func(fsm *FSM) { if fsm.current == s { @@ -216,17 +216,17 @@ func (f *FSM) Exit(fn func(state State)) { f.exit = fn } -func (f *FSM) enterStateInternal(state ExtendedState, fn func()) { +func (f *FSM) enterStateInternal(state NamedState, fn func()) { f.enterState[state.State()] = fn } -func (f *FSM) exitStateInternal(state ExtendedState, fn func()) { +func (f *FSM) exitStateInternal(state NamedState, fn func()) { f.exitState[state.State()] = fn } // Event send an Event to a machine, applying at most one transition. // true is returned if a transition has been applied, false otherwise. -func (f *FSM) Event(e ExtendedEvent) bool { +func (f *FSM) Event(e NamedEvent) bool { for i := range f.transitions { times := f.times if i != f.previous { @@ -255,22 +255,22 @@ func AddDocumentationPathToIgnore(path string) { documentationPathToIgnore = append(documentationPathToIgnore, path) } -// State return the value of a State to be compliant with ExtendedState +// State return the value of a State to be compliant with NamedState func (f State) State() State { return f } -// String return the value as a string of a State to be compliant with ExtendedState +// String return the value as a string of a State to be compliant with NamedState func (f State) String() string { return strconv.Itoa(int(f)) } -// Event return the value of an Event to be compliant with ExtendedEvent +// Event return the value of an Event to be compliant with NamedEvent func (e Event) Event() Event { return e } -// String return the value as a string of an Event to be compliant with ExtendedEvent +// String return the value as a string of an Event to be compliant with NamedEvent func (e Event) String() string { return strconv.Itoa(int(e)) } diff --git a/fsm_doc.go b/fsm_doc.go index 0bcd94b..962a7d0 100644 --- a/fsm_doc.go +++ b/fsm_doc.go @@ -26,7 +26,7 @@ type transition struct { } // Src defines the source States for a Transition. -func Src(s ...ExtendedState) Option { +func Src(s ...NamedState) Option { return func(t *transition) { srcInternal(s...)(t) @@ -37,7 +37,7 @@ func Src(s ...ExtendedState) Option { } // On defines the Event that triggers a Transition. -func On(e ExtendedEvent) Option { +func On(e NamedEvent) Option { return func(t *transition) { onInternal(e)(t) @@ -46,7 +46,7 @@ func On(e ExtendedEvent) Option { } // Dst defines the new State the machine switches to after a Transition. -func Dst(s ExtendedState) Option { +func Dst(s NamedState) Option { return func(t *transition) { dstInternal(s)(t) @@ -86,12 +86,12 @@ func Times(n int) Option { } // EnterState sets a func that will be called when entering a specific state. -func (f *FSM) EnterState(state ExtendedState, fn func()) { +func (f *FSM) EnterState(state NamedState, fn func()) { f.enterStateInternal(state, fn) } // ExitState sets a func that will be called when exiting a specific state. -func (f *FSM) ExitState(state ExtendedState, fn func()) { +func (f *FSM) ExitState(state NamedState, fn func()) { f.exitStateInternal(state, fn) } diff --git a/fsm_nodoc.go b/fsm_nodoc.go index 8441e03..3552bb0 100644 --- a/fsm_nodoc.go +++ b/fsm_nodoc.go @@ -11,17 +11,17 @@ type transition struct { } // Src defines the source States for a Transition. -func Src(s ...ExtendedState) Option { +func Src(s ...NamedState) Option { return srcInternal(s...) } // On defines the Event that triggers a Transition. -func On(e ExtendedEvent) Option { +func On(e NamedEvent) Option { return onInternal(e) } // Dst defines the new State the machine switches to after a Transition. -func Dst(s ExtendedState) Option { +func Dst(s NamedState) Option { return dstInternal(s) } @@ -47,12 +47,12 @@ func Times(n int) Option { } // EnterState sets a func that will be called when entering a specific state. -func (f *FSM) EnterState(state ExtendedState, fn func()) { +func (f *FSM) EnterState(state NamedState, fn func()) { f.enterStateInternal(state, fn) } // ExitState sets a func that will be called when exiting a specific state. -func (f *FSM) ExitState(state ExtendedState, fn func()) { +func (f *FSM) ExitState(state NamedState, fn func()) { f.exitStateInternal(state, fn) }