statemachine 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +15 -0
- data/TODO +4 -0
- data/lib/statemachine.rb +5 -0
- data/lib/statemachine/builder.rb +111 -0
- data/lib/statemachine/proc_calling.rb +6 -41
- data/lib/statemachine/state.rb +7 -30
- data/lib/statemachine/state_machine.rb +63 -54
- data/lib/statemachine/super_state.rb +10 -42
- data/lib/statemachine/transition.rb +23 -21
- data/lib/statemachine/version.rb +2 -2
- data/spec/builder_spec.rb +131 -0
- data/spec/sm_action_parameterization_spec.rb +38 -53
- data/spec/sm_entry_exit_actions_spec.rb +35 -27
- data/spec/sm_odds_n_ends_spec.rb +47 -5
- data/spec/sm_simple_spec.rb +3 -36
- data/spec/sm_super_state_spec.rb +13 -54
- data/spec/sm_turnstile_spec.rb +18 -20
- data/spec/spec_helper.rb +15 -11
- data/spec/transition_spec.rb +68 -58
- metadata +7 -3
@@ -6,56 +6,58 @@ module StateMachine
|
|
6
6
|
|
7
7
|
include ProcCalling
|
8
8
|
|
9
|
-
attr_reader :
|
10
|
-
attr_accessor :
|
9
|
+
attr_reader :origin_id, :event, :action
|
10
|
+
attr_accessor :destination_id
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
@
|
14
|
-
@
|
12
|
+
def initialize(origin_id, destination_id, event, action)
|
13
|
+
@origin_id = origin_id
|
14
|
+
@destination_id = destination_id
|
15
15
|
@event = event
|
16
16
|
@action = action
|
17
17
|
end
|
18
18
|
|
19
|
-
def invoke(origin, args)
|
20
|
-
|
19
|
+
def invoke(origin, statemachine, args)
|
20
|
+
destination = statemachine.get_state(@destination_id)
|
21
|
+
exits, entries = exits_and_entries(origin, destination)
|
21
22
|
exits.each { |exited_state| exited_state.exit(args) }
|
22
23
|
|
23
|
-
call_proc(@action, args, "transition action from #{origin} invoked by '#{event}' event") if @action
|
24
|
-
|
25
|
-
entries.each { |entered_state| entered_state.enter(args) }
|
24
|
+
call_proc(@action, args, "transition action from #{origin} invoked by '#{@event}' event") if @action
|
26
25
|
|
27
|
-
terminal_state =
|
28
|
-
while terminal_state and terminal_state.
|
26
|
+
terminal_state = destination
|
27
|
+
while terminal_state and not terminal_state.is_concrete?
|
29
28
|
terminal_state = terminal_state.start_state
|
30
|
-
terminal_state
|
29
|
+
entries << terminal_state
|
31
30
|
end
|
31
|
+
terminal_state.activate if terminal_state
|
32
|
+
|
33
|
+
entries.each { |entered_state| entered_state.enter(args) }
|
32
34
|
end
|
33
35
|
|
34
|
-
def exits_and_entries(origin)
|
36
|
+
def exits_and_entries(origin, destination)
|
35
37
|
exits = []
|
36
|
-
entries = exits_and_entries_helper(exits, origin)
|
38
|
+
entries = exits_and_entries_helper(exits, origin, destination)
|
37
39
|
|
38
40
|
return exits, entries.reverse
|
39
41
|
end
|
40
42
|
|
41
43
|
def to_s
|
42
|
-
return "#{
|
44
|
+
return "#{@origin_id} ---#{@event}---> #{@destination_id} : #{action}"
|
43
45
|
end
|
44
46
|
|
45
47
|
private
|
46
48
|
|
47
|
-
def exits_and_entries_helper(exits, exit_state)
|
48
|
-
entries = entries_to_destination(exit_state)
|
49
|
+
def exits_and_entries_helper(exits, exit_state, destination)
|
50
|
+
entries = entries_to_destination(exit_state, destination)
|
49
51
|
return entries if entries
|
50
52
|
return [] if exit_state == nil
|
51
53
|
|
52
54
|
exits << exit_state
|
53
|
-
exits_and_entries_helper(exits, exit_state.superstate)
|
55
|
+
exits_and_entries_helper(exits, exit_state.superstate, destination)
|
54
56
|
end
|
55
57
|
|
56
|
-
def entries_to_destination(exit_state)
|
58
|
+
def entries_to_destination(exit_state, destination)
|
57
59
|
entries = []
|
58
|
-
state =
|
60
|
+
state = destination
|
59
61
|
while state
|
60
62
|
entries << state
|
61
63
|
return entries if exit_state == state.superstate
|
data/lib/statemachine/version.rb
CHANGED
@@ -0,0 +1,131 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
context "Builder" do
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@log = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def check_switch(sm)
|
10
|
+
sm.state.should_be :off
|
11
|
+
|
12
|
+
sm.toggle
|
13
|
+
@log[0].should_eql "toggle on"
|
14
|
+
sm.state.should_be :on
|
15
|
+
|
16
|
+
sm.toggle
|
17
|
+
@log[1].should_eql "toggle off"
|
18
|
+
sm.state.should_be :off
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "Building a the switch, relaxed" do
|
22
|
+
sm = StateMachine.build do |b|
|
23
|
+
b.trans :off, :toggle, :on, Proc.new { @log << "toggle on" }
|
24
|
+
b.trans :on, :toggle, :off, Proc.new { @log << "toggle off" }
|
25
|
+
end
|
26
|
+
|
27
|
+
check_switch sm
|
28
|
+
end
|
29
|
+
|
30
|
+
specify "Building a the switch, strict" do
|
31
|
+
sm = StateMachine.build do |b|
|
32
|
+
b.state(:off) { |s| s.event :toggle, :on, Proc.new { @log << "toggle on" } }
|
33
|
+
b.state(:on) { |s| s.event :toggle, :off, Proc.new { @log << "toggle off" } }
|
34
|
+
end
|
35
|
+
|
36
|
+
check_switch sm
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "Adding a superstate to the switch" do
|
40
|
+
sm = StateMachine.build do |b|
|
41
|
+
b.superstate :operation do |o|
|
42
|
+
o.event :admin, :testing, lambda { @log << "testing" }
|
43
|
+
o.trans :off, :toggle, :on, lambda { @log << "toggle on" }
|
44
|
+
o.trans :on, :toggle, :off, lambda { @log << "toggle off" }
|
45
|
+
o.start_state :on
|
46
|
+
end
|
47
|
+
b.trans :testing, :resume, :operation, lambda { @log << "resuming" }
|
48
|
+
b.start_state :off
|
49
|
+
end
|
50
|
+
|
51
|
+
sm.state.should_be :off
|
52
|
+
sm.toggle
|
53
|
+
sm.admin
|
54
|
+
sm.state.should_be :testing
|
55
|
+
sm.resume
|
56
|
+
sm.state.should_be :on
|
57
|
+
@log.join(",").should_eql "toggle on,testing,resuming"
|
58
|
+
end
|
59
|
+
|
60
|
+
specify "entry exit actions" do
|
61
|
+
sm = StateMachine.build do |sm|
|
62
|
+
sm.state :off do |off|
|
63
|
+
off.on_entry { @log << "enter off" }
|
64
|
+
off.event :toggle, :on, lambda { @log << "toggle on" }
|
65
|
+
off.on_exit { @log << "exit off" }
|
66
|
+
end
|
67
|
+
sm.trans :on, :toggle, :off, lambda { @log << "toggle off" }
|
68
|
+
end
|
69
|
+
|
70
|
+
sm.toggle
|
71
|
+
sm.state.should_be :on
|
72
|
+
sm.toggle
|
73
|
+
sm.state.should_be :off
|
74
|
+
|
75
|
+
@log.join(",").should_eql "exit off,toggle on,toggle off,enter off"
|
76
|
+
end
|
77
|
+
|
78
|
+
specify "History state" do
|
79
|
+
sm = StateMachine.build do |b|
|
80
|
+
b.superstate :operation do |o|
|
81
|
+
o.event :admin, :testing, lambda { @log << "testing" }
|
82
|
+
o.state :off do |off|
|
83
|
+
off.on_entry { @log << "enter off" }
|
84
|
+
off.event :toggle, :on, lambda { @log << "toggle on" }
|
85
|
+
end
|
86
|
+
o.trans :on, :toggle, :off, lambda { @log << "toggle off" }
|
87
|
+
o.start_state :on
|
88
|
+
end
|
89
|
+
b.trans :testing, :resume, :operation_H, lambda { @log << "resuming" }
|
90
|
+
b.start_state :off
|
91
|
+
end
|
92
|
+
|
93
|
+
sm.admin
|
94
|
+
sm.resume
|
95
|
+
sm.state.should_be :off
|
96
|
+
|
97
|
+
@log.join(",").should_eql "testing,resuming,enter off"
|
98
|
+
end
|
99
|
+
|
100
|
+
specify "entry and exit action created from superstate builder" do
|
101
|
+
sm = StateMachine.build do |b|
|
102
|
+
b.trans :off, :toggle, :on, Proc.new { @log << "toggle on" }
|
103
|
+
b.on_entry_of(:off) { @log << "entering off" }
|
104
|
+
b.trans :on, :toggle, :off, Proc.new { @log << "toggle off" }
|
105
|
+
b.on_exit_of(:on) { @log << "exiting on" }
|
106
|
+
end
|
107
|
+
|
108
|
+
sm.toggle
|
109
|
+
sm.toggle
|
110
|
+
|
111
|
+
@log.join(",").should_eql "toggle on,exiting on,toggle off,entering off"
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
specify "superstate as startstate" do
|
116
|
+
|
117
|
+
lambda do
|
118
|
+
sm = StateMachine.build do |b|
|
119
|
+
b.superstate :mario_bros do |m|
|
120
|
+
m.trans :luigi, :bother, :mario
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
sm.state.should_be :luigi
|
125
|
+
end.should_not_raise(Exception)
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
|
130
|
+
end
|
131
|
+
|
@@ -5,110 +5,95 @@ context "State Machine Odds And Ends" do
|
|
5
5
|
|
6
6
|
setup do
|
7
7
|
create_switch
|
8
|
-
@sm.run
|
9
8
|
end
|
10
9
|
|
11
10
|
specify "action with one parameter" do
|
12
|
-
@sm.
|
11
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |value| @status = value } }
|
13
12
|
@sm.set "blue"
|
14
|
-
@status.
|
15
|
-
@sm.state.
|
13
|
+
@status.should_eql "blue"
|
14
|
+
@sm.state.should_be :on
|
16
15
|
end
|
17
16
|
|
18
17
|
specify "action with two parameters" do
|
19
|
-
@sm.
|
18
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b| @status = [a, b].join(",") } }
|
20
19
|
@sm.set "blue", "green"
|
21
|
-
@status.
|
22
|
-
@sm.state.
|
20
|
+
@status.should_eql "blue,green"
|
21
|
+
@sm.state.should_be :on
|
23
22
|
end
|
24
23
|
|
25
24
|
specify "action with three parameters" do
|
26
|
-
@sm.
|
25
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } }
|
27
26
|
@sm.set "blue", "green", "red"
|
28
|
-
@status.
|
29
|
-
@sm.state.
|
27
|
+
@status.should_eql "blue,green,red"
|
28
|
+
@sm.state.should_be :on
|
30
29
|
end
|
31
30
|
|
32
31
|
specify "action with four parameters" do
|
33
|
-
@sm.
|
32
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d| @status = [a, b, c, d].join(",") } }
|
34
33
|
@sm.set "blue", "green", "red", "orange"
|
35
|
-
@status.
|
36
|
-
@sm.state.
|
34
|
+
@status.should_eql "blue,green,red,orange"
|
35
|
+
@sm.state.should_be :on
|
37
36
|
end
|
38
37
|
|
39
38
|
specify "action with five parameters" do
|
40
|
-
@sm.
|
39
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d, e| @status = [a, b, c, d, e].join(",") } }
|
41
40
|
@sm.set "blue", "green", "red", "orange", "yellow"
|
42
|
-
@status.
|
43
|
-
@sm.state.
|
41
|
+
@status.should_eql "blue,green,red,orange,yellow"
|
42
|
+
@sm.state.should_be :on
|
44
43
|
end
|
45
44
|
|
46
45
|
specify "action with six parameters" do
|
47
|
-
@sm.
|
46
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d, e, f| @status = [a, b, c, d, e, f].join(",") } }
|
48
47
|
@sm.set "blue", "green", "red", "orange", "yellow", "indigo"
|
49
|
-
@status.
|
50
|
-
@sm.state.
|
48
|
+
@status.should_eql "blue,green,red,orange,yellow,indigo"
|
49
|
+
@sm.state.should_be :on
|
51
50
|
end
|
52
51
|
|
53
52
|
specify "action with seven parameters" do
|
54
|
-
@sm.
|
53
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d, e, f, g| @status = [a, b, c, d, e, f, g].join(",") } }
|
55
54
|
@sm.set "blue", "green", "red", "orange", "yellow", "indigo", "violet"
|
56
|
-
@status.
|
57
|
-
@sm.state.
|
55
|
+
@status.should_eql "blue,green,red,orange,yellow,indigo,violet"
|
56
|
+
@sm.state.should_be :on
|
58
57
|
end
|
59
58
|
|
60
59
|
specify "action with eight parameters" do
|
61
|
-
@sm.
|
60
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c, d, e, f, g, h| @status = [a, b, c, d, e, f, g, h].join(",") } }
|
62
61
|
@sm.set "blue", "green", "red", "orange", "yellow", "indigo", "violet", "ultra-violet"
|
63
|
-
@status.
|
64
|
-
@sm.state.
|
65
|
-
end
|
66
|
-
|
67
|
-
specify "To many parameters" do
|
68
|
-
@sm.add(:off, :set, :on, Proc.new { |a, b, c, d, e, f, g, h, i| @status = [a, b, c, d, e, f, g, h, i].join(",") } )
|
69
|
-
begin
|
70
|
-
@sm.process_event(:set, "blue", "green", "red", "orange", "yellow", "indigo", "violet", "ultra-violet", "Yikes!")
|
71
|
-
rescue StateMachine::StateMachineException => e
|
72
|
-
e.message.should_equal "Too many arguments(9). (transition action from 'off' state invoked by 'set' event)"
|
73
|
-
end
|
62
|
+
@status.should_eql "blue,green,red,orange,yellow,indigo,violet,ultra-violet"
|
63
|
+
@sm.state.should_be :on
|
74
64
|
end
|
75
65
|
|
76
66
|
specify "calling process_event with parameters" do
|
77
|
-
@sm.
|
67
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } }
|
78
68
|
@sm.process_event(:set, "blue", "green", "red")
|
79
|
-
@status.
|
80
|
-
@sm.state.
|
69
|
+
@status.should_eql "blue,green,red"
|
70
|
+
@sm.state.should_be :on
|
81
71
|
end
|
82
72
|
|
83
73
|
specify "Insufficient params" do
|
84
|
-
@sm.
|
85
|
-
|
86
|
-
|
87
|
-
rescue StateMachine::StateMachineException => e
|
88
|
-
e.message.should_equal "Insufficient parameters. (transition action from 'off' state invoked by 'set' event)"
|
89
|
-
end
|
74
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, b, c| @status = [a, b, c].join(",") } }
|
75
|
+
lambda { @sm.set "blue", "green" }.should_raise(StateMachine::StateMachineException,
|
76
|
+
"Insufficient parameters. (transition action from 'off' state invoked by 'set' event)")
|
90
77
|
end
|
91
78
|
|
92
79
|
specify "infinate args" do
|
93
|
-
|
80
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |*a| @status = a.join(",") } }
|
94
81
|
@sm.set(1, 2, 3)
|
95
|
-
@status.
|
82
|
+
@status.should_eql "1,2,3"
|
96
83
|
|
97
84
|
@sm.state = :off
|
98
85
|
@sm.set(1, 2, 3, 4, 5, 6)
|
99
|
-
@status.
|
86
|
+
@status.should_eql "1,2,3,4,5,6"
|
100
87
|
end
|
101
88
|
|
102
89
|
specify "Insufficient params when params are infinate" do
|
103
|
-
@sm.
|
90
|
+
StateMachine.build(@sm) { |s| s.trans :off, :set, :on, Proc.new { |a, *b| @status = a.to_s + ":" + b.join(",") } }
|
104
91
|
@sm.set(1, 2, 3)
|
105
|
-
@status.
|
92
|
+
@status.should_eql "1:2,3"
|
106
93
|
|
107
94
|
@sm.state = :off
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
e.message.should_equal "Insufficient parameters. (transition action from 'off' state invoked by 'set' event)"
|
112
|
-
end
|
95
|
+
|
96
|
+
lambda { @sm.set }.should_raise(StateMachine::StateMachineException,
|
97
|
+
"Insufficient parameters. (transition action from 'off' state invoked by 'set' event)")
|
113
98
|
end
|
114
99
|
end
|
@@ -4,73 +4,81 @@ context "State Machine Entry and Exit Actions" do
|
|
4
4
|
|
5
5
|
setup do
|
6
6
|
@log = []
|
7
|
-
@sm = StateMachine
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
@sm = StateMachine.build do |s|
|
8
|
+
s.trans :off, :toggle, :on, Proc.new { @log << "on" }
|
9
|
+
s.trans :on, :toggle, :off, Proc.new { @log << "off" }
|
10
|
+
end
|
11
11
|
end
|
12
12
|
|
13
13
|
specify "entry action" do
|
14
|
-
@sm
|
14
|
+
@sm.get_state(:on).entry_action = Proc.new { @log << "entered_on" }
|
15
15
|
|
16
16
|
@sm.toggle
|
17
17
|
|
18
|
-
@log.join(",").
|
18
|
+
@log.join(",").should_eql "on,entered_on"
|
19
19
|
end
|
20
20
|
|
21
21
|
specify "exit action" do
|
22
|
-
@sm
|
22
|
+
@sm.get_state(:off).exit_action = Proc.new { @log << "exited_off" }
|
23
23
|
|
24
24
|
@sm.toggle
|
25
25
|
|
26
|
-
@log.join(",").
|
26
|
+
@log.join(",").should_eql "exited_off,on"
|
27
27
|
end
|
28
28
|
|
29
29
|
specify "exit and entry" do
|
30
|
-
@sm
|
31
|
-
@sm
|
30
|
+
@sm.get_state(:off).exit_action = Proc.new { @log << "exited_off" }
|
31
|
+
@sm.get_state(:on).entry_action = Proc.new { @log << "entered_on" }
|
32
32
|
|
33
33
|
@sm.toggle
|
34
34
|
|
35
|
-
@log.join(",").
|
35
|
+
@log.join(",").should_eql "exited_off,on,entered_on"
|
36
36
|
end
|
37
37
|
|
38
38
|
specify "entry and exit actions may be parameterized" do
|
39
|
-
@sm
|
40
|
-
@sm
|
39
|
+
@sm.get_state(:off).exit_action = Proc.new { |a| @log << "exited_off(#{a})" }
|
40
|
+
@sm.get_state(:on).entry_action = Proc.new { |a, b| @log << "entered_on(#{a},#{b})" }
|
41
41
|
|
42
42
|
@sm.toggle "one", "two"
|
43
43
|
|
44
|
-
@log.join(",").
|
44
|
+
@log.join(",").should_eql "exited_off(one),on,entered_on(one,two)"
|
45
45
|
end
|
46
46
|
|
47
47
|
specify "current state is set prior to exit and entry actions" do
|
48
|
-
@sm
|
49
|
-
@sm
|
48
|
+
@sm.get_state(:off).exit_action = Proc.new { @log << @sm.state }
|
49
|
+
@sm.get_state(:on).entry_action = Proc.new { @log << @sm.state }
|
50
50
|
|
51
51
|
@sm.toggle
|
52
52
|
|
53
|
-
@log.join(",").
|
53
|
+
@log.join(",").should_eql "off,on,on"
|
54
54
|
end
|
55
55
|
|
56
56
|
specify "current state is set prior to exit and entry actions even with super states" do
|
57
|
-
@sm
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
@sm = StateMachine::StateMachine.new
|
58
|
+
StateMachine.build(@sm) do |s|
|
59
|
+
s.superstate :off_super do |off_s|
|
60
|
+
off_s.on_exit {@log << @sm.state}
|
61
|
+
off_s.trans :off, :toggle, :on, Proc.new { @log << "on" }
|
62
|
+
off_s.event :toggle, :on, Proc.new { @log << "super_on" }
|
63
|
+
end
|
64
|
+
s.superstate :on_super do |on_s|
|
65
|
+
on_s.on_entry { @log << @sm.state }
|
66
|
+
on_s.trans :on, :toggle, :off, Proc.new { @log << "off" }
|
67
|
+
on_s.event :toggle, :off, Proc.new { @log << "super_off" }
|
68
|
+
end
|
69
|
+
s.start_state :off
|
70
|
+
end
|
63
71
|
|
64
72
|
@sm.toggle
|
65
|
-
@log.join(",").
|
73
|
+
@log.join(",").should_eql "off,super_on,on"
|
66
74
|
end
|
67
75
|
|
68
76
|
specify "entry actions invokes another event" do
|
69
|
-
@sm
|
77
|
+
@sm.get_state(:on).entry_action = Proc.new { @sm.toggle }
|
70
78
|
|
71
79
|
@sm.toggle
|
72
|
-
@log.join(",").
|
73
|
-
@sm.state.
|
80
|
+
@log.join(",").should_eql "on,off"
|
81
|
+
@sm.state.should_be :off
|
74
82
|
end
|
75
83
|
|
76
84
|
|