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
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StateMachines
|
4
|
+
class Machine
|
5
|
+
module Utilities
|
6
|
+
protected
|
7
|
+
|
8
|
+
# Looks up other machines that have been defined in the owner class and
|
9
|
+
# are targeting the same attribute as this machine. When accessing
|
10
|
+
# sibling machines, they will be automatically copied for the current
|
11
|
+
# class if they haven't been already. This ensures that any configuration
|
12
|
+
# changes made to the sibling machines only affect this class and not any
|
13
|
+
# base class that may have originally defined the machine.
|
14
|
+
def sibling_machines
|
15
|
+
owner_class.state_machines.each_with_object([]) do |(name, machine), machines|
|
16
|
+
machines << (owner_class.state_machine(name) {}) if machine.attribute == attribute && machine != self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Looks up the ancestor class that has the given method defined. This
|
21
|
+
# is used to find the method owner which is used to determine where to
|
22
|
+
# define new methods.
|
23
|
+
def owner_class_ancestor_has_method?(scope, method)
|
24
|
+
return false unless owner_class_has_method?(scope, method)
|
25
|
+
|
26
|
+
superclasses = owner_class.ancestors.select { |ancestor| ancestor.is_a?(Class) }[1..]
|
27
|
+
|
28
|
+
if scope == :class
|
29
|
+
current = owner_class.singleton_class
|
30
|
+
superclass = superclasses.first
|
31
|
+
else
|
32
|
+
current = owner_class
|
33
|
+
superclass = owner_class.superclass
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generate the list of modules that *only* occur in the owner class, but
|
37
|
+
# were included *prior* to the helper modules, in addition to the
|
38
|
+
# superclasses
|
39
|
+
ancestors = current.ancestors - superclass.ancestors + superclasses
|
40
|
+
helper_module_index = ancestors.index(@helper_modules[scope])
|
41
|
+
ancestors = helper_module_index ? ancestors[helper_module_index..].reverse : ancestors.reverse
|
42
|
+
|
43
|
+
# Search for for the first ancestor that defined this method
|
44
|
+
ancestors.detect do |ancestor|
|
45
|
+
ancestor = ancestor.singleton_class if scope == :class && ancestor.is_a?(Class)
|
46
|
+
ancestor.method_defined?(method) || ancestor.private_method_defined?(method)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Determines whether the given method is defined in the owner class or
|
51
|
+
# in a superclass.
|
52
|
+
def owner_class_has_method?(scope, method)
|
53
|
+
target = scope == :class ? owner_class.singleton_class : owner_class
|
54
|
+
target.method_defined?(method) || target.private_method_defined?(method)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Pluralizes the given word using #pluralize (if available) or simply
|
58
|
+
# adding an "s" to the end of the word
|
59
|
+
def pluralize(word)
|
60
|
+
word = word.to_s
|
61
|
+
if word.respond_to?(:pluralize)
|
62
|
+
word.pluralize
|
63
|
+
else
|
64
|
+
"#{word}s"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Generates the results for the given scope based on one or more states to
|
69
|
+
# filter by
|
70
|
+
def run_scope(scope, machine, klass, states)
|
71
|
+
values = states.flatten.compact.map { |state| machine.states.fetch(state).value }
|
72
|
+
scope.call(klass, values)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds sibling machine configurations to the current machine. This
|
76
|
+
# will add states from other machines that have the same attribute.
|
77
|
+
def add_sibling_machine_configs
|
78
|
+
# Add existing states
|
79
|
+
sibling_machines.each do |machine|
|
80
|
+
machine.states.each { |state| states << state unless states[state.name] }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StateMachines
|
4
|
+
class Machine
|
5
|
+
module Validation
|
6
|
+
# Frozen constant to avoid repeated array allocations
|
7
|
+
DANGEROUS_PATTERNS = [
|
8
|
+
/`.*`/, # Backticks (shell execution)
|
9
|
+
/system\s*\(/, # System calls
|
10
|
+
/exec\s*\(/, # Exec calls
|
11
|
+
/eval\s*\(/, # Nested eval
|
12
|
+
/require\s+['"]/, # Require statements
|
13
|
+
/load\s+['"]/, # Load statements
|
14
|
+
/File\./, # File operations
|
15
|
+
/IO\./, # IO operations
|
16
|
+
/Dir\./, # Directory operations
|
17
|
+
/Kernel\./ # Kernel operations
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Validates string input before eval to prevent code injection
|
23
|
+
# This is a basic safety check - not foolproof security
|
24
|
+
def validate_eval_string(method_string)
|
25
|
+
# Check for obviously dangerous patterns
|
26
|
+
DANGEROUS_PATTERNS.each do |pattern|
|
27
|
+
raise SecurityError, "Potentially dangerous code detected in eval string: #{method_string.inspect}" if method_string.match?(pattern)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Basic syntax validation (cross-platform)
|
31
|
+
begin
|
32
|
+
SyntaxValidator.validate!(method_string, '(eval)')
|
33
|
+
rescue SyntaxError => e
|
34
|
+
raise ArgumentError, "Invalid Ruby syntax in eval string: #{e.message}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|