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