simply_fsm 0.2.3 → 0.3.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.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +1 -1
- data/lib/simply_fsm/simply_fsm.rb +278 -0
- data/lib/simply_fsm/version.rb +1 -1
- data/lib/simply_fsm.rb +1 -277
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e96963bd4f2d0cf605ca52b79cc74c3b3ba29d66df452683c6425f6e4243cf62
|
|
4
|
+
data.tar.gz: ca0ad99246f5f27eb9749ba2dd41c652bd424c55828cdeb4f6365478d8866488
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 37de0fd3446033ef390fb8aa6bf616c936de2125e65b876724904b989163d816f52971198a5fb2055a9412589565633a5fbaaa93eaed35912da549bc366d1446
|
|
7
|
+
data.tar.gz: 97165581f0a8f1955ffbb568d99958e84a99de955041ead58b52a5ed100eee52fd8cc58175f32702a11c0d4d9c86a62ff858cc306b3b3a0efd52ec5e3cfdcf02
|
data/.rspec
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
- None right now
|
|
4
4
|
|
|
5
|
+
## [0.3.0]
|
|
6
|
+
|
|
7
|
+
- Moving main source under the `lib/simply_fsm` folder to remove `require` statement
|
|
8
|
+
- Why? Eventually want to be able to use this with `mruby`
|
|
9
|
+
|
|
5
10
|
## [0.2.3] - 2022-04-09
|
|
6
11
|
|
|
7
12
|
- Add `rake yard` to generate local documentation
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Include *SimplyFSM* in a class to be able to defined state machines.
|
|
5
|
+
#
|
|
6
|
+
module SimplyFSM
|
|
7
|
+
#
|
|
8
|
+
# Provides a +state_machine+ for the including class.
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Defines the constructor for defining a state machine
|
|
15
|
+
module ClassMethods
|
|
16
|
+
#
|
|
17
|
+
# Declare a state machine called +name+ which can then be defined
|
|
18
|
+
# by a DSL defined by the methods of *StateMachine*.
|
|
19
|
+
#
|
|
20
|
+
# @param [String] name of the state machine.
|
|
21
|
+
# @param [Hash] opts to specify options such as:
|
|
22
|
+
# - +fail+ lambda that is called with the event name when any event fails to transition
|
|
23
|
+
#
|
|
24
|
+
def state_machine(name, opts = {}, &block)
|
|
25
|
+
fsm = StateMachine.new(name, self, fail: opts[:fail])
|
|
26
|
+
fsm.instance_eval(&block)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# The DSL for defining a state machine. These methods are used within the declaration of a +state_machine+.
|
|
32
|
+
#
|
|
33
|
+
# @attr_reader [String] initial_state The initial state of the state machine
|
|
34
|
+
# @attr_reader [Array] states All the states of the state machine
|
|
35
|
+
# @attr_reader [Array] events All the events of the state machine
|
|
36
|
+
# @attr_reader [String] name
|
|
37
|
+
# @attr_reader [String] full_name The name of the owning class combined with the state machine's name
|
|
38
|
+
#
|
|
39
|
+
class StateMachine
|
|
40
|
+
attr_reader :initial_state, :states, :events, :name, :full_name
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# @!visibility private
|
|
44
|
+
def initialize(name, owner_class, fail: nil)
|
|
45
|
+
@owner_class = owner_class
|
|
46
|
+
@name = name.to_sym
|
|
47
|
+
@full_name = "#{owner_class.name}/#{name}"
|
|
48
|
+
@states = []
|
|
49
|
+
@events = []
|
|
50
|
+
@initial_state = nil
|
|
51
|
+
@fail_handler = fail
|
|
52
|
+
|
|
53
|
+
setup_base_methods
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# Declare a supported +state_name+, and optionally specify one as the +initial+ state.
|
|
58
|
+
#
|
|
59
|
+
# @param [String] state_name
|
|
60
|
+
# @param [Boolean] initial to indicate if this is the initial state of the state machine
|
|
61
|
+
#
|
|
62
|
+
def state(state_name, initial: false)
|
|
63
|
+
return if state_name.nil? || @states.include?(state_name)
|
|
64
|
+
|
|
65
|
+
status = state_name.to_sym
|
|
66
|
+
state_machine_name = @name
|
|
67
|
+
@states << status
|
|
68
|
+
@initial_state = status if initial
|
|
69
|
+
|
|
70
|
+
make_owner_method "#{state_name}?", lambda {
|
|
71
|
+
send(state_machine_name) == status
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# Define an event by +event_name+
|
|
77
|
+
#
|
|
78
|
+
# @param [String] event_name
|
|
79
|
+
# @param [Hash,Array] transitions either one (Hash) or many (Array of Hashes) transitions +from+ one state +to+ another state.
|
|
80
|
+
# @param [Lambda] guard if specified must return +true+ before any transitions are attempted
|
|
81
|
+
# @param [Lambda] fail called with event name if specified when all the attempted transitions fail
|
|
82
|
+
# @yield when the transition attempt succeeds.
|
|
83
|
+
def event(event_name, transitions:, guard: nil, fail: nil, &after)
|
|
84
|
+
return unless event_exists?(event_name) && transitions
|
|
85
|
+
|
|
86
|
+
@events << event_name
|
|
87
|
+
may_event_name = "may_#{event_name}?"
|
|
88
|
+
|
|
89
|
+
if transitions.is_a?(Array)
|
|
90
|
+
setup_multi_transition_may_event_method transitions: transitions, guard: guard,
|
|
91
|
+
may_event_name: may_event_name
|
|
92
|
+
setup_multi_transition_event_method event_name,
|
|
93
|
+
transitions: transitions, guard: guard,
|
|
94
|
+
var_name: "@#{@name}", fail: fail || @fail_handler
|
|
95
|
+
return
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
to = transitions[:to]
|
|
99
|
+
setup_may_event_method may_event_name, transitions[:from] || :any, transitions[:when], guard
|
|
100
|
+
setup_event_method event_name, var_name: "@#{@name}",
|
|
101
|
+
may_event_name: may_event_name, to: to,
|
|
102
|
+
fail: fail || @fail_handler, &after
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def setup_multi_transition_may_event_method(transitions:, guard:, may_event_name:)
|
|
108
|
+
state_machine_name = @name
|
|
109
|
+
|
|
110
|
+
make_owner_method may_event_name, lambda {
|
|
111
|
+
if !guard || instance_exec(&guard)
|
|
112
|
+
current = send(state_machine_name)
|
|
113
|
+
# Check each transition, and first one that succeeds ends the scan
|
|
114
|
+
transitions.each do |t|
|
|
115
|
+
next if cannot_transition?(t[:from], t[:when], current)
|
|
116
|
+
|
|
117
|
+
return true
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
false
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def setup_fail_lambda_for(fail)
|
|
125
|
+
return unless fail
|
|
126
|
+
|
|
127
|
+
if fail.is_a?(String) || fail.is_a?(Symbol)
|
|
128
|
+
->(event_name) { send(fail, event_name) }
|
|
129
|
+
else
|
|
130
|
+
fail
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def setup_multi_transition_event_method(event_name, transitions:, guard:, var_name:, fail:)
|
|
135
|
+
state_machine_name = @name
|
|
136
|
+
fail_lambda = setup_fail_lambda_for(fail)
|
|
137
|
+
make_owner_method event_name, lambda {
|
|
138
|
+
if !guard || instance_exec(&guard)
|
|
139
|
+
current = send(state_machine_name)
|
|
140
|
+
# Check each transition, and first one that succeeds ends the scan
|
|
141
|
+
transitions.each do |t|
|
|
142
|
+
next if cannot_transition?(t[:from], t[:when], current)
|
|
143
|
+
|
|
144
|
+
instance_variable_set(var_name, t[:to])
|
|
145
|
+
return true
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
instance_exec(event_name, &fail_lambda) if fail_lambda
|
|
149
|
+
false
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def event_exists?(event_name)
|
|
154
|
+
event_name && !@events.include?(event_name)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def setup_event_method(event_name, var_name:, may_event_name:, to:, fail:, &after)
|
|
158
|
+
fail_lambda = setup_fail_lambda_for(fail)
|
|
159
|
+
method_lambda = lambda {
|
|
160
|
+
if send(may_event_name)
|
|
161
|
+
instance_variable_set(var_name, to)
|
|
162
|
+
instance_exec(&after) if after
|
|
163
|
+
return true
|
|
164
|
+
end
|
|
165
|
+
instance_exec(event_name, &fail_lambda) if fail_lambda
|
|
166
|
+
false
|
|
167
|
+
}
|
|
168
|
+
make_owner_method event_name, method_lambda
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def setup_may_event_method(may_event_name, from, cond, guard)
|
|
172
|
+
state_machine_name = @name
|
|
173
|
+
#
|
|
174
|
+
# Instead of one "may_event?" method that checks all variations every time it's called, here we check
|
|
175
|
+
# the event definition and define the most optimal lambda to ensure the check is as fast as possible
|
|
176
|
+
method_lambda = if from == :any
|
|
177
|
+
from_any_may_event_lambda(guard, cond, state_machine_name)
|
|
178
|
+
else
|
|
179
|
+
guarded_or_conditional_may_event_lambda(from, guard, cond, state_machine_name)
|
|
180
|
+
end
|
|
181
|
+
make_owner_method may_event_name, method_lambda
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def from_any_may_event_lambda(guard, cond, _state_machine_name)
|
|
185
|
+
if !guard && !cond
|
|
186
|
+
-> { true } # unguarded transition from any state
|
|
187
|
+
elsif !cond
|
|
188
|
+
guard # guarded transition from any state
|
|
189
|
+
elsif !guard
|
|
190
|
+
cond # conditional unguarded transition from any state
|
|
191
|
+
else
|
|
192
|
+
-> { instance_exec(&guard) && instance_exec(&cond) }
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def guarded_or_conditional_may_event_lambda(from, guard, cond, state_machine_name)
|
|
197
|
+
if !guard && !cond
|
|
198
|
+
guardless_may_event_lambda(from, state_machine_name)
|
|
199
|
+
elsif !cond
|
|
200
|
+
guarded_may_event_lambda(from, guard, state_machine_name)
|
|
201
|
+
elsif !guard
|
|
202
|
+
guarded_may_event_lambda(from, cond, state_machine_name)
|
|
203
|
+
else
|
|
204
|
+
guarded_and_conditional_may_event_lambda(from, guard, cond, state_machine_name)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def guarded_may_event_lambda(from, guard, state_machine_name)
|
|
209
|
+
if from.is_a?(Array)
|
|
210
|
+
lambda { # guarded transition from choice of states
|
|
211
|
+
current = send(state_machine_name)
|
|
212
|
+
from.include?(current) && instance_exec(&guard)
|
|
213
|
+
}
|
|
214
|
+
else
|
|
215
|
+
lambda { # guarded transition from one state
|
|
216
|
+
current = send(state_machine_name)
|
|
217
|
+
from == current && instance_exec(&guard)
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def guarded_and_conditional_may_event_lambda(from, guard, cond, state_machine_name)
|
|
223
|
+
if from.is_a?(Array)
|
|
224
|
+
lambda { # guarded transition from choice of states
|
|
225
|
+
current = send(state_machine_name)
|
|
226
|
+
from.include?(current) && instance_exec(&guard) && instance_exec(&cond)
|
|
227
|
+
}
|
|
228
|
+
else
|
|
229
|
+
lambda { # guarded transition from one state
|
|
230
|
+
current = send(state_machine_name)
|
|
231
|
+
from == current && instance_exec(&guard) && instance_exec(&cond)
|
|
232
|
+
}
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def guardless_may_event_lambda(from, state_machine_name)
|
|
237
|
+
if from.is_a?(Array)
|
|
238
|
+
lambda { # unguarded transition from choice of states
|
|
239
|
+
current = send(state_machine_name)
|
|
240
|
+
from.include?(current)
|
|
241
|
+
}
|
|
242
|
+
else
|
|
243
|
+
lambda { # unguarded transition from one state
|
|
244
|
+
current = send(state_machine_name)
|
|
245
|
+
from == current
|
|
246
|
+
}
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def setup_base_methods
|
|
251
|
+
var_name = "@#{name}"
|
|
252
|
+
fsm = self
|
|
253
|
+
make_owner_method @name, lambda {
|
|
254
|
+
instance_variable_get(var_name) ||
|
|
255
|
+
fsm.initial_state
|
|
256
|
+
}
|
|
257
|
+
make_owner_method "#{@name}_states", -> { fsm.states }
|
|
258
|
+
make_owner_method "#{@name}_events", -> { fsm.events }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def make_owner_method(method_name, method_definition)
|
|
262
|
+
@owner_class.define_method(method_name, method_definition)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
private
|
|
267
|
+
|
|
268
|
+
def state_match?(from, current)
|
|
269
|
+
return true if from == :any
|
|
270
|
+
return from.include?(current) if from.is_a?(Array)
|
|
271
|
+
|
|
272
|
+
from == current
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def cannot_transition?(from, cond, current)
|
|
276
|
+
(from && !state_match?(from, current)) || (cond && !instance_exec(&cond))
|
|
277
|
+
end
|
|
278
|
+
end
|
data/lib/simply_fsm/version.rb
CHANGED
data/lib/simply_fsm.rb
CHANGED
|
@@ -1,280 +1,4 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "simply_fsm/version"
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# Include *SimplyFSM* in a class to be able to defined state machines.
|
|
7
|
-
#
|
|
8
|
-
module SimplyFSM
|
|
9
|
-
#
|
|
10
|
-
# Provides a +state_machine+ for the including class.
|
|
11
|
-
def self.included(base)
|
|
12
|
-
base.extend(ClassMethods)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
# Defines the constructor for defining a state machine
|
|
17
|
-
module ClassMethods
|
|
18
|
-
#
|
|
19
|
-
# Declare a state machine called +name+ which can then be defined
|
|
20
|
-
# by a DSL defined by the methods of *StateMachine*.
|
|
21
|
-
#
|
|
22
|
-
# @param [String] name of the state machine.
|
|
23
|
-
# @param [Hash] opts to specify options such as:
|
|
24
|
-
# - +fail+ lambda that is called with the event name when any event fails to transition
|
|
25
|
-
#
|
|
26
|
-
def state_machine(name, opts = {}, &block)
|
|
27
|
-
fsm = StateMachine.new(name, self, fail: opts[:fail])
|
|
28
|
-
fsm.instance_eval(&block)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
##
|
|
33
|
-
# The DSL for defining a state machine. These methods are used within the declaration of a +state_machine+.
|
|
34
|
-
#
|
|
35
|
-
# @attr_reader [String] initial_state The initial state of the state machine
|
|
36
|
-
# @attr_reader [Array] states All the states of the state machine
|
|
37
|
-
# @attr_reader [Array] events All the events of the state machine
|
|
38
|
-
# @attr_reader [String] name
|
|
39
|
-
# @attr_reader [String] full_name The name of the owning class combined with the state machine's name
|
|
40
|
-
#
|
|
41
|
-
class StateMachine
|
|
42
|
-
attr_reader :initial_state, :states, :events, :name, :full_name
|
|
43
|
-
|
|
44
|
-
#
|
|
45
|
-
# @!visibility private
|
|
46
|
-
def initialize(name, owner_class, fail: nil)
|
|
47
|
-
@owner_class = owner_class
|
|
48
|
-
@name = name.to_sym
|
|
49
|
-
@full_name = "#{owner_class.name}/#{name}"
|
|
50
|
-
@states = []
|
|
51
|
-
@events = []
|
|
52
|
-
@initial_state = nil
|
|
53
|
-
@fail_handler = fail
|
|
54
|
-
|
|
55
|
-
setup_base_methods
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
#
|
|
59
|
-
# Declare a supported +state_name+, and optionally specify one as the +initial+ state.
|
|
60
|
-
#
|
|
61
|
-
# @param [String] state_name
|
|
62
|
-
# @param [Boolean] initial to indicate if this is the initial state of the state machine
|
|
63
|
-
#
|
|
64
|
-
def state(state_name, initial: false)
|
|
65
|
-
return if state_name.nil? || @states.include?(state_name)
|
|
66
|
-
|
|
67
|
-
status = state_name.to_sym
|
|
68
|
-
state_machine_name = @name
|
|
69
|
-
@states << status
|
|
70
|
-
@initial_state = status if initial
|
|
71
|
-
|
|
72
|
-
make_owner_method "#{state_name}?", lambda {
|
|
73
|
-
send(state_machine_name) == status
|
|
74
|
-
}
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
##
|
|
78
|
-
# Define an event by +event_name+
|
|
79
|
-
#
|
|
80
|
-
# @param [String] event_name
|
|
81
|
-
# @param [Hash,Array] transitions either one (Hash) or many (Array of Hashes) transitions +from+ one state +to+ another state.
|
|
82
|
-
# @param [Lambda] guard if specified must return +true+ before any transitions are attempted
|
|
83
|
-
# @param [Lambda] fail called with event name if specified when all the attempted transitions fail
|
|
84
|
-
# @yield when the transition attempt succeeds.
|
|
85
|
-
def event(event_name, transitions:, guard: nil, fail: nil, &after)
|
|
86
|
-
return unless event_exists?(event_name) && transitions
|
|
87
|
-
|
|
88
|
-
@events << event_name
|
|
89
|
-
may_event_name = "may_#{event_name}?"
|
|
90
|
-
|
|
91
|
-
if transitions.is_a?(Array)
|
|
92
|
-
setup_multi_transition_may_event_method transitions: transitions, guard: guard,
|
|
93
|
-
may_event_name: may_event_name
|
|
94
|
-
setup_multi_transition_event_method event_name,
|
|
95
|
-
transitions: transitions, guard: guard,
|
|
96
|
-
var_name: "@#{@name}", fail: fail || @fail_handler
|
|
97
|
-
return
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
to = transitions[:to]
|
|
101
|
-
setup_may_event_method may_event_name, transitions[:from] || :any, transitions[:when], guard
|
|
102
|
-
setup_event_method event_name, var_name: "@#{@name}",
|
|
103
|
-
may_event_name: may_event_name, to: to,
|
|
104
|
-
fail: fail || @fail_handler, &after
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
private
|
|
108
|
-
|
|
109
|
-
def setup_multi_transition_may_event_method(transitions:, guard:, may_event_name:)
|
|
110
|
-
state_machine_name = @name
|
|
111
|
-
|
|
112
|
-
make_owner_method may_event_name, lambda {
|
|
113
|
-
if !guard || instance_exec(&guard)
|
|
114
|
-
current = send(state_machine_name)
|
|
115
|
-
# Check each transition, and first one that succeeds ends the scan
|
|
116
|
-
transitions.each do |t|
|
|
117
|
-
next if cannot_transition?(t[:from], t[:when], current)
|
|
118
|
-
|
|
119
|
-
return true
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
false
|
|
123
|
-
}
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def setup_fail_lambda_for(fail)
|
|
127
|
-
return unless fail
|
|
128
|
-
|
|
129
|
-
if fail.is_a?(String) || fail.is_a?(Symbol)
|
|
130
|
-
->(event_name) { send(fail, event_name) }
|
|
131
|
-
else
|
|
132
|
-
fail
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def setup_multi_transition_event_method(event_name, transitions:, guard:, var_name:, fail:)
|
|
137
|
-
state_machine_name = @name
|
|
138
|
-
fail_lambda = setup_fail_lambda_for(fail)
|
|
139
|
-
make_owner_method event_name, lambda {
|
|
140
|
-
if !guard || instance_exec(&guard)
|
|
141
|
-
current = send(state_machine_name)
|
|
142
|
-
# Check each transition, and first one that succeeds ends the scan
|
|
143
|
-
transitions.each do |t|
|
|
144
|
-
next if cannot_transition?(t[:from], t[:when], current)
|
|
145
|
-
|
|
146
|
-
instance_variable_set(var_name, t[:to])
|
|
147
|
-
return true
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
instance_exec(event_name, &fail_lambda) if fail_lambda
|
|
151
|
-
false
|
|
152
|
-
}
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def event_exists?(event_name)
|
|
156
|
-
event_name && !@events.include?(event_name)
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def setup_event_method(event_name, var_name:, may_event_name:, to:, fail:, &after)
|
|
160
|
-
fail_lambda = setup_fail_lambda_for(fail)
|
|
161
|
-
method_lambda = lambda {
|
|
162
|
-
if send(may_event_name)
|
|
163
|
-
instance_variable_set(var_name, to)
|
|
164
|
-
instance_exec(&after) if after
|
|
165
|
-
return true
|
|
166
|
-
end
|
|
167
|
-
instance_exec(event_name, &fail_lambda) if fail_lambda
|
|
168
|
-
false
|
|
169
|
-
}
|
|
170
|
-
make_owner_method event_name, method_lambda
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def setup_may_event_method(may_event_name, from, cond, guard)
|
|
174
|
-
state_machine_name = @name
|
|
175
|
-
#
|
|
176
|
-
# Instead of one "may_event?" method that checks all variations every time it's called, here we check
|
|
177
|
-
# the event definition and define the most optimal lambda to ensure the check is as fast as possible
|
|
178
|
-
method_lambda = if from == :any
|
|
179
|
-
from_any_may_event_lambda(guard, cond, state_machine_name)
|
|
180
|
-
else
|
|
181
|
-
guarded_or_conditional_may_event_lambda(from, guard, cond, state_machine_name)
|
|
182
|
-
end
|
|
183
|
-
make_owner_method may_event_name, method_lambda
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def from_any_may_event_lambda(guard, cond, _state_machine_name)
|
|
187
|
-
if !guard && !cond
|
|
188
|
-
-> { true } # unguarded transition from any state
|
|
189
|
-
elsif !cond
|
|
190
|
-
guard # guarded transition from any state
|
|
191
|
-
elsif !guard
|
|
192
|
-
cond # conditional unguarded transition from any state
|
|
193
|
-
else
|
|
194
|
-
-> { instance_exec(&guard) && instance_exec(&cond) }
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def guarded_or_conditional_may_event_lambda(from, guard, cond, state_machine_name)
|
|
199
|
-
if !guard && !cond
|
|
200
|
-
guardless_may_event_lambda(from, state_machine_name)
|
|
201
|
-
elsif !cond
|
|
202
|
-
guarded_may_event_lambda(from, guard, state_machine_name)
|
|
203
|
-
elsif !guard
|
|
204
|
-
guarded_may_event_lambda(from, cond, state_machine_name)
|
|
205
|
-
else
|
|
206
|
-
guarded_and_conditional_may_event_lambda(from, guard, cond, state_machine_name)
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def guarded_may_event_lambda(from, guard, state_machine_name)
|
|
211
|
-
if from.is_a?(Array)
|
|
212
|
-
lambda { # guarded transition from choice of states
|
|
213
|
-
current = send(state_machine_name)
|
|
214
|
-
from.include?(current) && instance_exec(&guard)
|
|
215
|
-
}
|
|
216
|
-
else
|
|
217
|
-
lambda { # guarded transition from one state
|
|
218
|
-
current = send(state_machine_name)
|
|
219
|
-
from == current && instance_exec(&guard)
|
|
220
|
-
}
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def guarded_and_conditional_may_event_lambda(from, guard, cond, state_machine_name)
|
|
225
|
-
if from.is_a?(Array)
|
|
226
|
-
lambda { # guarded transition from choice of states
|
|
227
|
-
current = send(state_machine_name)
|
|
228
|
-
from.include?(current) && instance_exec(&guard) && instance_exec(&cond)
|
|
229
|
-
}
|
|
230
|
-
else
|
|
231
|
-
lambda { # guarded transition from one state
|
|
232
|
-
current = send(state_machine_name)
|
|
233
|
-
from == current && instance_exec(&guard) && instance_exec(&cond)
|
|
234
|
-
}
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def guardless_may_event_lambda(from, state_machine_name)
|
|
239
|
-
if from.is_a?(Array)
|
|
240
|
-
lambda { # unguarded transition from choice of states
|
|
241
|
-
current = send(state_machine_name)
|
|
242
|
-
from.include?(current)
|
|
243
|
-
}
|
|
244
|
-
else
|
|
245
|
-
lambda { # unguarded transition from one state
|
|
246
|
-
current = send(state_machine_name)
|
|
247
|
-
from == current
|
|
248
|
-
}
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
def setup_base_methods
|
|
253
|
-
var_name = "@#{name}"
|
|
254
|
-
fsm = self
|
|
255
|
-
make_owner_method @name, lambda {
|
|
256
|
-
instance_variable_get(var_name) ||
|
|
257
|
-
fsm.initial_state
|
|
258
|
-
}
|
|
259
|
-
make_owner_method "#{@name}_states", -> { fsm.states }
|
|
260
|
-
make_owner_method "#{@name}_events", -> { fsm.events }
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
def make_owner_method(method_name, method_definition)
|
|
264
|
-
@owner_class.define_method(method_name, method_definition)
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
private
|
|
269
|
-
|
|
270
|
-
def state_match?(from, current)
|
|
271
|
-
return true if from == :any
|
|
272
|
-
return from.include?(current) if from.is_a?(Array)
|
|
273
|
-
|
|
274
|
-
from == current
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
def cannot_transition?(from, cond, current)
|
|
278
|
-
(from && !state_match?(from, current)) || (cond && !instance_exec(&cond))
|
|
279
|
-
end
|
|
280
|
-
end
|
|
4
|
+
require "simply_fsm/simply_fsm"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simply_fsm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- nogginly
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-05-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rdoc
|
|
@@ -74,6 +74,7 @@ files:
|
|
|
74
74
|
- bin/console
|
|
75
75
|
- bin/setup
|
|
76
76
|
- lib/simply_fsm.rb
|
|
77
|
+
- lib/simply_fsm/simply_fsm.rb
|
|
77
78
|
- lib/simply_fsm/version.rb
|
|
78
79
|
- simply_fsm.gemspec
|
|
79
80
|
- spec/spec_helper.rb
|