state_machines 0.6.0 → 0.30.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/README.md +205 -14
- data/lib/state_machines/branch.rb +20 -17
- data/lib/state_machines/callback.rb +13 -12
- data/lib/state_machines/core.rb +3 -3
- data/lib/state_machines/core_ext/class/state_machine.rb +2 -0
- data/lib/state_machines/core_ext.rb +2 -0
- data/lib/state_machines/error.rb +7 -4
- data/lib/state_machines/eval_helpers.rb +93 -26
- data/lib/state_machines/event.rb +41 -29
- data/lib/state_machines/event_collection.rb +6 -5
- data/lib/state_machines/extensions.rb +7 -5
- data/lib/state_machines/helper_module.rb +3 -1
- data/lib/state_machines/integrations/base.rb +3 -1
- data/lib/state_machines/integrations.rb +13 -14
- data/lib/state_machines/machine/action_hooks.rb +53 -0
- data/lib/state_machines/machine/callbacks.rb +59 -0
- data/lib/state_machines/machine/class_methods.rb +93 -0
- data/lib/state_machines/machine/configuration.rb +124 -0
- data/lib/state_machines/machine/event_methods.rb +59 -0
- data/lib/state_machines/machine/helper_generators.rb +125 -0
- data/lib/state_machines/machine/integration.rb +70 -0
- data/lib/state_machines/machine/parsing.rb +77 -0
- data/lib/state_machines/machine/rendering.rb +17 -0
- data/lib/state_machines/machine/scoping.rb +44 -0
- data/lib/state_machines/machine/state_methods.rb +101 -0
- data/lib/state_machines/machine/utilities.rb +85 -0
- data/lib/state_machines/machine/validation.rb +39 -0
- data/lib/state_machines/machine.rb +83 -673
- data/lib/state_machines/machine_collection.rb +23 -15
- data/lib/state_machines/macro_methods.rb +4 -2
- data/lib/state_machines/matcher.rb +8 -5
- data/lib/state_machines/matcher_helpers.rb +3 -1
- data/lib/state_machines/node_collection.rb +23 -18
- data/lib/state_machines/options_validator.rb +72 -0
- data/lib/state_machines/path.rb +7 -5
- data/lib/state_machines/path_collection.rb +7 -4
- data/lib/state_machines/state.rb +76 -47
- data/lib/state_machines/state_collection.rb +5 -3
- data/lib/state_machines/state_context.rb +11 -8
- data/lib/state_machines/stdio_renderer.rb +74 -0
- data/lib/state_machines/syntax_validator.rb +57 -0
- data/lib/state_machines/test_helper.rb +568 -0
- data/lib/state_machines/transition.rb +45 -41
- data/lib/state_machines/transition_collection.rb +27 -26
- data/lib/state_machines/version.rb +3 -1
- data/lib/state_machines.rb +4 -1
- metadata +32 -16
- data/lib/state_machines/assertions.rb +0 -40
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'options_validator'
|
4
|
+
|
1
5
|
module StateMachines
|
2
6
|
# Represents a module which will get evaluated within the context of a state.
|
3
7
|
#
|
@@ -50,7 +54,6 @@ module StateMachines
|
|
50
54
|
# vehicle.simulate = true
|
51
55
|
# vehicle.moving? # => false
|
52
56
|
class StateContext < Module
|
53
|
-
|
54
57
|
include EvalHelpers
|
55
58
|
|
56
59
|
# The state machine for which this context's state is defined
|
@@ -66,7 +69,7 @@ module StateMachines
|
|
66
69
|
|
67
70
|
state_name = state.name
|
68
71
|
machine_name = machine.name
|
69
|
-
@condition =
|
72
|
+
@condition = ->(object) { object.class.state_machine(machine_name).states.matches?(object, state_name) }
|
70
73
|
end
|
71
74
|
|
72
75
|
# Creates a new transition that determines what to change the current state
|
@@ -86,15 +89,15 @@ module StateMachines
|
|
86
89
|
# See StateMachines::Machine#transition for a description of the possible
|
87
90
|
# configurations for defining transitions.
|
88
91
|
def transition(options)
|
89
|
-
|
92
|
+
StateMachines::OptionsValidator.assert_valid_keys!(options, :from, :to, :on, :if, :unless)
|
90
93
|
raise ArgumentError, 'Must specify :on event' unless options[:on]
|
91
94
|
raise ArgumentError, 'Must specify either :to or :from state' unless !options[:to] ^ !options[:from]
|
92
95
|
|
93
|
-
machine.transition(options.merge(options[:to] ? {from: state.name} : {to: state.name}))
|
96
|
+
machine.transition(options.merge(options[:to] ? { from: state.name } : { to: state.name }))
|
94
97
|
end
|
95
98
|
|
96
99
|
# Hooks in condition-merging to methods that don't exist in this module
|
97
|
-
def method_missing(*args, &
|
100
|
+
def method_missing(*args, &)
|
98
101
|
# Get the configuration
|
99
102
|
if args.last.is_a?(Hash)
|
100
103
|
options = args.last
|
@@ -119,13 +122,13 @@ module StateMachines
|
|
119
122
|
object = condition_args.first || self
|
120
123
|
|
121
124
|
proxy.evaluate_method(object, proxy_condition) &&
|
122
|
-
|
123
|
-
|
125
|
+
Array(if_condition).all? { |condition| proxy.evaluate_method(object, condition) } &&
|
126
|
+
!Array(unless_condition).any? { |condition| proxy.evaluate_method(object, condition) }
|
124
127
|
end
|
125
128
|
|
126
129
|
# Evaluate the method on the owner class with the condition proxied
|
127
130
|
# through
|
128
|
-
machine.owner_class.send(*args, &
|
131
|
+
machine.owner_class.send(*args, &)
|
129
132
|
end
|
130
133
|
end
|
131
134
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StateMachines
|
4
|
+
module STDIORenderer
|
5
|
+
module_function def draw_machine(machine, io: $stdout)
|
6
|
+
draw_class(machine: machine, io: io)
|
7
|
+
draw_states(machine: machine, io: io)
|
8
|
+
draw_events(machine: machine, io: io)
|
9
|
+
end
|
10
|
+
|
11
|
+
module_function def draw_class(machine:, io: $stdout)
|
12
|
+
io.puts "Class: #{machine.owner_class.name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
module_function def draw_states(machine:, io: $stdout)
|
16
|
+
io.puts ' States:'
|
17
|
+
if machine.states.to_a.empty?
|
18
|
+
io.puts ' - None'
|
19
|
+
else
|
20
|
+
machine.states.each do |state|
|
21
|
+
io.puts " - #{state.name}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module_function def draw_event(event, _graph, options: {}, io: $stdout)
|
27
|
+
io = io || options[:io] || $stdout
|
28
|
+
io.puts " Event: #{event.name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
module_function def draw_branch(branch, _graph, _event, options: {}, io: $stdout)
|
32
|
+
io = io || options[:io] || $stdout
|
33
|
+
io.puts " Branch: #{branch.inspect}"
|
34
|
+
end
|
35
|
+
|
36
|
+
module_function def draw_state(state, _graph, options: {}, io: $stdout)
|
37
|
+
io = io || options[:io] || $stdout
|
38
|
+
io.puts " State: #{state.name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
module_function def draw_events(machine:, io: $stdout)
|
42
|
+
io.puts ' Events:'
|
43
|
+
if machine.events.to_a.empty?
|
44
|
+
io.puts ' - None'
|
45
|
+
else
|
46
|
+
machine.events.each do |event|
|
47
|
+
io.puts " - #{event.name}"
|
48
|
+
event.branches.each do |branch|
|
49
|
+
branch.state_requirements.each do |requirement|
|
50
|
+
out = +' - '
|
51
|
+
out << "#{draw_requirement(requirement[:from])} => #{draw_requirement(requirement[:to])}"
|
52
|
+
out << " IF #{branch.if_condition}" if branch.if_condition
|
53
|
+
out << " UNLESS #{branch.unless_condition}" if branch.unless_condition
|
54
|
+
io.puts out
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module_function def draw_requirement(requirement)
|
62
|
+
case requirement
|
63
|
+
when StateMachines::BlacklistMatcher
|
64
|
+
"ALL EXCEPT #{requirement.values.join(', ')}"
|
65
|
+
when StateMachines::AllMatcher
|
66
|
+
'ALL'
|
67
|
+
when StateMachines::LoopbackMatcher
|
68
|
+
'SAME'
|
69
|
+
else
|
70
|
+
requirement.values.join(', ')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ripper'
|
4
|
+
|
5
|
+
module StateMachines
|
6
|
+
# Cross-platform syntax validation for eval strings
|
7
|
+
# Supports CRuby, JRuby, TruffleRuby via pluggable backends
|
8
|
+
module SyntaxValidator
|
9
|
+
# Public API: raises SyntaxError if code is invalid
|
10
|
+
def validate!(code, filename = '(eval)')
|
11
|
+
backend.validate!(code, filename)
|
12
|
+
end
|
13
|
+
module_function :validate!
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Lazily pick the best backend for this platform
|
18
|
+
# Prefer RubyVM for performance on CRuby, fallback to Ripper for compatibility
|
19
|
+
def backend
|
20
|
+
@backend ||= if RubyVmBackend.available?
|
21
|
+
RubyVmBackend
|
22
|
+
else
|
23
|
+
RipperBackend
|
24
|
+
end
|
25
|
+
end
|
26
|
+
module_function :backend
|
27
|
+
|
28
|
+
# MRI backend using RubyVM::InstructionSequence
|
29
|
+
module RubyVmBackend
|
30
|
+
def available?
|
31
|
+
RUBY_ENGINE == 'ruby'
|
32
|
+
end
|
33
|
+
module_function :available?
|
34
|
+
|
35
|
+
def validate!(code, filename)
|
36
|
+
# compile will raise a SyntaxError on bad syntax
|
37
|
+
RubyVM::InstructionSequence.compile(code, filename)
|
38
|
+
true
|
39
|
+
end
|
40
|
+
module_function :validate!
|
41
|
+
end
|
42
|
+
|
43
|
+
# Universal Ruby backend via Ripper
|
44
|
+
module RipperBackend
|
45
|
+
def validate!(code, filename)
|
46
|
+
sexp = Ripper.sexp(code)
|
47
|
+
if sexp.nil?
|
48
|
+
# Ripper.sexp returns nil on a parse error, but no exception
|
49
|
+
raise SyntaxError, "syntax error in #{filename}"
|
50
|
+
end
|
51
|
+
|
52
|
+
true
|
53
|
+
end
|
54
|
+
module_function :validate!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|