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
@@ -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 = lambda { |object| object.class.state_machine(machine_name).states.matches?(object, state_name) }
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
- options.assert_valid_keys(:from, :to, :on, :if, :unless)
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, &block)
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
- Array(if_condition).all? { |condition| proxy.evaluate_method(object, condition) } &&
123
- !Array(unless_condition).any? { |condition| proxy.evaluate_method(object, condition) }
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, &block)
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