davidlee-state-fu 0.0.2 → 0.2.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 +13 -1
- data/lib/state-fu.rb +4 -3
- data/lib/state_fu/active_support_lite/array/access.rb +53 -0
- data/lib/state_fu/active_support_lite/array/conversions.rb +196 -0
- data/lib/state_fu/active_support_lite/array/extract_options.rb +20 -0
- data/lib/state_fu/active_support_lite/array/grouping.rb +106 -0
- data/lib/state_fu/active_support_lite/array/random_access.rb +12 -0
- data/lib/state_fu/active_support_lite/array/wrapper.rb +24 -0
- data/lib/state_fu/active_support_lite/array.rb +7 -0
- data/lib/state_fu/active_support_lite/blank.rb +58 -0
- data/lib/state_fu/active_support_lite/cattr_reader.rb +54 -0
- data/lib/state_fu/active_support_lite/inheritable_attributes.rb +1 -0
- data/lib/state_fu/active_support_lite/keys.rb +52 -0
- data/lib/state_fu/active_support_lite/object.rb +6 -0
- data/lib/state_fu/active_support_lite/string.rb +33 -0
- data/lib/state_fu/active_support_lite/symbol.rb +15 -0
- data/lib/state_fu/binding.rb +113 -58
- data/lib/state_fu/core_ext.rb +12 -13
- data/lib/state_fu/event.rb +4 -4
- data/lib/state_fu/exceptions.rb +12 -0
- data/lib/state_fu/helper.rb +74 -12
- data/lib/state_fu/lathe.rb +15 -0
- data/lib/state_fu/machine.rb +17 -25
- data/lib/state_fu/mock_transition.rb +38 -0
- data/lib/state_fu/persistence/active_record.rb +2 -2
- data/lib/state_fu/persistence/attribute.rb +4 -4
- data/lib/state_fu/persistence/base.rb +1 -1
- data/lib/state_fu/sprocket.rb +4 -0
- data/lib/state_fu/state.rb +4 -4
- data/lib/state_fu/transition.rb +24 -22
- data/spec/helper.rb +5 -3
- data/spec/integration/binding_extension_spec.rb +41 -0
- data/spec/integration/dynamic_requirement_spec.rb +160 -0
- data/spec/integration/example_01_document_spec.rb +2 -1
- data/spec/integration/lathe_extension_spec.rb +67 -0
- data/spec/integration/requirement_reflection_spec.rb +71 -11
- data/spec/integration/temp_spec.rb +17 -0
- data/spec/integration/transition_spec.rb +31 -19
- data/spec/units/binding_spec.rb +6 -0
- data/spec/units/event_spec.rb +6 -5
- data/spec/units/lathe_spec.rb +4 -2
- metadata +40 -17
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class String
|
|
2
|
+
def underscore
|
|
3
|
+
self.gsub(/::/, '/').
|
|
4
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
|
5
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
|
6
|
+
tr("-", "_").
|
|
7
|
+
downcase
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def demodulize
|
|
11
|
+
gsub(/^.*::/, '')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def constantize
|
|
15
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
|
|
16
|
+
raise NameError, "#{self} is not a valid constant name!"
|
|
17
|
+
end
|
|
18
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def classify # DOES NOT SINGULARISE
|
|
22
|
+
camelize(self.sub(/.*\./, ''))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def camelize( first_letter_in_uppercase = true)
|
|
26
|
+
if first_letter_in_uppercase
|
|
27
|
+
gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
|
28
|
+
else
|
|
29
|
+
first + camelize(lower_case_and_underscored_word)[1..-1]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
unless :to_proc.respond_to?(:to_proc)
|
|
2
|
+
class Symbol
|
|
3
|
+
# Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
|
|
4
|
+
#
|
|
5
|
+
# # The same as people.collect { |p| p.name }
|
|
6
|
+
# people.collect(&:name)
|
|
7
|
+
#
|
|
8
|
+
# # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
|
|
9
|
+
# people.select(&:manager?).collect(&:salary)
|
|
10
|
+
def to_proc
|
|
11
|
+
Proc.new { |*args| args.shift.__send__(self, *args) }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
data/lib/state_fu/binding.rb
CHANGED
|
@@ -17,11 +17,11 @@ module StateFu
|
|
|
17
17
|
StateFu::Persistence.prepare_field( object.class, field_name )
|
|
18
18
|
# add a persister
|
|
19
19
|
@persister = StateFu::Persistence.for( self, field_name )
|
|
20
|
-
Logger.info( "Persister added:
|
|
20
|
+
Logger.info( "Persister (#{@persister.class}) added: #{method_name} as field #{field_name}" )
|
|
21
21
|
|
|
22
22
|
# define event methods on self( binding ) and @object
|
|
23
23
|
StateFu::MethodFactory.new( self ).install!
|
|
24
|
-
|
|
24
|
+
machine.helpers.inject_into( self )
|
|
25
25
|
# StateFu::Persistence.prepare_field( @object.class, field_name )
|
|
26
26
|
end
|
|
27
27
|
alias_method :o, :object
|
|
@@ -45,7 +45,11 @@ module StateFu
|
|
|
45
45
|
alias_method :state, :current_state
|
|
46
46
|
|
|
47
47
|
def current_state_name
|
|
48
|
-
|
|
48
|
+
begin
|
|
49
|
+
current_state.name.to_sym
|
|
50
|
+
rescue NoMethodError
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
49
53
|
end
|
|
50
54
|
alias_method :name, :current_state_name
|
|
51
55
|
alias_method :state_name, :current_state_name
|
|
@@ -58,43 +62,40 @@ module StateFu
|
|
|
58
62
|
alias_method :events_from_current_state, :events
|
|
59
63
|
|
|
60
64
|
# the subset of events() whose requirements for firing are met
|
|
61
|
-
def valid_events
|
|
65
|
+
def valid_events( *args )
|
|
62
66
|
return nil unless current_state
|
|
63
|
-
return [] unless current_state.exitable_by?( self )
|
|
64
|
-
events.select {|e| e.fireable_by?( self ) }.extend EventArray
|
|
67
|
+
return [] unless current_state.exitable_by?( self, *args )
|
|
68
|
+
events.select {|e| e.fireable_by?( self, *args ) }.extend EventArray
|
|
65
69
|
end
|
|
66
70
|
|
|
67
|
-
def invalid_events
|
|
68
|
-
(events - valid_events).extend StateArray
|
|
71
|
+
def invalid_events( *args )
|
|
72
|
+
( events - valid_events( *args ) ).extend StateArray
|
|
69
73
|
end
|
|
70
74
|
|
|
71
|
-
def unmet_requirements_for(event, target)
|
|
75
|
+
def unmet_requirements_for( event, target )
|
|
72
76
|
raise NotImplementedError
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
# the counterpart to valid_events - the states we can arrive at in
|
|
76
80
|
# the firing of one event, taking into account event and state
|
|
77
81
|
# transition requirements
|
|
78
|
-
def valid_next_states
|
|
79
|
-
valid_transitions
|
|
82
|
+
def valid_next_states( *args )
|
|
83
|
+
vt = valid_transitions( *args )
|
|
84
|
+
vt && vt.values.flatten.uniq.extend( StateArray )
|
|
80
85
|
end
|
|
81
86
|
|
|
82
87
|
def next_states
|
|
83
88
|
events.map(&:targets).compact.flatten.uniq.extend StateArray
|
|
84
89
|
end
|
|
85
90
|
|
|
86
|
-
def invalid_next_states
|
|
87
|
-
states - valid_states
|
|
88
|
-
end
|
|
89
|
-
|
|
90
91
|
# returns a hash of { event => [states] } whose transition
|
|
91
92
|
# requirements are met
|
|
92
|
-
def valid_transitions
|
|
93
|
-
h
|
|
94
|
-
return nil
|
|
95
|
-
|
|
93
|
+
def valid_transitions( *args )
|
|
94
|
+
h = {}
|
|
95
|
+
return nil unless ve = valid_events( *args )
|
|
96
|
+
ve.each do |e|
|
|
96
97
|
h[e] = e.targets.select do |s|
|
|
97
|
-
s.enterable_by?( self )
|
|
98
|
+
s.enterable_by?( self, *args )
|
|
98
99
|
end
|
|
99
100
|
end
|
|
100
101
|
h
|
|
@@ -105,8 +106,21 @@ module StateFu
|
|
|
105
106
|
event, target = parse_destination( event_or_array )
|
|
106
107
|
StateFu::Transition.new( self, event, target, *args, &block )
|
|
107
108
|
end
|
|
108
|
-
alias_method :fire,
|
|
109
|
-
alias_method :
|
|
109
|
+
alias_method :fire, :transition
|
|
110
|
+
alias_method :fire_event, :transition
|
|
111
|
+
alias_method :trigger, :transition
|
|
112
|
+
alias_method :trigger_event, :transition
|
|
113
|
+
alias_method :begin_transition, :transition
|
|
114
|
+
|
|
115
|
+
def blank_mock_transition( *args, &block )
|
|
116
|
+
StateFu::MockTransition.new( self, nil, nil, *args, &block )
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def mock_transition( event_or_array, *args, &block )
|
|
120
|
+
event, target = nil
|
|
121
|
+
event, target = parse_destination( event_or_array )
|
|
122
|
+
StateFu::MockTransition.new( self, event, target, *args, &block )
|
|
123
|
+
end
|
|
110
124
|
|
|
111
125
|
# sanitize args for fire! and fireable?
|
|
112
126
|
def parse_destination( event_or_array )
|
|
@@ -125,7 +139,7 @@ module StateFu
|
|
|
125
139
|
end
|
|
126
140
|
|
|
127
141
|
# check that the event and target are "valid" (all requirements met)
|
|
128
|
-
def fireable?( event_or_array )
|
|
142
|
+
def fireable?( event_or_array, *args )
|
|
129
143
|
event, target = parse_destination( event_or_array )
|
|
130
144
|
begin
|
|
131
145
|
t = transition( [event, target] )
|
|
@@ -134,11 +148,18 @@ module StateFu
|
|
|
134
148
|
nil
|
|
135
149
|
end
|
|
136
150
|
end
|
|
137
|
-
alias_method :event?,
|
|
138
|
-
alias_method :
|
|
139
|
-
alias_method :
|
|
140
|
-
alias_method :
|
|
141
|
-
alias_method :
|
|
151
|
+
alias_method :event?, :fireable?
|
|
152
|
+
alias_method :event_fireable?, :fireable?
|
|
153
|
+
alias_method :can_fire?, :fireable?
|
|
154
|
+
alias_method :can_fire_event?, :fireable?
|
|
155
|
+
alias_method :trigger?, :fireable?
|
|
156
|
+
alias_method :triggerable?, :fireable?
|
|
157
|
+
alias_method :can_trigger?, :fireable?
|
|
158
|
+
alias_method :can_trigger_event?, :fireable?
|
|
159
|
+
alias_method :event_triggerable?, :fireable?
|
|
160
|
+
alias_method :transition?, :fireable?
|
|
161
|
+
alias_method :can_transition?, :fireable?
|
|
162
|
+
alias_method :transitionable?, :fireable?
|
|
142
163
|
|
|
143
164
|
# construct an event transition and fire it
|
|
144
165
|
def fire!( event_or_array, *args, &block)
|
|
@@ -153,37 +174,53 @@ module StateFu
|
|
|
153
174
|
|
|
154
175
|
# evaluate a requirement depending whether it's a method or proc,
|
|
155
176
|
# and its arity - see helper.rb (ContextualEval) for the smarts
|
|
177
|
+
|
|
178
|
+
# TODO - enable requirement block / method to know the target
|
|
156
179
|
def evaluate_requirement( name )
|
|
157
|
-
|
|
180
|
+
puts "DEPRECATED: evaluate_requirement #{name}"
|
|
181
|
+
evaluate_requirement_with_args( name )
|
|
158
182
|
end
|
|
159
183
|
|
|
160
|
-
def
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return msg
|
|
164
|
-
else
|
|
165
|
-
if dest.is_a?( StateFu::Transition )
|
|
166
|
-
t = dest
|
|
167
|
-
else
|
|
168
|
-
event, target = parse_destination( event_or_array )
|
|
169
|
-
t = transition( event, target )
|
|
170
|
-
end
|
|
171
|
-
case msg
|
|
172
|
-
when Symbol
|
|
173
|
-
t.evaluate_named_proc_or_method( msg )
|
|
174
|
-
when Proc
|
|
175
|
-
t.evaluate &msg
|
|
176
|
-
end
|
|
177
|
-
end
|
|
184
|
+
def evaluate_requirement_with_args( name, *args )
|
|
185
|
+
t = blank_mock_transition( *args )
|
|
186
|
+
evaluate_named_proc_or_method( name, t )
|
|
178
187
|
end
|
|
179
188
|
|
|
189
|
+
def evaluate_requirement_with_transition( name, t )
|
|
190
|
+
evaluate_named_proc_or_method( name, t )
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# TODO SPECME HACKERY CRUFT FIXME THISSUCKS and needs *args
|
|
194
|
+
# def evaluate_requirement_message( name, dest )
|
|
195
|
+
# # puts "#{name} #{dest}"
|
|
196
|
+
# msg = machine.requirement_messages[name]
|
|
197
|
+
# if [String, NilClass].include?( msg.class )
|
|
198
|
+
# return msg
|
|
199
|
+
# else
|
|
200
|
+
# if dest.is_a?( StateFu::Transition )
|
|
201
|
+
# t = dest
|
|
202
|
+
# else
|
|
203
|
+
# event, target = parse_destination( event_or_array )
|
|
204
|
+
# t = transition( event, target )
|
|
205
|
+
# end
|
|
206
|
+
# case msg
|
|
207
|
+
# when Symbol, Proc
|
|
208
|
+
# puts t.class
|
|
209
|
+
# evaluate_named_proc_or_method( msg, t )
|
|
210
|
+
# # when Proc
|
|
211
|
+
# # t.evaluate &msg
|
|
212
|
+
# end
|
|
213
|
+
# end
|
|
214
|
+
# end
|
|
215
|
+
|
|
180
216
|
# if there is one simple event, return a transition for it
|
|
181
217
|
# else return nil
|
|
182
218
|
# TODO - not convinced about the method name / aliases - but next
|
|
183
219
|
# is reserved :/
|
|
184
220
|
def next_transition( *args, &block )
|
|
185
|
-
|
|
186
|
-
|
|
221
|
+
vts = valid_transitions( *args )
|
|
222
|
+
return nil if vts.nil?
|
|
223
|
+
next_transition_candidates = vts.select {|e, s| s.length == 1 }
|
|
187
224
|
if next_transition_candidates.length == 1
|
|
188
225
|
nt = next_transition_candidates.first
|
|
189
226
|
evt = nt[0]
|
|
@@ -192,11 +229,15 @@ module StateFu
|
|
|
192
229
|
end
|
|
193
230
|
end
|
|
194
231
|
|
|
195
|
-
def next_state
|
|
196
|
-
next_transition
|
|
232
|
+
def next_state( *args )
|
|
233
|
+
nt = next_transition( *args )
|
|
234
|
+
nt && nt.target
|
|
197
235
|
end
|
|
198
236
|
|
|
199
|
-
|
|
237
|
+
def next_event( *args )
|
|
238
|
+
nt = next_transition( *args )
|
|
239
|
+
nt && nt.event
|
|
240
|
+
end
|
|
200
241
|
|
|
201
242
|
# if there is a next_transition, create, fire & return it
|
|
202
243
|
# otherwise raise an InvalidTransition
|
|
@@ -205,10 +246,10 @@ module StateFu
|
|
|
205
246
|
t.fire!
|
|
206
247
|
t
|
|
207
248
|
else
|
|
208
|
-
|
|
249
|
+
vts = valid_transitions( *args )
|
|
250
|
+
n = vts && vts.length
|
|
209
251
|
raise InvalidTransition.
|
|
210
|
-
new( self, current_state,
|
|
211
|
-
"there are #{n} candidate transitions, need exactly 1")
|
|
252
|
+
new( self, current_state, vts, "there are #{n} candidate transitions, need exactly 1")
|
|
212
253
|
end
|
|
213
254
|
end
|
|
214
255
|
alias_method :next_state!, :next!
|
|
@@ -247,15 +288,29 @@ module StateFu
|
|
|
247
288
|
|
|
248
289
|
# if there is one possible cyclical event, evaluate its
|
|
249
290
|
# requirements (true/false), else nil
|
|
250
|
-
def cycle?
|
|
251
|
-
if t = cycle
|
|
291
|
+
def cycle?( *args )
|
|
292
|
+
if t = cycle( *args )
|
|
252
293
|
t.requirements_met?
|
|
253
294
|
end
|
|
254
295
|
end
|
|
255
296
|
|
|
256
297
|
# display something sensible that doesn't take up the whole screen
|
|
257
298
|
def inspect
|
|
258
|
-
|
|
299
|
+
'|<= ' + self.class.to_s + ' ' +
|
|
300
|
+
attrs = [[:current_state, state_name.inspect],
|
|
301
|
+
[:object_type , @object.class],
|
|
302
|
+
[:method_name , method_name.inspect],
|
|
303
|
+
[:field_name , persister.field_name.inspect],
|
|
304
|
+
[:machine , machine.inspect]].
|
|
305
|
+
map {|x| x.join('=') }.join( " " ) + ' =>|'
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def == other
|
|
309
|
+
if other.respond_to?(:to_sym) && current_state_name.is_a?(Symbol)
|
|
310
|
+
other.to_sym == current_state_name || super( other )
|
|
311
|
+
else
|
|
312
|
+
super( other )
|
|
313
|
+
end
|
|
259
314
|
end
|
|
260
315
|
|
|
261
316
|
end
|
data/lib/state_fu/core_ext.rb
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
|
|
3
|
-
# unless Object.const_defined?('ActiveSupport')
|
|
4
|
-
#
|
|
5
|
-
# require 'active_support/core_ext/array'
|
|
6
|
-
# require 'active_support/core_ext/blank'
|
|
7
|
-
# require 'active_support/core_ext/class'
|
|
8
|
-
# require 'active_support/core_ext/module'
|
|
9
|
-
# require 'active_support/core_ext/hash/keys'
|
|
10
|
-
#
|
|
11
|
-
# class Hash #:nodoc:
|
|
12
|
-
# include ActiveSupport::CoreExtensions::Hash::Keys
|
|
13
|
-
# end
|
|
14
|
-
# end
|
|
15
|
-
|
|
16
3
|
class Symbol
|
|
17
4
|
unless instance_methods.include?(:'<=>')
|
|
18
5
|
# Logger.log ..
|
|
@@ -21,3 +8,15 @@ class Symbol
|
|
|
21
8
|
end
|
|
22
9
|
end
|
|
23
10
|
end
|
|
11
|
+
|
|
12
|
+
unless Object.const_defined?('ActiveSupport')
|
|
13
|
+
Dir[File.join(File.dirname( __FILE__), 'active_support_lite','**' )].sort.each do |lib|
|
|
14
|
+
next unless File.file?( lib )
|
|
15
|
+
require lib
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Hash #:nodoc:
|
|
19
|
+
include ActiveSupport::CoreExtensions::Hash::Keys
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
data/lib/state_fu/event.rb
CHANGED
|
@@ -32,7 +32,7 @@ module StateFu
|
|
|
32
32
|
if [args].flatten == [:ALL]
|
|
33
33
|
@origins = machine.states
|
|
34
34
|
else
|
|
35
|
-
@origins = machine.find_or_create_states_by_name( *args.flatten )
|
|
35
|
+
@origins = machine.find_or_create_states_by_name( *args.flatten ).extend( StateArray )
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@ module StateFu
|
|
|
40
40
|
if [args].flatten == [:ALL]
|
|
41
41
|
@targets = machine.states
|
|
42
42
|
else
|
|
43
|
-
@targets = machine.find_or_create_states_by_name( *args.flatten )
|
|
43
|
+
@targets = machine.find_or_create_states_by_name( *args.flatten ).extend( StateArray )
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -88,9 +88,9 @@ module StateFu
|
|
|
88
88
|
self.targets= *args
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
def fireable_by?( binding )
|
|
91
|
+
def fireable_by?( binding, *args )
|
|
92
92
|
requirements.reject do |r|
|
|
93
|
-
binding.
|
|
93
|
+
binding.evaluate_requirement_with_args( r, *args )
|
|
94
94
|
end.empty?
|
|
95
95
|
end
|
|
96
96
|
|
data/lib/state_fu/exceptions.rb
CHANGED
|
@@ -5,6 +5,18 @@ module StateFu
|
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
class RequirementError < Exception
|
|
8
|
+
attr_reader :transition
|
|
9
|
+
DEFAULT_MESSAGE = "The transition was halted"
|
|
10
|
+
|
|
11
|
+
def initialize( transition, message=DEFAULT_MESSAGE, options={})
|
|
12
|
+
@transition = transition
|
|
13
|
+
@options = options
|
|
14
|
+
super( message )
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def inspect
|
|
18
|
+
"<StateFu::RequirementError #{message} #{@transition.origin.name}=[#{@transition.event.name}]=>#{transition.target.name}"
|
|
19
|
+
end
|
|
8
20
|
end
|
|
9
21
|
|
|
10
22
|
class TransitionHalted < Exception
|
data/lib/state_fu/helper.rb
CHANGED
|
@@ -112,10 +112,39 @@ module StateFu
|
|
|
112
112
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
+
module ModuleRefArray
|
|
116
|
+
def modules
|
|
117
|
+
self.map do |h|
|
|
118
|
+
case h
|
|
119
|
+
when String, Symbol
|
|
120
|
+
Object.const_get( h.to_s.classify )
|
|
121
|
+
when Module
|
|
122
|
+
h
|
|
123
|
+
else
|
|
124
|
+
raise ArgumentError.new( h.class.inspect )
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end # modules
|
|
128
|
+
|
|
129
|
+
def inject_into( obj )
|
|
130
|
+
metaclass = class << obj; self; end
|
|
131
|
+
mods = self.modules()
|
|
132
|
+
metaclass.class_eval do
|
|
133
|
+
mods.each do |mod|
|
|
134
|
+
include( mod )
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
115
140
|
# Array extender. Used by Machine to keep a list of helpers to mix into
|
|
116
141
|
# context objects.
|
|
117
142
|
module HelperArray
|
|
143
|
+
include ModuleRefArray
|
|
144
|
+
end
|
|
118
145
|
|
|
146
|
+
module ToolArray
|
|
147
|
+
include ModuleRefArray
|
|
119
148
|
end
|
|
120
149
|
|
|
121
150
|
# Extend an Array with this. It's a fairly compact implementation,
|
|
@@ -155,29 +184,62 @@ module StateFu
|
|
|
155
184
|
|
|
156
185
|
module ContextualEval
|
|
157
186
|
module InstanceMethods
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
187
|
+
|
|
188
|
+
# if we use &block syntax it stuffs the arity up, so we have to
|
|
189
|
+
# pass it as a normal argument
|
|
190
|
+
def limit_arguments( block, *args )
|
|
191
|
+
case block.arity
|
|
192
|
+
when -1, 0
|
|
193
|
+
nil
|
|
194
|
+
else
|
|
195
|
+
args[0 .. (block.arity - 1) ]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def evaluate( *args, &proc )
|
|
200
|
+
if proc.arity > 0
|
|
201
|
+
args = limit_arguments( proc, *args )
|
|
202
|
+
object.instance_exec( *args, &proc )
|
|
161
203
|
else
|
|
162
204
|
instance_eval( &proc )
|
|
163
205
|
end
|
|
164
206
|
end
|
|
165
207
|
|
|
166
|
-
def
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
208
|
+
def call_on_object_with_optional_args( name, *args )
|
|
209
|
+
if meth = object.method( name )
|
|
210
|
+
args = limit_arguments( meth, *args )
|
|
211
|
+
if args.nil?
|
|
212
|
+
object.send( name )
|
|
213
|
+
else
|
|
214
|
+
object.send( name, *args )
|
|
215
|
+
end
|
|
171
216
|
else
|
|
172
|
-
|
|
217
|
+
raise NoMethodError.new( "undefined method #{name} for #{object.inspect}" )
|
|
173
218
|
end
|
|
174
219
|
end
|
|
175
220
|
|
|
176
|
-
def
|
|
221
|
+
def call_on_object_with_self( name )
|
|
222
|
+
call_on_object_with_optional_args( name, self )
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def evaluate_named_proc_or_method( name, *args )
|
|
177
226
|
if (name.is_a?( Proc ) && proc = name) || proc = machine.named_procs[ name ]
|
|
178
|
-
evaluate &proc
|
|
227
|
+
evaluate( *args, &proc )
|
|
228
|
+
else
|
|
229
|
+
call_on_object_with_optional_args( name, *args )
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def find_event_target( evt, tgt )
|
|
234
|
+
case tgt
|
|
235
|
+
when StateFu::State
|
|
236
|
+
tgt
|
|
237
|
+
when Symbol
|
|
238
|
+
binding && binding.machine.states[ tgt ] # || raise( tgt.inspect )
|
|
239
|
+
when NilClass
|
|
240
|
+
evt.respond_to?(:target) && evt.target
|
|
179
241
|
else
|
|
180
|
-
|
|
242
|
+
raise ArgumentError.new( "#{tgt.class} is not a Symbol, StateFu::State or nil (#{evt})" )
|
|
181
243
|
end
|
|
182
244
|
end
|
|
183
245
|
end
|
data/lib/state_fu/lathe.rb
CHANGED
|
@@ -9,6 +9,10 @@ module StateFu
|
|
|
9
9
|
@machine = machine
|
|
10
10
|
@sprocket = sprocket
|
|
11
11
|
@options = options.symbolize_keys!
|
|
12
|
+
|
|
13
|
+
# extend ourself with any previously defined tools
|
|
14
|
+
machine.tools.inject_into( self )
|
|
15
|
+
|
|
12
16
|
if sprocket
|
|
13
17
|
sprocket.apply!( options )
|
|
14
18
|
end
|
|
@@ -38,6 +42,10 @@ module StateFu
|
|
|
38
42
|
!child?
|
|
39
43
|
end
|
|
40
44
|
|
|
45
|
+
def master_lathe
|
|
46
|
+
machine.lathe
|
|
47
|
+
end
|
|
48
|
+
|
|
41
49
|
# instantiate a child lathe and apply the given block
|
|
42
50
|
def apply_to( sprocket, options, &block )
|
|
43
51
|
StateFu::Lathe.new( machine, sprocket, options, &block )
|
|
@@ -116,6 +124,13 @@ module StateFu
|
|
|
116
124
|
machine.helper *modules
|
|
117
125
|
end
|
|
118
126
|
|
|
127
|
+
# helpers are mixed into all binding / transition contexts
|
|
128
|
+
def tool( *modules )
|
|
129
|
+
machine.tool *modules
|
|
130
|
+
# inject them into self for immediate use
|
|
131
|
+
modules.flatten.extend( ToolArray ).inject_into( self )
|
|
132
|
+
end
|
|
133
|
+
|
|
119
134
|
#
|
|
120
135
|
# event definition
|
|
121
136
|
#
|
data/lib/state_fu/machine.rb
CHANGED
|
@@ -20,13 +20,14 @@ module StateFu
|
|
|
20
20
|
## Instance Methods
|
|
21
21
|
##
|
|
22
22
|
|
|
23
|
-
attr_reader :states, :events, :options, :helpers, :named_procs, :requirement_messages
|
|
23
|
+
attr_reader :states, :events, :options, :helpers, :named_procs, :requirement_messages, :tools
|
|
24
24
|
|
|
25
25
|
def initialize( name, options={}, &block )
|
|
26
26
|
# TODO - name isn't actually used anywhere yet - remove from constructor
|
|
27
27
|
@states = [].extend( StateArray )
|
|
28
28
|
@events = [].extend( EventArray )
|
|
29
29
|
@helpers = [].extend( HelperArray )
|
|
30
|
+
@tools = [].extend( ToolArray )
|
|
30
31
|
@named_procs = {}
|
|
31
32
|
@requirement_messages = {}
|
|
32
33
|
@options = options
|
|
@@ -40,27 +41,15 @@ module StateFu
|
|
|
40
41
|
alias_method :lathe, :apply!
|
|
41
42
|
|
|
42
43
|
def helper_modules
|
|
43
|
-
helpers.
|
|
44
|
-
case h
|
|
45
|
-
when String, Symbol
|
|
46
|
-
Object.const_get( h.to_s.classify )
|
|
47
|
-
when Module
|
|
48
|
-
h
|
|
49
|
-
else
|
|
50
|
-
raise ArgumentError.new( h.class.inspect )
|
|
51
|
-
end
|
|
52
|
-
end
|
|
44
|
+
helpers.modules
|
|
53
45
|
end
|
|
54
46
|
|
|
55
47
|
def inject_helpers_into( obj )
|
|
56
|
-
|
|
48
|
+
helpers.inject_into( obj )
|
|
49
|
+
end
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
mods.each do |mod|
|
|
61
|
-
include( mod )
|
|
62
|
-
end
|
|
63
|
-
end
|
|
51
|
+
def inject_tools_into( obj )
|
|
52
|
+
tools.inject_into( obj )
|
|
64
53
|
end
|
|
65
54
|
|
|
66
55
|
# the modules listed here will be mixed into Binding and
|
|
@@ -77,6 +66,11 @@ module StateFu
|
|
|
77
66
|
modules_to_add.each { |mod| helpers << mod }
|
|
78
67
|
end
|
|
79
68
|
|
|
69
|
+
# same deal but for extending Lathe
|
|
70
|
+
def tool *modules_to_add
|
|
71
|
+
modules_to_add.each { |mod| tools << mod }
|
|
72
|
+
end
|
|
73
|
+
|
|
80
74
|
# make it so a class which has included StateFu has a binding to
|
|
81
75
|
# this machine
|
|
82
76
|
def bind!( klass, name=StateFu::DEFAULT_MACHINE, field_name = nil )
|
|
@@ -144,14 +138,12 @@ module StateFu
|
|
|
144
138
|
end
|
|
145
139
|
end
|
|
146
140
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
@states.each { |s| m.states() << duplify.call(s) }
|
|
152
|
-
@events.each { |e| m.events() << duplify.call(e) }
|
|
153
|
-
end
|
|
141
|
+
# Marshal, the poor man's X-Ray photocopier.
|
|
142
|
+
# TODO: a version which will not break its teeth on procs
|
|
143
|
+
def deep_clone
|
|
144
|
+
Marshal::load(Marshal.dump(self))
|
|
154
145
|
end
|
|
146
|
+
alias_method :deep_copy, :deep_clone
|
|
155
147
|
|
|
156
148
|
def inspect
|
|
157
149
|
"#<#{self.class} ##{__id__} states=#{state_names.inspect} events=#{event_names.inspect} options=#{options.inspect}>"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module StateFu
|
|
2
|
+
class MockTransition
|
|
3
|
+
include StateFu::Helper
|
|
4
|
+
include ContextualEval
|
|
5
|
+
|
|
6
|
+
attr_reader( :binding,
|
|
7
|
+
:machine,
|
|
8
|
+
:origin,
|
|
9
|
+
:target,
|
|
10
|
+
:event,
|
|
11
|
+
:args,
|
|
12
|
+
:errors,
|
|
13
|
+
:object,
|
|
14
|
+
:options,
|
|
15
|
+
:current_hook_slot,
|
|
16
|
+
:current_hook )
|
|
17
|
+
|
|
18
|
+
attr_accessor :test_only, :args, :options
|
|
19
|
+
|
|
20
|
+
def initialize( binding, event, target=nil, *args, &block )
|
|
21
|
+
@options = args.extract_options!.symbolize_keys!
|
|
22
|
+
if @binding = binding
|
|
23
|
+
@machine = binding.machine rescue nil
|
|
24
|
+
@object = binding.object rescue nil
|
|
25
|
+
@origin = binding.current_state rescue nil
|
|
26
|
+
end
|
|
27
|
+
@event = event
|
|
28
|
+
@target = find_event_target( event, target )
|
|
29
|
+
@args = args
|
|
30
|
+
@errors = []
|
|
31
|
+
@testing = true
|
|
32
|
+
|
|
33
|
+
machine.respond_to?(:inject_helpers_into) && machine.inject_helpers_into( self )
|
|
34
|
+
apply!( &block ) if block_given?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|