/usr/lib/ruby/vendor_ruby/celluloid/fsm.rb is in ruby-celluloid 0.16.0-4.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | module Celluloid
# Simple finite state machines with integrated Celluloid timeout support
# Inspired by Erlang's gen_fsm (http://www.erlang.org/doc/man/gen_fsm.html)
#
# Basic usage:
#
# class MyMachine
# include Celluloid::FSM # NOTE: this does NOT pull in the Celluloid module
# end
#
# Inside an actor:
#
# #
# machine = MyMachine.new(current_actor)
module FSM
class UnattachedError < Celluloid::Error; end # Not attached to an actor
DEFAULT_STATE = :default # Default state name unless one is explicitly set
# Included hook to extend class methods
def self.included(klass)
klass.send :extend, ClassMethods
end
module ClassMethods
# Obtain or set the default state
# Passing a state name sets the default state
def default_state(new_default = nil)
if new_default
@default_state = new_default.to_sym
else
defined?(@default_state) ? @default_state : DEFAULT_STATE
end
end
# Obtain the valid states for this FSM
def states
@states ||= {}
end
# Declare an FSM state and optionally provide a callback block to fire
# Options:
# * to: a state or array of states this state can transition to
def state(*args, &block)
if args.last.is_a? Hash
# Stringify keys :/
options = args.pop.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
else
options = {}
end
args.each do |name|
name = name.to_sym
default_state name if options['default']
states[name] = State.new(name, options['to'], &block)
end
end
end
attr_reader :actor
# Be kind and call super if you must redefine initialize
def initialize(actor = nil)
@state = self.class.default_state
@delayed_transition = nil
@actor = actor
@actor ||= Celluloid.current_actor if Celluloid.actor?
end
# Obtain the current state of the FSM
attr_reader :state
# Attach this FSM to an actor. This allows FSMs to wait for and initiate
# events in the context of a particular actor
def attach(actor)
@actor = actor
end
alias_method :actor=, :attach
# Transition to another state
# Options:
# * delay: don't transition immediately, wait the given number of seconds.
# This will return a Celluloid::Timer object you can use to
# cancel the pending state transition.
#
# Note: making additional state transitions will cancel delayed transitions
def transition(state_name, options = {})
new_state = validate_and_sanitize_new_state(state_name)
return unless new_state
if handle_delayed_transitions(new_state, options[:delay])
return @delayed_transition
end
transition_with_callbacks!(new_state)
end
# Immediate state transition with no sanity checks, or callbacks. "Dangerous!"
def transition!(state_name)
@state = state_name
end
protected
def validate_and_sanitize_new_state(state_name)
state_name = state_name.to_sym
return if current_state_name == state_name
if current_state and not current_state.valid_transition? state_name
valid = current_state.transitions.map(&:to_s).join(", ")
raise ArgumentError, "#{self.class} can't change state from '#{@state}' to '#{state_name}', only to: #{valid}"
end
new_state = states[state_name]
unless new_state
return if state_name == default_state
raise ArgumentError, "invalid state for #{self.class}: #{state_name}"
end
new_state
end
def transition_with_callbacks!(state_name)
transition! state_name.name
state_name.call(self)
end
def states
self.class.states
end
def default_state
self.class.default_state
end
def current_state
states[@state]
end
def current_state_name
current_state && current_state.name || ''
end
def handle_delayed_transitions(new_state, delay)
if delay
raise UnattachedError, "can't delay unless attached" unless @actor
@delayed_transition.cancel if @delayed_transition
@delayed_transition = @actor.after(delay) do
transition_with_callbacks!(new_state)
end
return @delayed_transition
end
if defined?(@delayed_transition) and @delayed_transition
@delayed_transition.cancel
@delayed_transition = nil
end
end
# FSM states as declared by Celluloid::FSM.state
class State
attr_reader :name, :transitions
def initialize(name, transitions = nil, &block)
@name, @block = name, block
@transitions = nil
@transitions = Array(transitions).map { |t| t.to_sym } if transitions
end
def call(obj)
obj.instance_eval(&@block) if @block
end
def valid_transition?(new_state)
# All transitions are allowed unless expressly
return true unless @transitions
@transitions.include? new_state.to_sym
end
end
end
end
|