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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +205 -14
  3. data/lib/state_machines/branch.rb +20 -17
  4. data/lib/state_machines/callback.rb +13 -12
  5. data/lib/state_machines/core.rb +3 -3
  6. data/lib/state_machines/core_ext/class/state_machine.rb +2 -0
  7. data/lib/state_machines/core_ext.rb +2 -0
  8. data/lib/state_machines/error.rb +7 -4
  9. data/lib/state_machines/eval_helpers.rb +93 -26
  10. data/lib/state_machines/event.rb +41 -29
  11. data/lib/state_machines/event_collection.rb +6 -5
  12. data/lib/state_machines/extensions.rb +7 -5
  13. data/lib/state_machines/helper_module.rb +3 -1
  14. data/lib/state_machines/integrations/base.rb +3 -1
  15. data/lib/state_machines/integrations.rb +13 -14
  16. data/lib/state_machines/machine/action_hooks.rb +53 -0
  17. data/lib/state_machines/machine/callbacks.rb +59 -0
  18. data/lib/state_machines/machine/class_methods.rb +93 -0
  19. data/lib/state_machines/machine/configuration.rb +124 -0
  20. data/lib/state_machines/machine/event_methods.rb +59 -0
  21. data/lib/state_machines/machine/helper_generators.rb +125 -0
  22. data/lib/state_machines/machine/integration.rb +70 -0
  23. data/lib/state_machines/machine/parsing.rb +77 -0
  24. data/lib/state_machines/machine/rendering.rb +17 -0
  25. data/lib/state_machines/machine/scoping.rb +44 -0
  26. data/lib/state_machines/machine/state_methods.rb +101 -0
  27. data/lib/state_machines/machine/utilities.rb +85 -0
  28. data/lib/state_machines/machine/validation.rb +39 -0
  29. data/lib/state_machines/machine.rb +83 -673
  30. data/lib/state_machines/machine_collection.rb +23 -15
  31. data/lib/state_machines/macro_methods.rb +4 -2
  32. data/lib/state_machines/matcher.rb +8 -5
  33. data/lib/state_machines/matcher_helpers.rb +3 -1
  34. data/lib/state_machines/node_collection.rb +23 -18
  35. data/lib/state_machines/options_validator.rb +72 -0
  36. data/lib/state_machines/path.rb +7 -5
  37. data/lib/state_machines/path_collection.rb +7 -4
  38. data/lib/state_machines/state.rb +76 -47
  39. data/lib/state_machines/state_collection.rb +5 -3
  40. data/lib/state_machines/state_context.rb +11 -8
  41. data/lib/state_machines/stdio_renderer.rb +74 -0
  42. data/lib/state_machines/syntax_validator.rb +57 -0
  43. data/lib/state_machines/test_helper.rb +568 -0
  44. data/lib/state_machines/transition.rb +45 -41
  45. data/lib/state_machines/transition_collection.rb +27 -26
  46. data/lib/state_machines/version.rb +3 -1
  47. data/lib/state_machines.rb +4 -1
  48. metadata +32 -16
  49. 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