From 083fa85ee5301f3e330510e7ea1a279d4f47c208 Mon Sep 17 00:00:00 2001 From: Alexander Kiryukhin Date: Wed, 15 Jul 2020 15:26:24 +0300 Subject: Update --- README.md | 44 ++++++++++++++++++++------------------ images/example.dot | 10 +++++++++ images/example.png | Bin 12535 -> 32662 bytes workflow.go | 61 +++++++++++++++++++++-------------------------------- workflow_test.go | 37 ++++++++++++++++---------------- 5 files changed, 76 insertions(+), 76 deletions(-) create mode 100644 images/example.dot diff --git a/README.md b/README.md index efde801..4f43fab 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,21 @@ Simple state machine. Inspired by [Symfony Workflow](https://github.com/symfony/ ```go o := new(ObjectImplementedPlaceer) -w := NewWorkflow("initial") -w.AddTransition("From initial to A", []Place{"initial"}, "A") -w.AddTransition("From initial to B", []Place{"initial"}, "B") -w.AddTransition("From A to C", []Place{"A"}, "C") -w.AddTransition("From B,C to D", []Place{"B", "C"}, "D") -w.AddTransition("From C,D to Finish", []Place{"C", "D"}, "Finish") - -w.Can(o, "From initial to A") // == nil -w.Can(o, "From A to C") // == ErrCantApply - -w.GetEnabledTransitions(o) // []string{"From initial to A", "From initial to B"} -w.Apply(o, "From inital to A") // o now at "A" place -w.GetEnabledTransitions(o) // []string{"From A to C"} +w := NewWorkflow("Start") +w.AddTransition("Start", "A") +w.AddTransition("Start", "B") +w.AddTransition("A", "C") +w.AddTransition("B", "D") +w.AddTransition( "C", "D") +w.AddTransition("C", "Finish") +w.AddTransition("D", "Finish") + +w.Can(o, "A") // == nil +w.Can(o, "C") // == ErrTransitionNotFound + +w.GetEnabledTransitions(o) // []Place{"A", "B"} +w.Apply(o, "A") // o now at "A" place +w.GetEnabledTransitions(o) // []Place{"C"} w.DumpToDot() // See above ``` @@ -30,14 +32,14 @@ w.DumpToDot() // See above ``` digraph { - initial[color="blue"]; - initial -> A[label="From initial to A"]; - initial -> B[label="From initial to B"]; - A -> C[label="From A to C"]; - B -> D[label="From B,C to D"]; - C -> D[label="From B,C to D"]; - C -> Finish[label="From C,D to Finish"]; - D -> Finish[label="From C,D to Finish"]; + Start[color="blue"] + Start -> A[label="Start → A"]; + Start -> B[label="Start → B"]; + A -> C[label="A → C"]; + B -> D[label="B → D"]; + C -> D[label="C → D"]; + C -> Finish[label="C → Finish"]; + D -> Finish[label="D → Finish"]; } ``` diff --git a/images/example.dot b/images/example.dot new file mode 100644 index 0000000..9bf46f3 --- /dev/null +++ b/images/example.dot @@ -0,0 +1,10 @@ +digraph { + Start[color="blue"] + Start -> A[label="Start → A"]; + Start -> B[label="Start → B"]; + A -> C[label="A → C"]; + B -> D[label="B → D"]; + C -> D[label="C → D"]; + C -> Finish[label="C → Finish"]; + D -> Finish[label="D → Finish"]; +} \ No newline at end of file diff --git a/images/example.png b/images/example.png index bcbf7d4..e72822e 100644 Binary files a/images/example.png and b/images/example.png differ diff --git a/workflow.go b/workflow.go index 203401d..89d5459 100644 --- a/workflow.go +++ b/workflow.go @@ -7,91 +7,83 @@ import ( ) var ( - // ErrCantApply error if transition is not applicable to object - ErrCantApply = errors.New("cant apply transition") // ErrTransitionNotFound error if no transition with this name ErrTransitionNotFound = errors.New("transition not found") ) // Workflow state machine type Workflow struct { - transitions map[string]transition + transitions map[Place][]Place initialPlace Place } // NewWorkflow returns new Workflow instance func NewWorkflow(initialPlace Place) *Workflow { - return &Workflow{initialPlace: initialPlace, transitions: map[string]transition{}} + return &Workflow{initialPlace: initialPlace, transitions: map[Place][]Place{}} } // Can returns nil if transition applicable to object and error if not -func (w *Workflow) Can(obj Placeer, transition string) error { +func (w *Workflow) Can(obj Placeer, to Place) error { currentPlace := obj.GetPlace() if currentPlace == "" { currentPlace = w.initialPlace } - tr, ok := w.transitions[transition] + tr, ok := w.transitions[currentPlace] if !ok { return ErrTransitionNotFound } - for _, f := range tr.From { - if f == currentPlace { + for _, f := range tr { + if f == to { return nil } } - return ErrCantApply + return ErrTransitionNotFound } // GetEnabledTransitions return all applicable transitions for object -func (w *Workflow) GetEnabledTransitions(obj Placeer) []string { +func (w *Workflow) GetEnabledTransitions(obj Placeer) []Place { currentPlace := obj.GetPlace() if currentPlace == "" { currentPlace = w.initialPlace } - var result = make([]string, 0) - for name, t := range w.transitions { - for _, f := range t.From { - if f == currentPlace { - result = append(result, name) - break - } - } + if _, ok := w.transitions[currentPlace]; !ok { + return nil } - return result + return w.transitions[currentPlace] } // Apply next state from transition to object -func (w *Workflow) Apply(obj Placeer, transition string) error { +func (w *Workflow) Apply(obj Placeer, to Place) error { currentPlace := obj.GetPlace() if currentPlace == "" { currentPlace = w.initialPlace } - tr, ok := w.transitions[transition] + tr, ok := w.transitions[currentPlace] if !ok { return ErrTransitionNotFound } - for _, f := range tr.From { - if f == currentPlace { - return obj.SetPlace(tr.To) + for _, f := range tr { + if f == to { + return obj.SetPlace(to) } } - return ErrCantApply + return ErrTransitionNotFound } // AddTransition to workflow -func (w *Workflow) AddTransition(name string, from []Place, to Place) { - w.transitions[name] = transition{ - From: from, - To: to, +func (w *Workflow) AddTransition(from Place, to Place) { + if _, ok := w.transitions[from]; !ok { + w.transitions[from] = []Place{} } + w.transitions[from] = append(w.transitions[from], to) } // DumpToDot dumps transitions to Graphviz Dot format func (w *Workflow) DumpToDot() []byte { buf := bytes.NewBufferString(fmt.Sprintf("digraph {\n%s[color=\"blue\"]\n", w.initialPlace)) - for name, t := range w.transitions { - for _, f := range t.From { - _, _ = buf.WriteString(fmt.Sprintf("%s -> %s[label=\"%s\"];\n", f, t.To, name)) + for from, to := range w.transitions { + for _, place := range to { + _, _ = buf.WriteString(fmt.Sprintf("%s -> %s[label=\"%s\"];\n", from, place, fmt.Sprintf("%s → %s", from, place))) } } buf.WriteString("}") @@ -100,8 +92,3 @@ func (w *Workflow) DumpToDot() []byte { // Place is one of state type Place string - -type transition struct { - From []Place - To Place -} diff --git a/workflow_test.go b/workflow_test.go index cb4fe3b..00aec5b 100644 --- a/workflow_test.go +++ b/workflow_test.go @@ -1,14 +1,18 @@ package workflow -import "testing" +import ( + "testing" +) func getTestWorkflow() *Workflow { - w := NewWorkflow("initial") - w.AddTransition("From initial to A", []Place{"initial"}, "A") - w.AddTransition("From initial to B", []Place{"initial"}, "B") - w.AddTransition("From A to C", []Place{"A"}, "C") - w.AddTransition("From B,C to D", []Place{"B", "C"}, "D") - w.AddTransition("From C,D to Finish", []Place{"C", "D"}, "Finish") + w := NewWorkflow("Start") + w.AddTransition("Start", "A") + w.AddTransition("Start", "B") + w.AddTransition("A", "C") + w.AddTransition("B", "D") + w.AddTransition("C", "D") + w.AddTransition("C", "Finish") + w.AddTransition("D", "Finish") return w } @@ -28,16 +32,16 @@ func (t *testObject) SetPlace(p Place) error { func TestWorkflow_Can(t *testing.T) { o := new(testObject) w := getTestWorkflow() - if err := w.Can(o, "From initial to A"); err != nil { + if err := w.Can(o, "A"); err != nil { t.Error("Must has transition") } - if err := w.Can(o, "From A to C"); err == nil { + if err := w.Can(o, "C"); err == nil { t.Error("Must has no transition") } } func TestWorkflow_GetEnabledTransitions(t *testing.T) { - w:=getTestWorkflow() + w := getTestWorkflow() o := new(testObject) if len(w.GetEnabledTransitions(o)) != 2 { t.Error("Must be exactly 2 transitions from initial") @@ -47,19 +51,16 @@ func TestWorkflow_GetEnabledTransitions(t *testing.T) { func TestWorkflow_Apply(t *testing.T) { o := new(testObject) w := getTestWorkflow() - if err := w.Apply(o, "From initial to A"); err != nil { + if err := w.Apply(o, "A"); err != nil { t.Error(err) } if o.GetPlace() != "A" { t.Error("Must be at A place") } - if err := w.Apply(o, "From B,C to D"); err != ErrCantApply { - t.Error("Must be cant move") - } - if err := w.Apply(o, "From A to D"); err != ErrTransitionNotFound { + if err := w.Apply(o, "Finish"); err != ErrTransitionNotFound { t.Error("Must be transition not found") } - if err := w.Apply(o, "From A to C"); err != nil { + if err := w.Apply(o, "C"); err != nil { t.Error(err) } if o.GetPlace() != "C" { @@ -69,7 +70,7 @@ func TestWorkflow_Apply(t *testing.T) { func TestWorkflow_DumpToDot(t *testing.T) { dump := getTestWorkflow().DumpToDot() - if len(dump) != 288 { - t.Error("Len must be 288") + if len(dump) != 242 { + t.Errorf("Len must be 242, got %d", len(dump)) } } -- cgit v1.2.3