simplefsm 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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: []