finite_machine 0.11.3 → 0.12.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 +34 -0
- data/README.md +564 -569
- data/Rakefile +5 -1
- data/benchmarks/memory_profile.rb +11 -0
- data/benchmarks/memory_usage.rb +16 -9
- data/finite_machine.gemspec +10 -3
- data/lib/finite_machine.rb +34 -46
- data/lib/finite_machine/async_call.rb +5 -21
- data/lib/finite_machine/callable.rb +4 -4
- data/lib/finite_machine/catchable.rb +4 -2
- data/lib/finite_machine/choice_merger.rb +19 -19
- data/lib/finite_machine/const.rb +16 -0
- data/lib/finite_machine/definition.rb +2 -2
- data/lib/finite_machine/dsl.rb +66 -149
- 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} +39 -51
- 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/message_queue.rb +39 -30
- data/lib/finite_machine/observer.rb +55 -37
- data/lib/finite_machine/safety.rb +12 -10
- data/lib/finite_machine/state_definition.rb +3 -5
- data/lib/finite_machine/state_machine.rb +83 -64
- data/lib/finite_machine/state_parser.rb +51 -79
- data/lib/finite_machine/subscribers.rb +1 -1
- data/lib/finite_machine/threadable.rb +3 -1
- data/lib/finite_machine/transition.rb +30 -31
- 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 +3 -1
- data/lib/finite_machine/undefined_transition.rb +5 -6
- data/lib/finite_machine/version.rb +2 -2
- data/spec/integration/system_spec.rb +36 -38
- data/spec/performance/benchmark_spec.rb +13 -21
- data/spec/unit/alias_target_spec.rb +22 -41
- data/spec/unit/async_callbacks_spec.rb +8 -13
- data/spec/unit/auto_methods_spec.rb +44 -0
- data/spec/unit/callable/call_spec.rb +1 -3
- data/spec/unit/callbacks_spec.rb +372 -463
- data/spec/unit/can_spec.rb +13 -23
- data/spec/unit/cancel_callbacks_spec.rb +46 -0
- data/spec/unit/choice_spec.rb +105 -141
- data/spec/unit/define_spec.rb +31 -31
- data/spec/unit/definition_spec.rb +24 -41
- data/spec/unit/event_names_spec.rb +6 -10
- data/spec/unit/events_map/add_spec.rb +23 -0
- data/spec/unit/events_map/choice_transition_spec.rb +25 -0
- data/spec/unit/events_map/clear_spec.rb +13 -0
- data/spec/unit/events_map/events_spec.rb +16 -0
- data/spec/unit/events_map/inspect_spec.rb +22 -0
- data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
- data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
- data/spec/unit/events_map/states_for_spec.rb +17 -0
- data/spec/unit/events_spec.rb +91 -160
- data/spec/unit/handlers_spec.rb +34 -66
- data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
- data/spec/unit/hook_event/build_spec.rb +1 -3
- data/spec/unit/hook_event/eql_spec.rb +1 -3
- data/spec/unit/hook_event/initialize_spec.rb +2 -4
- data/spec/unit/hook_event/notify_spec.rb +2 -4
- data/spec/unit/hooks/clear_spec.rb +1 -1
- data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
- data/spec/unit/hooks/inspect_spec.rb +16 -8
- data/spec/unit/hooks/register_spec.rb +4 -9
- data/spec/unit/if_unless_spec.rb +76 -115
- data/spec/unit/initial_spec.rb +50 -82
- data/spec/unit/inspect_spec.rb +14 -9
- data/spec/unit/is_spec.rb +12 -18
- data/spec/unit/log_transitions_spec.rb +4 -10
- data/spec/unit/logger_spec.rb +1 -3
- data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
- data/spec/unit/new_spec.rb +50 -0
- data/spec/unit/respond_to_spec.rb +2 -6
- data/spec/unit/state_parser/parse_spec.rb +9 -12
- data/spec/unit/states_spec.rb +12 -18
- data/spec/unit/subscribers_spec.rb +1 -3
- data/spec/unit/target_spec.rb +60 -93
- data/spec/unit/terminated_spec.rb +15 -25
- data/spec/unit/transition/check_conditions_spec.rb +16 -15
- data/spec/unit/transition/inspect_spec.rb +6 -6
- data/spec/unit/transition/matches_spec.rb +5 -7
- data/spec/unit/transition/states_spec.rb +5 -7
- data/spec/unit/transition/to_state_spec.rb +5 -13
- data/spec/unit/trigger_spec.rb +5 -9
- data/spec/unit/undefined_transition/eql_spec.rb +1 -3
- metadata +86 -49
- data/.gitignore +0 -18
- data/.rspec +0 -5
- data/.travis.yml +0 -27
- data/Gemfile +0 -16
- data/assets/finite_machine_logo.png +0 -0
- data/lib/finite_machine/async_proxy.rb +0 -55
- data/spec/unit/async_events_spec.rb +0 -107
- 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/states_for_spec.rb +0 -17
- data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
- data/spec/unit/state_parser/inspect_spec.rb +0 -25
data/Rakefile
CHANGED
data/benchmarks/memory_usage.rb
CHANGED
@@ -1,21 +1,28 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative '../lib/finite_machine'
|
4
4
|
|
5
|
-
|
5
|
+
5.times do
|
6
6
|
puts
|
7
7
|
|
8
8
|
GC.start
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
gc_before = GC.stat
|
11
|
+
objects_before = ObjectSpace.count_objects
|
12
|
+
p objects_before[:T_OBJECT]
|
12
13
|
|
13
14
|
1_000.times do
|
14
|
-
FiniteMachine.
|
15
|
+
FiniteMachine.new do
|
16
|
+
initial :green
|
17
|
+
|
18
|
+
events { event :slow, :green => :yellow }
|
19
|
+
end
|
15
20
|
end
|
16
21
|
|
17
|
-
|
18
|
-
|
22
|
+
objects_after = ObjectSpace.count_objects
|
23
|
+
gc_after = GC.stat
|
24
|
+
p objects_after[:T_OBJECT]
|
19
25
|
|
20
|
-
p "GC count: #{
|
26
|
+
p "GC count: #{gc_after[:count] - gc_before[:count]}"
|
27
|
+
p "Objects count: #{objects_after[:T_OBJECT] - objects_before[:T_OBJECT]}"
|
21
28
|
end
|
data/finite_machine.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
lib = File.expand_path('../lib', __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'finite_machine/version'
|
@@ -10,14 +9,22 @@ Gem::Specification.new do |spec|
|
|
10
9
|
spec.email = [""]
|
11
10
|
spec.description = %q{A minimal finite state machine with a straightforward syntax. You can quickly model states, add callbacks and use object-oriented techniques to integrate with ORMs.}
|
12
11
|
spec.summary = %q{A minimal finite state machine with a straightforward syntax.}
|
13
|
-
spec.homepage = "http://
|
12
|
+
spec.homepage = "http://piotrmurach.github.io/finite_machine/"
|
14
13
|
spec.license = "MIT"
|
15
14
|
|
16
|
-
spec.files =
|
15
|
+
spec.files = Dir['{lib,spec,examples,benchmarks}/**/*.rb']
|
16
|
+
spec.files += Dir['tasks/*', 'finite_machine.gemspec']
|
17
|
+
spec.files += Dir['README.md', 'CHANGELOG.md', 'LICENSE.txt', 'Rakefile']
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
20
|
spec.require_paths = ["lib"]
|
20
21
|
|
22
|
+
spec.required_ruby_version = '>= 2.0.0'
|
23
|
+
|
24
|
+
spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
|
25
|
+
|
21
26
|
spec.add_development_dependency 'bundler', '>= 1.5.0', '< 2.0'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.1'
|
28
|
+
spec.add_development_dependency 'rspec-benchmark', '~> 0.4.0'
|
22
29
|
spec.add_development_dependency 'rake'
|
23
30
|
end
|
data/lib/finite_machine.rb
CHANGED
@@ -1,33 +1,12 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
require
|
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/message_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"
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
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)
|
@@ -74,21 +50,33 @@ module FiniteMachine
|
|
74
50
|
class << self
|
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
|
89
|
-
StateMachine.new(*args, &block)
|
63
|
+
def new(*args, **options, &block)
|
64
|
+
StateMachine.new(*args, **options, &block)
|
65
|
+
end
|
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)
|
90
79
|
end
|
91
|
-
alias_method :new, :define
|
92
80
|
end
|
93
81
|
end # FiniteMachine
|
94
82
|
|
@@ -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: []
|
@@ -1,43 +1,43 @@
|
|
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_method :default, :choice
|
43
43
|
end # ChoiceMerger
|
@@ -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
|
data/lib/finite_machine/dsl.rb
CHANGED
@@ -1,59 +1,68 @@
|
|
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
|
+
# Configure state machine properties
|
42
|
+
#
|
43
|
+
# @api private
|
36
44
|
def call(&block)
|
37
|
-
|
38
|
-
# top_level.instance_eval(&block)
|
45
|
+
instance_eval(&block)
|
39
46
|
end
|
40
47
|
end # GenericDSL
|
41
48
|
|
42
49
|
# A class responsible for adding state machine specific dsl
|
43
50
|
class DSL < GenericDSL
|
44
|
-
|
45
|
-
|
46
|
-
attr_threadsafe :initial_event
|
51
|
+
include Safety
|
47
52
|
|
48
53
|
# Initialize top level DSL
|
49
54
|
#
|
50
55
|
# @api public
|
51
|
-
def initialize(machine, attrs
|
56
|
+
def initialize(machine, **attrs)
|
52
57
|
super(machine, attrs)
|
53
|
-
machine.state = FiniteMachine::DEFAULT_STATE
|
54
|
-
self.defer = true
|
55
58
|
|
56
|
-
|
59
|
+
@machine.state = FiniteMachine::DEFAULT_STATE
|
60
|
+
@defer_initial = true
|
61
|
+
@silent_initial = true
|
62
|
+
|
63
|
+
initial(@attrs[:initial]) if @attrs[:initial]
|
64
|
+
terminal(@attrs[:terminal]) if @attrs[:terminal]
|
65
|
+
log_transitions(@attrs.fetch(:log_transitions, false))
|
57
66
|
end
|
58
67
|
|
59
68
|
# Define initial state
|
@@ -87,11 +96,11 @@ module FiniteMachine
|
|
87
96
|
# @return [StateMachine]
|
88
97
|
#
|
89
98
|
# @api public
|
90
|
-
def initial(value, options
|
99
|
+
def initial(value, **options)
|
91
100
|
state = (value && !value.is_a?(Hash)) ? value : raise_missing_state
|
92
|
-
name,
|
93
|
-
|
94
|
-
event(name, FiniteMachine::DEFAULT_STATE => state, silent:
|
101
|
+
name, @defer_initial, @silent_initial = *parse_initial(options)
|
102
|
+
@initial_event = name
|
103
|
+
event(name, FiniteMachine::DEFAULT_STATE => state, silent: @silent_initial)
|
95
104
|
end
|
96
105
|
|
97
106
|
# Trigger initial event
|
@@ -100,51 +109,8 @@ module FiniteMachine
|
|
100
109
|
#
|
101
110
|
# @api private
|
102
111
|
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
|
112
|
+
method = @silent_initial ? :transition : :trigger
|
113
|
+
@machine.public_send(method, :"#{@initial_event}") unless @defer_initial
|
148
114
|
end
|
149
115
|
|
150
116
|
# Define terminal state
|
@@ -159,39 +125,44 @@ module FiniteMachine
|
|
159
125
|
self.final_state = values
|
160
126
|
end
|
161
127
|
|
162
|
-
#
|
128
|
+
# Create event and associate transition
|
163
129
|
#
|
164
130
|
# @example
|
165
|
-
#
|
166
|
-
#
|
167
|
-
# end
|
131
|
+
# event :go, :green => :yellow
|
132
|
+
# event :go, :green => :yellow, if: :lights_on?
|
168
133
|
#
|
169
|
-
# @
|
134
|
+
# @param [Symbol] name
|
135
|
+
# the event name
|
136
|
+
# @param [Hash] transitions
|
137
|
+
# the event transitions and conditions
|
138
|
+
#
|
139
|
+
# @return [Transition]
|
170
140
|
#
|
171
141
|
# @api public
|
172
|
-
def
|
173
|
-
|
142
|
+
def event(name, transitions = {}, &block)
|
143
|
+
detect_event_conflict!(name) if machine.auto_methods?
|
144
|
+
|
145
|
+
if block_given?
|
146
|
+
merger = ChoiceMerger.new(machine, name, transitions)
|
147
|
+
merger.instance_eval(&block)
|
148
|
+
else
|
149
|
+
transition_builder = TransitionBuilder.new(machine, name, transitions)
|
150
|
+
transition_builder.call(transitions)
|
151
|
+
end
|
174
152
|
end
|
175
153
|
|
176
|
-
#
|
154
|
+
# Add error handler
|
177
155
|
#
|
178
|
-
# @
|
179
|
-
# callbacks do
|
180
|
-
# on_enter :green do |event| ... end
|
181
|
-
# end
|
156
|
+
# @param [Array] exceptions
|
182
157
|
#
|
183
|
-
# @
|
158
|
+
# @example
|
159
|
+
# handle InvalidStateError, with: :log_errors
|
184
160
|
#
|
185
|
-
# @
|
186
|
-
def callbacks(&block)
|
187
|
-
observer.call(&block)
|
188
|
-
end
|
189
|
-
|
190
|
-
# Error handler that throws exception when machine is in illegal state
|
161
|
+
# @return [Array[Exception]]
|
191
162
|
#
|
192
163
|
# @api public
|
193
|
-
def
|
194
|
-
|
164
|
+
def handle(*exceptions, &block)
|
165
|
+
@machine.handle(*exceptions, &block)
|
195
166
|
end
|
196
167
|
|
197
168
|
# Decide whether to log transitions
|
@@ -203,16 +174,6 @@ module FiniteMachine
|
|
203
174
|
|
204
175
|
private
|
205
176
|
|
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
177
|
# Parse initial options
|
217
178
|
#
|
218
179
|
# @param [Hash] options
|
@@ -240,48 +201,4 @@ module FiniteMachine
|
|
240
201
|
'Provide state to transition :to for the initial event'
|
241
202
|
end
|
242
203
|
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
204
|
end # FiniteMachine
|