state_machines 0.6.0 → 0.20.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 +93 -13
- data/lib/state_machines/branch.rb +8 -4
- data/lib/state_machines/callback.rb +2 -0
- data/lib/state_machines/core.rb +3 -2
- 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 +2 -0
- data/lib/state_machines/eval_helpers.rb +38 -9
- data/lib/state_machines/event.rb +22 -7
- data/lib/state_machines/event_collection.rb +2 -0
- data/lib/state_machines/extensions.rb +2 -0
- data/lib/state_machines/helper_module.rb +2 -0
- data/lib/state_machines/integrations/base.rb +2 -0
- data/lib/state_machines/integrations.rb +2 -0
- data/lib/state_machines/machine/class_methods.rb +79 -0
- data/lib/state_machines/machine.rb +21 -67
- data/lib/state_machines/machine_collection.rb +5 -1
- data/lib/state_machines/macro_methods.rb +2 -0
- data/lib/state_machines/matcher.rb +3 -0
- data/lib/state_machines/matcher_helpers.rb +2 -0
- data/lib/state_machines/node_collection.rb +5 -1
- data/lib/state_machines/options_validator.rb +72 -0
- data/lib/state_machines/path.rb +5 -1
- data/lib/state_machines/path_collection.rb +5 -1
- data/lib/state_machines/state.rb +71 -43
- data/lib/state_machines/state_collection.rb +2 -0
- data/lib/state_machines/state_context.rb +5 -1
- data/lib/state_machines/stdio_renderer.rb +74 -0
- data/lib/state_machines/test_helper.rb +305 -0
- data/lib/state_machines/transition.rb +2 -0
- data/lib/state_machines/transition_collection.rb +5 -1
- data/lib/state_machines/version.rb +3 -1
- data/lib/state_machines.rb +4 -1
- metadata +11 -9
- 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 collection of state machines for a class
|
3
7
|
class MachineCollection < Hash
|
@@ -20,7 +24,7 @@ module StateMachines
|
|
20
24
|
# * <tt>:to</tt> - A hash to write the initialized state to instead of
|
21
25
|
# writing to the object. Default is to write directly to the object.
|
22
26
|
def initialize_states(object, options = {}, attributes = {})
|
23
|
-
|
27
|
+
StateMachines::OptionsValidator.assert_valid_keys!(options, :static, :dynamic, :to)
|
24
28
|
options = {static: true, dynamic: true}.merge(options)
|
25
29
|
|
26
30
|
result = yield if block_given?
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module StateMachines
|
2
4
|
# Provides a general strategy pattern for determining whether a match is found
|
3
5
|
# for a value. The algorithm that actually determines the match depends on
|
@@ -33,6 +35,7 @@ module StateMachines
|
|
33
35
|
def -(blacklist)
|
34
36
|
BlacklistMatcher.new(blacklist)
|
35
37
|
end
|
38
|
+
alias_method :except, :-
|
36
39
|
|
37
40
|
# Always returns true
|
38
41
|
def matches?(value, context = {})
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'options_validator'
|
4
|
+
|
1
5
|
module StateMachines
|
2
6
|
# Represents a collection of nodes in a state machine, be it events or states.
|
3
7
|
# Nodes will not differentiate between the String and Symbol versions of the
|
@@ -16,7 +20,7 @@ module StateMachines
|
|
16
20
|
# hashed indices for in order to perform quick lookups. Default is to
|
17
21
|
# index by the :name attribute
|
18
22
|
def initialize(machine, options = {})
|
19
|
-
|
23
|
+
StateMachines::OptionsValidator.assert_valid_keys!(options, :index)
|
20
24
|
options = { index: :name }.merge(options)
|
21
25
|
|
22
26
|
@machine = machine
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StateMachines
|
4
|
+
# Define the module if it doesn't exist yet
|
5
|
+
# Module for validating options without monkey-patching Hash
|
6
|
+
# Provides the same functionality as the Hash monkey patch but in a cleaner way
|
7
|
+
module OptionsValidator
|
8
|
+
class << self
|
9
|
+
# Validates that all keys in the options hash are in the list of valid keys
|
10
|
+
#
|
11
|
+
# @param options [Hash] The options hash to validate
|
12
|
+
# @param valid_keys [Array<Symbol>] List of valid key names
|
13
|
+
# @param caller_info [String] Information about the calling method for better error messages
|
14
|
+
# @raise [ArgumentError] If any invalid keys are found
|
15
|
+
def assert_valid_keys!(options, *valid_keys, caller_info: nil)
|
16
|
+
return if options.empty?
|
17
|
+
|
18
|
+
valid_keys.flatten!
|
19
|
+
invalid_keys = options.keys - valid_keys
|
20
|
+
|
21
|
+
return if invalid_keys.empty?
|
22
|
+
|
23
|
+
caller_context = caller_info ? " in #{caller_info}" : ''
|
24
|
+
raise ArgumentError, "Unknown key#{'s' if invalid_keys.length > 1}: #{invalid_keys.map(&:inspect).join(', ')}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}#{caller_context}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Validates that at most one of the exclusive keys is present in the options hash
|
28
|
+
#
|
29
|
+
# @param options [Hash] The options hash to validate
|
30
|
+
# @param exclusive_keys [Array<Symbol>] List of mutually exclusive keys
|
31
|
+
# @param caller_info [String] Information about the calling method for better error messages
|
32
|
+
# @raise [ArgumentError] If more than one exclusive key is found
|
33
|
+
def assert_exclusive_keys!(options, *exclusive_keys, caller_info: nil)
|
34
|
+
return if options.empty?
|
35
|
+
|
36
|
+
conflicting_keys = exclusive_keys & options.keys
|
37
|
+
return if conflicting_keys.length <= 1
|
38
|
+
|
39
|
+
caller_context = caller_info ? " in #{caller_info}" : ''
|
40
|
+
raise ArgumentError, "Conflicting keys: #{conflicting_keys.join(', ')}#{caller_context}"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Validates options using a more convenient interface that works with both
|
44
|
+
# hash-style and kwargs-style method definitions
|
45
|
+
#
|
46
|
+
# @param valid_keys [Array<Symbol>] List of valid key names
|
47
|
+
# @param exclusive_key_groups [Array<Array<Symbol>>] Groups of mutually exclusive keys
|
48
|
+
# @param caller_info [String] Information about the calling method
|
49
|
+
# @return [Proc] A validation proc that can be called with options
|
50
|
+
def validator(valid_keys: [], exclusive_key_groups: [], caller_info: nil)
|
51
|
+
proc do |options|
|
52
|
+
assert_valid_keys!(options, *valid_keys, caller_info: caller_info) unless valid_keys.empty?
|
53
|
+
|
54
|
+
exclusive_key_groups.each do |group|
|
55
|
+
assert_exclusive_keys!(options, *group, caller_info: caller_info)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Helper method for backwards compatibility - allows gradual migration
|
61
|
+
# from Hash monkey patch to this module
|
62
|
+
#
|
63
|
+
# @param options [Hash] The options to validate
|
64
|
+
# @param valid_keys [Array<Symbol>] Valid keys
|
65
|
+
# @return [Hash] The same options hash (for chaining)
|
66
|
+
def validate_and_return(options, *valid_keys)
|
67
|
+
assert_valid_keys!(options, *valid_keys)
|
68
|
+
options
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/state_machines/path.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'options_validator'
|
4
|
+
|
1
5
|
module StateMachines
|
2
6
|
# A path represents a sequence of transitions that can be run for a particular
|
3
7
|
# object. Paths can walk to new transitions, revealing all of the possible
|
@@ -20,7 +24,7 @@ module StateMachines
|
|
20
24
|
# * <tt>:guard</tt> - Whether to guard transitions with the if/unless
|
21
25
|
# conditionals defined for each one
|
22
26
|
def initialize(object, machine, options = {})
|
23
|
-
|
27
|
+
StateMachines::OptionsValidator.assert_valid_keys!(options, :target, :guard)
|
24
28
|
|
25
29
|
@object = object
|
26
30
|
@machine = machine
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'options_validator'
|
4
|
+
|
1
5
|
module StateMachines
|
2
6
|
# Represents a collection of paths that are generated based on a set of
|
3
7
|
# requirements regarding what states to start and end on
|
@@ -25,7 +29,7 @@ module StateMachines
|
|
25
29
|
# conditionals defined for each one
|
26
30
|
def initialize(object, machine, options = {})
|
27
31
|
options = {deep: false, from: machine.states.match!(object).name}.merge(options)
|
28
|
-
|
32
|
+
StateMachines::OptionsValidator.assert_valid_keys!(options, :from, :to, :deep, :guard)
|
29
33
|
|
30
34
|
@object = object
|
31
35
|
@machine = machine
|
data/lib/state_machines/state.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'options_validator'
|
4
|
+
|
1
5
|
module StateMachines
|
2
6
|
# A state defines a value that an attribute can be in after being transitioned
|
3
7
|
# 0 or more times. States can represent a value of any type in Ruby, though
|
@@ -8,7 +12,6 @@ module StateMachines
|
|
8
12
|
# StateMachines::Machine#state for more information about how state-driven
|
9
13
|
# behavior can be utilized.
|
10
14
|
class State
|
11
|
-
|
12
15
|
# The state machine for which this state is defined
|
13
16
|
attr_reader :machine
|
14
17
|
|
@@ -31,7 +34,7 @@ module StateMachines
|
|
31
34
|
|
32
35
|
# Whether or not this state is the initial state to use for new objects
|
33
36
|
attr_accessor :initial
|
34
|
-
|
37
|
+
alias initial? initial
|
35
38
|
|
36
39
|
# A custom lambda block for determining whether a given value matches this
|
37
40
|
# state
|
@@ -50,38 +53,57 @@ module StateMachines
|
|
50
53
|
# (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
|
51
54
|
# By default, the configured value is matched.
|
52
55
|
# * <tt>:human_name</tt> - The human-readable version of this state's name
|
53
|
-
def initialize(machine, name, options =
|
54
|
-
|
56
|
+
def initialize(machine, name, options = nil, initial: false, value: :__not_provided__, cache: nil, if: nil, human_name: nil, **extra_options) # :nodoc:
|
57
|
+
# Handle both old hash style and new kwargs style for backward compatibility
|
58
|
+
if options.is_a?(Hash)
|
59
|
+
# Old style: initialize(machine, name, {initial: true, value: 'foo'})
|
60
|
+
StateMachines::OptionsValidator.assert_valid_keys!(options, :initial, :value, :cache, :if, :human_name)
|
61
|
+
initial = options.fetch(:initial, false)
|
62
|
+
value = options.include?(:value) ? options[:value] : :__not_provided__
|
63
|
+
cache = options[:cache]
|
64
|
+
if_condition = options[:if]
|
65
|
+
human_name = options[:human_name]
|
66
|
+
else
|
67
|
+
# New style: initialize(machine, name, initial: true, value: 'foo')
|
68
|
+
# options parameter should be nil in this case
|
69
|
+
raise ArgumentError, "Unexpected positional argument: #{options.inspect}" unless options.nil?
|
70
|
+
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) # 'if' is a keyword, need special handling
|
72
|
+
end
|
55
73
|
|
56
74
|
@machine = machine
|
57
75
|
@name = name
|
58
76
|
@qualified_name = name && machine.namespace ? :"#{machine.namespace}_#{name}" : name
|
59
|
-
@human_name =
|
60
|
-
@value =
|
61
|
-
@cache =
|
62
|
-
@matcher =
|
63
|
-
@initial =
|
77
|
+
@human_name = human_name || (@name ? @name.to_s.tr('_', ' ') : 'nil')
|
78
|
+
@value = value == :__not_provided__ ? name&.to_s : value
|
79
|
+
@cache = cache
|
80
|
+
@matcher = if_condition
|
81
|
+
@initial = initial == true
|
64
82
|
@context = StateContext.new(self)
|
65
83
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
84
|
+
return unless name
|
85
|
+
|
86
|
+
conflicting_machines = machine.owner_class.state_machines.select do |_other_name, other_machine|
|
87
|
+
other_machine != machine && other_machine.states[qualified_name, :qualified_name]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Output a warning if another machine has a conflicting qualified name
|
91
|
+
# for a different attribute
|
92
|
+
if (conflict = conflicting_machines.detect do |_other_name, other_machine|
|
93
|
+
other_machine.attribute != machine.attribute
|
94
|
+
end)
|
95
|
+
_name, other_machine = conflict
|
96
|
+
warn "State #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}"
|
97
|
+
elsif conflicting_machines.empty?
|
98
|
+
# Only bother adding predicates when another machine for the same
|
99
|
+
# attribute hasn't already done so
|
100
|
+
add_predicate
|
79
101
|
end
|
80
102
|
end
|
81
103
|
|
82
104
|
# Creates a copy of this state, excluding the context to prevent conflicts
|
83
105
|
# across different machines.
|
84
|
-
def initialize_copy(orig)
|
106
|
+
def initialize_copy(orig) # :nodoc:
|
85
107
|
super
|
86
108
|
@context = StateContext.new(self)
|
87
109
|
end
|
@@ -96,7 +118,7 @@ module StateMachines
|
|
96
118
|
# Any objects in a final state will remain so forever given the current
|
97
119
|
# machine's definition.
|
98
120
|
def final?
|
99
|
-
|
121
|
+
machine.events.none? do |event|
|
100
122
|
event.branches.any? do |branch|
|
101
123
|
branch.state_requirements.any? do |requirement|
|
102
124
|
requirement[:from].matches?(name) && !requirement[:to].matches?(name, from: name)
|
@@ -126,7 +148,7 @@ module StateMachines
|
|
126
148
|
# description or just the internal name
|
127
149
|
def description(options = {})
|
128
150
|
label = options[:human_name] ? human_name : name
|
129
|
-
description = label ? label.to_s : label.inspect
|
151
|
+
description = +(label ? label.to_s : label.inspect)
|
130
152
|
description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s
|
131
153
|
description
|
132
154
|
end
|
@@ -186,19 +208,19 @@ module StateMachines
|
|
186
208
|
# Evaluate the method definitions and track which ones were added
|
187
209
|
old_methods = context_methods
|
188
210
|
context.class_eval(&block)
|
189
|
-
new_methods = context_methods.to_a.
|
211
|
+
new_methods = context_methods.to_a.reject { |(name, method)| old_methods[name] == method }
|
190
212
|
|
191
213
|
# Alias new methods so that the only execute when the object is in this state
|
192
214
|
new_methods.each do |(method_name, _method)|
|
193
215
|
context_name = context_name_for(method_name)
|
194
|
-
context.class_eval <<-
|
216
|
+
context.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
195
217
|
alias_method :"#{context_name}", :#{method_name}
|
196
218
|
def #{method_name}(*args, &block)
|
197
219
|
state = self.class.state_machine(#{machine.name.inspect}).states.fetch(#{name.inspect})
|
198
220
|
options = {:method_missing => lambda {super(*args, &block)}, :method_name => #{method_name.inspect}}
|
199
221
|
state.call(self, :"#{context_name}", *(args + [options]), &block)
|
200
222
|
end
|
201
|
-
|
223
|
+
END_EVAL
|
202
224
|
end
|
203
225
|
|
204
226
|
true
|
@@ -218,29 +240,28 @@ module StateMachines
|
|
218
240
|
# will be raised.
|
219
241
|
def call(object, method, *args, &block)
|
220
242
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
221
|
-
options = {method_name: method}.merge(options)
|
243
|
+
options = { method_name: method }.merge(options)
|
222
244
|
state = machine.states.match!(object)
|
223
245
|
|
224
246
|
if state == self && object.respond_to?(method)
|
225
247
|
object.send(method, *args, &block)
|
226
|
-
elsif method_missing = options[:method_missing]
|
248
|
+
elsif (method_missing = options[:method_missing])
|
227
249
|
# Dispatch to the superclass since the object either isn't in this state
|
228
250
|
# or this state doesn't handle the method
|
229
251
|
begin
|
230
252
|
method_missing.call
|
231
|
-
rescue NoMethodError =>
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
end
|
253
|
+
rescue NoMethodError => e
|
254
|
+
raise unless e.name.to_s == options[:method_name].to_s && e.args == args
|
255
|
+
|
256
|
+
# No valid context for this method
|
257
|
+
raise InvalidContext.new(object,
|
258
|
+
"State #{state.name.inspect} for #{machine.name.inspect} is not a valid context for calling ##{options[:method_name]}")
|
238
259
|
end
|
239
260
|
end
|
240
261
|
end
|
241
262
|
|
242
|
-
def draw(graph, options = {})
|
243
|
-
|
263
|
+
def draw(graph, options = {}, io = $stdout)
|
264
|
+
machine.renderer.draw_state(self, graph, options, io)
|
244
265
|
end
|
245
266
|
|
246
267
|
# Generates a nicely formatted description of this state's contents.
|
@@ -254,7 +275,7 @@ module StateMachines
|
|
254
275
|
"#<#{self.class} #{attributes.map { |attr, value| "#{attr}=#{value.inspect}" } * ' '}>"
|
255
276
|
end
|
256
277
|
|
257
|
-
|
278
|
+
private
|
258
279
|
|
259
280
|
# Should the value be cached after it's evaluated for the first time?
|
260
281
|
def cache_value?
|
@@ -264,9 +285,16 @@ module StateMachines
|
|
264
285
|
# Adds a predicate method to the owner class so long as a name has
|
265
286
|
# actually been configured for the state
|
266
287
|
def add_predicate
|
267
|
-
|
268
|
-
|
269
|
-
|
288
|
+
predicate_method = "#{qualified_name}?"
|
289
|
+
|
290
|
+
if machine.send(:owner_class_ancestor_has_method?, :instance, predicate_method)
|
291
|
+
warn "Instance method #{predicate_method.inspect} is already defined in #{machine.owner_class.ancestors.first.inspect}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true."
|
292
|
+
elsif machine.send(:owner_class_has_method?, :instance, predicate_method)
|
293
|
+
warn "Instance method #{predicate_method.inspect} is already defined in #{machine.owner_class.inspect}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true."
|
294
|
+
else
|
295
|
+
machine.define_helper(:instance, predicate_method) do |machine, object|
|
296
|
+
machine.states.matches?(object, name)
|
297
|
+
end
|
270
298
|
end
|
271
299
|
end
|
272
300
|
|
@@ -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
|
#
|
@@ -86,7 +90,7 @@ module StateMachines
|
|
86
90
|
# See StateMachines::Machine#transition for a description of the possible
|
87
91
|
# configurations for defining transitions.
|
88
92
|
def transition(options)
|
89
|
-
|
93
|
+
StateMachines::OptionsValidator.assert_valid_keys!(options, :from, :to, :on, :if, :unless)
|
90
94
|
raise ArgumentError, 'Must specify :on event' unless options[:on]
|
91
95
|
raise ArgumentError, 'Must specify either :to or :from state' unless !options[:to] ^ !options[:from]
|
92
96
|
|
@@ -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
|