finite_machine 0.13.0 → 0.14.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/CHANGELOG.md +8 -0
- data/LICENSE.txt +1 -1
- data/README.md +201 -143
- data/lib/finite_machine.rb +6 -6
- data/lib/finite_machine/catchable.rb +20 -12
- data/lib/finite_machine/choice_merger.rb +2 -2
- data/lib/finite_machine/definition.rb +1 -1
- data/lib/finite_machine/dsl.rb +35 -5
- data/lib/finite_machine/env.rb +1 -1
- data/lib/finite_machine/events_map.rb +5 -6
- data/lib/finite_machine/hook_event.rb +1 -1
- data/lib/finite_machine/hooks.rb +2 -2
- data/lib/finite_machine/message_queue.rb +6 -3
- data/lib/finite_machine/observer.rb +7 -7
- data/lib/finite_machine/safety.rb +6 -6
- data/lib/finite_machine/state_machine.rb +19 -16
- data/lib/finite_machine/state_parser.rb +8 -8
- data/lib/finite_machine/subscribers.rb +1 -1
- data/lib/finite_machine/threadable.rb +1 -1
- data/lib/finite_machine/transition.rb +6 -5
- data/lib/finite_machine/transition_builder.rb +4 -4
- data/lib/finite_machine/transition_event.rb +1 -1
- data/lib/finite_machine/version.rb +1 -1
- metadata +12 -9
data/lib/finite_machine.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "logger"
|
4
4
|
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require_relative
|
5
|
+
require_relative "finite_machine/const"
|
6
|
+
require_relative "finite_machine/logger"
|
7
|
+
require_relative "finite_machine/definition"
|
8
|
+
require_relative "finite_machine/state_machine"
|
9
|
+
require_relative "finite_machine/version"
|
10
10
|
|
11
11
|
module FiniteMachine
|
12
12
|
# Default state name
|
@@ -33,7 +33,7 @@ module FiniteMachine
|
|
33
33
|
if block_given?
|
34
34
|
options[:with] = block
|
35
35
|
else
|
36
|
-
raise ArgumentError,
|
36
|
+
raise ArgumentError, "Need to provide error handler."
|
37
37
|
end
|
38
38
|
end
|
39
39
|
evaluate_exceptions(exceptions, options)
|
@@ -71,7 +71,7 @@ module FiniteMachine
|
|
71
71
|
#
|
72
72
|
# @api private
|
73
73
|
def extract_const(class_name)
|
74
|
-
class_name.split(
|
74
|
+
class_name.split("::").reduce(FiniteMachine) do |constant, part|
|
75
75
|
constant.const_get(part)
|
76
76
|
end
|
77
77
|
end
|
@@ -85,9 +85,9 @@ module FiniteMachine
|
|
85
85
|
target.method(handler)
|
86
86
|
when Proc
|
87
87
|
if handler.arity.zero?
|
88
|
-
|
88
|
+
-> { instance_exec(&handler) }
|
89
89
|
else
|
90
|
-
|
90
|
+
->(exception) { instance_exec(exception, &handler) }
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
@@ -101,16 +101,24 @@ module FiniteMachine
|
|
101
101
|
# @api private
|
102
102
|
def evaluate_exceptions(exceptions, options)
|
103
103
|
exceptions.each do |exception|
|
104
|
-
key =
|
105
|
-
exception.name
|
106
|
-
elsif exception.is_a?(String)
|
107
|
-
exception
|
108
|
-
else
|
109
|
-
raise ArgumentError, "#{exception} isn't an Exception"
|
110
|
-
end
|
111
|
-
|
104
|
+
key = extract_exception_name(exception)
|
112
105
|
error_handlers << [key, options[:with]]
|
113
106
|
end
|
114
107
|
end
|
108
|
+
|
109
|
+
# Extract exception name
|
110
|
+
#
|
111
|
+
# @param [Class,Exception,String] exception
|
112
|
+
#
|
113
|
+
# @api private
|
114
|
+
def extract_exception_name(exception)
|
115
|
+
if exception.is_a?(Class) && exception <= Exception
|
116
|
+
exception.name
|
117
|
+
elsif exception.is_a?(String)
|
118
|
+
exception
|
119
|
+
else
|
120
|
+
raise ArgumentError, "#{exception} isn't an Exception"
|
121
|
+
end
|
122
|
+
end
|
115
123
|
end # Catchable
|
116
124
|
end # FiniteMachine
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "transition_builder"
|
4
4
|
|
5
5
|
module FiniteMachine
|
6
6
|
# A class responsible for merging choice options
|
@@ -39,6 +39,6 @@ module FiniteMachine
|
|
39
39
|
@transitions.merge(conditions))
|
40
40
|
transition_builder.call(@transitions[:from] => to)
|
41
41
|
end
|
42
|
-
|
42
|
+
alias default choice
|
43
43
|
end # ChoiceMerger
|
44
44
|
end # FiniteMachine
|
data/lib/finite_machine/dsl.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
3
|
+
require_relative "choice_merger"
|
4
|
+
require_relative "safety"
|
5
|
+
require_relative "transition_builder"
|
6
6
|
|
7
7
|
module FiniteMachine
|
8
8
|
# A generic DSL for describing the state machine
|
@@ -38,6 +38,13 @@ module FiniteMachine
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
# Check if message can be handled by this DSL
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
def respond_to_missing?(method_name, include_private = false)
|
45
|
+
@machine.respond_to?(method_name) || super
|
46
|
+
end
|
47
|
+
|
41
48
|
# Configure state machine properties
|
42
49
|
#
|
43
50
|
# @api private
|
@@ -65,6 +72,29 @@ module FiniteMachine
|
|
65
72
|
log_transitions(@attrs.fetch(:log_transitions, false))
|
66
73
|
end
|
67
74
|
|
75
|
+
# Add aliases for the target object
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# FiniteMachine.define do
|
79
|
+
# target_alias :engine
|
80
|
+
#
|
81
|
+
# on_transition do |event|
|
82
|
+
# engine.state = event.to
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# @param [Array<Symbol>] aliases
|
87
|
+
# the names for target alias
|
88
|
+
#
|
89
|
+
# @api public
|
90
|
+
def alias_target(*aliases)
|
91
|
+
aliases.each do |alias_name|
|
92
|
+
next if env.aliases.include?(alias_name)
|
93
|
+
|
94
|
+
env.aliases << alias_name
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
68
98
|
# Define initial state
|
69
99
|
#
|
70
100
|
# @param [Symbol] value
|
@@ -197,8 +227,8 @@ module FiniteMachine
|
|
197
227
|
#
|
198
228
|
# @api private
|
199
229
|
def raise_missing_state
|
200
|
-
|
201
|
-
|
230
|
+
raise MissingInitialStateError,
|
231
|
+
"Provide state to transition :to for the initial event"
|
202
232
|
end
|
203
233
|
end # DSL
|
204
234
|
end # FiniteMachine
|
data/lib/finite_machine/env.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "concurrent/map"
|
4
|
+
require "forwardable"
|
5
5
|
|
6
|
-
require_relative
|
7
|
-
require_relative
|
6
|
+
require_relative "threadable"
|
7
|
+
require_relative "undefined_transition"
|
8
8
|
|
9
9
|
module FiniteMachine
|
10
10
|
# A class responsible for storing mappings between event namess and
|
@@ -182,8 +182,7 @@ module FiniteMachine
|
|
182
182
|
# @api public
|
183
183
|
def match_transition_with(name, from_state, *conditions)
|
184
184
|
find(name).find do |trans|
|
185
|
-
trans.matches?(from_state) &&
|
186
|
-
trans.check_conditions(*conditions)
|
185
|
+
trans.matches?(from_state) && trans.check_conditions(*conditions)
|
187
186
|
end
|
188
187
|
end
|
189
188
|
|
data/lib/finite_machine/hooks.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require
|
3
|
+
require_relative "listener"
|
4
|
+
require "thread"
|
5
5
|
|
6
6
|
module FiniteMachine
|
7
7
|
# Allows for storage of asynchronous messages such as events
|
@@ -31,6 +31,7 @@ module FiniteMachine
|
|
31
31
|
# @api private
|
32
32
|
def start
|
33
33
|
return if running?
|
34
|
+
|
34
35
|
@mutex.synchronize { spawn_thread }
|
35
36
|
end
|
36
37
|
|
@@ -114,6 +115,7 @@ module FiniteMachine
|
|
114
115
|
# @api public
|
115
116
|
def join(timeout = nil)
|
116
117
|
return unless @thread
|
118
|
+
|
117
119
|
timeout.nil? ? @thread.join : @thread.join(timeout)
|
118
120
|
end
|
119
121
|
|
@@ -126,7 +128,7 @@ module FiniteMachine
|
|
126
128
|
#
|
127
129
|
# @api public
|
128
130
|
def shutdown
|
129
|
-
|
131
|
+
raise EventQueueDeadError, "event queue already dead" if @dead
|
130
132
|
|
131
133
|
queue = []
|
132
134
|
@mutex.synchronize do
|
@@ -184,6 +186,7 @@ module FiniteMachine
|
|
184
186
|
end
|
185
187
|
event = @queue.pop
|
186
188
|
break unless event
|
189
|
+
|
187
190
|
notify_listeners(event)
|
188
191
|
event.dispatch
|
189
192
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require_relative
|
3
|
+
require_relative "async_call"
|
4
|
+
require_relative "callable"
|
5
|
+
require_relative "hook_event"
|
6
|
+
require_relative "hooks"
|
7
|
+
require_relative "message_queue"
|
8
|
+
require_relative "safety"
|
9
|
+
require_relative "transition_event"
|
10
10
|
|
11
11
|
module FiniteMachine
|
12
12
|
# A class responsible for observing state changes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "hook_event"
|
4
4
|
|
5
5
|
module FiniteMachine
|
6
6
|
# Module responsible for safety checks against known methods
|
@@ -39,7 +39,7 @@ module FiniteMachine
|
|
39
39
|
name: event_name,
|
40
40
|
type: :instance,
|
41
41
|
method: method_name,
|
42
|
-
source:
|
42
|
+
source: "FiniteMachine"
|
43
43
|
}
|
44
44
|
end
|
45
45
|
end
|
@@ -89,8 +89,8 @@ module FiniteMachine
|
|
89
89
|
# @api private
|
90
90
|
def wrong_event_name?(name, event_type)
|
91
91
|
machine.states.include?(name) &&
|
92
|
-
|
93
|
-
|
92
|
+
!machine.events.include?(name) &&
|
93
|
+
event_type < HookEvent::Anyaction
|
94
94
|
end
|
95
95
|
|
96
96
|
# Check if state name exists
|
@@ -104,8 +104,8 @@ module FiniteMachine
|
|
104
104
|
# @api private
|
105
105
|
def wrong_state_name?(name, event_type)
|
106
106
|
machine.events.include?(name) &&
|
107
|
-
|
108
|
-
|
107
|
+
!machine.states.include?(name) &&
|
108
|
+
event_type < HookEvent::Anystate
|
109
109
|
end
|
110
110
|
|
111
111
|
def raise_invalid_callback_error(message)
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "forwardable"
|
4
4
|
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require_relative
|
10
|
-
require_relative
|
11
|
-
require_relative
|
12
|
-
require_relative
|
5
|
+
require_relative "catchable"
|
6
|
+
require_relative "dsl"
|
7
|
+
require_relative "env"
|
8
|
+
require_relative "events_map"
|
9
|
+
require_relative "hook_event"
|
10
|
+
require_relative "observer"
|
11
|
+
require_relative "threadable"
|
12
|
+
require_relative "subscribers"
|
13
13
|
|
14
14
|
module FiniteMachine
|
15
15
|
# Base class for state machine
|
@@ -64,7 +64,8 @@ module FiniteMachine
|
|
64
64
|
# Allow or not logging of transitions
|
65
65
|
attr_threadsafe :log_transitions
|
66
66
|
|
67
|
-
def_delegators :dsl, :initial, :terminal, :event, :trigger_init
|
67
|
+
def_delegators :dsl, :initial, :terminal, :event, :trigger_init,
|
68
|
+
:alias_target
|
68
69
|
|
69
70
|
# Initialize state machine
|
70
71
|
#
|
@@ -194,7 +195,7 @@ module FiniteMachine
|
|
194
195
|
# fsm.can?(:go) # => true
|
195
196
|
#
|
196
197
|
# @example
|
197
|
-
# fsm.can?(:go,
|
198
|
+
# fsm.can?(:go, "Piotr") # checks condition with parameter "Piotr"
|
198
199
|
#
|
199
200
|
# @param [String] event
|
200
201
|
#
|
@@ -202,7 +203,7 @@ module FiniteMachine
|
|
202
203
|
#
|
203
204
|
# @api public
|
204
205
|
def can?(*args)
|
205
|
-
event_name
|
206
|
+
event_name = args.shift
|
206
207
|
events_map.can_perform?(event_name, current, *args)
|
207
208
|
end
|
208
209
|
|
@@ -284,7 +285,7 @@ module FiniteMachine
|
|
284
285
|
else
|
285
286
|
exception = InvalidStateError
|
286
287
|
catch_error(exception) ||
|
287
|
-
|
288
|
+
raise(exception, "inappropriate current state '#{current}'")
|
288
289
|
|
289
290
|
false
|
290
291
|
end
|
@@ -390,8 +391,10 @@ module FiniteMachine
|
|
390
391
|
# @api public
|
391
392
|
def inspect
|
392
393
|
sync_shared do
|
393
|
-
"<##{self.class}:0x#{object_id.to_s(16)}
|
394
|
-
"@
|
394
|
+
"<##{self.class}:0x#{object_id.to_s(16)} " \
|
395
|
+
"@current=#{current.inspect} " \
|
396
|
+
"@states=#{states} " \
|
397
|
+
"@events=#{events} " \
|
395
398
|
"@transitions=#{events_map.state_transitions}>"
|
396
399
|
end
|
397
400
|
end
|
@@ -407,7 +410,7 @@ module FiniteMachine
|
|
407
410
|
#
|
408
411
|
# @api private
|
409
412
|
def raise_transition_error(error)
|
410
|
-
|
413
|
+
raise TransitionError, Logger.format_error(error)
|
411
414
|
end
|
412
415
|
|
413
416
|
# Forward the message to observer or self
|
@@ -7,7 +7,9 @@ module FiniteMachine
|
|
7
7
|
#
|
8
8
|
# @api private
|
9
9
|
class StateParser
|
10
|
-
|
10
|
+
NON_STATE_KEYS = %i[name if unless silent].freeze
|
11
|
+
|
12
|
+
STATE_KEYS = %i[from to].freeze
|
11
13
|
|
12
14
|
# Extract states from user defined attributes
|
13
15
|
#
|
@@ -35,7 +37,7 @@ module FiniteMachine
|
|
35
37
|
# @api private
|
36
38
|
def self.ensure_only_states!(attrs)
|
37
39
|
attributes = attrs.dup
|
38
|
-
|
40
|
+
NON_STATE_KEYS.each { |key| attributes.delete(key) }
|
39
41
|
raise_not_enough_transitions unless attributes.any?
|
40
42
|
attributes
|
41
43
|
end
|
@@ -69,7 +71,7 @@ module FiniteMachine
|
|
69
71
|
#
|
70
72
|
# @api public
|
71
73
|
def self.contains_from_to_keys?(attrs)
|
72
|
-
|
74
|
+
STATE_KEYS.any? { |key| attrs.keys.include?(key) }
|
73
75
|
end
|
74
76
|
private_class_method :contains_from_to_keys?
|
75
77
|
|
@@ -79,9 +81,8 @@ module FiniteMachine
|
|
79
81
|
#
|
80
82
|
# @api private
|
81
83
|
def self.convert_from_to_attributes_to_states_hash(attrs)
|
82
|
-
Array(attrs[:from] || ANY_STATE).
|
84
|
+
Array(attrs[:from] || ANY_STATE).each_with_object({}) do |state, hash|
|
83
85
|
hash[state] = attrs[:to] || state
|
84
|
-
hash
|
85
86
|
end
|
86
87
|
end
|
87
88
|
private_class_method :convert_from_to_attributes_to_states_hash
|
@@ -99,13 +100,12 @@ module FiniteMachine
|
|
99
100
|
#
|
100
101
|
# @api private
|
101
102
|
def self.convert_attributes_to_states_hash(attrs)
|
102
|
-
attrs.
|
103
|
+
attrs.each_with_object({}) do |(k, v), hash|
|
103
104
|
if k.respond_to?(:to_ary)
|
104
105
|
k.each { |el| hash[el] = v }
|
105
106
|
else
|
106
107
|
hash[k] = v
|
107
108
|
end
|
108
|
-
hash
|
109
109
|
end
|
110
110
|
end
|
111
111
|
private_class_method :convert_attributes_to_states_hash
|
@@ -119,7 +119,7 @@ module FiniteMachine
|
|
119
119
|
#
|
120
120
|
# @api private
|
121
121
|
def self.raise_not_enough_transitions
|
122
|
-
raise NotEnoughTransitionsError,
|
122
|
+
raise NotEnoughTransitionsError, "please provide state transitions"
|
123
123
|
end
|
124
124
|
private_class_method :raise_not_enough_transitions
|
125
125
|
end # StateParser
|