davidlee-state-fu 0.3.1 → 0.10.0
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.textile +124 -34
- data/Rakefile +36 -30
- data/lib/no_stdout.rb +1 -1
- data/lib/state-fu.rb +9 -8
- data/lib/state_fu/active_support_lite/array/access.rb +12 -5
- data/lib/state_fu/active_support_lite/array/conversions.rb +10 -4
- data/lib/state_fu/active_support_lite/array/extract_options.rb +5 -4
- data/lib/state_fu/active_support_lite/array/grouping.rb +7 -4
- data/lib/state_fu/active_support_lite/array/random_access.rb +4 -3
- data/lib/state_fu/active_support_lite/array/wrapper.rb +4 -3
- data/lib/state_fu/active_support_lite/array.rb +3 -1
- data/lib/state_fu/active_support_lite/blank.rb +18 -9
- data/lib/state_fu/active_support_lite/cattr_reader.rb +4 -1
- data/lib/state_fu/active_support_lite/keys.rb +8 -3
- data/lib/state_fu/active_support_lite/misc.rb +6 -4
- data/lib/state_fu/active_support_lite/module/delegation.rb +130 -0
- data/lib/state_fu/active_support_lite/module.rb +1 -0
- data/lib/state_fu/active_support_lite/object.rb +5 -2
- data/lib/state_fu/active_support_lite/string.rb +6 -1
- data/lib/state_fu/active_support_lite/symbol.rb +2 -1
- data/lib/state_fu/applicable.rb +41 -0
- data/lib/state_fu/{helper.rb → arrays.rb} +45 -121
- data/lib/state_fu/binding.rb +136 -159
- data/lib/state_fu/core_ext.rb +78 -10
- data/lib/state_fu/event.rb +112 -48
- data/lib/state_fu/exceptions.rb +80 -34
- data/lib/state_fu/executioner.rb +149 -0
- data/lib/state_fu/has_options.rb +16 -0
- data/lib/state_fu/hooks.rb +21 -16
- data/lib/state_fu/interface.rb +80 -83
- data/lib/state_fu/lathe.rb +361 -148
- data/lib/state_fu/logger.rb +122 -45
- data/lib/state_fu/machine.rb +60 -32
- data/lib/state_fu/method_factory.rb +180 -72
- data/lib/state_fu/methodical.rb +17 -0
- data/lib/state_fu/persistence/active_record.rb +6 -1
- data/lib/state_fu/persistence/attribute.rb +1 -0
- data/lib/state_fu/persistence/base.rb +8 -6
- data/lib/state_fu/persistence.rb +94 -23
- data/lib/state_fu/sprocket.rb +26 -11
- data/lib/state_fu/state.rb +8 -27
- data/lib/state_fu/transition.rb +207 -98
- data/lib/state_fu/transition_query.rb +214 -0
- data/lib/state_fu.rb +1 -0
- data/lib/tasks/spec_last.rake +46 -0
- data/lib/tasks/state_fu.rake +57 -0
- data/lib/vizier.rb +61 -61
- data/spec/custom_formatter.rb +49 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +2 -2
- data/spec/features/method_missing_only_once_spec.rb +28 -0
- data/spec/features/not_requirements_spec.rb +83 -46
- 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 +1 -1
- data/spec/features/{transition_boolean_comparison.rb → transition_boolean_comparison_spec.rb} +29 -18
- data/spec/helper.rb +6 -117
- data/spec/integration/active_record_persistence_spec.rb +18 -4
- data/spec/integration/binding_extension_spec.rb +1 -1
- data/spec/integration/class_accessor_spec.rb +49 -59
- data/spec/integration/event_definition_spec.rb +20 -20
- data/spec/integration/example_01_document_spec.rb +13 -8
- data/spec/integration/example_02_string_spec.rb +3 -2
- data/spec/integration/instance_accessor_spec.rb +16 -19
- data/spec/integration/lathe_extension_spec.rb +2 -2
- data/spec/integration/machine_duplication_spec.rb +59 -37
- data/spec/integration/relaxdb_persistence_spec.rb +6 -3
- data/spec/integration/requirement_reflection_spec.rb +66 -57
- data/spec/integration/state_definition_spec.rb +72 -66
- data/spec/integration/transition_spec.rb +169 -173
- data/spec/spec.opts +5 -3
- data/spec/spec_helper.rb +132 -0
- data/spec/state_fu_spec.rb +870 -0
- data/spec/units/binding_spec.rb +33 -22
- data/spec/units/event_spec.rb +3 -22
- data/spec/units/exceptions_spec.rb +7 -0
- data/spec/units/lathe_spec.rb +7 -7
- data/spec/units/machine_spec.rb +67 -75
- data/spec/units/method_factory_spec.rb +55 -48
- data/spec/units/sprocket_spec.rb +5 -7
- data/spec/units/state_spec.rb +33 -24
- metadata +31 -19
- data/lib/state_fu/active_support_lite/inheritable_attributes.rb +0 -1
- data/lib/state_fu/fu_space.rb +0 -51
- data/lib/state_fu/mock_transition.rb +0 -38
- data/spec/BDD/plotter_spec.rb +0 -115
- data/spec/integration/dynamic_requirement_spec.rb +0 -160
- data/spec/integration/ex_machine_for_accounts_spec.rb +0 -79
- data/spec/integration/sanity_spec.rb +0 -31
- data/spec/units/fu_space_spec.rb +0 -95
data/lib/state_fu/lathe.rb
CHANGED
|
@@ -7,26 +7,27 @@ module StateFu
|
|
|
7
7
|
# lifecycle, circuit, syntax, etc.
|
|
8
8
|
class Lathe
|
|
9
9
|
|
|
10
|
-
#
|
|
10
|
+
# @state_or_event can be either nil (the main Lathe for a Machine)
|
|
11
|
+
# or contain a State or Event (a child lathe for a nested block)
|
|
11
12
|
|
|
12
|
-
attr_reader :machine, :
|
|
13
|
+
attr_reader :machine, :state_or_event, :options
|
|
13
14
|
|
|
14
15
|
# you don't need to call this directly.
|
|
15
|
-
def initialize( machine,
|
|
16
|
-
@machine
|
|
17
|
-
@
|
|
18
|
-
@options
|
|
16
|
+
def initialize( machine, state_or_event = nil, options={}, &block )
|
|
17
|
+
@machine = machine
|
|
18
|
+
@state_or_event = state_or_event
|
|
19
|
+
@options = options.symbolize_keys!
|
|
19
20
|
|
|
20
21
|
# extend ourself with any previously defined tools
|
|
21
22
|
machine.tools.inject_into( self )
|
|
22
23
|
|
|
23
|
-
if
|
|
24
|
-
|
|
24
|
+
if state_or_event
|
|
25
|
+
state_or_event.apply!( options )
|
|
25
26
|
end
|
|
26
27
|
if block_given?
|
|
27
28
|
if block.arity == 1
|
|
28
|
-
if
|
|
29
|
-
yield
|
|
29
|
+
if state_or_event
|
|
30
|
+
yield state_or_event
|
|
30
31
|
else
|
|
31
32
|
raise ArgumentError
|
|
32
33
|
end
|
|
@@ -36,95 +37,30 @@ module StateFu
|
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
#
|
|
41
|
+
# utility methods
|
|
42
|
+
#
|
|
40
43
|
|
|
41
44
|
# a 'child' lathe is created by apply_to, to deal with nested
|
|
42
|
-
# blocks for states / events ( which are
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
+
# blocks for states / events ( which are state_or_events )
|
|
46
|
+
def nested?
|
|
47
|
+
!!state_or_event
|
|
45
48
|
end
|
|
49
|
+
alias_method :child?, :nested?
|
|
46
50
|
|
|
47
51
|
# is this the toplevel lathe for a machine?
|
|
48
52
|
def master?
|
|
49
|
-
!
|
|
53
|
+
!nested?
|
|
50
54
|
end
|
|
51
55
|
|
|
56
|
+
# get the top level Lathe for the machine
|
|
52
57
|
def master_lathe
|
|
53
58
|
machine.lathe
|
|
54
59
|
end
|
|
55
60
|
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
sprocket
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# require that the current sprocket be of a given type
|
|
63
|
-
def require_sprocket( *valid_types )
|
|
64
|
-
raise ArgumentError.new("Lathe is for a #{sprocket.class}, not one of #{valid_types.inspect}") unless valid_types.include?( sprocket.class )
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# ensure this is not a child lathe
|
|
68
|
-
def require_no_sprocket()
|
|
69
|
-
require_sprocket( NilClass )
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# abstract method for defining states / events
|
|
73
|
-
def define_sprocket( type, name, options={}, &block )
|
|
74
|
-
name = name.to_sym
|
|
75
|
-
klass = StateFu.const_get((a=type.to_s.split('',2);[a.first.upcase, a.last].join))
|
|
76
|
-
collection = machine.send("#{type}s")
|
|
77
|
-
options.symbolize_keys!
|
|
78
|
-
if sprocket = collection[name]
|
|
79
|
-
apply_to( sprocket, options, &block )
|
|
80
|
-
sprocket
|
|
81
|
-
else
|
|
82
|
-
sprocket = klass.new( machine, name, options )
|
|
83
|
-
collection << sprocket
|
|
84
|
-
apply_to( sprocket, options, &block )
|
|
85
|
-
sprocket
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def define_state( name, options={}, &block )
|
|
90
|
-
define_sprocket( :state, name, options, &block )
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def define_event( name, options={}, &block )
|
|
94
|
-
define_sprocket( :event, name, options, &block )
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def define_hook slot, method_name=nil, &block
|
|
98
|
-
unless sprocket.hooks.has_key?( slot )
|
|
99
|
-
raise ArgumentError, "invalid hook type #{slot.inspect} for #{sprocket.class}"
|
|
100
|
-
end
|
|
101
|
-
if block_given?
|
|
102
|
-
# unless (-1..1).include?( block.arity )
|
|
103
|
-
# raise ArgumentError, "unexpected block arity: #{block.arity}"
|
|
104
|
-
# end
|
|
105
|
-
case method_name
|
|
106
|
-
when Symbol
|
|
107
|
-
machine.named_procs[method_name] = block
|
|
108
|
-
hook = method_name
|
|
109
|
-
when NilClass
|
|
110
|
-
hook = block
|
|
111
|
-
# allow only one anonymous hook per slot in the interests of
|
|
112
|
-
# sanity - replace any pre-existing ones
|
|
113
|
-
sprocket.hooks[slot].delete_if { |h| Proc === h }
|
|
114
|
-
else
|
|
115
|
-
raise ArgumentError.new( method_name.inspect )
|
|
116
|
-
end
|
|
117
|
-
elsif method_name.is_a?( Symbol ) # no block
|
|
118
|
-
hook = method_name
|
|
119
|
-
# prevent duplicates
|
|
120
|
-
sprocket.hooks[slot].delete_if { |h| hook == h }
|
|
121
|
-
else
|
|
122
|
-
raise ArgumentError, "#{method_name.class} is not a symbol"
|
|
123
|
-
end
|
|
124
|
-
sprocket.hooks[slot] << hook
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
public
|
|
61
|
+
#
|
|
62
|
+
# methods for extending the DSL
|
|
63
|
+
#
|
|
128
64
|
|
|
129
65
|
# helpers are mixed into all binding / transition contexts
|
|
130
66
|
def helper( *modules )
|
|
@@ -139,21 +75,30 @@ module StateFu
|
|
|
139
75
|
end
|
|
140
76
|
|
|
141
77
|
#
|
|
142
|
-
# event definition
|
|
78
|
+
# event definition methods
|
|
143
79
|
#
|
|
144
80
|
|
|
81
|
+
# Defines an event. Any options supplied will be added to the event,
|
|
82
|
+
# except :from and :to which are used to define the origin / target
|
|
83
|
+
# states. Successive invocations will _update_ (not replace) previously
|
|
84
|
+
# defined events; origin / target states and options are always
|
|
85
|
+
# accumulated, not clobbered.
|
|
86
|
+
#
|
|
87
|
+
# Several different styles of definition are available. Consult the
|
|
88
|
+
# specs / features for examples.
|
|
89
|
+
|
|
145
90
|
def event( name, options={}, &block )
|
|
146
91
|
options.symbolize_keys!
|
|
147
|
-
|
|
148
|
-
if
|
|
149
|
-
targets = options.delete(:to)
|
|
92
|
+
valid_in_context( State, nil )
|
|
93
|
+
if nested? && state_or_event.is_a?(State) # in state block
|
|
94
|
+
targets = options.delete(:to) || options.delete(:transitions_to)
|
|
150
95
|
evt = define_event( name, options, &block )
|
|
151
|
-
evt.from
|
|
96
|
+
evt.from state_or_event unless state_or_event.nil?
|
|
152
97
|
evt.to( targets ) unless targets.nil?
|
|
153
98
|
evt
|
|
154
99
|
else # in master lathe
|
|
155
100
|
origins = options.delete( :from )
|
|
156
|
-
targets = options.delete( :to )
|
|
101
|
+
targets = options.delete( :to ) || options.delete(:transitions_to)
|
|
157
102
|
evt = define_event( name, options, &block )
|
|
158
103
|
evt.from origins unless origins.nil?
|
|
159
104
|
evt.to targets unless targets.nil?
|
|
@@ -161,96 +106,191 @@ module StateFu
|
|
|
161
106
|
end
|
|
162
107
|
end
|
|
163
108
|
|
|
109
|
+
# compatibility methods for activemodel state machine ##############
|
|
110
|
+
def transitions(options={})
|
|
111
|
+
valid_in_context(Event)
|
|
112
|
+
options.symbolize_keys!
|
|
113
|
+
|
|
114
|
+
target = options[:to]
|
|
115
|
+
origins = options[:from]
|
|
116
|
+
hook = options[:on_transition]
|
|
117
|
+
evt = state_or_event
|
|
118
|
+
|
|
119
|
+
if hook
|
|
120
|
+
evt.lathe() { triggers hook }
|
|
121
|
+
end
|
|
122
|
+
#
|
|
123
|
+
# TODO do some type checking
|
|
124
|
+
#
|
|
125
|
+
if origins && target
|
|
126
|
+
evt.add_to_sequence origins, target
|
|
127
|
+
end
|
|
128
|
+
evt
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def state_event name, options={}, &block
|
|
133
|
+
valid_in_context State
|
|
134
|
+
options.symbolize_keys!
|
|
135
|
+
state = state_or_event
|
|
136
|
+
targets = options.delete(:to) || options.delete(:transitions_to)
|
|
137
|
+
evt = define_state_or_event( Event, state.own_events, name, options, &block)
|
|
138
|
+
evt.from state
|
|
139
|
+
evt.to(targets) unless targets.nil?
|
|
140
|
+
evt
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def event name, options={}, &block
|
|
144
|
+
options.symbolize_keys!
|
|
145
|
+
valid_in_context State, nil
|
|
146
|
+
if nested? && state_or_event.is_a?(State) # in state block
|
|
147
|
+
targets = options.delete(:to) || options.delete(:transitions_to)
|
|
148
|
+
evt = define_event name, options, &block
|
|
149
|
+
evt.from state_or_event unless state_or_event.nil?
|
|
150
|
+
evt.to targets unless targets.nil?
|
|
151
|
+
evt
|
|
152
|
+
else # in master lathe
|
|
153
|
+
origins = options.delete(:from)|| options.delete(:transitions_from)
|
|
154
|
+
targets = options.delete(:to) || options.delete(:transitions_to)
|
|
155
|
+
evt = define_event name, options, &block
|
|
156
|
+
evt.from origins unless origins.nil?
|
|
157
|
+
evt.to targets unless targets.nil?
|
|
158
|
+
evt
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
#####################################
|
|
164
|
+
|
|
165
|
+
# define an event or state requirement.
|
|
166
|
+
# options:
|
|
167
|
+
# :on => :entry|:exit|array (state only) - check requirement on state entry, exit or both?
|
|
168
|
+
# default = :entry
|
|
169
|
+
# :message => string|proc|proc_name_symbol - message to be returned on requirement failure.
|
|
170
|
+
# if a proc or symbol (named proc identifier), evaluated at runtime; a proc should
|
|
171
|
+
# take one argument, which is a StateFu::Transition.
|
|
172
|
+
# :msg => alias for :message, for the morbidly terse
|
|
173
|
+
|
|
164
174
|
def requires( *args, &block )
|
|
165
|
-
|
|
175
|
+
valid_in_context Event, State
|
|
166
176
|
options = args.extract_options!.symbolize_keys!
|
|
167
|
-
options.assert_valid_keys
|
|
177
|
+
options.assert_valid_keys :on, :message, :msg
|
|
168
178
|
names = args
|
|
169
179
|
if block_given? && args.length > 1
|
|
170
180
|
raise ArgumentError.new("cannot supply a block for multiple requirements")
|
|
171
181
|
end
|
|
172
182
|
on = nil
|
|
173
183
|
names.each do |name|
|
|
174
|
-
raise ArgumentError.new(
|
|
175
|
-
case
|
|
176
|
-
when
|
|
184
|
+
raise ArgumentError.new(name.inspect) unless name.is_a?(Symbol)
|
|
185
|
+
case state_or_event
|
|
186
|
+
when State
|
|
177
187
|
on ||= [(options.delete(:on) || [:entry])].flatten
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
when
|
|
181
|
-
|
|
188
|
+
state_or_event.entry_requirements << name if on.include?( :entry )
|
|
189
|
+
state_or_event.exit_requirements << name if on.include?( :exit )
|
|
190
|
+
when Event
|
|
191
|
+
state_or_event.requirements << name
|
|
182
192
|
end
|
|
183
193
|
if block_given?
|
|
184
194
|
machine.named_procs[name] = block
|
|
185
195
|
end
|
|
186
196
|
if msg = options.delete(:message) || options.delete(:msg)
|
|
187
|
-
# TODO - move this into machine
|
|
188
197
|
raise ArgumentError, msg.inspect unless [String, Symbol, Proc].include?(msg.class)
|
|
189
198
|
machine.requirement_messages[name] = msg
|
|
190
199
|
end
|
|
191
200
|
end
|
|
192
201
|
end
|
|
202
|
+
alias_method :guard, :requires
|
|
193
203
|
alias_method :must, :requires
|
|
194
204
|
alias_method :must_be, :requires
|
|
195
205
|
alias_method :needs, :requires
|
|
196
|
-
alias_method :satisfy, :requires
|
|
197
|
-
alias_method :must_satisfy, :requires
|
|
198
206
|
|
|
199
207
|
# create an event from *and* to the current state.
|
|
200
208
|
# Creates a loop, useful (only) for hooking behaviours onto.
|
|
201
|
-
def cycle
|
|
202
|
-
|
|
203
|
-
|
|
209
|
+
def cycle name=nil, options={}, &block
|
|
210
|
+
_state = nil
|
|
211
|
+
if name.is_a?(Hash) && options.empty?
|
|
212
|
+
options = name
|
|
213
|
+
name = nil
|
|
214
|
+
end
|
|
215
|
+
if _state = options.delete(:state)
|
|
216
|
+
valid_unless_nested("when :state is supplied")
|
|
217
|
+
else
|
|
218
|
+
_state = state_or_event
|
|
219
|
+
valid_in_context( State, "unless :state is supplied" )
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
name ||= options.delete :on
|
|
223
|
+
name ||= "cycle_#{_state.to_sym}"
|
|
204
224
|
evt = define_event( name, options, &block )
|
|
205
|
-
evt.from
|
|
206
|
-
evt.to
|
|
225
|
+
evt.from _state
|
|
226
|
+
evt.to _state
|
|
207
227
|
evt
|
|
208
|
-
# raise NotImplementedError
|
|
209
228
|
end
|
|
210
229
|
|
|
211
230
|
#
|
|
212
231
|
# state definition
|
|
213
232
|
#
|
|
214
233
|
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
# define the initial_state (otherwise defaults to the first state mentioned)
|
|
235
|
+
def initial_state *args, &block
|
|
236
|
+
valid_unless_nested()
|
|
217
237
|
machine.initial_state= state( *args, &block)
|
|
218
238
|
end
|
|
219
239
|
|
|
220
|
-
|
|
221
|
-
|
|
240
|
+
# define a state; given a block, apply the block to a Lathe for the state
|
|
241
|
+
def state name, options={}, &block
|
|
242
|
+
valid_unless_nested()
|
|
222
243
|
define_state( name, options, &block )
|
|
223
244
|
end
|
|
224
245
|
|
|
246
|
+
# define a named proc
|
|
247
|
+
def define method_name, &block
|
|
248
|
+
machine.named_procs[method_name] = block
|
|
249
|
+
end
|
|
250
|
+
alias_method :named_proc, :define
|
|
251
|
+
|
|
252
|
+
#
|
|
253
|
+
# Event definition
|
|
254
|
+
#
|
|
255
|
+
|
|
256
|
+
# set the origin state(s) of an event (or, given a hash of symbols / arrays
|
|
257
|
+
# of symbols, set both the origins and targets)
|
|
258
|
+
# from :my_origin
|
|
259
|
+
# from [:column_a, :column_b]
|
|
260
|
+
# from :eden => :armageddon
|
|
261
|
+
# from [:beginning, :prelogue] => [:ende, :prologue]
|
|
225
262
|
def from *args, &block
|
|
226
|
-
|
|
227
|
-
|
|
263
|
+
valid_in_context Event
|
|
264
|
+
state_or_event.from( *args, &block )
|
|
228
265
|
end
|
|
229
266
|
|
|
267
|
+
# set the target state(s) of an event
|
|
268
|
+
# to :destination
|
|
269
|
+
# to [:end, :finale, :intermission]
|
|
230
270
|
def to *args, &block
|
|
231
|
-
|
|
232
|
-
|
|
271
|
+
valid_in_context Event
|
|
272
|
+
state_or_event.to( *args, &block )
|
|
233
273
|
end
|
|
234
274
|
|
|
235
275
|
#
|
|
236
276
|
# define chained events and states succinctly
|
|
237
277
|
# usage: chain 'state1 -event1-> state2 -event2-> state3'
|
|
238
|
-
def chain
|
|
278
|
+
def chain string
|
|
239
279
|
rx_word = /([a-zA-Z0-9_]+)/
|
|
240
280
|
rx_state = /^#{rx_word}$/
|
|
241
|
-
rx_event =
|
|
281
|
+
rx_event = /^(?:-|>)#{rx_word}-?>$/
|
|
242
282
|
previous = nil
|
|
243
283
|
string.split.each do |chunk|
|
|
244
284
|
case chunk
|
|
245
285
|
when rx_state
|
|
246
|
-
current = state
|
|
247
|
-
if previous.is_a?
|
|
248
|
-
previous.to
|
|
286
|
+
current = state $1
|
|
287
|
+
if previous.is_a? Event
|
|
288
|
+
previous.to current
|
|
249
289
|
end
|
|
250
290
|
when rx_event
|
|
251
|
-
current = event
|
|
252
|
-
if previous.is_a?
|
|
253
|
-
current.from
|
|
291
|
+
current = event $1
|
|
292
|
+
if previous.is_a? State
|
|
293
|
+
current.from previous
|
|
254
294
|
end
|
|
255
295
|
else
|
|
256
296
|
raise ArgumentError, "'#{chunk}' is not a valid token"
|
|
@@ -259,40 +299,213 @@ module StateFu
|
|
|
259
299
|
end
|
|
260
300
|
end
|
|
261
301
|
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
302
|
+
# chain_states :a => [:b,:c], :c => :d, :c => :d
|
|
303
|
+
# chain_states :a,:b,:c,:d, :a => :c
|
|
304
|
+
def connect_states *array, &b
|
|
305
|
+
array.flatten!
|
|
306
|
+
hash = array.extract_options!.symbolize_keys!
|
|
307
|
+
array.inject(nil) do |origin, target|
|
|
308
|
+
state target
|
|
309
|
+
if origin
|
|
310
|
+
event "#{origin.to_sym}_to_#{target.to_sym}", :from => origin, :to => target
|
|
311
|
+
end
|
|
312
|
+
origin = target
|
|
313
|
+
end
|
|
314
|
+
hash.each do |origin, target|
|
|
315
|
+
event "#{origin.to_sym}_to_#{target.to_sym}", :from => origin, :to => target
|
|
270
316
|
end
|
|
271
|
-
args.map { |name| self.send( type, name, options.dup, &block) }.extend StateArray
|
|
272
317
|
end
|
|
318
|
+
alias_method :connect, :connect_states
|
|
273
319
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
320
|
+
#
|
|
321
|
+
# Define a series of states at once, or return and iterate over all states yet defined
|
|
322
|
+
#
|
|
323
|
+
def states *args, &block
|
|
324
|
+
valid_unless_nested()
|
|
325
|
+
each_state_or_event 'state', *args, &block
|
|
277
326
|
end
|
|
278
327
|
alias_method :all_states, :states
|
|
279
328
|
alias_method :each_state, :states
|
|
280
329
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
330
|
+
#
|
|
331
|
+
# Define a series of events at once, or return and iterate over all events yet defined
|
|
332
|
+
#
|
|
333
|
+
def events *args, &block
|
|
334
|
+
valid_in_context nil, State
|
|
335
|
+
each_state_or_event 'event', *args, &block
|
|
284
336
|
end
|
|
285
337
|
alias_method :all_events, :events
|
|
286
338
|
alias_method :each_event, :events
|
|
287
339
|
|
|
288
340
|
# Bunch of silly little methods for defining events
|
|
341
|
+
#:nodoc
|
|
342
|
+
|
|
343
|
+
def before *a, &b; valid_in_context Event; define_hook :before, *a, &b; end
|
|
344
|
+
def on_exit *a, &b; valid_in_context State; define_hook :exit, *a, &b; end
|
|
345
|
+
def execute *a, &b; valid_in_context Event; define_hook :execute, *a, &b; end
|
|
346
|
+
def on_entry *a, &b; valid_in_context State; define_hook :entry, *a, &b; end
|
|
347
|
+
def after *a, &b; valid_in_context Event; define_hook :after, *a, &b; end
|
|
348
|
+
def accepted *a, &b; valid_in_context State; define_hook :accepted, *a, &b; end
|
|
349
|
+
|
|
350
|
+
def before_all *a, &b; valid_in_context nil; define_hook :before_all, *a, &b; end
|
|
351
|
+
def after_all *a, &b; valid_in_context nil; define_hook :after_all, *a, &b; end
|
|
352
|
+
|
|
353
|
+
alias_method :after_everything, :after_all
|
|
354
|
+
alias_method :before_everything, :before_all
|
|
355
|
+
|
|
356
|
+
def after_all *a
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def will *a, &b
|
|
360
|
+
valid_in_context State, Event
|
|
361
|
+
case state_or_event
|
|
362
|
+
when State
|
|
363
|
+
define_hook :entry, *a, &b
|
|
364
|
+
when Event
|
|
365
|
+
define_hook :execute, *a, &b
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
alias_method :fire, :will
|
|
369
|
+
alias_method :fires , :will
|
|
370
|
+
alias_method :firing, :will
|
|
371
|
+
alias_method :cause, :will
|
|
372
|
+
alias_method :causes, :will
|
|
373
|
+
alias_method :triggers, :will
|
|
374
|
+
alias_method :trigger, :will
|
|
375
|
+
alias_method :trigger, :will
|
|
376
|
+
|
|
377
|
+
alias_method :on_change, :accepted
|
|
378
|
+
#
|
|
379
|
+
#
|
|
380
|
+
#
|
|
381
|
+
|
|
382
|
+
private
|
|
383
|
+
|
|
384
|
+
# require that the current state_or_event be of a given type
|
|
385
|
+
def valid_in_context *valid_types
|
|
386
|
+
if valid_types.last.is_a?(String)
|
|
387
|
+
msg = valid_types.pop << " "
|
|
388
|
+
else
|
|
389
|
+
msg = ""
|
|
390
|
+
end
|
|
391
|
+
unless valid_types.include?( state_or_event.class ) || valid_types.include?(nil) && state_or_event.nil?
|
|
392
|
+
v = valid_types.dup.map do |t|
|
|
393
|
+
{
|
|
394
|
+
nil => "if not nested inside a block",
|
|
395
|
+
State => "inside a state definition block",
|
|
396
|
+
Event => "inside an event definition block"
|
|
397
|
+
}[t]
|
|
398
|
+
end
|
|
399
|
+
msg << "this command is only valid " << v.join(',')
|
|
400
|
+
raise ArgumentError, msg
|
|
401
|
+
end
|
|
402
|
+
end
|
|
289
403
|
|
|
290
|
-
|
|
291
|
-
def
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
404
|
+
# ensure this is not a child lathe
|
|
405
|
+
def valid_unless_nested(msg = nil)
|
|
406
|
+
valid_in_context( nil, msg )
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# instantiate a child Lathe and apply the given block
|
|
410
|
+
def apply_to state_or_event, options, &block
|
|
411
|
+
StateFu::Lathe.new( machine, state_or_event, options, &block )
|
|
412
|
+
state_or_event
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# abstract method for defining states / events
|
|
416
|
+
def define_state_or_event klass, collection, name, options={}, &block
|
|
417
|
+
name = name.to_sym
|
|
418
|
+
req = nil
|
|
419
|
+
msg = nil
|
|
420
|
+
options.symbolize_keys!
|
|
421
|
+
|
|
422
|
+
# allow requirements and messages to be added as options
|
|
423
|
+
if k = [:requires, :guard, :must, :must_be, :needs].detect {|k| options.has_key?(k) }
|
|
424
|
+
# Logger.debug("removing option #{k} - will use as requirement ..")
|
|
425
|
+
req = options.delete(k)
|
|
426
|
+
msg = options.delete(:message) || options.delete(:msg)
|
|
427
|
+
raise ArgumentError unless msg.nil? || req.is_a?(Symbol)
|
|
428
|
+
raise ArgumentError unless ([req, msg].map(&:class) - [String, Symbol, Proc, NilClass]).empty?
|
|
429
|
+
end
|
|
430
|
+
# TODO? allow hooks to be defined as options
|
|
431
|
+
|
|
432
|
+
unless state_or_event = collection[name]
|
|
433
|
+
state_or_event = klass.new machine, name, options
|
|
434
|
+
collection << state_or_event
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
apply_to state_or_event, options, &block
|
|
438
|
+
|
|
439
|
+
if req # install requirements
|
|
440
|
+
state_or_event.requirements << req
|
|
441
|
+
machine.requirement_messages[req] = msg if msg
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
state_or_event
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
#:nodoc
|
|
448
|
+
def define_state name, options={}, &block
|
|
449
|
+
collection = machine.states
|
|
450
|
+
define_state_or_event State, collection, name, options, &block
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
#:nodoc
|
|
454
|
+
def define_event name, options={}, &block
|
|
455
|
+
collection = machine.events
|
|
456
|
+
define_state_or_event Event, collection, name, options, &block
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
#:nodoc
|
|
460
|
+
def define_hook slot, *method_names, &block
|
|
461
|
+
raise "wtf" unless machine.is_a?(Machine)
|
|
462
|
+
hooks = (slot.to_s =~ /_all/ ? machine.hooks : state_or_event.hooks)
|
|
463
|
+
unless hooks.has_key? slot
|
|
464
|
+
raise ArgumentError, "invalid hook type #{slot.inspect} for #{state_or_event.class}"
|
|
465
|
+
end
|
|
466
|
+
if block_given?
|
|
467
|
+
method_name = method_names.first
|
|
468
|
+
# unless (-1..1).include?( block.arity )
|
|
469
|
+
# raise ArgumentError, "unexpected block arity: #{block.arity}"
|
|
470
|
+
# end
|
|
471
|
+
case method_name
|
|
472
|
+
when Symbol
|
|
473
|
+
machine.named_procs[method_name] = block
|
|
474
|
+
hook = method_name
|
|
475
|
+
when nil
|
|
476
|
+
hook = block
|
|
477
|
+
# allow only one anonymous hook per slot in the interests of
|
|
478
|
+
# sanity - replace any pre-existing ones
|
|
479
|
+
hooks[slot].delete_if { |h| Proc === h }
|
|
480
|
+
else
|
|
481
|
+
raise ArgumentError.new method_name.inspect
|
|
482
|
+
end
|
|
483
|
+
hooks[slot] << hook
|
|
484
|
+
else
|
|
485
|
+
method_names.each do |method_name|
|
|
486
|
+
if method_name.is_a? Symbol # no block
|
|
487
|
+
hook = method_name
|
|
488
|
+
# prevent duplicates
|
|
489
|
+
hooks[slot].delete_if { |h| hook == h }
|
|
490
|
+
hooks[slot] << hook
|
|
491
|
+
else
|
|
492
|
+
raise ArgumentError, "#{method_name.class} is not a symbol"
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
#:nodoc
|
|
500
|
+
def each_state_or_event type, *args, &block
|
|
501
|
+
options = args.extract_options!.symbolize_keys!
|
|
502
|
+
if args.empty? || args == [:ALL]
|
|
503
|
+
args = machine.send("#{type}s").except options.delete(:except)
|
|
504
|
+
end
|
|
505
|
+
args.map do |name|
|
|
506
|
+
self.send type, name, options.dup, &block
|
|
507
|
+
end.extend ArrayWithSymbolAccessor
|
|
508
|
+
end
|
|
296
509
|
|
|
297
510
|
end
|
|
298
511
|
end
|