state-fu 0.11.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.
- data/LICENSE +40 -0
- data/README.textile +293 -0
- data/Rakefile +114 -0
- data/lib/binding.rb +292 -0
- data/lib/event.rb +192 -0
- data/lib/executioner.rb +120 -0
- data/lib/hooks.rb +39 -0
- data/lib/interface.rb +132 -0
- data/lib/lathe.rb +538 -0
- data/lib/machine.rb +184 -0
- data/lib/method_factory.rb +243 -0
- data/lib/persistence.rb +116 -0
- data/lib/persistence/active_record.rb +34 -0
- data/lib/persistence/attribute.rb +47 -0
- data/lib/persistence/base.rb +100 -0
- data/lib/persistence/relaxdb.rb +23 -0
- data/lib/persistence/session.rb +7 -0
- data/lib/sprocket.rb +58 -0
- data/lib/state-fu.rb +56 -0
- data/lib/state.rb +48 -0
- data/lib/support/active_support_lite/array.rb +9 -0
- data/lib/support/active_support_lite/array/access.rb +60 -0
- data/lib/support/active_support_lite/array/conversions.rb +202 -0
- data/lib/support/active_support_lite/array/extract_options.rb +21 -0
- data/lib/support/active_support_lite/array/grouping.rb +109 -0
- data/lib/support/active_support_lite/array/random_access.rb +13 -0
- data/lib/support/active_support_lite/array/wrapper.rb +25 -0
- data/lib/support/active_support_lite/blank.rb +67 -0
- data/lib/support/active_support_lite/cattr_reader.rb +57 -0
- data/lib/support/active_support_lite/keys.rb +57 -0
- data/lib/support/active_support_lite/misc.rb +59 -0
- data/lib/support/active_support_lite/module.rb +1 -0
- data/lib/support/active_support_lite/module/delegation.rb +130 -0
- data/lib/support/active_support_lite/object.rb +9 -0
- data/lib/support/active_support_lite/string.rb +38 -0
- data/lib/support/active_support_lite/symbol.rb +16 -0
- data/lib/support/applicable.rb +41 -0
- data/lib/support/arrays.rb +197 -0
- data/lib/support/core_ext.rb +90 -0
- data/lib/support/exceptions.rb +106 -0
- data/lib/support/has_options.rb +16 -0
- data/lib/support/logger.rb +165 -0
- data/lib/support/methodical.rb +17 -0
- data/lib/support/no_stdout.rb +55 -0
- data/lib/support/plotter.rb +62 -0
- data/lib/support/vizier.rb +300 -0
- data/lib/tasks/spec_last.rake +55 -0
- data/lib/tasks/state_fu.rake +57 -0
- data/lib/transition.rb +338 -0
- data/lib/transition_query.rb +224 -0
- data/spec/custom_formatter.rb +49 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
- data/spec/features/method_missing_only_once_spec.rb +28 -0
- data/spec/features/not_requirements_spec.rb +118 -0
- data/spec/features/plotter_spec.rb +97 -0
- data/spec/features/shared_log_spec.rb +7 -0
- data/spec/features/singleton_machine_spec.rb +39 -0
- data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
- data/spec/features/transition_boolean_comparison_spec.rb +101 -0
- data/spec/helper.rb +13 -0
- data/spec/integration/active_record_persistence_spec.rb +202 -0
- data/spec/integration/binding_extension_spec.rb +41 -0
- data/spec/integration/class_accessor_spec.rb +117 -0
- data/spec/integration/event_definition_spec.rb +74 -0
- data/spec/integration/example_01_document_spec.rb +133 -0
- data/spec/integration/example_02_string_spec.rb +88 -0
- data/spec/integration/instance_accessor_spec.rb +97 -0
- data/spec/integration/lathe_extension_spec.rb +67 -0
- data/spec/integration/machine_duplication_spec.rb +101 -0
- data/spec/integration/relaxdb_persistence_spec.rb +97 -0
- data/spec/integration/requirement_reflection_spec.rb +270 -0
- data/spec/integration/state_definition_spec.rb +163 -0
- data/spec/integration/transition_spec.rb +1033 -0
- data/spec/spec.opts +9 -0
- data/spec/spec_helper.rb +132 -0
- data/spec/state_fu_spec.rb +948 -0
- data/spec/units/binding_spec.rb +192 -0
- data/spec/units/event_spec.rb +214 -0
- data/spec/units/exceptions_spec.rb +82 -0
- data/spec/units/lathe_spec.rb +570 -0
- data/spec/units/machine_spec.rb +229 -0
- data/spec/units/method_factory_spec.rb +366 -0
- data/spec/units/sprocket_spec.rb +69 -0
- data/spec/units/state_spec.rb +59 -0
- metadata +171 -0
data/lib/transition.rb
ADDED
@@ -0,0 +1,338 @@
|
|
1
|
+
module StateFu
|
2
|
+
|
3
|
+
# A 'context' class, created when an event is fired, or needs to be
|
4
|
+
# validated.
|
5
|
+
#
|
6
|
+
# This is what gets yielded to event hooks; it also gets attached
|
7
|
+
# to any TransitionHalted exceptions raised.
|
8
|
+
|
9
|
+
# TODO - make transition evaluate as true if accepted, false if failed, or nil unless fired
|
10
|
+
|
11
|
+
class Transition
|
12
|
+
include Applicable
|
13
|
+
include HasOptions
|
14
|
+
|
15
|
+
attr_reader :binding,
|
16
|
+
:machine,
|
17
|
+
:origin,
|
18
|
+
:target,
|
19
|
+
:event,
|
20
|
+
:args,
|
21
|
+
:errors,
|
22
|
+
:object,
|
23
|
+
:current_hook_slot,
|
24
|
+
:current_hook
|
25
|
+
|
26
|
+
alias_method :arguments, :args
|
27
|
+
|
28
|
+
def initialize( binding, event, target=nil, *argument_list, &block )
|
29
|
+
# ensure we have an Event
|
30
|
+
event = binding.machine.events[event] if event.is_a?(Symbol)
|
31
|
+
raise( UnknownTarget.new(self, "Not an event: #{event} #{self.inspect}" )) unless event.is_a? Event
|
32
|
+
|
33
|
+
@binding = binding
|
34
|
+
@machine = binding.machine
|
35
|
+
@object = binding.object
|
36
|
+
@origin = binding.current_state
|
37
|
+
|
38
|
+
# ensure we have a target
|
39
|
+
target = find_event_target( event, target ) || raise( UnknownTarget.new(self, "target cannot be determined: #{target.inspect} #{self.inspect}"))
|
40
|
+
|
41
|
+
@target = target
|
42
|
+
@event = event
|
43
|
+
@errors = []
|
44
|
+
|
45
|
+
if event.target_for_origin(origin) == target
|
46
|
+
# it's a "sequence"
|
47
|
+
# which is a hacky way of emulating simpler state machines with
|
48
|
+
# state-local events - and in which case, the targets & origins are
|
49
|
+
# valid. Quite likely this notion will be removed in time.
|
50
|
+
else
|
51
|
+
# ensure target is valid for the event
|
52
|
+
unless event.targets.include? target
|
53
|
+
raise IllegalTransition.new self, "Illegal target #{target} for #{event}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# ensure current_state is a valid origin for the event
|
57
|
+
unless event.origins.include? origin
|
58
|
+
raise IllegalTransition.new( self, "Illegal event #{event.name} for current state #{binding.state_name}" )
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
machine.inject_helpers_into( self )
|
63
|
+
self.args = argument_list
|
64
|
+
apply!(argument_list, &block )
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def valid?(*args)
|
69
|
+
self.args = args unless args.empty?
|
70
|
+
requirements_met?(true, true) # revalidate; exit on first failure
|
71
|
+
end
|
72
|
+
|
73
|
+
def args=(args)
|
74
|
+
@args = args.extend(TransitionArgsArray).init(self)
|
75
|
+
apply!(args) if args.last.is_a?(Hash) unless options.nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
def with(*args)
|
79
|
+
self.args = args unless args.empty?
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def with?(*args)
|
84
|
+
valid?
|
85
|
+
end
|
86
|
+
alias_method :valid_with?, :with?
|
87
|
+
|
88
|
+
#
|
89
|
+
# Requirements
|
90
|
+
#
|
91
|
+
|
92
|
+
def requirements
|
93
|
+
origin.exit_requirements + target.entry_requirements + event.requirements
|
94
|
+
end
|
95
|
+
|
96
|
+
def unmet_requirements(revalidate=false, fail_fast=false)
|
97
|
+
if revalidate
|
98
|
+
@unmet_requirements = nil
|
99
|
+
else
|
100
|
+
return @unmet_requirements if @unmet_requirements
|
101
|
+
end
|
102
|
+
result = [requirements].flatten.uniq.inject([]) do |unmet, requirement|
|
103
|
+
unmet << requirement unless evaluate(requirement)
|
104
|
+
break(unmet) if fail_fast && !unmet.empty?
|
105
|
+
unmet
|
106
|
+
end
|
107
|
+
raise self.inspect if result.nil?
|
108
|
+
# don't cache result if it might
|
109
|
+
@unmet_requirements = result unless (fail_fast && unmet_requirements.length != 0)
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
def first_unmet_requirement(revalidate=false)
|
114
|
+
unmet_requirements(revalidate, fail_fast=true)[0]
|
115
|
+
end
|
116
|
+
|
117
|
+
def unmet_requirement_messages(revalidate=false, fail_fast=false) # TODO
|
118
|
+
unmet_requirements(revalidate, fail_fast).map do |requirement|
|
119
|
+
evaluate_requirement_message(requirement, revalidate)
|
120
|
+
end.extend MessageArray
|
121
|
+
end
|
122
|
+
alias_method :error_messages, :unmet_requirement_messages
|
123
|
+
|
124
|
+
# return a hash of requirement_name => evaluated message
|
125
|
+
def requirement_errors(revalidate=false, fail_fast=false)
|
126
|
+
unmet_requirements(revalidate, fail_fast).
|
127
|
+
map { |requirement| [requirement, evaluate_requirement_message(requirement)]}.
|
128
|
+
to_h
|
129
|
+
end
|
130
|
+
|
131
|
+
def first_unmet_requirement_message(revalidate=false)
|
132
|
+
evaluate_requirement_message(first_unmet_requirement(revalidate), revalidate)
|
133
|
+
end
|
134
|
+
|
135
|
+
# raise a RequirementError unless all requirements are met.
|
136
|
+
def check_requirements!(revalidate=false, fail_fast=true) # TODO
|
137
|
+
raise RequirementError.new( self, unmet_requirement_messages.inspect ) unless requirements_met?(revalidate, fail_fast)
|
138
|
+
end
|
139
|
+
|
140
|
+
def requirements_met?(revalidate=false, fail_fast=false) # TODO
|
141
|
+
unmet_requirements(revalidate, fail_fast).empty?
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Hooks
|
146
|
+
#
|
147
|
+
def hooks_for(element, slot)
|
148
|
+
send(element).hooks[slot]
|
149
|
+
end
|
150
|
+
|
151
|
+
def hooks
|
152
|
+
StateFu::Hooks::ALL_HOOKS.map do |owner, slot|
|
153
|
+
[ [owner, slot], send(owner).hooks[slot] ]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def run_hook hook
|
158
|
+
evaluate hook
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Halt a transition mid-execution
|
163
|
+
#
|
164
|
+
|
165
|
+
# halt a transition with a message
|
166
|
+
# can be used to back out of a transition inside eg a state entry hook
|
167
|
+
def halt! message
|
168
|
+
raise TransitionHalted.new( self, message )
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Fire!
|
173
|
+
#
|
174
|
+
|
175
|
+
# actually fire the transition
|
176
|
+
def fire!(*arguments) # block?
|
177
|
+
raise TransitionAlreadyFired.new(self) if fired?
|
178
|
+
self.args = arguments unless arguments.empty?
|
179
|
+
|
180
|
+
check_requirements!
|
181
|
+
@fired = true
|
182
|
+
begin
|
183
|
+
# duplicated: see #hooks method
|
184
|
+
StateFu::Hooks::ALL_HOOKS.map do |owner, slot|
|
185
|
+
[ [owner, slot], send(owner).hooks[slot] ]
|
186
|
+
end.each do |address, hooks|
|
187
|
+
Logger.info("running #{address.inspect} hooks for #{object.class} #{object}")
|
188
|
+
owner,slot = *address
|
189
|
+
hooks.each do |hook|
|
190
|
+
Logger.info("running hook #{hooks} for #{object.class} #{object}")
|
191
|
+
@current_hook_slot = address
|
192
|
+
@current_hook = hook
|
193
|
+
run_hook hook
|
194
|
+
end
|
195
|
+
if slot == :entry
|
196
|
+
@accepted = true
|
197
|
+
@binding.persister.current_state = @target
|
198
|
+
Logger.info("State is now :#{@target.name} for #{object.class} #{object}")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
# transition complete
|
202
|
+
@current_hook_slot = nil
|
203
|
+
@current_hook = nil
|
204
|
+
rescue TransitionHalted => e
|
205
|
+
Logger.info("Transition halted for #{object.class} #{object}: #{e.inspect}")
|
206
|
+
@errors << e
|
207
|
+
end
|
208
|
+
self
|
209
|
+
end
|
210
|
+
|
211
|
+
#
|
212
|
+
# It can pretend it's a hash; so the transition makes a good argument to be
|
213
|
+
# passed to methods.
|
214
|
+
#
|
215
|
+
include Enumerable
|
216
|
+
|
217
|
+
def each *a, &b
|
218
|
+
options.each *a, &b
|
219
|
+
end
|
220
|
+
|
221
|
+
def halted?
|
222
|
+
!@errors.empty?
|
223
|
+
end
|
224
|
+
|
225
|
+
def fired?
|
226
|
+
!!@fired
|
227
|
+
end
|
228
|
+
|
229
|
+
def accepted?
|
230
|
+
!!@accepted
|
231
|
+
end
|
232
|
+
alias_method :complete?, :accepted?
|
233
|
+
|
234
|
+
def current_state
|
235
|
+
binding.current_state
|
236
|
+
end
|
237
|
+
|
238
|
+
# a little convenience for debugging / display
|
239
|
+
def destination
|
240
|
+
[event, target].map(&:to_sym)
|
241
|
+
end
|
242
|
+
|
243
|
+
#
|
244
|
+
# give as many choices as possible
|
245
|
+
#
|
246
|
+
|
247
|
+
alias_method :obj, :object
|
248
|
+
alias_method :instance, :object
|
249
|
+
alias_method :model, :object
|
250
|
+
alias_method :instance, :object
|
251
|
+
|
252
|
+
alias_method :destination, :target
|
253
|
+
alias_method :final_state, :target
|
254
|
+
alias_method :to, :target
|
255
|
+
|
256
|
+
alias_method :original_state, :origin
|
257
|
+
alias_method :initial_state, :origin
|
258
|
+
alias_method :from, :origin
|
259
|
+
|
260
|
+
def cycle?
|
261
|
+
origin == target
|
262
|
+
end
|
263
|
+
|
264
|
+
# an accepted transition == true
|
265
|
+
# an unaccepted transition == false
|
266
|
+
# same for === (for case equality)
|
267
|
+
def == other
|
268
|
+
case other
|
269
|
+
when true
|
270
|
+
accepted?
|
271
|
+
when false
|
272
|
+
!accepted?
|
273
|
+
when State, Symbol
|
274
|
+
current_state == other.to_sym
|
275
|
+
when Transition
|
276
|
+
inspect == other.inspect
|
277
|
+
else
|
278
|
+
super( other )
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# display nice and short
|
283
|
+
def inspect
|
284
|
+
s = self.to_s
|
285
|
+
s = s[0,s.length-1]
|
286
|
+
s << " event=#{event.to_sym.inspect}" if event
|
287
|
+
s << " origin=#{origin.to_sym.inspect}" if origin
|
288
|
+
s << " target=#{target.to_sym.inspect}" if target
|
289
|
+
s << " args=#{args.inspect}" if args
|
290
|
+
s << " options=#{options.inspect}" if options
|
291
|
+
s << ">"
|
292
|
+
s
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
|
297
|
+
def executioner
|
298
|
+
@executioner ||= Executioner.new( self ) do |ex|
|
299
|
+
machine.inject_helpers_into( ex )
|
300
|
+
machine.inject_methods_into( ex )
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def evaluate(method_name_or_proc)
|
305
|
+
executioner.evaluate(method_name_or_proc)
|
306
|
+
end
|
307
|
+
|
308
|
+
def evaluate_requirement_message( name, revalidate=false)
|
309
|
+
@requirement_messages ||= {}
|
310
|
+
name = name.to_sym
|
311
|
+
return @requirement_messages[name] if @requirement_messages[name] && !revalidate
|
312
|
+
msg = machine.requirement_messages[name.to_sym]
|
313
|
+
result = case msg
|
314
|
+
when String
|
315
|
+
msg
|
316
|
+
when Symbol, Proc
|
317
|
+
evaluate msg
|
318
|
+
else
|
319
|
+
name
|
320
|
+
end
|
321
|
+
@requirement_messages[name] = result
|
322
|
+
end
|
323
|
+
|
324
|
+
def find_event_target( evt, tgt )
|
325
|
+
case tgt
|
326
|
+
when StateFu::State
|
327
|
+
tgt
|
328
|
+
when Symbol
|
329
|
+
binding && binding.machine.states[ tgt ]
|
330
|
+
when NilClass
|
331
|
+
evt.respond_to?(:target) && evt.target
|
332
|
+
else
|
333
|
+
raise ArgumentError.new( "#{tgt.class} is not a Symbol, StateFu::State or nil (#{evt})" )
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
end
|
338
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
module StateFu
|
2
|
+
class TransitionQuery
|
3
|
+
attr_accessor :binding, :options, :result, :args, :block
|
4
|
+
|
5
|
+
def initialize(binding, options={})
|
6
|
+
defaults = { :valid => true, :cyclic => nil }
|
7
|
+
@options = defaults.merge(options).symbolize_keys
|
8
|
+
@binding = binding
|
9
|
+
end
|
10
|
+
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
def each *a, &b
|
14
|
+
result.each *a, &b
|
15
|
+
end
|
16
|
+
|
17
|
+
# calling result() will cause the set of transitions to be calculated -
|
18
|
+
# the cat will then be either dead or alive; until then it's a litte from
|
19
|
+
# column A, a little from column B.
|
20
|
+
def method_missing(method_name, *args, &block)
|
21
|
+
if result.respond_to?(method_name, true)
|
22
|
+
result.__send__(method_name, *args, &block)
|
23
|
+
else
|
24
|
+
super(method_name, *args, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# prepare the query with arguments / block
|
29
|
+
# so that they can be applied to the transition once one is selected
|
30
|
+
def with(*args, &block)
|
31
|
+
@args = args
|
32
|
+
@block = block if block_given?
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# build a list of possible transition destinations ([event, target])
|
37
|
+
# without actually constructing any transition objects
|
38
|
+
# def all_destinations
|
39
|
+
# binding.events.inject([]){ |arr, evt| arr += evt.targets.map{|tgt| [evt,tgt] }; arr}.uniq
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def all_destination_names
|
43
|
+
# all_destinations.map {|tuple| tuple.map(&:to_sym) }
|
44
|
+
# end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Chainable Filters
|
48
|
+
#
|
49
|
+
|
50
|
+
def cyclic
|
51
|
+
@options.merge! :cyclic => true
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def not_cyclic
|
56
|
+
@options.merge! :cyclic => false
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def valid
|
61
|
+
@options.merge! :valid => true
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def not_valid
|
66
|
+
@options.merge! :valid => false
|
67
|
+
self
|
68
|
+
end
|
69
|
+
alias_method :invalid, :not_valid
|
70
|
+
|
71
|
+
def to state
|
72
|
+
@options.merge! :target => state
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def for_event event
|
77
|
+
@options.merge! :event => event
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def simple
|
82
|
+
@options.merge! :simple => true
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Means to an outcome
|
88
|
+
#
|
89
|
+
|
90
|
+
# find a transition by event and optionally (optional if it can be inferred) target.
|
91
|
+
def find(destination=nil, &block)
|
92
|
+
# use the prepared event & target, and block, if none are supplied
|
93
|
+
event, target = destination.nil? ? [options[:event], options[:target]] : parse_destination(destination)
|
94
|
+
block ||= @block
|
95
|
+
returning Transition.new(binding, event, target, &block) do |transition|
|
96
|
+
if @args
|
97
|
+
transition.args = @args
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def singular
|
103
|
+
result.first if result.length == 1
|
104
|
+
end
|
105
|
+
|
106
|
+
def singular?
|
107
|
+
!!singular
|
108
|
+
end
|
109
|
+
|
110
|
+
def next
|
111
|
+
@options[:cyclic] ||= false
|
112
|
+
singular
|
113
|
+
end
|
114
|
+
|
115
|
+
def next_state
|
116
|
+
@options[:cyclic] ||= false
|
117
|
+
if result.map(&:target).uniq.length == 1
|
118
|
+
result.first.target
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def next_event
|
123
|
+
@options[:cyclic] ||= false
|
124
|
+
if result.map(&:event).uniq.length == 1
|
125
|
+
result.first.event
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def events
|
130
|
+
map {|t| t.event }.extend EventArray
|
131
|
+
end
|
132
|
+
|
133
|
+
def targets
|
134
|
+
map {|t| t.target }.extend StateArray
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
# extend result with this to provide a few conveniences
|
140
|
+
module Result
|
141
|
+
def states
|
142
|
+
map(&:target).uniq.extend StateArray
|
143
|
+
end
|
144
|
+
alias_method :targets, :states
|
145
|
+
alias_method :next_states, :states
|
146
|
+
|
147
|
+
def events
|
148
|
+
map(&:event).uniq.extend EventArray
|
149
|
+
end
|
150
|
+
end # Result
|
151
|
+
|
152
|
+
# looks a little complex because of all the places that previously set
|
153
|
+
# options can filter the set of transitions - but all it's doing is
|
154
|
+
# looping over each event, and each event's possible targets, and building
|
155
|
+
# a list of transitions.
|
156
|
+
|
157
|
+
def result
|
158
|
+
@result = binding.events.select do |e|
|
159
|
+
case options[:cyclic]
|
160
|
+
when true
|
161
|
+
e.cycle?
|
162
|
+
when false
|
163
|
+
!e.cycle?
|
164
|
+
else
|
165
|
+
true
|
166
|
+
end
|
167
|
+
end.map do |event|
|
168
|
+
next if options[:event] and event != options[:event]
|
169
|
+
returning [] do |ts|
|
170
|
+
|
171
|
+
# TODO hmm ... "sequences" ... undecided on these. see Event / Lathe for more detail
|
172
|
+
if options[:sequences]
|
173
|
+
if target = event.target_for_origin(current_state)
|
174
|
+
ts << binding.transition([event,target], *args) unless options[:cyclic]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# build a list of transitions from the possible events and their targets
|
179
|
+
if event.targets
|
180
|
+
next unless event.target if options[:simple]
|
181
|
+
event.targets.flatten.each do |target|
|
182
|
+
next if options[:target] and target != options[:target]
|
183
|
+
t = Transition.new(binding, event, target, *args)
|
184
|
+
ts << t if (t.valid? or !options[:valid])
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end.flatten.extend(Result)
|
190
|
+
|
191
|
+
if @args || @block
|
192
|
+
@result.each do |t|
|
193
|
+
t.apply!( &@block) if @block
|
194
|
+
t.args = @args if @args
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
@result
|
199
|
+
end # result
|
200
|
+
|
201
|
+
# sanitizes / extracts destination for find.
|
202
|
+
#
|
203
|
+
# takes a single, simple (one target only) event,
|
204
|
+
# or an array of [event, target],
|
205
|
+
# or one of the above with symbols in place of the objects themselves.
|
206
|
+
def parse_destination(destination)
|
207
|
+
event, target = destination
|
208
|
+
|
209
|
+
unless event.is_a?(Event)
|
210
|
+
event = binding.machine.events[event]
|
211
|
+
end
|
212
|
+
|
213
|
+
unless target.is_a?(State)
|
214
|
+
target = binding.machine.states[target] rescue nil
|
215
|
+
end
|
216
|
+
|
217
|
+
raise ArgumentError.new( [event,target].inspect ) unless
|
218
|
+
[[Event, State],[Event, NilClass]].include?( [event,target].map(&:class) )
|
219
|
+
[event, target]
|
220
|
+
end # parse_destination
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|