state_machines 0.20.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 +124 -13
- data/lib/state_machines/branch.rb +12 -13
- data/lib/state_machines/callback.rb +11 -12
- data/lib/state_machines/core.rb +0 -1
- data/lib/state_machines/error.rb +5 -4
- data/lib/state_machines/eval_helpers.rb +83 -45
- data/lib/state_machines/event.rb +23 -26
- data/lib/state_machines/event_collection.rb +4 -5
- data/lib/state_machines/extensions.rb +5 -5
- data/lib/state_machines/helper_module.rb +1 -1
- data/lib/state_machines/integrations/base.rb +1 -1
- data/lib/state_machines/integrations.rb +11 -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 +25 -11
- 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 +73 -617
- data/lib/state_machines/machine_collection.rb +18 -14
- data/lib/state_machines/macro_methods.rb +2 -2
- data/lib/state_machines/matcher.rb +6 -6
- data/lib/state_machines/matcher_helpers.rb +1 -1
- data/lib/state_machines/node_collection.rb +18 -17
- data/lib/state_machines/path.rb +2 -4
- data/lib/state_machines/path_collection.rb +2 -3
- data/lib/state_machines/state.rb +6 -5
- data/lib/state_machines/state_collection.rb +3 -3
- data/lib/state_machines/state_context.rb +6 -7
- data/lib/state_machines/stdio_renderer.rb +16 -16
- data/lib/state_machines/syntax_validator.rb +57 -0
- data/lib/state_machines/test_helper.rb +290 -27
- data/lib/state_machines/transition.rb +43 -41
- data/lib/state_machines/transition_collection.rb +22 -25
- data/lib/state_machines/version.rb +1 -1
- metadata +23 -9
@@ -25,20 +25,24 @@ module StateMachines
|
|
25
25
|
# writing to the object. Default is to write directly to the object.
|
26
26
|
def initialize_states(object, options = {}, attributes = {})
|
27
27
|
StateMachines::OptionsValidator.assert_valid_keys!(options, :static, :dynamic, :to)
|
28
|
-
options = {static: true, dynamic: true}.merge(options)
|
28
|
+
options = { static: true, dynamic: true }.merge(options)
|
29
29
|
|
30
30
|
result = yield if block_given?
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
if options[:static]
|
33
|
+
each_value do |machine|
|
34
|
+
unless machine.dynamic_initial_state?
|
35
|
+
force = options[:static] == :force || !attributes.keys.map(&:to_sym).include?(machine.attribute)
|
36
|
+
machine.initialize_state(object, force: force, to: options[:to])
|
37
|
+
end
|
36
38
|
end
|
37
|
-
end
|
39
|
+
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
if options[:dynamic]
|
42
|
+
each_value do |machine|
|
43
|
+
machine.initialize_state(object, force: options[:dynamic] == :force, to: options[:to]) if machine.dynamic_initial_state?
|
44
|
+
end
|
45
|
+
end
|
42
46
|
|
43
47
|
result
|
44
48
|
end
|
@@ -52,7 +56,7 @@ module StateMachines
|
|
52
56
|
transitions = events.collect do |event_name|
|
53
57
|
# Find the actual event being run
|
54
58
|
event = nil
|
55
|
-
detect { |
|
59
|
+
detect { |_name, machine| event = machine.events[event_name, :qualified_name] }
|
56
60
|
|
57
61
|
raise(InvalidEvent.new(object, event_name)) unless event
|
58
62
|
|
@@ -66,7 +70,7 @@ module StateMachines
|
|
66
70
|
# Run the events in parallel only if valid transitions were found for
|
67
71
|
# all of them
|
68
72
|
if events.length == transitions.length
|
69
|
-
TransitionCollection.new(transitions, {use_transactions: resolve_use_transactions, actions: run_action}).perform
|
73
|
+
TransitionCollection.new(transitions, { use_transactions: resolve_use_transactions, actions: run_action }).perform
|
70
74
|
else
|
71
75
|
false
|
72
76
|
end
|
@@ -78,14 +82,14 @@ module StateMachines
|
|
78
82
|
#
|
79
83
|
# These should only be fired as a result of the action being run.
|
80
84
|
def transitions(object, action, options = {})
|
81
|
-
transitions = map do |
|
85
|
+
transitions = map do |_name, machine|
|
82
86
|
machine.events.attribute_transition_for(object, true) if machine.action == action
|
83
87
|
end
|
84
88
|
|
85
|
-
AttributeTransitionCollection.new(transitions.compact, {use_transactions: resolve_use_transactions}.merge(options))
|
89
|
+
AttributeTransitionCollection.new(transitions.compact, { use_transactions: resolve_use_transactions }.merge(options))
|
86
90
|
end
|
87
91
|
|
88
|
-
|
92
|
+
protected
|
89
93
|
|
90
94
|
def resolve_use_transactions
|
91
95
|
use_transactions = nil
|
@@ -515,8 +515,8 @@ module StateMachines
|
|
515
515
|
# See StateMachines::Machine for more information about using integrations
|
516
516
|
# and the individual integration docs for information about the actual
|
517
517
|
# scopes that are generated.
|
518
|
-
def state_machine(
|
519
|
-
StateMachines::Machine.find_or_create(self,
|
518
|
+
def state_machine(*, &)
|
519
|
+
StateMachines::Machine.find_or_create(self, *, &)
|
520
520
|
end
|
521
521
|
end
|
522
522
|
end
|
@@ -32,13 +32,13 @@ module StateMachines
|
|
32
32
|
# matcher = StateMachines::AllMatcher.instance - [:parked, :idling]
|
33
33
|
# matcher.matches?(:parked) # => false
|
34
34
|
# matcher.matches?(:first_gear) # => true
|
35
|
-
def -(
|
36
|
-
BlacklistMatcher.new(
|
35
|
+
def -(other)
|
36
|
+
BlacklistMatcher.new(other)
|
37
37
|
end
|
38
|
-
|
38
|
+
alias except -
|
39
39
|
|
40
40
|
# Always returns true
|
41
|
-
def matches?(
|
41
|
+
def matches?(_value, _context = {})
|
42
42
|
true
|
43
43
|
end
|
44
44
|
|
@@ -63,7 +63,7 @@ module StateMachines
|
|
63
63
|
# matcher = StateMachines::WhitelistMatcher.new([:parked, :idling])
|
64
64
|
# matcher.matches?(:parked) # => true
|
65
65
|
# matcher.matches?(:first_gear) # => false
|
66
|
-
def matches?(value,
|
66
|
+
def matches?(value, _context = {})
|
67
67
|
values.include?(value)
|
68
68
|
end
|
69
69
|
|
@@ -83,7 +83,7 @@ module StateMachines
|
|
83
83
|
# matcher = StateMachines::BlacklistMatcher.new([:parked, :idling])
|
84
84
|
# matcher.matches?(:parked) # => false
|
85
85
|
# matcher.matches?(:first_gear) # => true
|
86
|
-
def matches?(value,
|
86
|
+
def matches?(value, _context = {})
|
87
87
|
!values.include?(value)
|
88
88
|
end
|
89
89
|
|
@@ -26,11 +26,10 @@ module StateMachines
|
|
26
26
|
@machine = machine
|
27
27
|
@nodes = []
|
28
28
|
@index_names = Array(options[:index])
|
29
|
-
@indices = @index_names.
|
29
|
+
@indices = @index_names.each_with_object({}) do |name, indices|
|
30
30
|
indices[name] = {}
|
31
31
|
indices[:"#{name}_to_s"] = {}
|
32
32
|
indices[:"#{name}_to_sym"] = {}
|
33
|
-
indices
|
34
33
|
end
|
35
34
|
@default_index = Array(options[:index]).first
|
36
35
|
@contexts = []
|
@@ -38,14 +37,16 @@ module StateMachines
|
|
38
37
|
|
39
38
|
# Creates a copy of this collection such that modifications don't affect
|
40
39
|
# the original collection
|
41
|
-
def initialize_copy(orig)
|
40
|
+
def initialize_copy(orig) # :nodoc:
|
42
41
|
super
|
43
42
|
|
44
43
|
nodes = @nodes
|
45
44
|
contexts = @contexts
|
46
45
|
@nodes = []
|
47
46
|
@contexts = []
|
48
|
-
@indices = @indices.
|
47
|
+
@indices = @indices.each_with_object({}) do |(name, *), indices|
|
48
|
+
indices[name] = {}
|
49
|
+
end
|
49
50
|
|
50
51
|
# Add nodes *prior* to copying over the contexts so that they don't get
|
51
52
|
# evaluated multiple times
|
@@ -116,8 +117,8 @@ module StateMachines
|
|
116
117
|
# ...produces:
|
117
118
|
#
|
118
119
|
# parked -- idling --
|
119
|
-
def each
|
120
|
-
@nodes.each
|
120
|
+
def each(&)
|
121
|
+
@nodes.each(&)
|
121
122
|
self
|
122
123
|
end
|
123
124
|
|
@@ -145,9 +146,9 @@ module StateMachines
|
|
145
146
|
# If the key cannot be found, then nil will be returned.
|
146
147
|
def [](key, index_name = @default_index)
|
147
148
|
index(index_name)[key] ||
|
148
|
-
|
149
|
-
|
150
|
-
|
149
|
+
index(:"#{index_name}_to_s")[key.to_s] ||
|
150
|
+
(to_sym?(key) && index(:"#{index_name}_to_sym")[:"#{key}"]) ||
|
151
|
+
nil
|
151
152
|
end
|
152
153
|
|
153
154
|
# Gets the node indexed by the given key. By default, this will look up the
|
@@ -163,17 +164,17 @@ module StateMachines
|
|
163
164
|
#
|
164
165
|
# collection['invalid', :value] # => IndexError: "invalid" is an invalid value
|
165
166
|
def fetch(key, index_name = @default_index)
|
166
|
-
self[key, index_name] ||
|
167
|
+
self[key, index_name] || raise(IndexError, "#{key.inspect} is an invalid #{index_name}")
|
167
168
|
end
|
168
169
|
|
169
|
-
|
170
|
+
protected
|
170
171
|
|
171
172
|
# Gets the given index. If the index does not exist, then an ArgumentError
|
172
173
|
# is raised.
|
173
174
|
def index(name)
|
174
|
-
|
175
|
+
raise ArgumentError, 'No indices configured' unless @indices.any?
|
175
176
|
|
176
|
-
@indices[name] ||
|
177
|
+
@indices[name] || raise(ArgumentError, "Invalid index: #{name.inspect}")
|
177
178
|
end
|
178
179
|
|
179
180
|
# Gets the value for the given attribute on the node
|
@@ -205,10 +206,10 @@ module StateMachines
|
|
205
206
|
new_key = value(node, name)
|
206
207
|
|
207
208
|
# Only replace the key if it's changed
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
209
|
+
return unless old_key != new_key
|
210
|
+
|
211
|
+
remove_from_index(name, old_key)
|
212
|
+
add_to_index(name, new_key, node)
|
212
213
|
end
|
213
214
|
|
214
215
|
# Determines whether the given value can be converted to a symbol
|
data/lib/state_machines/path.rb
CHANGED
@@ -7,8 +7,6 @@ module StateMachines
|
|
7
7
|
# object. Paths can walk to new transitions, revealing all of the possible
|
8
8
|
# branches that can be encountered in the object's state machine.
|
9
9
|
class Path < Array
|
10
|
-
|
11
|
-
|
12
10
|
# The object whose state machine is being walked
|
13
11
|
attr_reader :object
|
14
12
|
|
@@ -32,7 +30,7 @@ module StateMachines
|
|
32
30
|
@guard = options[:guard]
|
33
31
|
end
|
34
32
|
|
35
|
-
def initialize_copy(orig)
|
33
|
+
def initialize_copy(orig) # :nodoc:
|
36
34
|
super
|
37
35
|
@transitions = nil
|
38
36
|
end
|
@@ -90,7 +88,7 @@ module StateMachines
|
|
90
88
|
!empty? && (@target ? to_name == @target : transitions.empty?)
|
91
89
|
end
|
92
90
|
|
93
|
-
|
91
|
+
private
|
94
92
|
|
95
93
|
# Calculates the number of times the given state has been walked to
|
96
94
|
def times_walked_to(state)
|
@@ -6,7 +6,6 @@ module StateMachines
|
|
6
6
|
# Represents a collection of paths that are generated based on a set of
|
7
7
|
# requirements regarding what states to start and end on
|
8
8
|
class PathCollection < Array
|
9
|
-
|
10
9
|
# The object whose state machine is being walked
|
11
10
|
attr_reader :object
|
12
11
|
|
@@ -28,7 +27,7 @@ module StateMachines
|
|
28
27
|
# * <tt>:guard</tt> - Whether to guard transitions with the if/unless
|
29
28
|
# conditionals defined for each one
|
30
29
|
def initialize(object, machine, options = {})
|
31
|
-
options = {deep: false, from: machine.states.match!(object).name}.merge(options)
|
30
|
+
options = { deep: false, from: machine.states.match!(object).name }.merge(options)
|
32
31
|
StateMachines::OptionsValidator.assert_valid_keys!(options, :from, :to, :deep, :guard)
|
33
32
|
|
34
33
|
@object = object
|
@@ -71,7 +70,7 @@ module StateMachines
|
|
71
70
|
flat_map(&:events).uniq
|
72
71
|
end
|
73
72
|
|
74
|
-
|
73
|
+
private
|
75
74
|
|
76
75
|
# Gets the initial set of paths to walk
|
77
76
|
def initial_paths
|
data/lib/state_machines/state.rb
CHANGED
@@ -67,8 +67,9 @@ module StateMachines
|
|
67
67
|
# New style: initialize(machine, name, initial: true, value: 'foo')
|
68
68
|
# options parameter should be nil in this case
|
69
69
|
raise ArgumentError, "Unexpected positional argument: #{options.inspect}" unless options.nil?
|
70
|
+
|
70
71
|
StateMachines::OptionsValidator.assert_valid_keys!(extra_options, :initial, :value, :cache, :if, :human_name) unless extra_options.empty?
|
71
|
-
if_condition = binding.local_variable_get(:if)
|
72
|
+
if_condition = binding.local_variable_get(:if) # 'if' is a keyword, need special handling
|
72
73
|
end
|
73
74
|
|
74
75
|
@machine = machine
|
@@ -200,14 +201,14 @@ module StateMachines
|
|
200
201
|
#
|
201
202
|
# This can be called multiple times. Each time a new context is created,
|
202
203
|
# a new module will be included in the owner class.
|
203
|
-
def context(&
|
204
|
+
def context(&)
|
204
205
|
# Include the context
|
205
206
|
context = @context
|
206
207
|
machine.owner_class.class_eval { include context }
|
207
208
|
|
208
209
|
# Evaluate the method definitions and track which ones were added
|
209
210
|
old_methods = context_methods
|
210
|
-
context.class_eval(&
|
211
|
+
context.class_eval(&)
|
211
212
|
new_methods = context_methods.to_a.reject { |(name, method)| old_methods[name] == method }
|
212
213
|
|
213
214
|
# Alias new methods so that the only execute when the object is in this state
|
@@ -238,13 +239,13 @@ module StateMachines
|
|
238
239
|
#
|
239
240
|
# If the method has never been defined for this state, then a NoMethodError
|
240
241
|
# will be raised.
|
241
|
-
def call(object, method, *args, &
|
242
|
+
def call(object, method, *args, &)
|
242
243
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
243
244
|
options = { method_name: method }.merge(options)
|
244
245
|
state = machine.states.match!(object)
|
245
246
|
|
246
247
|
if state == self && object.respond_to?(method)
|
247
|
-
object.send(method, *args, &
|
248
|
+
object.send(method, *args, &)
|
248
249
|
elsif (method_missing = options[:method_missing])
|
249
250
|
# Dispatch to the superclass since the object either isn't in this state
|
250
251
|
# or this state doesn't handle the method
|
@@ -3,8 +3,8 @@
|
|
3
3
|
module StateMachines
|
4
4
|
# Represents a collection of states in a state machine
|
5
5
|
class StateCollection < NodeCollection
|
6
|
-
def initialize(machine)
|
7
|
-
super(machine, index: [
|
6
|
+
def initialize(machine) # :nodoc:
|
7
|
+
super(machine, index: %i[name qualified_name value])
|
8
8
|
end
|
9
9
|
|
10
10
|
# Determines whether the given object is in a specific state. If the
|
@@ -103,7 +103,7 @@ module StateMachines
|
|
103
103
|
order
|
104
104
|
end
|
105
105
|
|
106
|
-
|
106
|
+
private
|
107
107
|
|
108
108
|
# Gets the value for the given attribute on the node
|
109
109
|
def value(node, attribute)
|
@@ -54,7 +54,6 @@ module StateMachines
|
|
54
54
|
# vehicle.simulate = true
|
55
55
|
# vehicle.moving? # => false
|
56
56
|
class StateContext < Module
|
57
|
-
|
58
57
|
include EvalHelpers
|
59
58
|
|
60
59
|
# The state machine for which this context's state is defined
|
@@ -70,7 +69,7 @@ module StateMachines
|
|
70
69
|
|
71
70
|
state_name = state.name
|
72
71
|
machine_name = machine.name
|
73
|
-
@condition =
|
72
|
+
@condition = ->(object) { object.class.state_machine(machine_name).states.matches?(object, state_name) }
|
74
73
|
end
|
75
74
|
|
76
75
|
# Creates a new transition that determines what to change the current state
|
@@ -94,11 +93,11 @@ module StateMachines
|
|
94
93
|
raise ArgumentError, 'Must specify :on event' unless options[:on]
|
95
94
|
raise ArgumentError, 'Must specify either :to or :from state' unless !options[:to] ^ !options[:from]
|
96
95
|
|
97
|
-
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 }))
|
98
97
|
end
|
99
98
|
|
100
99
|
# Hooks in condition-merging to methods that don't exist in this module
|
101
|
-
def method_missing(*args, &
|
100
|
+
def method_missing(*args, &)
|
102
101
|
# Get the configuration
|
103
102
|
if args.last.is_a?(Hash)
|
104
103
|
options = args.last
|
@@ -123,13 +122,13 @@ module StateMachines
|
|
123
122
|
object = condition_args.first || self
|
124
123
|
|
125
124
|
proxy.evaluate_method(object, proxy_condition) &&
|
126
|
-
|
127
|
-
|
125
|
+
Array(if_condition).all? { |condition| proxy.evaluate_method(object, condition) } &&
|
126
|
+
!Array(unless_condition).any? { |condition| proxy.evaluate_method(object, condition) }
|
128
127
|
end
|
129
128
|
|
130
129
|
# Evaluate the method on the owner class with the condition proxied
|
131
130
|
# through
|
132
|
-
machine.owner_class.send(*args, &
|
131
|
+
machine.owner_class.send(*args, &)
|
133
132
|
end
|
134
133
|
end
|
135
134
|
end
|
@@ -13,9 +13,9 @@ module StateMachines
|
|
13
13
|
end
|
14
14
|
|
15
15
|
module_function def draw_states(machine:, io: $stdout)
|
16
|
-
io.puts
|
16
|
+
io.puts ' States:'
|
17
17
|
if machine.states.to_a.empty?
|
18
|
-
io.puts
|
18
|
+
io.puts ' - None'
|
19
19
|
else
|
20
20
|
machine.states.each do |state|
|
21
21
|
io.puts " - #{state.name}"
|
@@ -23,31 +23,31 @@ module StateMachines
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
module_function def draw_event(event,
|
26
|
+
module_function def draw_event(event, _graph, options: {}, io: $stdout)
|
27
27
|
io = io || options[:io] || $stdout
|
28
28
|
io.puts " Event: #{event.name}"
|
29
29
|
end
|
30
30
|
|
31
|
-
module_function def draw_branch(branch,
|
31
|
+
module_function def draw_branch(branch, _graph, _event, options: {}, io: $stdout)
|
32
32
|
io = io || options[:io] || $stdout
|
33
33
|
io.puts " Branch: #{branch.inspect}"
|
34
34
|
end
|
35
35
|
|
36
|
-
module_function def draw_state(state,
|
36
|
+
module_function def draw_state(state, _graph, options: {}, io: $stdout)
|
37
37
|
io = io || options[:io] || $stdout
|
38
38
|
io.puts " State: #{state.name}"
|
39
39
|
end
|
40
40
|
|
41
41
|
module_function def draw_events(machine:, io: $stdout)
|
42
|
-
io.puts
|
42
|
+
io.puts ' Events:'
|
43
43
|
if machine.events.to_a.empty?
|
44
|
-
io.puts
|
44
|
+
io.puts ' - None'
|
45
45
|
else
|
46
46
|
machine.events.each do |event|
|
47
47
|
io.puts " - #{event.name}"
|
48
48
|
event.branches.each do |branch|
|
49
49
|
branch.state_requirements.each do |requirement|
|
50
|
-
out = +
|
50
|
+
out = +' - '
|
51
51
|
out << "#{draw_requirement(requirement[:from])} => #{draw_requirement(requirement[:to])}"
|
52
52
|
out << " IF #{branch.if_condition}" if branch.if_condition
|
53
53
|
out << " UNLESS #{branch.unless_condition}" if branch.unless_condition
|
@@ -60,14 +60,14 @@ module StateMachines
|
|
60
60
|
|
61
61
|
module_function def draw_requirement(requirement)
|
62
62
|
case requirement
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
71
|
end
|
72
72
|
end
|
73
73
|
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
|