lib/finite_machine/dsl.rb in finite_machine-0.11.3 vs lib/finite_machine/dsl.rb in finite_machine-0.12.0
- old
+ new
@@ -1,61 +1,70 @@
-# encoding: utf-8
+# frozen_string_literal: true
+require_relative 'choice_merger'
+require_relative 'safety'
+require_relative 'transition_builder'
+
module FiniteMachine
# A generic DSL for describing the state machine
class GenericDSL
- include Threadable
+ # Initialize a generic DSL
+ #
+ # @api public
+ def initialize(machine, **attrs)
+ @machine = machine
+ @attrs = attrs
+ end
- class << self
- # @api private
- attr_accessor :top_level
+ # Expose any state constant
+ # @api public
+ def any_state
+ ANY_STATE
end
- attr_threadsafe :machine
-
- attr_threadsafe :attrs
-
- # Initialize a generic DSL
- #
+ # Expose any event constant
# @api public
- def initialize(machine, attrs = {})
- self.attrs = attrs
- self.machine = machine
+ def any_event
+ ANY_EVENT
end
# Delegate attributes to machine instance
#
# @api private
def method_missing(method_name, *args, &block)
- if machine.respond_to?(method_name)
- machine.send(method_name, *args, &block)
+ if @machine.respond_to?(method_name)
+ @machine.send(method_name, *args, &block)
else
super
end
end
+ # Configure state machine properties
+ #
+ # @api private
def call(&block)
- sync_exclusive { instance_eval(&block) }
- # top_level.instance_eval(&block)
+ instance_eval(&block)
end
end # GenericDSL
# A class responsible for adding state machine specific dsl
class DSL < GenericDSL
- attr_threadsafe :defer
+ include Safety
- attr_threadsafe :initial_event
-
# Initialize top level DSL
#
# @api public
- def initialize(machine, attrs = {})
+ def initialize(machine, **attrs)
super(machine, attrs)
- machine.state = FiniteMachine::DEFAULT_STATE
- self.defer = true
- initialize_attrs
+ @machine.state = FiniteMachine::DEFAULT_STATE
+ @defer_initial = true
+ @silent_initial = true
+
+ initial(@attrs[:initial]) if @attrs[:initial]
+ terminal(@attrs[:terminal]) if @attrs[:terminal]
+ log_transitions(@attrs.fetch(:log_transitions, false))
end
# Define initial state
#
# @param [Symbol] value
@@ -85,70 +94,27 @@
# @param [String, Hash] value
#
# @return [StateMachine]
#
# @api public
- def initial(value, options = {})
+ def initial(value, **options)
state = (value && !value.is_a?(Hash)) ? value : raise_missing_state
- name, self.defer, silent = *parse_initial(options)
- self.initial_event = name
- event(name, FiniteMachine::DEFAULT_STATE => state, silent: silent)
+ name, @defer_initial, @silent_initial = *parse_initial(options)
+ @initial_event = name
+ event(name, FiniteMachine::DEFAULT_STATE => state, silent: @silent_initial)
end
# Trigger initial event
#
# @return [nil]
#
# @api private
def trigger_init
- public_send(:"#{initial_event}") unless defer
+ method = @silent_initial ? :transition : :trigger
+ @machine.public_send(method, :"#{@initial_event}") unless @defer_initial
end
- # Attach state machine to an object
- #
- # This allows state machine to initiate events in the context
- # of a particular object
- #
- # @example
- # FiniteMachine.define do
- # target :red
- # end
- #
- # @param [Object] object
- #
- # @return [FiniteMachine::StateMachine]
- #
- # @api public
- def target(object = nil)
- if object.nil?
- env.target
- else
- env.target = object
- end
- end
-
- # Use alternative name for target
- #
- # @example
- # target_alias: :car
- #
- # callbacks {
- # on_transition do |event|
- # car.state = event.to
- # end
- # }
- #
- # @param [Symbol] alias_name
- # the name to alias target to
- #
- # @return [FiniteMachine::StateMachine]
- #
- # @api public
- def alias_target(alias_name)
- env.aliases << alias_name.to_sym
- end
-
# Define terminal state
#
# @example
# terminal :red
#
@@ -157,64 +123,59 @@
# @api public
def terminal(*values)
self.final_state = values
end
- # Define state machine events
+ # Create event and associate transition
#
# @example
- # events do
- # event :start, :red => :green
- # end
+ # event :go, :green => :yellow
+ # event :go, :green => :yellow, if: :lights_on?
#
- # @return [FiniteMachine::StateMachine]
+ # @param [Symbol] name
+ # the event name
+ # @param [Hash] transitions
+ # the event transitions and conditions
#
+ # @return [Transition]
+ #
# @api public
- def events(&block)
- events_dsl.call(&block)
+ def event(name, transitions = {}, &block)
+ detect_event_conflict!(name) if machine.auto_methods?
+
+ if block_given?
+ merger = ChoiceMerger.new(machine, name, transitions)
+ merger.instance_eval(&block)
+ else
+ transition_builder = TransitionBuilder.new(machine, name, transitions)
+ transition_builder.call(transitions)
+ end
end
- # Define state machine callbacks
+ # Add error handler
#
+ # @param [Array] exceptions
+ #
# @example
- # callbacks do
- # on_enter :green do |event| ... end
- # end
+ # handle InvalidStateError, with: :log_errors
#
- # @return [FiniteMachine::Observer]
+ # @return [Array[Exception]]
#
# @api public
- def callbacks(&block)
- observer.call(&block)
+ def handle(*exceptions, &block)
+ @machine.handle(*exceptions, &block)
end
- # Error handler that throws exception when machine is in illegal state
- #
- # @api public
- def handlers(&block)
- errors_dsl.call(&block)
- end
-
# Decide whether to log transitions
#
# @api public
def log_transitions(value)
self.log_transitions = value
end
private
- # Initialize state machine properties based off attributes
- #
- # @api private
- def initialize_attrs
- attrs[:initial] && initial(attrs[:initial])
- attrs[:target] && target(attrs[:target])
- attrs[:terminal] && terminal(attrs[:terminal])
- log_transitions(attrs.fetch(:log_transitions, false))
- end
-
# Parse initial options
#
# @param [Hash] options
# the options to extract for initial state setup
#
@@ -238,50 +199,6 @@
def raise_missing_state
fail MissingInitialStateError,
'Provide state to transition :to for the initial event'
end
end # DSL
-
- # A DSL for describing events
- class EventsDSL < GenericDSL
- include Safety
- # Create event and associate transition
- #
- # @example
- # event :go, :green => :yellow
- # event :go, :green => :yellow, if: :lights_on?
- #
- # @return [Transition]
- #
- # @api public
- def event(name, attrs = {}, &block)
- sync_exclusive do
- detect_event_conflict!(name)
- attributes = attrs.merge!(name: name)
- if block_given?
- merger = ChoiceMerger.new(machine, attributes)
- merger.instance_eval(&block)
- else
- transition_builder = TransitionBuilder.new(machine, attributes)
- transition_builder.call(attrs)
- end
- end
- end
- end # EventsDSL
-
- # A DSL for describing error conditions
- class ErrorsDSL < GenericDSL
- # Add error handler
- #
- # @param [Array] exceptions
- #
- # @example
- # handle InvalidStateError, with: :log_errors
- #
- # @return [Array[Exception]]
- #
- # @api public
- def handle(*exceptions, &block)
- machine.handle(*exceptions, &block)
- end
- end # ErrorsDSL
end # FiniteMachine