aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md44
-rw-r--r--images/example.dot10
-rw-r--r--images/example.pngbin12535 -> 32662 bytes
-rw-r--r--workflow.go61
-rw-r--r--workflow_test.go37
5 files changed, 76 insertions, 76 deletions
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
--- a/images/example.png
+++ b/images/example.png
Binary files 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))
}
}