barebone-fsm 0.0.2.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,22 +1,18 @@
1
1
  Copyright (c) 2013, Md. Imrul Hassan
2
2
 
3
- Permission is hereby granted, free of charge, to any person
4
- obtaining a copy of this software and associated documentation
5
- files (the "Software"), to deal in the Software without
6
- restriction, including without limitation the rights to use,
7
- copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- copies of the Software, and to permit persons to whom the
9
- Software is furnished to do so, subject to the following
10
- conditions:
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
11
9
 
12
- The above copyright notice and this permission notice shall be
13
- included in all copies or substantial portions of the Software.
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
14
12
 
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -3,17 +3,19 @@ This module, barebone-fsm, implements a basic finite-state machine (FSM).
3
3
  An FSM consists of a finite number of states,
4
4
  with one of them being the current state of the FSM.
5
5
  Transitions are defined between states, which are triggered on events.
6
- For details on FSM, see this wiki page: {http://en.wikipedia.org/wiki/Finite-state_machine FSM}.
6
+ For details on FSM, see this wiki page: {FSM}[http://en.wikipedia.org/wiki/Finite-state_machine].
7
7
 
8
- The motivation behind the module was to implement a very basic barebone FSM in Ruby.
8
+ The motivation behind the module was to implement a very basic barebone {FSM} in Ruby.
9
9
  Features are kept at minimum as well as the code.
10
10
  Only two classes for the FSM are defined as the following:
11
- * FSM -> the finite state machine class
12
- * FSMState -> the state class
11
+ * {FSM::FSM} -> the finite state machine class
12
+ * {FSM::FSMState} -> the state class
13
+ The {FSM} Module when included in a class also provides few convenience methods.
14
+ For further details see the README file.
13
15
 
14
16
  Author:: Md. Imrul Hassan (mailto:mihassan@gmail.com)
15
17
  Copyright:: Copyright (c) 2013, Md. Imrul Hassan
16
- License:: Barebone-fsm is released under the MIT license. Please check the {file:MIT-LICENSE LICENSE} file for more details.
18
+ License:: Barebone-fsm is released under the MIT license
17
19
 
18
20
  == Features
19
21
  Apart from having support for states and events, this module offers the following features:
@@ -21,7 +23,9 @@ Apart from having support for states and events, this module offers the followin
21
23
  2. Default event for each state
22
24
  3. Entry and exit events for each state
23
25
  4. DSL like coding style
24
- 5. Access to state variables (including @state and @event) inside event blocks.
26
+ 5. Access to state variables (including @state and @event) inside event blocks
27
+ 6. The module when included in a Class, provides methods to setup the state machine and trigger events
28
+ 7. Dynamic methods generated for the Class to check states, events and trigger events.
25
29
 
26
30
  == Usage
27
31
  The FSM can be setup and triggered Succinctly using Domain Specific Language(DSL) like coding style.
@@ -29,13 +33,13 @@ There are two methods to build and run which are defined for both FSM and FSMSta
29
33
  These methods, #build and #run, are alias of each other and can be used interchangebly.
30
34
  A basic fintite-state machine for a microwave is simulated in the following example:
31
35
 
32
- fsm = FSM::FSM.new(:stopped) # :stopped is the default state to fall back
36
+ fsm = FSM::FSM.new(:stopped)
33
37
 
34
- fsm.build do # the states and events can be configured within the build block
35
- state :stopped do # the events can be setup within the state block for :stopped state
36
- event :open do # the event block is run when the event :open is triggered
38
+ fsm.build do
39
+ state :stopped do
40
+ event :open do
37
41
  puts "[Stopped]: Door Opened"
38
- :open # the return value of the event block, :open, is the next state
42
+ :open
39
43
  end
40
44
  event :start do
41
45
  puts "[Stopped]: Started"
@@ -44,40 +48,43 @@ A basic fintite-state machine for a microwave is simulated in the following exam
44
48
  end
45
49
  state :open do
46
50
  event :close do
47
- puts "[#{@state}]: Door #{@event}" # the event block has access to state variables such as @state and @event
51
+ puts "[#{@state}]: Door #{@event}"
48
52
  :stopped
49
53
  end
50
54
  end
51
55
  state :started do
52
56
  event :open do
53
- @open_time = Time::now # new state variables can be defined
57
+ @open_time = Time::now
54
58
  puts "[Started]: Door Opened"
55
59
  :open
56
60
  end
57
61
  event :stop do
58
- puts "The door was opened at #{@open_time}" # state variables from other event blocks can be used
62
+ puts "The door was opened at #{@open_time}"
59
63
  puts "[Started]: Door Stopped after #{elapsed_time}"
60
64
  :stopped
61
65
  end
62
66
  end
63
67
  end
64
68
 
65
- fsm.run do # the events can be triggered from the run block
69
+ fsm.run do
66
70
  event :start
67
71
  event :open
68
72
  event :close
69
73
  event :start
70
74
  event :stop
71
75
  end
72
-
73
- puts fsm
74
76
 
75
77
  == Status
76
- The module works as it is, but further testing and documentation is needed.
78
+ The module works as it is, I have added some testing and documentation; it may be expanded.
77
79
  The api is not stable yet, it may go trhough lots of changes before the first stable version is released.
78
80
  I am open to any suggestion or request to support custom features.
79
81
 
80
82
  == Changes
83
+ * Version: 0.0.3
84
+ * Added Module level methods for easy access to states and events.
85
+ * Added documentations for all the methods.
86
+ * Added Module level dynamic methods to check states, events and to trigger events.
87
+ * Added some testing codes using RSpec.
81
88
  * Version: 0.0.2
82
89
  * State variables defined in other states can be accessed in the event block as well.
83
90
  * Version: 0.0.1.3
@@ -1,9 +1,8 @@
1
- require '../lib/barebone-fsm'
1
+ require_relative 'example_helper'
2
2
 
3
3
  fsm = FSM::FSM.new(:default)
4
-
5
4
  fsm.build do
6
-
5
+ @x = 0
7
6
  state :default do
8
7
  event :open do
9
8
  puts "#{@x} transition: default->open"
@@ -17,7 +16,6 @@ fsm.build do
17
16
 
18
17
  state :open do
19
18
  event :close do
20
- @x ||= 0
21
19
  @x+=1
22
20
  puts "#{@x} transition: open->close"
23
21
  :close
@@ -26,14 +24,13 @@ fsm.build do
26
24
 
27
25
  state :close do
28
26
  event :open do
29
- @x ||= 0
30
27
  @x+=1
31
28
  puts "#{@x} transition: close->open"
32
29
  :open
33
30
  end
34
31
  end
35
32
 
36
- end
33
+ end
37
34
 
38
35
  puts fsm
39
36
 
@@ -0,0 +1 @@
1
+ require_relative '../lib/barebone-fsm'
@@ -1,4 +1,4 @@
1
- require '../lib/barebone-fsm'
1
+ require_relative 'example_helper'
2
2
 
3
3
  fsm = FSM::FSM.new
4
4
 
@@ -44,6 +44,7 @@ fsm.run do
44
44
  event :close
45
45
  event :start
46
46
  event :stop
47
+
47
48
  end
48
49
 
49
50
  puts fsm
@@ -0,0 +1,38 @@
1
+ require_relative 'example_helper'
2
+
3
+ class Vehicle
4
+
5
+ include FSM
6
+
7
+ def initialize
8
+ build do
9
+ state :parked do event :start => :running, :open => :open end
10
+ state :running do event :park => :parked end
11
+ state :open do event :park => :parked end
12
+ end
13
+ end
14
+
15
+ def show_fsm
16
+ puts @fsm
17
+ end
18
+ end
19
+
20
+ veh = Vehicle.new
21
+
22
+ puts "It is parked" if veh.is_parked?
23
+
24
+ puts "It can start" if veh.can_start?
25
+
26
+ veh.start
27
+
28
+ puts veh.state
29
+
30
+ veh.state :turned_off
31
+ veh.state :parked do event :turn_off => :turned_off end
32
+ veh.event :turn_off
33
+
34
+ puts "It is parked" if veh.is_parked?
35
+
36
+ puts "It can start" if veh.can_start?
37
+
38
+ #veh.start
@@ -5,42 +5,59 @@
5
5
  # Transitions are defined between states, which are triggered on events.
6
6
  # For details on FSM, see this wiki page: {FSM}[http://en.wikipedia.org/wiki/Finite-state_machine].
7
7
  #
8
- # The motivation behind the module was to implement a very basic barebone FSM in Ruby.
8
+ # The motivation behind the module was to implement a very basic barebone {FSM} in Ruby.
9
9
  # Features are kept at minimum as well as the code.
10
10
  # Only two classes for the FSM are defined as the following:
11
- # * FSM -> the finite state machine class
12
- # * FSMState -> the state class
13
- #
11
+ # * {FSM::FSM} -> the finite state machine class
12
+ # * {FSM::FSMState} -> the state class
13
+ # The {FSM} Module when included in a class also provides few convenience methods.
14
14
  # For further details see the README file.
15
15
  #
16
16
  # Author:: Md. Imrul Hassan (mailto:mihassan@gmail.com)
17
- # Copyright:: Copyright: Md. Imrul Hassan, 2013
17
+ # Copyright:: Copyright (c) 2013, Md. Imrul Hassan
18
+ # License:: Barebone-fsm is released under the MIT license
18
19
  #
19
20
  module FSM
20
-
21
+
22
+ ##
21
23
  # FSMState class represents a state of the finite state machine.
22
24
  #
23
- # == Usage
24
- # state = FSMState.new :state_name
25
- # state.event(:event_name) do # creates the event when block is given
25
+ # @example Setup the event code for a state through block
26
+ # state = FSM::FSMState.new :state_name
27
+ # state.event(:event_name) do
26
28
  # puts "#{@event} triggered on state #{@state}"
27
- # :new_state
29
+ # :next_state
28
30
  # end
29
- # puts state
30
- # state.event :event_name # triggers the event when block is absent
31
+ #
32
+ # @example Setup multiple events specifying only the next state for each event using Hash map
33
+ # state = FSM::FSMState.new :initial_state
34
+ # state.event :go_left => :left_state, :go_right => :right_state
35
+ #
36
+ # @example Trigger an event for the previous example
37
+ # state.event :go_left
31
38
  #
32
39
  class FSMState
33
40
 
34
- # The readonly state variable represents an unique state.
35
- # Though it can have any data type, usage of symbol or string is preferable.
41
+ ##
42
+ # The name of the state.
36
43
  attr_reader :state
44
+ ##
45
+ # The events for this state mapped to corresponding event codes.
46
+ attr_reader :events
37
47
 
48
+ ##
49
+ # @param state_machine [FSM::FSM] the FSM object representing the state machine containing this state
50
+ # @param state_name [Symbol] the unique name for this state
51
+ #
38
52
  def initialize(state_machine, state_name)
39
53
  @fsm = state_machine
40
54
  @state = state_name
41
55
  @events = {}
42
56
  end
43
-
57
+
58
+ ##
59
+ # A String representation of the FSMState object.
60
+ #
44
61
  def to_s()
45
62
  @state.to_s +
46
63
  ": [" +
@@ -48,12 +65,44 @@ module FSM
48
65
  "]"
49
66
  end
50
67
 
51
- # When the event_block is provided, it sets up a new event for this state.
52
- # Otherwise, when the event_block is missing, the event_name is triggered.
68
+ ##
69
+ # Setup or trigger an event.
70
+ # It sets up a new event when the event_block is provided or event_name is a Hash map.
71
+ # The event_name is triggered otherwise.
53
72
  # If the event is nil or not already setup, then the default event is triggered.
73
+ #
74
+ # @overload event(event_name, &event_block)
75
+ # Sets up an event for this state with the given event_block parameter.
76
+ # @param event_name [Symbol] the name of the event to set up
77
+ # @param event_block [block] the block of code to run when this event will be triggered
78
+ #
79
+ # @overload event(events_hash)
80
+ # Sets up multiple events where each element of the Hash map corresponds to one event.
81
+ # @param events_hash [Hash<Symbol,Symbol>] the Hash object mapping events to the corresponding next states
82
+ #
83
+ # @overload event(event_name)
84
+ # Triggers the event named event_name for this state.
85
+ # @param event_name [Symbol] the name of the event to trigger
86
+ #
87
+ # @example Setup an event by block
88
+ # state.event(:event_name) do
89
+ # puts "#{@event} triggered on state #{@state}"
90
+ # :next_state
91
+ # end
92
+ #
93
+ # @example Setup an event by Hash
94
+ # state.event :go_left => :left_state, :go_right => :right_state
95
+ #
96
+ # @example Trigger an event
97
+ # state.event :go_left
98
+ #
54
99
  def event(event_name, &event_block)
55
- if event_name and block_given? then
100
+ if block_given? then
56
101
  @events[event_name] = event_block
102
+ elsif event_name.is_a?(Hash) then
103
+ event_name.each{ |ev, st|
104
+ @events[ev] = Proc.new{st}
105
+ }
57
106
  elsif event_name and @events.has_key? event_name then
58
107
  @fsm.event = event_name
59
108
  @fsm.instance_eval &@events[event_name]
@@ -63,9 +112,27 @@ module FSM
63
112
  end
64
113
  end
65
114
 
66
- # The #build/#run method sets up the events as given in the build_block.
115
+ ##
116
+ # The #build/#run method sets up the events described as DSL code in the build_block.
67
117
  # Only event method is supported within the build_block with the name of the event and an optional block supplied.
68
- # The operation for each such line is carried out by the #event method.
118
+ # The operation for each such line is carried out by the {FSM::FSMState#event} method.
119
+ # @see FSM::FSMState#event
120
+ #
121
+ # @param build_block [block] the block of code with sevaral event methods
122
+ #
123
+ # @example Using block to setup events
124
+ # state.build do
125
+ # event :event_name do
126
+ # puts "#{@event} triggered on state #{@state}"
127
+ # :next_state
128
+ # end
129
+ # end
130
+ #
131
+ # @example Using block to trigger events
132
+ # state.build do
133
+ # event :event_name
134
+ # end
135
+ #
69
136
  def build(&build_block)
70
137
  self.instance_eval &build_block
71
138
  end
@@ -74,27 +141,51 @@ module FSM
74
141
 
75
142
  end
76
143
 
144
+ ##
77
145
  # This class implements the finite-state machine.
78
146
  # FSM class exposes the states it contains and can trigger an event.
79
- # States are created on the fly first time it's referenced through index operator [].
147
+ # States are created on the fly first time it's referenced through index operator [] or #state method.
80
148
  #
81
149
  # The FSM state transits to the default state if the latest event does not define the next state.
82
150
  # If default state is not set, then state does not change on undefined state.
83
151
  # The initial state of the FSM is the first state mentioned.
84
152
  # This can be either the default state if defined or the first referenced state.
85
153
  #
86
- # == Usage
87
- # fsm = FSM.new :default_state
88
- # fsm[:default_state].event(:first_event) do # the state is defined and referenced at the same time
154
+ # @example Setting up finite state machine and triggering events without blocks
155
+ # fsm = FSM::FSM.new(:default_state)
156
+ # fsm[:default_state].event(:first_event) do
89
157
  # puts "The first transition from the default_state to state_name"
90
- # :state_name # the next state is defined here
158
+ # :state_name
159
+ # end
160
+ # fsm.event :first_event
161
+ #
162
+ # @example Setting up finite state machine and triggering events with blocks
163
+ # fsm = FSM::FSM.new
164
+ # fsm.build do
165
+ # state :initial_state do
166
+ # event :an_event => :next_state
167
+ # end
168
+ # end
169
+ # fsm.run do
170
+ # event :an_event
91
171
  # end
92
172
  #
93
173
  class FSM
94
174
 
95
- attr_writer :event
175
+ ##
176
+ # The list of all the states.
177
+ attr_reader :states
96
178
 
97
- # Creates a new FSM object with an optional default state set.
179
+ ##
180
+ # Creates a new FSM object with an optional default state.
181
+ # @param default_state [Symbol] the name for the default state
182
+ #
183
+ # @example Create a new finite state machine without default state
184
+ # fsm = FSM::FSM.new
185
+ #
186
+ # @example Create a new finite state machine with default state
187
+ # fsm = FSM::FSM.new(:default_state)
188
+ #
98
189
  def initialize(default_state=nil)
99
190
  @states = {}
100
191
  if default_state then
@@ -102,7 +193,10 @@ module FSM
102
193
  @states[@default] = FSMState.new(self, @default)
103
194
  end
104
195
  end
105
-
196
+
197
+ ##
198
+ # A String representation of the FSM object.
199
+ #
106
200
  def to_s()
107
201
  "FSM" +
108
202
  ": {" +
@@ -112,8 +206,24 @@ module FSM
112
206
  "}"
113
207
  end
114
208
 
115
- # It returns the FSMState object for state_name.
116
- # If the state is missing, then it first sets up the state, and then returns the newly created state.
209
+ ##
210
+ # Creates and/or returns an FSMState object.
211
+ # If an FSMState object with state_name is present, then returns that object.
212
+ # Otherwise, it creates a new FSMState object first and then returns that object.
213
+ # If state_name is not given, then current state object is returned.
214
+ #
215
+ # @overload []()
216
+ # The current FSMState object is returned.
217
+ #
218
+ # @overload [](state_name)
219
+ # Returns the FSMState object with state_name.
220
+ # If it is not setup yet, a new FSMState object is created first and then returned.
221
+ #
222
+ # @example
223
+ # fsm = FSM::FSM.new
224
+ # new_state = fsm[:new_state]
225
+ # print fsm[:new_state].state
226
+ #
117
227
  def [](state_name=nil)
118
228
  state_name ||= @state
119
229
  if state_name and not @states.has_key? state_name then
@@ -123,9 +233,39 @@ module FSM
123
233
  @states[state_name]
124
234
  end
125
235
 
126
- # When the state_block is provided, it sets up a new state.
127
- # Otherwise, when the state_block is missing, the FSMState object for state_name is returned.
128
- # If called without any parameter, then the current state is returned.
236
+ ##
237
+ # Sets up and/or returns an FSMState object.
238
+ # It sets up a new state with all its events when the state_block is provided.
239
+ # If state_block is missing, then it creates and/or returns an FSMState object with state_name.
240
+ # If state_name is not given, then current state object is returned.
241
+ #
242
+ # @overload state(state_name, state_block)
243
+ # Sets up a state with the given state_block parameter.
244
+ # @param state_name [Symbol] the name of the state to set up
245
+ # @param state_block [block] the block of code to setuo the state
246
+ #
247
+ # @overload state(state_name)
248
+ # Create and/or return a state object.
249
+ # @param state_name [Symbol] the name of the state
250
+ #
251
+ # @overload state()
252
+ # Return current state object.
253
+ #
254
+ # @example Setup a state by block
255
+ # fsm = FSM::FSM.new
256
+ # new_state = fsm.state(:new_state) do
257
+ # event :an_event => :next_state
258
+ # end
259
+ #
260
+ # @example Create a new state
261
+ # fsm = FSM::FSM.new
262
+ # new_state = fsm.state(:new_state)
263
+ #
264
+ # @example Return the current state
265
+ # fsm = FSM::FSM.new
266
+ # new_state = fsm.state(:new_state)
267
+ # print fsm.state.state
268
+ #
129
269
  def state(state_name=nil, &state_block)
130
270
  if block_given? then
131
271
  self.[](state_name).build &state_block
@@ -134,23 +274,55 @@ module FSM
134
274
  end
135
275
  end
136
276
 
137
- # It triggers the event_name event and changes the state of the FSM to its new state.
277
+ ##
278
+ # Trigger a series of events.
138
279
  # The :entry and :exit events are called on the leaving state and the entering state.
139
280
  # If the event does not mention the new state, then the state changes to the default state.
140
- def event(event_name)
141
- @event = event_name
142
- @states[@state].event :exit
143
- new_state = @states[@state].event event_name
144
- new_state = nil if not @states.has_key? new_state
145
- new_state ||= @default
146
- new_state ||= @state
147
- @state = new_state
148
- @states[@state].event :enter
281
+ #
282
+ # @param event_names [Array<Symbol>] the list of event names
283
+ #
284
+ # @example Triggers two events
285
+ # state.event(:first_event, :second_event)
286
+ #
287
+ def event(*event_names)
288
+ for event_name in event_names do
289
+ @event = event_name
290
+ @states[@state].event :exit
291
+ new_state = @states[@state].event event_name
292
+ new_state = nil if not @states.has_key? new_state
293
+ new_state ||= @default
294
+ new_state ||= @state
295
+ @state = new_state
296
+ @states[@state].event :enter
297
+ end
149
298
  end
150
299
 
151
- # The #build/#run method sets up the states and events as given in the build_block.
152
- # Only state and event methods are supported within the build_block with the name of the state/event and a block supplied.
300
+ # @private
301
+ def event=(event_name)
302
+ @event = event_name
303
+ end
304
+
305
+ ##
306
+ # The #build/#run method sets up the states and events as DSL code in the build_block.
307
+ # Only state and event methods are supported within the build_block with the name of the state/event and an optional block supplied.
153
308
  # The operation for each such line is carried out by the #state/#event method.
309
+ # @see FSM::FSM#state
310
+ # @see FSM::FSM#event
311
+ #
312
+ # @example Using block to setup states and trigger events
313
+ # fsm = FSM::FSM.new
314
+ # fsm.build do
315
+ # state :stopped do
316
+ # event :run => :running
317
+ # end
318
+ # state :running do
319
+ # event :stop => :stopped
320
+ # end
321
+ # end
322
+ # fsm.run do
323
+ # event :run, :stop
324
+ # end
325
+ #
154
326
  def build(&build_block)
155
327
  self.instance_eval &build_block
156
328
  end
@@ -159,4 +331,158 @@ module FSM
159
331
 
160
332
  end
161
333
 
334
+ ##
335
+ # The #build/#run method sets up the states and events as DSL code.
336
+ # Only state and event methods are supported within the build_block with the name of the state/event and an optional block supplied.
337
+ # The operation for each such line is carried out by the {FSM::FSM#state}/{FSM::FSM#event} method.
338
+ # @see FSM::FSM#state
339
+ # @see FSM::FSM#event
340
+ #
341
+ # @example Using block to setup states and trigger events
342
+ # fsm = FSM::FSM.new
343
+ # fsm.build do
344
+ # state :stopped do
345
+ # event :run => :running
346
+ # end
347
+ # state :running do
348
+ # event :stop => :stopped
349
+ # end
350
+ # end
351
+ # fsm.run do
352
+ # event :run, :stop
353
+ # end
354
+ #
355
+ def build(&build_block)
356
+ @fsm ||= FSM.new
357
+ @fsm.build(&build_block)
358
+ end
359
+ alias_method :run, :build
360
+
361
+ ##
362
+ # Trigger a series of events.
363
+ # The :entry and :exit events are called on the leaving state and the entering state.
364
+ # If the event does not mention the new state, then the state changes to the default state.
365
+ # @param event_names [Array<Symbol>] the list of event names
366
+ # @see FSM::FSM#event
367
+ #
368
+ def event(*event_names)
369
+ @fsm.event(*event_names)
370
+ end
371
+
372
+ ##
373
+ # Sets up and/or returns an FSMState object.
374
+ # It sets up a new state with all its events when the state_block is provided.
375
+ # If state_block is missing, then it creates and/or returns an FSMState object with state_name.
376
+ # If state_name is not given, then current state object is returned.
377
+ # @see FSM::FSM#state
378
+ #
379
+ # @overload state(state_name, state_block)
380
+ # Sets up a state with the given state_block parameter.
381
+ # @param state_name [Symbol] the name of the state to set up
382
+ # @param state_block [block] the block of code to setuo the state
383
+ #
384
+ # @overload state(state_name)
385
+ # Create and/or return a state object.
386
+ # @param state_name [Symbol] the name of the state
387
+ #
388
+ # @overload state()
389
+ # Return current state object.
390
+ #
391
+ def state(state_name=nil, &state_block)
392
+ @fsm.state(state_name, &state_block)
393
+ end
394
+
395
+ # The list of names of all the states
396
+ def states
397
+ @fsm.states.keys
398
+ end
399
+
400
+ ##
401
+ # @!method is_state?
402
+ # Checks if the current state is state.
403
+ #
404
+ # @example Vehicle running
405
+ # class Vehicle
406
+ # include FSM
407
+ # def initialize
408
+ # build do
409
+ # state :parked do event :start => :running end
410
+ # state :running do event :park => :parked end
411
+ # end
412
+ # end
413
+ # end
414
+ # veh = Vehicle.new
415
+ # puts "Vehicle running." if veh.is_running?
416
+ # veh.start
417
+ # puts "Vehicle running." if veh.is_running?
418
+
419
+ ##
420
+ # @!method can_trigger?
421
+ # Checks if trigger is an event on the current state.
422
+ #
423
+ # @example Vehicle start if it can
424
+ # class Vehicle
425
+ # include FSM
426
+ # def initialize
427
+ # build do
428
+ # state :parked do event :start => :running end
429
+ # state :running do event :park => :parked end
430
+ # end
431
+ # end
432
+ # end
433
+ # veh = Vehicle.new
434
+ # veh.start if veh.can_start?
435
+
436
+ ##
437
+ # @!method trigger
438
+ # Triggers the evnt for current state.
439
+ # It should be used with #can_trigger? method, as if the method is only defined id it can be triggered.
440
+ #
441
+ # @example Vehicle start if it can
442
+ # class Vehicle
443
+ # include FSM
444
+ # def initialize
445
+ # build do
446
+ # state :parked do event :start => :running end
447
+ # state :running do event :park => :parked end
448
+ # end
449
+ # end
450
+ # end
451
+ # veh = Vehicle.new
452
+ # # veh.park will generate NoMethodError as vehicle can not park from parked state
453
+ # veh.park if veh.can_park?
454
+ # veh.start if veh.can_start?
455
+
456
+ def respont_to?(sym)
457
+ if @fsm then
458
+ match_can_method?(sym) || match_is_method?(sym) || match_run_method?(sym) || super
459
+ end
460
+ end
461
+
462
+ def method_missing(sym, *args, &block)
463
+ if match = match_can_method?(sym) then
464
+ @fsm.state.events.include?(match)
465
+ elsif match = match_is_method?(sym) then
466
+ @fsm.state.state == match
467
+ elsif match = match_run_method?(sym)
468
+ @fsm.event(match)
469
+ else
470
+ super
471
+ end
472
+ end
473
+
474
+ private
475
+
476
+ def match_can_method?(sym)
477
+ sym.to_s =~ /can_(.*)\?/; $1 && $1.to_sym
478
+ end
479
+
480
+ def match_is_method?(sym)
481
+ sym.to_s =~ /is_(.*)\?/; $1 && $1.to_sym
482
+ end
483
+
484
+ def match_run_method?(sym)
485
+ @fsm.state.events.include?(sym)
486
+ end
487
+
162
488
  end
@@ -0,0 +1,113 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe FSM::FSM do
4
+ subject{FSM::FSM.new()}
5
+
6
+ describe "#initialize" do
7
+ context "with default state" do
8
+ subject{FSM::FSM.new(:default)}
9
+ its(:states){should have(1).item}
10
+ end
11
+ context "without default state" do
12
+ its(:states){should be_empty}
13
+ end
14
+ end
15
+
16
+ describe "#to_s" do
17
+ context "with default state" do
18
+ subject{FSM::FSM.new(:default)}
19
+ specify{subject.to_s.should be_eql("FSM: {default: []}")}
20
+ end
21
+ context "without default state" do
22
+ specify{subject.to_s.should be_eql("FSM: {}")}
23
+ end
24
+ context "with one state" do
25
+ before{subject.state(:first_state)}
26
+ specify{subject.to_s.should be_eql("FSM: {>first_state: []}")}
27
+ end
28
+ context "with two states" do
29
+ before{subject.state(:first_state)}
30
+ before{subject.state(:second_state)}
31
+ specify{subject.to_s.should be_eql("FSM: {>first_state: [], second_state: []}")}
32
+ end
33
+ context "with states and events" do
34
+ before{subject.state(:first_state){event :first_event => :second_state}}
35
+ before{subject.state(:second_state){event :second_event => :first_state}}
36
+ specify{subject.to_s.should be_eql("FSM: {>first_state: [first_event], second_state: [second_event]}")}
37
+ end
38
+ end
39
+
40
+ describe "#state" do
41
+ context "with a state block" do
42
+ before{subject.state(:first_state){event :first_event => :second_state}}
43
+ before{subject.state(:second_state){event :second_event => :first_state}}
44
+ its(:states){should_not be_empty}
45
+ its(:states){should have(2).items}
46
+ describe "state" do
47
+ specify{subject.state.state.should be(:first_state)}
48
+ end
49
+ end
50
+ context "without a state block" do
51
+ before{subject.state(:first_state)}
52
+ its(:states){should_not be_empty}
53
+ its(:states){should have(1).items}
54
+ describe "state" do
55
+ specify{subject.state.state.should be(:first_state)}
56
+ end
57
+ end
58
+ context "without any states" do
59
+ its(:states){should be_empty}
60
+ its(:state){should be_nil}
61
+ end
62
+ context "with some states" do
63
+ before{subject.state(:first_state)}
64
+ its(:states){should_not be_empty}
65
+ its(:states){should have(1).items}
66
+ its(:state){should_not be_nil}
67
+ end
68
+ end
69
+
70
+ describe "#event" do
71
+ before{subject.state(:first_state){event :first_event => :second_state}}
72
+ before{subject.state(:second_state){event :second_event => :first_state}}
73
+ before{subject.event :first_event}
74
+ describe "state" do
75
+ specify{subject.state.state.should be(:second_state)}
76
+ end
77
+ end
78
+
79
+ describe "#build" do
80
+ before{subject.build{state :a_state do event :an_event do :new_state end end}}
81
+ its(:states){should_not be_empty}
82
+ its(:states){should have(1).items}
83
+ its(:state){should_not be_nil}
84
+ describe "state" do
85
+ specify{subject.state.state.should be(:a_state)}
86
+ end
87
+ end
88
+
89
+ describe "#run" do
90
+ subject{FSM::FSM.new(:default)}
91
+ before{subject.state(:first_state){event :first_event => :second_state}}
92
+ before{subject.state(:second_state){event :second_event => :first_state}}
93
+
94
+ context "when triggered event exists" do
95
+ before{subject.run{event :first_event}}
96
+ describe "state" do
97
+ specify{subject.state.state.should be(:second_state)}
98
+ end
99
+ end
100
+ context "when triggered event does not exist" do
101
+ before{subject.run{event :unknown_event}}
102
+ describe "state" do
103
+ specify{subject.state.state.should be(:default)}
104
+ end
105
+ end
106
+ context "when neither triggered event nor default state exists" do
107
+ describe "state" do
108
+ specify{subject.state.state.should be(:first_state)}
109
+ end
110
+ end
111
+ end
112
+
113
+ end
@@ -0,0 +1,117 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe FSM::FSMState do
4
+ let(:fsm){FSM::FSM.new}
5
+ subject{FSM::FSMState.new(fsm, :state_name)}
6
+
7
+ describe "#initialize" do
8
+ context "with parameter :state_name" do
9
+ its(:state){should be(:state_name)}
10
+ its(:state){should_not be(:wrong_name)}
11
+ its(:events){should be_a_kind_of(Hash)}
12
+ its(:events){should be_empty}
13
+ end
14
+ end
15
+
16
+ describe "#to_s" do
17
+ context "with no events" do
18
+ specify{subject.to_s.should be_eql("state_name: []")}
19
+ end
20
+ context "with one event" do
21
+ before{subject.event(:first_event => :new_state)}
22
+ specify{subject.to_s.should be_eql("state_name: [first_event]")}
23
+ end
24
+ context "with two events" do
25
+ before{subject.event(:first_event => :new_state, :second_event => :new_state)}
26
+ specify{subject.to_s.should be_eql("state_name: [first_event, second_event]")}
27
+ end
28
+ end
29
+
30
+ describe "#event" do
31
+ context "with an event block" do
32
+ before{subject.event(:an_event){:new_state}}
33
+ its(:events){should_not be_empty}
34
+ its(:events){should include(:an_event)}
35
+ its(:events){should have(1).item}
36
+ context "when the event is triggered" do
37
+ describe "the new state" do
38
+ specify{subject.event(:an_event).should be(:new_state)}
39
+ end
40
+ end
41
+ end
42
+ context "with a Hash map" do
43
+ before{subject.event(:an_event=>:new_state)}
44
+ its(:events){should_not be_empty}
45
+ its(:events){should include(:an_event)}
46
+ its(:events){should have(1).item}
47
+ context "when the event is triggered" do
48
+ describe "the new state" do
49
+ specify{subject.event(:an_event).should be(:new_state)}
50
+ end
51
+ end
52
+ end
53
+ context "when triggered event exists" do
54
+ before{subject.event(:an_event){:new_state}}
55
+ describe "the new state" do
56
+ specify{subject.event(:an_event).should be(:new_state)}
57
+ end
58
+ end
59
+ context "when triggered event does not exist" do
60
+ before{subject.event(:default){:new_state}}
61
+ it "should trigger default event" do
62
+ subject.event(:an_event).should be(:new_state)
63
+ end
64
+ end
65
+ context "when neither triggered event nor default event exists" do
66
+ it "should return nil" do
67
+ subject.event(:an_event).should be_nil
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "#build" do
73
+ context "with an event block" do
74
+ before{subject.build{event :an_event do :new_state end }}
75
+ its(:events){should_not be_empty}
76
+ its(:events){should include(:an_event)}
77
+ its(:events){should have(1).item}
78
+ context "when the event is triggered" do
79
+ describe "the new state" do
80
+ specify{subject.event(:an_event).should be(:new_state)}
81
+ end
82
+ end
83
+ end
84
+ context "with a Hash map" do
85
+ before{subject.build{event :an_event=>:new_state}}
86
+ its(:events){should_not be_empty}
87
+ its(:events){should include(:an_event)}
88
+ its(:events){should have(1).item}
89
+ context "when the event is triggered" do
90
+ describe "the new state" do
91
+ specify{subject.event(:an_event).should be(:new_state)}
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "#run" do
98
+ context "when triggered event exists" do
99
+ before{subject.event(:an_event){:new_state}}
100
+ describe "the new state" do
101
+ specify{subject.run{event :an_event}.should be(:new_state)}
102
+ end
103
+ end
104
+ context "when triggered event does not exist" do
105
+ before{subject.event(:default){:new_state}}
106
+ it "should trigger default event" do
107
+ subject.run{event :an_event}.should be(:new_state)
108
+ end
109
+ end
110
+ context "when neither triggered event nor default event exists" do
111
+ it "should return nil" do
112
+ subject.run{event :an_event}.should be_nil
113
+ end
114
+ end
115
+ end
116
+
117
+ end
@@ -0,0 +1 @@
1
+ require_relative '../lib/barebone-fsm'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: barebone-fsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2.1
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-20 00:00:00.000000000 Z
12
+ date: 2013-05-18 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A barebone implementation of the finite state machine keeping simplicity
15
15
  in mind.
@@ -20,8 +20,12 @@ extra_rdoc_files:
20
20
  - README.rdoc
21
21
  files:
22
22
  - example/door.rb
23
+ - example/example_helper.rb
23
24
  - example/microwave.rb
24
- - spec/barebone-fsm_spec.rb
25
+ - example/vehicle_class.rb
26
+ - spec/fsm_spec.rb
27
+ - spec/fsmstate_spec.rb
28
+ - spec/spec_helper.rb
25
29
  - lib/barebone-fsm.rb
26
30
  - README.rdoc
27
31
  - MIT-LICENSE
@@ -1,58 +0,0 @@
1
- require '../lib/barebone-fsm'
2
-
3
- describe FSM::FSM do
4
-
5
- context "with default state" do
6
-
7
- before :each do
8
- @fsm = FSM::FSM.new :default
9
- end
10
-
11
- it "should set the default state" do
12
- @fsm.build do
13
- state :start do
14
- end
15
- end
16
- @fsm[].state.should be_eql(:start)
17
- @fsm.run do
18
- event :undefined
19
- end
20
- @fsm[].state.should be_eql(:default)
21
- end
22
-
23
- end
24
-
25
- context "without default state" do
26
-
27
- before :each do
28
- @fsm = FSM::FSM.new
29
- end
30
-
31
- it "should not set the default state" do
32
- @fsm.build do
33
- state :start do
34
- end
35
- end
36
- @fsm[].state.should be_eql(:start)
37
- @fsm.run do
38
- event :undefined
39
- end
40
- @fsm[].state.should be_eql(:start)
41
- end
42
-
43
- end
44
-
45
- it "should create states on the fly" do
46
- @fsm = FSM::FSM.new
47
- @fsm[:new_state].state.should eq(:new_state)
48
- @fsm.state(:another_state).state.should eq(:another_state)
49
- end
50
-
51
- it "should set current state to the first accessed state" do
52
- @fsm = FSM::FSM.new
53
- @fsm[:new_state].state.should eq(:new_state)
54
- @fsm.state(:another_state).state.should eq(:another_state)
55
- @fsm.state().state.should eq(:new_state)
56
- end
57
-
58
- end