finite_machine 0.11.3 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|