finite_machine 0.11.2 → 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 +5 -5
- data/CHANGELOG.md +80 -0
- data/LICENSE.txt +1 -1
- data/README.md +679 -624
- data/lib/finite_machine.rb +35 -45
- data/lib/finite_machine/async_call.rb +5 -21
- data/lib/finite_machine/callable.rb +4 -4
- data/lib/finite_machine/catchable.rb +24 -14
- data/lib/finite_machine/choice_merger.rb +20 -20
- data/lib/finite_machine/const.rb +16 -0
- data/lib/finite_machine/definition.rb +3 -3
- data/lib/finite_machine/dsl.rb +98 -151
- data/lib/finite_machine/env.rb +4 -2
- data/lib/finite_machine/event_definition.rb +7 -15
- data/lib/finite_machine/{events_chain.rb → events_map.rb} +40 -53
- data/lib/finite_machine/hook_event.rb +60 -61
- data/lib/finite_machine/hooks.rb +44 -36
- data/lib/finite_machine/listener.rb +2 -2
- data/lib/finite_machine/logger.rb +5 -4
- data/lib/finite_machine/{event_queue.rb → message_queue.rb} +76 -32
- data/lib/finite_machine/observer.rb +71 -34
- data/lib/finite_machine/safety.rb +16 -14
- data/lib/finite_machine/state_definition.rb +3 -5
- data/lib/finite_machine/state_machine.rb +93 -76
- data/lib/finite_machine/state_parser.rb +55 -83
- data/lib/finite_machine/subscribers.rb +2 -2
- data/lib/finite_machine/threadable.rb +3 -1
- data/lib/finite_machine/transition.rb +34 -34
- data/lib/finite_machine/transition_builder.rb +23 -32
- data/lib/finite_machine/transition_event.rb +12 -11
- data/lib/finite_machine/two_phase_lock.rb +8 -6
- data/lib/finite_machine/undefined_transition.rb +5 -6
- data/lib/finite_machine/version.rb +2 -2
- metadata +58 -142
- data/.gitignore +0 -18
- data/.rspec +0 -5
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -26
- data/Gemfile +0 -15
- data/Rakefile +0 -8
- data/assets/finite_machine_logo.png +0 -0
- data/examples/atm.rb +0 -45
- data/examples/bug_system.rb +0 -145
- data/finite_machine.gemspec +0 -23
- data/lib/finite_machine/async_proxy.rb +0 -30
- data/spec/integration/system_spec.rb +0 -95
- data/spec/spec_helper.rb +0 -33
- data/spec/unit/alias_target_spec.rb +0 -108
- data/spec/unit/async_events_spec.rb +0 -138
- data/spec/unit/callable/call_spec.rb +0 -113
- data/spec/unit/callbacks_spec.rb +0 -942
- data/spec/unit/can_spec.rb +0 -98
- data/spec/unit/choice_spec.rb +0 -331
- data/spec/unit/define_spec.rb +0 -55
- data/spec/unit/definition_spec.rb +0 -115
- data/spec/unit/event_names_spec.rb +0 -19
- data/spec/unit/event_queue_spec.rb +0 -52
- data/spec/unit/events_chain/add_spec.rb +0 -25
- data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
- data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
- data/spec/unit/events_chain/clear_spec.rb +0 -15
- data/spec/unit/events_chain/events_spec.rb +0 -18
- data/spec/unit/events_chain/inspect_spec.rb +0 -24
- data/spec/unit/events_chain/match_transition_spec.rb +0 -37
- data/spec/unit/events_chain/move_to_spec.rb +0 -48
- data/spec/unit/events_chain/states_for_spec.rb +0 -17
- data/spec/unit/events_spec.rb +0 -459
- data/spec/unit/handlers_spec.rb +0 -152
- data/spec/unit/hook_event/build_spec.rb +0 -15
- data/spec/unit/hook_event/eql_spec.rb +0 -36
- data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
- data/spec/unit/hook_event/initialize_spec.rb +0 -25
- data/spec/unit/hook_event/notify_spec.rb +0 -14
- data/spec/unit/hooks/call_spec.rb +0 -24
- data/spec/unit/hooks/clear_spec.rb +0 -16
- data/spec/unit/hooks/inspect_spec.rb +0 -17
- data/spec/unit/hooks/register_spec.rb +0 -22
- data/spec/unit/if_unless_spec.rb +0 -353
- data/spec/unit/initial_spec.rb +0 -222
- data/spec/unit/inspect_spec.rb +0 -17
- data/spec/unit/is_spec.rb +0 -55
- data/spec/unit/log_transitions_spec.rb +0 -30
- data/spec/unit/logger_spec.rb +0 -38
- data/spec/unit/respond_to_spec.rb +0 -38
- data/spec/unit/state_parser/inspect_spec.rb +0 -25
- data/spec/unit/state_parser/parse_spec.rb +0 -59
- data/spec/unit/states_spec.rb +0 -34
- data/spec/unit/subscribers_spec.rb +0 -42
- data/spec/unit/target_spec.rb +0 -225
- data/spec/unit/terminated_spec.rb +0 -95
- data/spec/unit/transition/check_conditions_spec.rb +0 -54
- data/spec/unit/transition/inspect_spec.rb +0 -25
- data/spec/unit/transition/matches_spec.rb +0 -23
- data/spec/unit/transition/states_spec.rb +0 -31
- data/spec/unit/transition/to_state_spec.rb +0 -27
- data/spec/unit/trigger_spec.rb +0 -22
- data/spec/unit/undefined_transition/eql_spec.rb +0 -17
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
data/lib/finite_machine.rb
CHANGED
@@ -1,33 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "logger"
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require "finite_machine/safety"
|
11
|
-
require "finite_machine/callable"
|
12
|
-
require "finite_machine/catchable"
|
13
|
-
require "finite_machine/choice_merger"
|
14
|
-
require "finite_machine/async_proxy"
|
15
|
-
require "finite_machine/async_call"
|
16
|
-
require "finite_machine/hook_event"
|
17
|
-
require "finite_machine/env"
|
18
|
-
require "finite_machine/event_queue"
|
19
|
-
require "finite_machine/events_chain"
|
20
|
-
require "finite_machine/logger"
|
21
|
-
require "finite_machine/transition"
|
22
|
-
require "finite_machine/transition_builder"
|
23
|
-
require "finite_machine/transition_event"
|
24
|
-
require "finite_machine/dsl"
|
25
|
-
require "finite_machine/definition"
|
26
|
-
require "finite_machine/state_machine"
|
27
|
-
require "finite_machine/subscribers"
|
28
|
-
require "finite_machine/observer"
|
29
|
-
require "finite_machine/listener"
|
30
|
-
require "finite_machine/two_phase_lock"
|
4
|
+
|
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"
|
31
10
|
|
32
11
|
module FiniteMachine
|
33
12
|
# Default state name
|
@@ -36,22 +15,19 @@ module FiniteMachine
|
|
36
15
|
# Initial default event name
|
37
16
|
DEFAULT_EVENT_NAME = :init
|
38
17
|
|
39
|
-
# Describe any state
|
40
|
-
ANY_STATE = :any
|
18
|
+
# Describe any transition state
|
19
|
+
ANY_STATE = Const.new(:any)
|
41
20
|
|
42
21
|
# Describe any event name
|
43
|
-
ANY_EVENT = :any_event
|
44
|
-
|
45
|
-
# Returned when transition has successfully performed
|
46
|
-
SUCCEEDED = 1
|
47
|
-
|
48
|
-
# Returned when transition is cancelled in callback
|
49
|
-
CANCELLED = 2
|
22
|
+
ANY_EVENT = Const.new(:any_event)
|
50
23
|
|
51
24
|
# When transition between states is invalid
|
52
25
|
TransitionError = Class.new(::StandardError)
|
53
26
|
|
54
|
-
#
|
27
|
+
# When failed to process callback
|
28
|
+
CallbackError = Class.new(::StandardError)
|
29
|
+
|
30
|
+
# Raised when transitioning to invalid state
|
55
31
|
InvalidStateError = Class.new(::ArgumentError)
|
56
32
|
|
57
33
|
InvalidEventError = Class.new(::NoMethodError)
|
@@ -66,30 +42,44 @@ module FiniteMachine
|
|
66
42
|
MissingInitialStateError = Class.new(::StandardError)
|
67
43
|
|
68
44
|
# Raised when event queue is already dead
|
69
|
-
|
45
|
+
MessageQueueDeadError = Class.new(::StandardError)
|
70
46
|
|
71
47
|
# Raised when argument is already defined
|
72
48
|
AlreadyDefinedError = Class.new(::ArgumentError)
|
73
49
|
|
74
|
-
|
50
|
+
module ClassMethods
|
75
51
|
attr_accessor :logger
|
76
52
|
|
77
|
-
#
|
78
|
-
# and then delegate calls to StateMachine instance etc...
|
53
|
+
# Initialize an instance of finite machine
|
79
54
|
#
|
80
55
|
# @example
|
81
|
-
# FiniteMachine.
|
56
|
+
# FiniteMachine.new do
|
82
57
|
# ...
|
83
58
|
# end
|
84
59
|
#
|
85
60
|
# @return [FiniteMachine::StateMachine]
|
86
61
|
#
|
87
62
|
# @api public
|
88
|
-
def
|
63
|
+
def new(*args, &block)
|
89
64
|
StateMachine.new(*args, &block)
|
90
65
|
end
|
91
|
-
|
66
|
+
|
67
|
+
# A factory method for creating reusable FiniteMachine definitions
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# TrafficLights = FiniteMachine.define
|
71
|
+
# lights_fm_a = TrafficLights.new
|
72
|
+
# lights_fm_b = TrafficLights.new
|
73
|
+
#
|
74
|
+
# @return [Class]
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def define(&block)
|
78
|
+
Class.new(Definition, &block)
|
79
|
+
end
|
92
80
|
end
|
81
|
+
|
82
|
+
extend ClassMethods
|
93
83
|
end # FiniteMachine
|
94
84
|
|
95
85
|
FiniteMachine.logger = Logger.new(STDERR)
|
@@ -1,14 +1,13 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
|
-
# An asynchronouse call representation
|
4
|
+
# An immutable asynchronouse call representation that wraps
|
5
|
+
# the {Callable} object
|
5
6
|
#
|
6
|
-
# Used internally by {
|
7
|
+
# Used internally by {MessageQueue} to dispatch events
|
7
8
|
#
|
8
9
|
# @api private
|
9
10
|
class AsyncCall
|
10
|
-
include Threadable
|
11
|
-
|
12
11
|
# Create asynchronous call instance
|
13
12
|
#
|
14
13
|
# @param [Object] context
|
@@ -19,15 +18,12 @@ module FiniteMachine
|
|
19
18
|
# @example
|
20
19
|
# AsyncCall.new(context, Callable.new(:method), :a, :b)
|
21
20
|
#
|
22
|
-
# @return [self]
|
23
|
-
#
|
24
21
|
# @api public
|
25
22
|
def initialize(context, callable, *args, &block)
|
26
23
|
@context = context
|
27
24
|
@callable = callable
|
28
25
|
@arguments = args.dup
|
29
26
|
@block = block
|
30
|
-
@mutex = Mutex.new
|
31
27
|
freeze
|
32
28
|
end
|
33
29
|
|
@@ -37,19 +33,7 @@ module FiniteMachine
|
|
37
33
|
#
|
38
34
|
# @api private
|
39
35
|
def dispatch
|
40
|
-
@
|
41
|
-
callable.call(context, *arguments, &block)
|
42
|
-
end
|
36
|
+
@callable.call(@context, *@arguments, &@block)
|
43
37
|
end
|
44
|
-
|
45
|
-
protected
|
46
|
-
|
47
|
-
attr_threadsafe :context
|
48
|
-
|
49
|
-
attr_threadsafe :callable
|
50
|
-
|
51
|
-
attr_threadsafe :arguments
|
52
|
-
|
53
|
-
attr_threadsafe :block
|
54
38
|
end # AsyncCall
|
55
39
|
end # FiniteMachine
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
4
|
# A generic interface for executing strings, symbol methods or procs.
|
@@ -20,7 +20,7 @@ module FiniteMachine
|
|
20
20
|
#
|
21
21
|
# @api public
|
22
22
|
def invert
|
23
|
-
|
23
|
+
->(*args, &block) { !call(*args, &block) }
|
24
24
|
end
|
25
25
|
|
26
26
|
# Execute action
|
@@ -34,10 +34,10 @@ module FiniteMachine
|
|
34
34
|
target.public_send(object.to_sym, *args, &block)
|
35
35
|
when String
|
36
36
|
string = args.empty? ? "-> { #{object} }" : "-> { #{object}(*#{args}) }"
|
37
|
-
value = eval
|
37
|
+
value = eval(string)
|
38
38
|
target.instance_exec(&value)
|
39
39
|
when ::Proc
|
40
|
-
object.arity.zero? ?
|
40
|
+
object.arity.zero? ? object.call : object.call(target, *args)
|
41
41
|
else
|
42
42
|
raise ArgumentError, "Unknown callable #{object}"
|
43
43
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
4
|
# A mixin to allow for specifying error handlers
|
5
5
|
module Catchable
|
6
|
-
|
6
|
+
# Extends object with error handling methods
|
7
|
+
#
|
8
|
+
# @api private
|
7
9
|
def self.included(base)
|
8
10
|
base.module_eval do
|
9
11
|
attr_threadsafe :error_handlers, default: []
|
@@ -31,7 +33,7 @@ module FiniteMachine
|
|
31
33
|
if block_given?
|
32
34
|
options[:with] = block
|
33
35
|
else
|
34
|
-
raise ArgumentError,
|
36
|
+
raise ArgumentError, "Need to provide error handler."
|
35
37
|
end
|
36
38
|
end
|
37
39
|
evaluate_exceptions(exceptions, options)
|
@@ -69,7 +71,7 @@ module FiniteMachine
|
|
69
71
|
#
|
70
72
|
# @api private
|
71
73
|
def extract_const(class_name)
|
72
|
-
class_name.split(
|
74
|
+
class_name.split("::").reduce(FiniteMachine) do |constant, part|
|
73
75
|
constant.const_get(part)
|
74
76
|
end
|
75
77
|
end
|
@@ -83,9 +85,9 @@ module FiniteMachine
|
|
83
85
|
target.method(handler)
|
84
86
|
when Proc
|
85
87
|
if handler.arity.zero?
|
86
|
-
|
88
|
+
-> { instance_exec(&handler) }
|
87
89
|
else
|
88
|
-
|
90
|
+
->(exception) { instance_exec(exception, &handler) }
|
89
91
|
end
|
90
92
|
end
|
91
93
|
end
|
@@ -99,16 +101,24 @@ module FiniteMachine
|
|
99
101
|
# @api private
|
100
102
|
def evaluate_exceptions(exceptions, options)
|
101
103
|
exceptions.each do |exception|
|
102
|
-
key =
|
103
|
-
exception.name
|
104
|
-
elsif exception.is_a?(String)
|
105
|
-
exception
|
106
|
-
else
|
107
|
-
raise ArgumentError, "#{exception} isn't an Exception"
|
108
|
-
end
|
109
|
-
|
104
|
+
key = extract_exception_name(exception)
|
110
105
|
error_handlers << [key, options[:with]]
|
111
106
|
end
|
112
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
|
113
123
|
end # Catchable
|
114
124
|
end # FiniteMachine
|
@@ -1,44 +1,44 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "transition_builder"
|
2
4
|
|
3
5
|
module FiniteMachine
|
4
6
|
# A class responsible for merging choice options
|
5
7
|
class ChoiceMerger
|
6
|
-
include Threadable
|
7
|
-
|
8
|
-
# The context where choice is executed
|
9
|
-
attr_threadsafe :machine
|
10
|
-
|
11
|
-
# The options passed in to the machine
|
12
|
-
attr_threadsafe :options
|
13
|
-
|
14
8
|
# Initialize a ChoiceMerger
|
15
9
|
#
|
10
|
+
# @param [StateMachine] machine
|
11
|
+
# @param [String] name
|
12
|
+
# @param [Hash] transitions
|
13
|
+
# the transitions and attributes
|
14
|
+
#
|
16
15
|
# @api private
|
17
|
-
def initialize(machine,
|
18
|
-
|
19
|
-
|
16
|
+
def initialize(machine, name, transitions = {})
|
17
|
+
@machine = machine
|
18
|
+
@name = name
|
19
|
+
@transitions = transitions
|
20
20
|
end
|
21
21
|
|
22
22
|
# Create choice transition
|
23
23
|
#
|
24
24
|
# @example
|
25
|
-
# event from: :green do
|
25
|
+
# event :stop, from: :green do
|
26
26
|
# choice :yellow
|
27
27
|
# end
|
28
28
|
#
|
29
29
|
# @param [Symbol] to
|
30
30
|
# the to state
|
31
|
-
# @param [Hash]
|
31
|
+
# @param [Hash] conditions
|
32
|
+
# the conditions associated with this choice
|
32
33
|
#
|
33
34
|
# @return [FiniteMachine::Transition]
|
34
35
|
#
|
35
36
|
# @api public
|
36
|
-
def choice(to,
|
37
|
-
|
38
|
-
|
39
|
-
transition_builder
|
40
|
-
transition_builder.call(options[:from] => to)
|
37
|
+
def choice(to, conditions = {})
|
38
|
+
transition_builder = TransitionBuilder.new(@machine, @name,
|
39
|
+
@transitions.merge(conditions))
|
40
|
+
transition_builder.call(@transitions[:from] => to)
|
41
41
|
end
|
42
|
-
|
42
|
+
alias default choice
|
43
43
|
end # ChoiceMerger
|
44
44
|
end # FiniteMachine
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
4
|
# A class responsible for defining standalone state machine
|
@@ -38,7 +38,7 @@ module FiniteMachine
|
|
38
38
|
# @api public
|
39
39
|
def self.new(*args)
|
40
40
|
context = self
|
41
|
-
FiniteMachine.
|
41
|
+
FiniteMachine.new(*args) do
|
42
42
|
context.deferreds.each { |d| d.call(self) }
|
43
43
|
end
|
44
44
|
end
|
@@ -49,7 +49,7 @@ module FiniteMachine
|
|
49
49
|
def self.inherited(subclass)
|
50
50
|
super
|
51
51
|
|
52
|
-
|
52
|
+
deferreds.each { |d| subclass.add_deferred(d) }
|
53
53
|
end
|
54
54
|
|
55
55
|
# Delay lookup of DSL method
|
data/lib/finite_machine/dsl.rb
CHANGED
@@ -1,59 +1,98 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "choice_merger"
|
4
|
+
require_relative "safety"
|
5
|
+
require_relative "transition_builder"
|
2
6
|
|
3
7
|
module FiniteMachine
|
4
8
|
# A generic DSL for describing the state machine
|
5
9
|
class GenericDSL
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
10
|
+
# Initialize a generic DSL
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
def initialize(machine, attrs)
|
14
|
+
@machine = machine
|
15
|
+
@attrs = attrs
|
11
16
|
end
|
12
17
|
|
13
|
-
|
14
|
-
|
15
|
-
|
18
|
+
# Expose any state constant
|
19
|
+
# @api public
|
20
|
+
def any_state
|
21
|
+
ANY_STATE
|
22
|
+
end
|
16
23
|
|
17
|
-
#
|
18
|
-
#
|
24
|
+
# Expose any event constant
|
19
25
|
# @api public
|
20
|
-
def
|
21
|
-
|
22
|
-
self.machine = machine
|
26
|
+
def any_event
|
27
|
+
ANY_EVENT
|
23
28
|
end
|
24
29
|
|
25
30
|
# Delegate attributes to machine instance
|
26
31
|
#
|
27
32
|
# @api private
|
28
33
|
def method_missing(method_name, *args, &block)
|
29
|
-
if machine.respond_to?(method_name)
|
30
|
-
machine.send(method_name, *args, &block)
|
34
|
+
if @machine.respond_to?(method_name)
|
35
|
+
@machine.send(method_name, *args, &block)
|
31
36
|
else
|
32
37
|
super
|
33
38
|
end
|
34
39
|
end
|
35
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
|
+
|
48
|
+
# Configure state machine properties
|
49
|
+
#
|
50
|
+
# @api private
|
36
51
|
def call(&block)
|
37
|
-
|
38
|
-
# top_level.instance_eval(&block)
|
52
|
+
instance_eval(&block)
|
39
53
|
end
|
40
54
|
end # GenericDSL
|
41
55
|
|
42
56
|
# A class responsible for adding state machine specific dsl
|
43
57
|
class DSL < GenericDSL
|
44
|
-
|
45
|
-
|
46
|
-
attr_threadsafe :initial_event
|
58
|
+
include Safety
|
47
59
|
|
48
60
|
# Initialize top level DSL
|
49
61
|
#
|
50
62
|
# @api public
|
51
|
-
def initialize(machine, attrs
|
63
|
+
def initialize(machine, attrs)
|
52
64
|
super(machine, attrs)
|
53
|
-
machine.state = FiniteMachine::DEFAULT_STATE
|
54
|
-
self.defer = true
|
55
65
|
|
56
|
-
|
66
|
+
@machine.state = FiniteMachine::DEFAULT_STATE
|
67
|
+
@defer_initial = true
|
68
|
+
@silent_initial = true
|
69
|
+
|
70
|
+
initial(@attrs[:initial]) if @attrs[:initial]
|
71
|
+
terminal(@attrs[:terminal]) if @attrs[:terminal]
|
72
|
+
log_transitions(@attrs.fetch(:log_transitions, false))
|
73
|
+
end
|
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
|
57
96
|
end
|
58
97
|
|
59
98
|
# Define initial state
|
@@ -89,9 +128,9 @@ module FiniteMachine
|
|
89
128
|
# @api public
|
90
129
|
def initial(value, options = {})
|
91
130
|
state = (value && !value.is_a?(Hash)) ? value : raise_missing_state
|
92
|
-
name,
|
93
|
-
|
94
|
-
event(name, FiniteMachine::DEFAULT_STATE => state, silent:
|
131
|
+
name, @defer_initial, @silent_initial = *parse_initial(options)
|
132
|
+
@initial_event = name
|
133
|
+
event(name, FiniteMachine::DEFAULT_STATE => state, silent: @silent_initial)
|
95
134
|
end
|
96
135
|
|
97
136
|
# Trigger initial event
|
@@ -100,51 +139,8 @@ module FiniteMachine
|
|
100
139
|
#
|
101
140
|
# @api private
|
102
141
|
def trigger_init
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
# Attach state machine to an object
|
107
|
-
#
|
108
|
-
# This allows state machine to initiate events in the context
|
109
|
-
# of a particular object
|
110
|
-
#
|
111
|
-
# @example
|
112
|
-
# FiniteMachine.define do
|
113
|
-
# target :red
|
114
|
-
# end
|
115
|
-
#
|
116
|
-
# @param [Object] object
|
117
|
-
#
|
118
|
-
# @return [FiniteMachine::StateMachine]
|
119
|
-
#
|
120
|
-
# @api public
|
121
|
-
def target(object = nil)
|
122
|
-
if object.nil?
|
123
|
-
env.target
|
124
|
-
else
|
125
|
-
env.target = object
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# Use alternative name for target
|
130
|
-
#
|
131
|
-
# @example
|
132
|
-
# target_alias: :car
|
133
|
-
#
|
134
|
-
# callbacks {
|
135
|
-
# on_transition do |event|
|
136
|
-
# car.state = event.to
|
137
|
-
# end
|
138
|
-
# }
|
139
|
-
#
|
140
|
-
# @param [Symbol] alias_name
|
141
|
-
# the name to alias target to
|
142
|
-
#
|
143
|
-
# @return [FiniteMachine::StateMachine]
|
144
|
-
#
|
145
|
-
# @api public
|
146
|
-
def alias_target(alias_name)
|
147
|
-
env.aliases << alias_name.to_sym
|
142
|
+
method = @silent_initial ? :transition : :trigger
|
143
|
+
@machine.public_send(method, :"#{@initial_event}") unless @defer_initial
|
148
144
|
end
|
149
145
|
|
150
146
|
# Define terminal state
|
@@ -156,42 +152,47 @@ module FiniteMachine
|
|
156
152
|
#
|
157
153
|
# @api public
|
158
154
|
def terminal(*values)
|
159
|
-
self.
|
155
|
+
self.terminal_states = values
|
160
156
|
end
|
161
157
|
|
162
|
-
#
|
158
|
+
# Create event and associate transition
|
163
159
|
#
|
164
160
|
# @example
|
165
|
-
#
|
166
|
-
#
|
167
|
-
# end
|
161
|
+
# event :go, :green => :yellow
|
162
|
+
# event :go, :green => :yellow, if: :lights_on?
|
168
163
|
#
|
169
|
-
# @
|
164
|
+
# @param [Symbol] name
|
165
|
+
# the event name
|
166
|
+
# @param [Hash] transitions
|
167
|
+
# the event transitions and conditions
|
168
|
+
#
|
169
|
+
# @return [Transition]
|
170
170
|
#
|
171
171
|
# @api public
|
172
|
-
def
|
173
|
-
|
172
|
+
def event(name, transitions = {}, &block)
|
173
|
+
detect_event_conflict!(name) if machine.auto_methods?
|
174
|
+
|
175
|
+
if block_given?
|
176
|
+
merger = ChoiceMerger.new(machine, name, transitions)
|
177
|
+
merger.instance_eval(&block)
|
178
|
+
else
|
179
|
+
transition_builder = TransitionBuilder.new(machine, name, transitions)
|
180
|
+
transition_builder.call(transitions)
|
181
|
+
end
|
174
182
|
end
|
175
183
|
|
176
|
-
#
|
184
|
+
# Add error handler
|
177
185
|
#
|
178
|
-
# @
|
179
|
-
# callbacks do
|
180
|
-
# on_enter :green do |event| ... end
|
181
|
-
# end
|
186
|
+
# @param [Array] exceptions
|
182
187
|
#
|
183
|
-
# @
|
188
|
+
# @example
|
189
|
+
# handle InvalidStateError, with: :log_errors
|
184
190
|
#
|
185
|
-
# @
|
186
|
-
def callbacks(&block)
|
187
|
-
observer.call(&block)
|
188
|
-
end
|
189
|
-
|
190
|
-
# Error handler that throws exception when machine is in illegal state
|
191
|
+
# @return [Array[Exception]]
|
191
192
|
#
|
192
193
|
# @api public
|
193
|
-
def
|
194
|
-
|
194
|
+
def handle(*exceptions, &block)
|
195
|
+
@machine.handle(*exceptions, &block)
|
195
196
|
end
|
196
197
|
|
197
198
|
# Decide whether to log transitions
|
@@ -203,16 +204,6 @@ module FiniteMachine
|
|
203
204
|
|
204
205
|
private
|
205
206
|
|
206
|
-
# Initialize state machine properties based off attributes
|
207
|
-
#
|
208
|
-
# @api private
|
209
|
-
def initialize_attrs
|
210
|
-
attrs[:initial] && initial(attrs[:initial])
|
211
|
-
attrs[:target] && target(attrs[:target])
|
212
|
-
attrs[:terminal] && terminal(attrs[:terminal])
|
213
|
-
log_transitions(attrs.fetch(:log_transitions, false))
|
214
|
-
end
|
215
|
-
|
216
207
|
# Parse initial options
|
217
208
|
#
|
218
209
|
# @param [Hash] options
|
@@ -236,52 +227,8 @@ module FiniteMachine
|
|
236
227
|
#
|
237
228
|
# @api private
|
238
229
|
def raise_missing_state
|
239
|
-
|
240
|
-
|
230
|
+
raise MissingInitialStateError,
|
231
|
+
"Provide state to transition :to for the initial event"
|
241
232
|
end
|
242
233
|
end # DSL
|
243
|
-
|
244
|
-
# A DSL for describing events
|
245
|
-
class EventsDSL < GenericDSL
|
246
|
-
include Safety
|
247
|
-
# Create event and associate transition
|
248
|
-
#
|
249
|
-
# @example
|
250
|
-
# event :go, :green => :yellow
|
251
|
-
# event :go, :green => :yellow, if: :lights_on?
|
252
|
-
#
|
253
|
-
# @return [Transition]
|
254
|
-
#
|
255
|
-
# @api public
|
256
|
-
def event(name, attrs = {}, &block)
|
257
|
-
sync_exclusive do
|
258
|
-
detect_event_conflict!(name)
|
259
|
-
attributes = attrs.merge!(name: name)
|
260
|
-
if block_given?
|
261
|
-
merger = ChoiceMerger.new(machine, attributes)
|
262
|
-
merger.instance_eval(&block)
|
263
|
-
else
|
264
|
-
transition_builder = TransitionBuilder.new(machine, attributes)
|
265
|
-
transition_builder.call(attrs)
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end # EventsDSL
|
270
|
-
|
271
|
-
# A DSL for describing error conditions
|
272
|
-
class ErrorsDSL < GenericDSL
|
273
|
-
# Add error handler
|
274
|
-
#
|
275
|
-
# @param [Array] exceptions
|
276
|
-
#
|
277
|
-
# @example
|
278
|
-
# handle InvalidStateError, with: :log_errors
|
279
|
-
#
|
280
|
-
# @return [Array[Exception]]
|
281
|
-
#
|
282
|
-
# @api public
|
283
|
-
def handle(*exceptions, &block)
|
284
|
-
machine.handle(*exceptions, &block)
|
285
|
-
end
|
286
|
-
end # ErrorsDSL
|
287
234
|
end # FiniteMachine
|