simplefsm 0.2.0 → 0.2.1

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/simplefsm.rb +105 -89
  3. metadata +13 -16
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 838b9cda4f9f7b0f591b871cc616ca400ad394f6
4
+ data.tar.gz: ae9d5e7ec54dbfb8231dc60268badbcd886101c0
5
+ SHA512:
6
+ metadata.gz: cd41f768c7adc8096bb0b996ab030d7e27dea9b317258eaec4b690462b65b985f787c665f20c0ed9de1f9b09c105ebebc229b53ab2edc9ca0746b88a1eb5e454
7
+ data.tar.gz: e540da439383cdd2b15becb409ca0e41ddad1d20ed8e3f34f450c2059470171acb2cb367e5cfabcf69ee24d3e6e147f0c22f3f63ca407d4b0b2d48f2c0267a3d
data/lib/simplefsm.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # SimpleFSM - a DSL for finite state machines
2
2
  #
3
- #
4
3
  # This module provides a domain specific language (DSL) that
5
4
  # can be used to model a finite state machine (FSM) for any domain,
6
5
  # including complex communication applications
@@ -12,43 +11,54 @@
12
11
  # after the fsm keyword.
13
12
  #
14
13
  # Authors:: Edin Pjanic (mailto:edin.pjanic@untz.com), Amer Hasanovic (mailto:amer.hasanovic@untz.com)
15
- # License:: MIT License
14
+ # License:: MIT License
16
15
 
17
16
  module SimpleFSM
18
- VERSION = '0.2.0'
17
+ VERSION = '0.2.1'
19
18
 
19
+ # TransitionFactory instance is a temporary helper object
20
+ # for making transitions from a transitions_for code block parameter
21
+ class TransitionFactory
22
+ def initialize
23
+ @transitions = []
24
+ end
25
+
26
+ def event ev, args
27
+ t = {:event => ev}.merge!(args)
28
+ @transitions << t
29
+ end
30
+
31
+ def transitions
32
+ @transitions
33
+ end
34
+ end
35
+
36
+ #Instance and class methods that are to be injected to the host class
20
37
  def initialize
21
- @state ||= {}
38
+ self.current_state = {}
22
39
  super
23
40
  end
24
41
 
25
42
  # start the machine
26
43
  def run
27
- @state = @@states.first
28
- if @state[:on]
29
- send(@state[:on][:enter], nil) if @state[:on].has_key?(:enter)
44
+ self.current_state= @@states.first
45
+ if current_state[:on]
46
+ self.send(current_state[:on][:enter], nil) if current_state[:on].has_key?(:enter)
30
47
  end
31
48
  end
32
49
 
33
- # get the current FSM state name
34
- def state
35
- @state[:state] #.to_sym
36
- end
37
-
38
50
  # injecting the class methods for FSM definition
39
51
  def self.included klass
40
52
  klass.class_eval do
41
53
  @@states ||= []
42
54
  @@events ||= []
43
55
  @@transitions ||= {}
44
- @@current_state_setup = nil
45
56
 
46
57
  def self.fsm (&block)
47
58
  instance_eval(&block)
48
59
 
49
60
  #Events methods definition
50
61
  # - one method is defined for every event specified
51
-
52
62
  @@events.each do |ev|
53
63
  Kernel.send :define_method, ev do |*args|
54
64
  fsm_prepare_state args
@@ -62,27 +72,27 @@ module SimpleFSM
62
72
  args = []
63
73
  end
64
74
 
65
- if @state.class == Hash
66
- st = @state[:state]
75
+ if current_state.class == Hash
76
+ st = current_state[:state]
67
77
  else
68
- st = @state
78
+ st = current_state
69
79
  end
70
80
 
71
81
  statetrans = @@transitions[st]
72
82
  uniquestates = []
73
83
 
74
84
  if statetrans
75
- # All transitions for this event in the current state
85
+ # Get all transitions for this event in the current state
76
86
  trans = statetrans.select{|t| !t.select{|k, v| k==:event and v == ev}.empty?}
77
87
 
78
88
  if trans and trans.size>0
79
- # The first transition that is triggered
89
+ # Index of the first transition that is triggered
80
90
  index_triggered = trans.index do |v|
81
91
  # Guard specifiers:
82
92
  # :guard - all must be true
83
93
  # :guard_not - all must be false
84
94
  # :guard_or - at least one must be true
85
- # All guard specified specifiers must evaluate to true
95
+ # All guard specifiers must evaluate to true
86
96
  # in order for transition to be triggered.
87
97
  guard_all = true
88
98
  guards_and = []
@@ -101,18 +111,21 @@ module SimpleFSM
101
111
  guards_not.flatten!
102
112
  end
103
113
 
104
- guard_all &&= guards_and.all? {|g| send(g, args) } if guards_and.size > 0
105
- guard_all &&= guards_or.any? {|g| send(g, args) } if guards_or.size > 0
106
- guard_all &&= !guards_not.any? {|g| send(g, args) } if guards_not.size > 0
114
+ # TODO: think again about those guards
115
+ guard_all &&= guards_and.all? {|g| self.send(g, args) } if guards_and.size > 0
116
+ guard_all &&= guards_or.any? {|g| self.send(g, args) } if guards_or.size > 0
117
+ guard_all &&= !guards_not.any? {|g| self.send(g, args) } if guards_not.size > 0
107
118
  guard_all
108
119
  end
109
120
  if index_triggered
110
121
  trans_triggered = trans[index_triggered]
111
122
  new_state = trans_triggered[:new] if trans_triggered.has_key?(:new)
112
123
 
113
- #START of :action keyword => call procs for event
124
+ #START of :action keyword
125
+ # Call procs for the current event
114
126
  # :do keyword - is not prefered
115
127
  # because it confuses source code editors
128
+ # :action is a prefered one
116
129
  action_keys = ['do'.to_sym, :action]
117
130
 
118
131
  doprocs = []
@@ -121,7 +134,7 @@ module SimpleFSM
121
134
  end
122
135
  doprocs.flatten!
123
136
 
124
- doprocs.each {|p| send(p, args)} if doprocs.size > 0
137
+ doprocs.each {|p| self.send(p, args)} if doprocs.size > 0
125
138
  #END of :action keyword
126
139
 
127
140
  do_transform new_state, args
@@ -160,72 +173,56 @@ module SimpleFSM
160
173
  #add state in case it haven't been defined
161
174
  add_state_data sname
162
175
 
163
- erroneous_trans = trans.select{|a| !a.has_key?(:event) or !a.has_key?(:new)}
164
-
165
- if !erroneous_trans.empty?
166
- raise "Error in transitions for :#{sname}." +
167
- "Transition MUST contain keys :event and :new.\n" +
168
- "In: " + erroneous_trans.inspect
169
- return
176
+ if block_given?
177
+ tf = TransitionFactory.new
178
+ tf.send :define_singleton_method, :yield_block, &block
179
+ tf.yield_block
180
+ trans << tf.transitions
181
+ trans.flatten!
170
182
  end
183
+ trans.each{ |t| check_transition t, sname }
171
184
 
172
185
  trans.each do |t|
173
- if t.class != Hash
174
- raise "Error in transitions for :#{sname} in \'#{t.inspect}\'." +
175
- "Transition must be a Hash Array."
176
- return
177
- end
178
-
179
- add_transition sname, t
180
-
186
+ add_transition t, sname
181
187
  @@events << t[:event] if !@@events.any? { |e| t[:event] == e }
182
188
  end
183
189
 
184
- # if events block is given
185
- if block_given?
186
- @@current_state_setup = sname
187
- yield
188
- @@current_state_setup = nil
189
- end
190
+ end
190
191
 
192
+ # event keyword
193
+ # returns transition that is to be added inside transitions_for method
194
+ def self.event ev, args #, &block
195
+ return {:event => ev}.merge!(args)
191
196
  end
192
197
 
193
- # the event keyword
194
- def self.event ev, args
195
- if !args or !args.is_a?(Hash)
196
- raise "Error in event description for event: #{ev}." +
197
- "Transition MUST be a Hash and at least MUST contain key :new.\n"
198
- return
198
+ ## Private class methods ######################
199
+ # Check whether given transition is valid
200
+ def self.check_transition tran, st='unknown'
201
+ ev = tran[:event] if tran.is_a?(Hash) and tran.has_key?(:event)
202
+ ev ||= "unknown"
199
203
 
200
- end
201
- if !args.has_key?(:new)
202
- raise "Error in transitions for :#{sname}." +
203
- "Transition MUST contain keys :event and :new.\n" +
204
- "In: " + erroneous_trans.inspect
205
- return
206
- end
207
-
208
- t = {:event => ev}.merge!(args)
204
+ if !tran or !tran.is_a?(Hash) or !tran.has_key?(:event) or !tran.has_key?(:new)
209
205
 
210
- if !@@current_state_setup
211
- t
212
- else
213
- add_transition @@current_state_setup, t
206
+ raise "Error in transition specification for event '#{ev}' of state '#{st}'.\n" +
207
+ "\t-> Transition MUST be a Hash and at least MUST contain both keywords 'event' and 'new'.\n" +
208
+ "\t-> Transition data: #{tran}.\n"
209
+ return
214
210
  end
215
211
  end
216
212
 
217
- ## private class methods ######################
218
-
219
213
  # Add transition to state's transitions if it does not exist
220
- def self.add_transition st, t
214
+ def self.add_transition t, st
221
215
  if !@@transitions[st].any? {|v| v == t}
222
216
  @@transitions[st] << t
217
+
218
+ #add the state to @@states if it does not exist
223
219
  add_state_data t[:new]
224
220
  end
225
221
 
226
222
  @@events << t[:event] if !@@events.any? { |e| t[:event] == e }
227
223
  end
228
224
 
225
+
229
226
  def self.add_state_data sname, data={:on=>nil}, overwrite=false
230
227
  return if !sname
231
228
  symname = sname.to_sym
@@ -237,19 +234,19 @@ module SimpleFSM
237
234
  end
238
235
  end
239
236
 
240
-
241
237
  private_class_method :fsm, :state, :transitions_for, :event
238
+ private_class_method :add_state_data , :add_transition, :check_transition
242
239
 
243
- private_class_method :add_state_data, :add_transition
244
240
 
245
241
  end
246
242
  end
247
243
 
244
+ # Private instance methods
248
245
  private
249
246
 
250
247
  # perform transition from current to the next state
251
248
  def do_transform sname, args
252
- # if new state is nil => don't change state
249
+ # if new state is nil then don't change state
253
250
  return if !sname
254
251
 
255
252
  symname = sname.to_sym
@@ -259,14 +256,15 @@ module SimpleFSM
259
256
  return
260
257
  end
261
258
 
262
- onexit = @state[:on][:exit] if @state.has_key?(:on) and @state[:on] and @state[:on].has_key?(:exit)
259
+ # onexit, onenter = nil, nil
260
+ onexit = current_state[:on][:exit] if current_state.has_key?(:on) and current_state[:on] and current_state[:on].has_key?(:exit)
263
261
  if newstate[:on]
264
262
  onenter = newstate[:on][:enter] if newstate[:on].has_key?(:enter)
265
263
  end
266
264
 
267
- send(onexit, args) if onexit
268
- @state = newstate
269
- send(onenter, args) if onenter
265
+ self.send(onexit, args) if onexit
266
+ self.current_state = newstate
267
+ self.send(onenter, args) if onenter
270
268
  end
271
269
 
272
270
  def fsm_responds_to? ev
@@ -281,31 +279,49 @@ module SimpleFSM
281
279
  get_state_events(st).any?{|e| e == ev}
282
280
  end
283
281
 
284
- def get_state_events state
282
+ def get_state_events st
285
283
  ev = []
286
- tr = @@transitions[state[:state]]
287
- if tr
288
- ts = tr.each{|t| ev << t[:event]}
289
- ev.uniq
290
- else
291
- []
292
- end
284
+ tr = @@transitions[st[:state]]
285
+ ev << tr.map{|tran| tran[:event]} if tr
286
+ ev.uniq!
287
+ ev
293
288
  end
294
289
 
290
+ # PLEASE OVERRIDE !
291
+ # ###################################################################
295
292
  # The following methods should be overriden according to the application.
296
293
  #
297
294
  # They are called when any event is fired in order
298
- # to perform state loading/saving if the state is saved in
295
+ # to perform state loading/saving in case state is saved in
299
296
  # an external database or similar facility.
300
297
  #
301
- # fsm_prepare_state method is called before and
302
- # fsm_save_state method is called after
303
- # actual state transition and all consequent actions.
304
- def fsm_prepare_state args
298
+ # #current_state is a private accessor that returns a full state object
299
+ # #state is a public method that returns only the sate's name
300
+ # #fsm_prepare_state method is called before and
301
+ # #fsm_save_state method is called after actual state transition and all consequent actions.
302
+
303
+ def current_state
305
304
  @state
306
305
  end
307
306
 
307
+ def current_state= (st)
308
+ @state = st
309
+ end
310
+
311
+ def state
312
+ current_state[:state] #.to_sym
313
+ end
314
+
315
+ def fsm_prepare_state args
316
+ current_state
317
+ end
318
+
308
319
  def fsm_save_state args
309
- @state
320
+ current_state
310
321
  end
322
+
323
+ public :state
324
+ private :current_state, :current_state=
325
+ private :fsm_prepare_state, :fsm_save_state
326
+
311
327
  end
metadata CHANGED
@@ -1,18 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplefsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
5
- prerelease:
4
+ version: 0.2.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Edin Pjanic
9
8
  - Amer Hasanovic
10
- autorequire:
9
+ autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2013-03-22 00:00:00.000000000 Z
12
+ date: 2017-04-16 00:00:00.000000000 Z
14
13
  dependencies: []
15
- description: A simple and lightweight domain specific language (DSL) for modeling finite state machines (FSM).
14
+ description: A simple and lightweight domain specific language (DSL) for modeling
15
+ finite state machines (FSM).
16
16
  email:
17
17
  - edin@ictlab.com.ba
18
18
  - amer@ictlab.com.ba
@@ -23,7 +23,8 @@ files:
23
23
  - lib/simplefsm.rb
24
24
  homepage: http://github.com/edictlab/SimpleFSM
25
25
  licenses: []
26
- post_install_message:
26
+ metadata: {}
27
+ post_install_message:
27
28
  rdoc_options: []
28
29
  require_paths:
29
30
  - lib
@@ -31,20 +32,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
31
32
  requirements:
32
33
  - - ">="
33
34
  - !ruby/object:Gem::Version
34
- version: !binary |-
35
- MA==
36
- none: false
35
+ version: '0'
37
36
  required_rubygems_version: !ruby/object:Gem::Requirement
38
37
  requirements:
39
38
  - - ">="
40
39
  - !ruby/object:Gem::Version
41
- version: !binary |-
42
- MA==
43
- none: false
40
+ version: '0'
44
41
  requirements: []
45
- rubyforge_project:
46
- rubygems_version: 1.8.24
47
- signing_key:
48
- specification_version: 3
42
+ rubyforge_project:
43
+ rubygems_version: 2.5.1
44
+ signing_key:
45
+ specification_version: 4
49
46
  summary: SimpleFSM - a Ruby DSL for FSMs
50
47
  test_files: []