simplificator-fsm 0.2.1 → 0.2.2
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/README.markdown +19 -5
- data/VERSION.yml +1 -1
- data/lib/fsm/builder.rb +1 -0
- data/lib/fsm/machine.rb +34 -29
- data/lib/fsm/options.rb +0 -1
- data/test/transition_test.rb +15 -9
- metadata +2 -2
data/README.markdown
CHANGED
@@ -5,6 +5,7 @@ FSM is a simple finite state machine
|
|
5
5
|
## Usage
|
6
6
|
class Water
|
7
7
|
include FSM
|
8
|
+
# The state machine is specified as a block in define_fsm.
|
8
9
|
define_fsm do
|
9
10
|
# now define all the states
|
10
11
|
# you can add :enter / :exit callbacks (callback can be a String, Symbol or Proc)
|
@@ -14,12 +15,12 @@ FSM is a simple finite state machine
|
|
14
15
|
state(:liquid)
|
15
16
|
state(:solid, :enter => :on_enter_solid, :exit => :on_exit_solid)
|
16
17
|
|
17
|
-
# define all valid transitions (name, from, to)
|
18
|
+
# define all valid transitions (arguments are name of transition, from state name, to state name)
|
18
19
|
# you can define callbacks which are called only on this transition
|
19
|
-
transition(:
|
20
|
-
transition(:
|
21
|
-
transition(:
|
22
|
-
transition(:
|
20
|
+
transition(:heat_up, :solid, :liquid, :event => :liquified)
|
21
|
+
transition(:heat_up, :liquid, :gas) # look mam.... two transitions with same name
|
22
|
+
transition(:cool_down, :gas, :liquid)
|
23
|
+
transition(:cool_down, :liquid, :solid)
|
23
24
|
|
24
25
|
# define the attribute which is used to store the state (defaults to :state)
|
25
26
|
state(:state_of_material)
|
@@ -27,6 +28,19 @@ FSM is a simple finite state machine
|
|
27
28
|
# define the initial state (defaults to the first state defined - :gas in this sample)
|
28
29
|
initial(:liquid)
|
29
30
|
end
|
31
|
+
|
32
|
+
private
|
33
|
+
# callbacks here...
|
34
|
+
def ...
|
30
35
|
end
|
36
|
+
|
37
|
+
# then you can call these methods
|
38
|
+
w = Water.new
|
39
|
+
w.heat # the name of the transition is the name of the method
|
40
|
+
w.reachable_state_names
|
41
|
+
w.available_transition_names
|
42
|
+
w.cool_down # again... it's the name of the transition
|
43
|
+
w.state_of_material
|
44
|
+
|
31
45
|
## Copyright
|
32
46
|
Copyright (c) 2009 simplificator GmbH. See LICENSE for details.
|
data/VERSION.yml
CHANGED
data/lib/fsm/builder.rb
CHANGED
data/lib/fsm/machine.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module FSM
|
2
2
|
class Machine
|
3
|
-
attr_accessor(:initial_state_name, :current_state_attribute_name, :states)
|
3
|
+
attr_accessor(:initial_state_name, :current_state_attribute_name, :states, :transitions)
|
4
4
|
|
5
5
|
def initialize(target_class)
|
6
6
|
@target_class = target_class
|
7
|
-
self.states =
|
7
|
+
self.states = []
|
8
|
+
self.transitions = []
|
8
9
|
self.current_state_attribute_name = :state
|
9
10
|
end
|
10
11
|
|
@@ -17,29 +18,24 @@ module FSM
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def state(name, options = {})
|
20
|
-
raise "State is already defined: '#{name}'" if self.
|
21
|
-
self.states
|
21
|
+
raise "State is already defined: '#{name}'" if self.state_for_name(name, true)
|
22
|
+
self.states << State.new(name, options)
|
22
23
|
self.initial_state_name=(name) unless self.initial_state_name
|
23
24
|
end
|
24
25
|
|
25
26
|
def initial_state_name=(value)
|
26
|
-
raise UnknownState.new("Unknown state '#{value}'. Define states first with state(name)") unless self.
|
27
|
+
raise UnknownState.new("Unknown state '#{value}'. Define states first with state(name)") unless self.state_for_name(value, true)
|
27
28
|
@initial_state_name = value
|
28
29
|
end
|
29
30
|
|
30
31
|
def transition(name, from_name, to_name, options = {})
|
31
32
|
raise ArgumentError.new("name, from_name and to_name are required") if name.nil? || from_name.nil? || to_name.nil?
|
32
|
-
raise UnknownState.new("Unknown source state '#{from}'") unless self.states[from_name]
|
33
|
-
raise UnknownState.new("Unknown target state '#{to}'") unless self.states[to_name]
|
34
|
-
|
35
|
-
from_state = self.states[from_name]
|
36
|
-
to_state = self.states[to_name]
|
37
33
|
|
34
|
+
from_state = self.state_for_name(from_name)
|
35
|
+
to_state = self.state_for_name(to_name)
|
38
36
|
transition = Transition.new(name, from_state, to_state, options)
|
39
37
|
from_state.add_transition(transition)
|
40
|
-
|
41
|
-
define_transition_method(name)
|
42
|
-
|
38
|
+
self.transitions << transition
|
43
39
|
end
|
44
40
|
|
45
41
|
def self.get_current_state_name(target)
|
@@ -55,24 +51,45 @@ module FSM
|
|
55
51
|
def reachable_states(target)
|
56
52
|
reachables = []
|
57
53
|
current_state_name = Machine.get_current_state_name(target)
|
58
|
-
self.states.map do |
|
54
|
+
self.states.map do |state|
|
59
55
|
reachables += state.to_states if state.name == current_state_name
|
60
56
|
end
|
61
57
|
reachables
|
62
58
|
end
|
63
59
|
|
64
60
|
def available_transitions(target)
|
65
|
-
|
61
|
+
current_state_name = Machine.get_current_state_name(target)
|
62
|
+
state = state_for_name(current_state_name)
|
63
|
+
state.transitions.values
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def build_transition_methods
|
68
|
+
names = self.transitions.map() {|transition| transition.name}.uniq
|
69
|
+
names.each do |name|
|
70
|
+
define_transition_method(name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Lookup a State by it's name
|
75
|
+
# raises ArgumentError if state can not be found unless quiet is set to true
|
76
|
+
def state_for_name(name, quiet = false)
|
77
|
+
state = self.states.detect() {|state| state.name == name}
|
78
|
+
raise ArgumentError.new("Unknonw state '#{name}'") unless quiet || state
|
79
|
+
state
|
66
80
|
end
|
67
81
|
|
68
82
|
private
|
69
83
|
|
84
|
+
# defines a transition method on the target class.
|
85
|
+
# this is the method triggering the state change.
|
86
|
+
#
|
70
87
|
def define_transition_method(name)
|
71
88
|
@target_class.instance_eval do
|
72
89
|
define_method(name) do |*args|
|
73
90
|
machine = Machine[self.class]
|
74
91
|
from_name = Machine.get_current_state_name(self)
|
75
|
-
from_state = machine.
|
92
|
+
from_state = machine.state_for_name(from_name)
|
76
93
|
|
77
94
|
entry = from_state.transitions.detect() {|to_name, tr| tr.name == name}
|
78
95
|
transition = entry.last if entry
|
@@ -83,19 +100,7 @@ module FSM
|
|
83
100
|
transition.fire_event(self, args)
|
84
101
|
to_state.enter(self)
|
85
102
|
Machine.set_current_state_name(self, to_state.name)
|
86
|
-
true
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
#to_state = machine.states[to_name]
|
91
|
-
#transition = from_state.transitions[to_name]
|
92
|
-
#raise InvalidStateTransition.new("No transition defined from #{from_name} -> #{to_name}") unless transition
|
93
|
-
|
94
|
-
#from_state.exit(self)
|
95
|
-
#transition.fire_event(self, args)
|
96
|
-
#to_state.enter(self)
|
97
|
-
#Machine.set_current_state_name(self, to_name)
|
98
|
-
#true # at the moment always return true ... as soon as we have guards or thelike this could be false as well
|
103
|
+
true # at the moment always return true ... as soon as we have guards or thelike this could be false as well
|
99
104
|
end
|
100
105
|
end
|
101
106
|
end
|
data/lib/fsm/options.rb
CHANGED
data/test/transition_test.rb
CHANGED
@@ -2,37 +2,43 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class TransitionTest < Test::Unit::TestCase
|
4
4
|
context 'Initializer' do
|
5
|
+
|
6
|
+
|
5
7
|
should 'require name, from and to' do
|
8
|
+
bli_state = FSM::State.new('bli')
|
9
|
+
blo_state = FSM::State.new('blo')
|
6
10
|
assert_raise(ArgumentError) do
|
7
11
|
FSM::Transition.new(nil, nil, nil)
|
8
12
|
end
|
9
13
|
assert_raise(ArgumentError) do
|
10
|
-
FSM::Transition.new(
|
14
|
+
FSM::Transition.new(:name, nil, nil)
|
11
15
|
end
|
12
16
|
assert_raise(ArgumentError) do
|
13
|
-
FSM::Transition.new(nil,
|
17
|
+
FSM::Transition.new(nil, bli_state, nil)
|
14
18
|
end
|
15
19
|
assert_raise(ArgumentError) do
|
16
|
-
FSM::Transition.new(nil, nil,
|
20
|
+
FSM::Transition.new(nil, nil, blo_state)
|
17
21
|
end
|
18
22
|
assert_raise(ArgumentError) do
|
19
|
-
FSM::Transition.new(
|
23
|
+
FSM::Transition.new(:name, bli_state, nil)
|
20
24
|
end
|
21
25
|
assert_raise(ArgumentError) do
|
22
|
-
FSM::Transition.new(nil,
|
26
|
+
FSM::Transition.new(nil, blo_state, blo_state)
|
23
27
|
end
|
24
28
|
assert_raise(ArgumentError) do
|
25
|
-
FSM::Transition.new(
|
29
|
+
FSM::Transition.new(:name, nil, bli_state)
|
26
30
|
end
|
27
31
|
|
28
|
-
FSM::Transition.new(
|
32
|
+
FSM::Transition.new(:name, bli_state, blo_state)
|
29
33
|
end
|
30
34
|
|
31
35
|
should 'allow only valid options' do
|
36
|
+
bli_state = FSM::State.new('bli')
|
37
|
+
blo_state = FSM::State.new('blo')
|
32
38
|
assert_raise(ArgumentError) do
|
33
|
-
FSM::Transition.new(
|
39
|
+
FSM::Transition.new(:name, bli_state, blo_state, :foo => 12)
|
34
40
|
end
|
35
|
-
FSM::Transition.new(
|
41
|
+
FSM::Transition.new(:name, bli_state, blo_state, :event => :some)
|
36
42
|
end
|
37
43
|
end
|
38
44
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simplificator-fsm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- simplificator
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-19 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|