state-fu 0.11.1
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.
- data/LICENSE +40 -0
- data/README.textile +293 -0
- data/Rakefile +114 -0
- data/lib/binding.rb +292 -0
- data/lib/event.rb +192 -0
- data/lib/executioner.rb +120 -0
- data/lib/hooks.rb +39 -0
- data/lib/interface.rb +132 -0
- data/lib/lathe.rb +538 -0
- data/lib/machine.rb +184 -0
- data/lib/method_factory.rb +243 -0
- data/lib/persistence.rb +116 -0
- data/lib/persistence/active_record.rb +34 -0
- data/lib/persistence/attribute.rb +47 -0
- data/lib/persistence/base.rb +100 -0
- data/lib/persistence/relaxdb.rb +23 -0
- data/lib/persistence/session.rb +7 -0
- data/lib/sprocket.rb +58 -0
- data/lib/state-fu.rb +56 -0
- data/lib/state.rb +48 -0
- data/lib/support/active_support_lite/array.rb +9 -0
- data/lib/support/active_support_lite/array/access.rb +60 -0
- data/lib/support/active_support_lite/array/conversions.rb +202 -0
- data/lib/support/active_support_lite/array/extract_options.rb +21 -0
- data/lib/support/active_support_lite/array/grouping.rb +109 -0
- data/lib/support/active_support_lite/array/random_access.rb +13 -0
- data/lib/support/active_support_lite/array/wrapper.rb +25 -0
- data/lib/support/active_support_lite/blank.rb +67 -0
- data/lib/support/active_support_lite/cattr_reader.rb +57 -0
- data/lib/support/active_support_lite/keys.rb +57 -0
- data/lib/support/active_support_lite/misc.rb +59 -0
- data/lib/support/active_support_lite/module.rb +1 -0
- data/lib/support/active_support_lite/module/delegation.rb +130 -0
- data/lib/support/active_support_lite/object.rb +9 -0
- data/lib/support/active_support_lite/string.rb +38 -0
- data/lib/support/active_support_lite/symbol.rb +16 -0
- data/lib/support/applicable.rb +41 -0
- data/lib/support/arrays.rb +197 -0
- data/lib/support/core_ext.rb +90 -0
- data/lib/support/exceptions.rb +106 -0
- data/lib/support/has_options.rb +16 -0
- data/lib/support/logger.rb +165 -0
- data/lib/support/methodical.rb +17 -0
- data/lib/support/no_stdout.rb +55 -0
- data/lib/support/plotter.rb +62 -0
- data/lib/support/vizier.rb +300 -0
- data/lib/tasks/spec_last.rake +55 -0
- data/lib/tasks/state_fu.rake +57 -0
- data/lib/transition.rb +338 -0
- data/lib/transition_query.rb +224 -0
- data/spec/custom_formatter.rb +49 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
- data/spec/features/method_missing_only_once_spec.rb +28 -0
- data/spec/features/not_requirements_spec.rb +118 -0
- data/spec/features/plotter_spec.rb +97 -0
- data/spec/features/shared_log_spec.rb +7 -0
- data/spec/features/singleton_machine_spec.rb +39 -0
- data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
- data/spec/features/transition_boolean_comparison_spec.rb +101 -0
- data/spec/helper.rb +13 -0
- data/spec/integration/active_record_persistence_spec.rb +202 -0
- data/spec/integration/binding_extension_spec.rb +41 -0
- data/spec/integration/class_accessor_spec.rb +117 -0
- data/spec/integration/event_definition_spec.rb +74 -0
- data/spec/integration/example_01_document_spec.rb +133 -0
- data/spec/integration/example_02_string_spec.rb +88 -0
- data/spec/integration/instance_accessor_spec.rb +97 -0
- data/spec/integration/lathe_extension_spec.rb +67 -0
- data/spec/integration/machine_duplication_spec.rb +101 -0
- data/spec/integration/relaxdb_persistence_spec.rb +97 -0
- data/spec/integration/requirement_reflection_spec.rb +270 -0
- data/spec/integration/state_definition_spec.rb +163 -0
- data/spec/integration/transition_spec.rb +1033 -0
- data/spec/spec.opts +9 -0
- data/spec/spec_helper.rb +132 -0
- data/spec/state_fu_spec.rb +948 -0
- data/spec/units/binding_spec.rb +192 -0
- data/spec/units/event_spec.rb +214 -0
- data/spec/units/exceptions_spec.rb +82 -0
- data/spec/units/lathe_spec.rb +570 -0
- data/spec/units/machine_spec.rb +229 -0
- data/spec/units/method_factory_spec.rb +366 -0
- data/spec/units/sprocket_spec.rb +69 -0
- data/spec/units/state_spec.rb +59 -0
- metadata +171 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
module ActiveSupport #:nodoc:all:
|
2
|
+
module CoreExtensions #:nodoc
|
3
|
+
module Hash #:nodoc
|
4
|
+
module Keys
|
5
|
+
# Return a new hash with all keys converted to strings.
|
6
|
+
#:nodoc
|
7
|
+
def stringify_keys
|
8
|
+
inject({}) do |options, (key, value)|
|
9
|
+
options[key.to_s] = value
|
10
|
+
options
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Destructively convert all keys to strings.
|
15
|
+
#:nodoc
|
16
|
+
def stringify_keys!
|
17
|
+
keys.each do |key|
|
18
|
+
self[key.to_s] = delete(key)
|
19
|
+
end
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return a new hash with all keys converted to symbols.
|
24
|
+
#:nodoc
|
25
|
+
def symbolize_keys
|
26
|
+
inject({}) do |options, (key, value)|
|
27
|
+
options[(key.to_sym rescue key) || key] = value
|
28
|
+
options
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Destructively convert all keys to symbols.
|
33
|
+
#:nodoc
|
34
|
+
def symbolize_keys!
|
35
|
+
self.replace(self.symbolize_keys)
|
36
|
+
end
|
37
|
+
|
38
|
+
alias_method :to_options, :symbolize_keys
|
39
|
+
alias_method :to_options!, :symbolize_keys!
|
40
|
+
|
41
|
+
# Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
|
42
|
+
# Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
|
43
|
+
# as keys, this will fail.
|
44
|
+
#
|
45
|
+
# ==== Examples
|
46
|
+
# { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
|
47
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age"
|
48
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
49
|
+
#:nodoc
|
50
|
+
def assert_valid_keys(*valid_keys)
|
51
|
+
unknown_keys = keys - [valid_keys].flatten
|
52
|
+
raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Object #:nodoc:all
|
2
|
+
# Returns +value+ after yielding +value+ to the block. This simplifies the
|
3
|
+
# process of constructing an object, performing work on the object, and then
|
4
|
+
# returning the object from a method. It is a Ruby-ized realization of the K
|
5
|
+
# combinator, courtesy of Mikael Brockman.
|
6
|
+
#
|
7
|
+
# ==== Examples
|
8
|
+
#
|
9
|
+
# # Without returning
|
10
|
+
# def foo
|
11
|
+
# values = []
|
12
|
+
# values << "bar"
|
13
|
+
# values << "baz"
|
14
|
+
# return values
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# foo # => ['bar', 'baz']
|
18
|
+
#
|
19
|
+
# # returning with a local variable
|
20
|
+
# def foo
|
21
|
+
# returning values = [] do
|
22
|
+
# values << 'bar'
|
23
|
+
# values << 'baz'
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# foo # => ['bar', 'baz']
|
28
|
+
#
|
29
|
+
# # returning with a block argument
|
30
|
+
# def foo
|
31
|
+
# returning [] do |values|
|
32
|
+
# values << 'bar'
|
33
|
+
# values << 'baz'
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# foo # => ['bar', 'baz']
|
38
|
+
#:nodoc
|
39
|
+
def returning(value)
|
40
|
+
yield(value)
|
41
|
+
value
|
42
|
+
end
|
43
|
+
|
44
|
+
# Yields <code>x</code> to the block, and then returns <code>x</code>.
|
45
|
+
# The primary purpose of this method is to "tap into" a method chain,
|
46
|
+
# in order to perform operations on intermediate results within the chain.
|
47
|
+
#
|
48
|
+
# (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
|
49
|
+
# tap { |x| puts "array: #{x.inspect}" }.
|
50
|
+
# select { |x| x%2 == 0 }.
|
51
|
+
# tap { |x| puts "evens: #{x.inspect}" }.
|
52
|
+
# map { |x| x*x }.
|
53
|
+
# tap { |x| puts "squares: #{x.inspect}" }
|
54
|
+
#:nodoc
|
55
|
+
def tap
|
56
|
+
yield self
|
57
|
+
self
|
58
|
+
end unless Object.respond_to?(:tap)
|
59
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join( File.dirname( __FILE__),'module/delegation')
|
@@ -0,0 +1,130 @@
|
|
1
|
+
class Module
|
2
|
+
# Provides a delegate class method to easily expose contained objects' methods
|
3
|
+
# as your own. Pass one or more methods (specified as symbols or strings)
|
4
|
+
# and the name of the target object as the final <tt>:to</tt> option (also a symbol
|
5
|
+
# or string). At least one method and the <tt>:to</tt> option are required.
|
6
|
+
#
|
7
|
+
# Delegation is particularly useful with Active Record associations:
|
8
|
+
#
|
9
|
+
# class Greeter < ActiveRecord::Base
|
10
|
+
# def hello() "hello" end
|
11
|
+
# def goodbye() "goodbye" end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class Foo < ActiveRecord::Base
|
15
|
+
# belongs_to :greeter
|
16
|
+
# delegate :hello, :to => :greeter
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Foo.new.hello # => "hello"
|
20
|
+
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
|
21
|
+
#
|
22
|
+
# Multiple delegates to the same target are allowed:
|
23
|
+
#
|
24
|
+
# class Foo < ActiveRecord::Base
|
25
|
+
# belongs_to :greeter
|
26
|
+
# delegate :hello, :goodbye, :to => :greeter
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Foo.new.goodbye # => "goodbye"
|
30
|
+
#
|
31
|
+
# Methods can be delegated to instance variables, class variables, or constants
|
32
|
+
# by providing them as a symbols:
|
33
|
+
#
|
34
|
+
# class Foo
|
35
|
+
# CONSTANT_ARRAY = [0,1,2,3]
|
36
|
+
# @@class_array = [4,5,6,7]
|
37
|
+
#
|
38
|
+
# def initialize
|
39
|
+
# @instance_array = [8,9,10,11]
|
40
|
+
# end
|
41
|
+
# delegate :sum, :to => :CONSTANT_ARRAY
|
42
|
+
# delegate :min, :to => :@@class_array
|
43
|
+
# delegate :max, :to => :@instance_array
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# Foo.new.sum # => 6
|
47
|
+
# Foo.new.min # => 4
|
48
|
+
# Foo.new.max # => 11
|
49
|
+
#
|
50
|
+
# Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
|
51
|
+
# is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
|
52
|
+
# delegated to.
|
53
|
+
#
|
54
|
+
# Person = Struct.new(:name, :address)
|
55
|
+
#
|
56
|
+
# class Invoice < Struct.new(:client)
|
57
|
+
# delegate :name, :address, :to => :client, :prefix => true
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# john_doe = Person.new("John Doe", "Vimmersvej 13")
|
61
|
+
# invoice = Invoice.new(john_doe)
|
62
|
+
# invoice.client_name # => "John Doe"
|
63
|
+
# invoice.client_address # => "Vimmersvej 13"
|
64
|
+
#
|
65
|
+
# It is also possible to supply a custom prefix.
|
66
|
+
#
|
67
|
+
# class Invoice < Struct.new(:client)
|
68
|
+
# delegate :name, :address, :to => :client, :prefix => :customer
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# invoice = Invoice.new(john_doe)
|
72
|
+
# invoice.customer_name # => "John Doe"
|
73
|
+
# invoice.customer_address # => "Vimmersvej 13"
|
74
|
+
#
|
75
|
+
# If the object to which you delegate can be nil, you may want to use the
|
76
|
+
# :allow_nil option. In that case, it returns nil instead of raising a
|
77
|
+
# NoMethodError exception:
|
78
|
+
#
|
79
|
+
# class Foo
|
80
|
+
# attr_accessor :bar
|
81
|
+
# def initialize(bar = nil)
|
82
|
+
# @bar = bar
|
83
|
+
# end
|
84
|
+
# delegate :zoo, :to => :bar
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
|
88
|
+
#
|
89
|
+
# class Foo
|
90
|
+
# attr_accessor :bar
|
91
|
+
# def initialize(bar = nil)
|
92
|
+
# @bar = bar
|
93
|
+
# end
|
94
|
+
# delegate :zoo, :to => :bar, :allow_nil => true
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# Foo.new.zoo # returns nil
|
98
|
+
#
|
99
|
+
def delegate(*methods)
|
100
|
+
options = methods.pop
|
101
|
+
unless options.is_a?(Hash) && to = options[:to]
|
102
|
+
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
|
103
|
+
end
|
104
|
+
|
105
|
+
if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
|
106
|
+
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
|
107
|
+
end
|
108
|
+
|
109
|
+
prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_"
|
110
|
+
|
111
|
+
file, line = caller.first.split(':', 2)
|
112
|
+
line = line.to_i
|
113
|
+
|
114
|
+
methods.each do |method|
|
115
|
+
on_nil =
|
116
|
+
if options[:allow_nil]
|
117
|
+
'return'
|
118
|
+
else
|
119
|
+
%(raise "#{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
120
|
+
end
|
121
|
+
|
122
|
+
module_eval(<<-EOS, file, line)
|
123
|
+
def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
|
124
|
+
#{on_nil} if #{to}.nil?
|
125
|
+
#{to}.__send__(#{method.inspect}, *args, &block) # client && client.__send__(:name, *args, &block)
|
126
|
+
end # end
|
127
|
+
EOS
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class String #:nodoc:all
|
2
|
+
#:nodoc
|
3
|
+
def underscore
|
4
|
+
self.gsub(/::/, '/').
|
5
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
6
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
7
|
+
tr("-", "_").
|
8
|
+
downcase
|
9
|
+
end
|
10
|
+
|
11
|
+
#:nodoc
|
12
|
+
def demodulize
|
13
|
+
gsub(/^.*::/, '')
|
14
|
+
end
|
15
|
+
|
16
|
+
#:nodoc
|
17
|
+
def constantize
|
18
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
|
19
|
+
raise NameError, "#{self} is not a valid constant name!"
|
20
|
+
end
|
21
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
22
|
+
end
|
23
|
+
|
24
|
+
#:nodoc
|
25
|
+
def classify # DOES NOT SINGULARISE
|
26
|
+
camelize(self.sub(/.*\./, ''))
|
27
|
+
end
|
28
|
+
|
29
|
+
#:nodoc
|
30
|
+
def camelize( first_letter_in_uppercase = true)
|
31
|
+
if first_letter_in_uppercase
|
32
|
+
gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
33
|
+
else
|
34
|
+
first + camelize(lower_case_and_underscored_word)[1..-1]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
unless :to_proc.respond_to?(:to_proc) #:nodoc:all
|
2
|
+
class Symbol
|
3
|
+
# Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
|
4
|
+
#
|
5
|
+
# # The same as people.collect { |p| p.name }
|
6
|
+
# people.collect(&:name)
|
7
|
+
#
|
8
|
+
# # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
|
9
|
+
# people.select(&:manager?).collect(&:salary)
|
10
|
+
#:nodoc
|
11
|
+
def to_proc
|
12
|
+
Proc.new { |*args| args.shift.__send__(self, *args) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module StateFu
|
2
|
+
module Applicable
|
3
|
+
module InstanceMethods
|
4
|
+
|
5
|
+
# if given a hash of options (or a splatted arglist containing
|
6
|
+
# one), merge them into @options. If given a block, eval it
|
7
|
+
# (yielding self if the block expects it)
|
8
|
+
|
9
|
+
def apply!( _options=nil, &block )
|
10
|
+
if _options.is_a?(Array)
|
11
|
+
_options = _options.dup.extract_options!.symbolize_keys
|
12
|
+
else
|
13
|
+
_options ||= {}
|
14
|
+
_options = _options.symbolize_keys!
|
15
|
+
end
|
16
|
+
|
17
|
+
@options = @options.nil?? _options : @options.merge(_options)
|
18
|
+
returning self do
|
19
|
+
if block_given?
|
20
|
+
case block.arity.abs
|
21
|
+
when 1, -1
|
22
|
+
instance_exec self, &block
|
23
|
+
when 0
|
24
|
+
instance_exec &block
|
25
|
+
else
|
26
|
+
raise ArgumentError, "Your block wants too many arguments!"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.included( mod )
|
37
|
+
mod.send( :include, InstanceMethods )
|
38
|
+
mod.extend( ClassMethods )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module StateFu
|
2
|
+
|
3
|
+
# Stuff shared between StateArray and EventArray
|
4
|
+
module ArrayWithSymbolAccessor
|
5
|
+
|
6
|
+
# Pass a symbol to the array and get the object with that .name
|
7
|
+
# [<Foo @name=:bob>][:bob]
|
8
|
+
# => <Foo @name=:bob>
|
9
|
+
|
10
|
+
def []( idx )
|
11
|
+
begin
|
12
|
+
super( idx )
|
13
|
+
rescue TypeError => e
|
14
|
+
if idx.respond_to?(:to_sym)
|
15
|
+
self.detect { |i| i == idx || i.respond_to?(:name) && i.name == idx.to_sym }
|
16
|
+
else
|
17
|
+
raise e
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# so we can go Machine.states.names
|
23
|
+
# mildly helpful with irb + readline
|
24
|
+
def names
|
25
|
+
map(&:name)
|
26
|
+
end
|
27
|
+
|
28
|
+
# SPECME
|
29
|
+
def except *syms
|
30
|
+
reject {|el| syms.flatten.compact.map(&:to_sym).include?(el.to_sym) } #.extend ArrayWithSymbolAccessor
|
31
|
+
end
|
32
|
+
|
33
|
+
def only *syms
|
34
|
+
select {|el| syms.flatten.compact.map(&:to_sym).include?(el.to_sym) } #.extend ArrayWithSymbolAccessor
|
35
|
+
end
|
36
|
+
|
37
|
+
def all
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def rand
|
42
|
+
self.rand
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
module TransitionArgsArray
|
48
|
+
attr_reader :transition
|
49
|
+
|
50
|
+
def init(t)
|
51
|
+
@transition = t
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
delegate :options, :to => :transition
|
56
|
+
delegate :binding, :to => :transition
|
57
|
+
delegate :machine, :to => :transition
|
58
|
+
delegate :origin, :to => :transition
|
59
|
+
delegate :target, :to => :transition
|
60
|
+
|
61
|
+
def []( index )
|
62
|
+
begin
|
63
|
+
super( index )
|
64
|
+
rescue TypeError
|
65
|
+
options[index]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
# Array extender. Used by Machine to keep a list of states.
|
72
|
+
module StateArray
|
73
|
+
include ArrayWithSymbolAccessor
|
74
|
+
|
75
|
+
# is there exactly one possible event to fire, with a single
|
76
|
+
# target event?
|
77
|
+
def next?
|
78
|
+
raise NotImplementedError
|
79
|
+
end
|
80
|
+
|
81
|
+
# if next?, return the state
|
82
|
+
def next
|
83
|
+
raise NotImplementedError
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
# Array extender. Used by Machine to keep a list of events.
|
89
|
+
module EventArray
|
90
|
+
include ArrayWithSymbolAccessor
|
91
|
+
|
92
|
+
# return all events transitioning from the given state
|
93
|
+
def from( origin )
|
94
|
+
select { |e| e.respond_to?(:from?) && e.from?( origin ) }
|
95
|
+
end
|
96
|
+
|
97
|
+
# return all events transitioning to the given state
|
98
|
+
def to( target )
|
99
|
+
select { |e| e.respond_to?(:to?) && e.to?( target ) }
|
100
|
+
end
|
101
|
+
|
102
|
+
# is there exactly one possible event to fire, with a single
|
103
|
+
# target event?
|
104
|
+
def next?
|
105
|
+
raise NotImplementedError
|
106
|
+
end
|
107
|
+
|
108
|
+
# if next?, return the event
|
109
|
+
def next
|
110
|
+
raise NotImplementedError
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
module ModuleRefArray
|
116
|
+
def modules
|
117
|
+
self.map do |h|
|
118
|
+
case h
|
119
|
+
when String, Symbol
|
120
|
+
mod_name = h.to_s.split('/').inject(Object) do |mod, part|
|
121
|
+
mod = mod.const_get( part.camelize )
|
122
|
+
end
|
123
|
+
when Module
|
124
|
+
h
|
125
|
+
else
|
126
|
+
raise ArgumentError.new( h.class.inspect )
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end # modules
|
130
|
+
|
131
|
+
def inject_into( obj )
|
132
|
+
metaclass = class << obj; self; end
|
133
|
+
mods = self.modules()
|
134
|
+
metaclass.class_eval do
|
135
|
+
mods.each do |mod|
|
136
|
+
include( mod )
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Array extender. Used by Machine to keep a list of helpers to mix into
|
143
|
+
# context objects.
|
144
|
+
module HelperArray
|
145
|
+
include ModuleRefArray
|
146
|
+
end
|
147
|
+
|
148
|
+
module ToolArray
|
149
|
+
include ModuleRefArray
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
module MessageArray
|
154
|
+
def strings
|
155
|
+
select { |m| m.is_a? String }
|
156
|
+
end
|
157
|
+
|
158
|
+
def symbols
|
159
|
+
select { |m| m.is_a? Symbol }
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Extend an Array with this. It's a fairly compact implementation,
|
164
|
+
# though it won't be super fast with lots of elements.
|
165
|
+
# items. Internally objects are stored as a list of
|
166
|
+
# [:key, 'value'] pairs.
|
167
|
+
module OrderedHash
|
168
|
+
# if given a symbol / string, treat it as a key
|
169
|
+
def []( index )
|
170
|
+
begin
|
171
|
+
super( index )
|
172
|
+
rescue TypeError
|
173
|
+
( x = self.detect { |i| i.first == index }) && x[1]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# hash-style setter
|
178
|
+
def []=( index, value )
|
179
|
+
begin
|
180
|
+
super( index, value )
|
181
|
+
rescue TypeError
|
182
|
+
( x = self.detect { |i| i.first == index }) ?
|
183
|
+
x[1] = value : self << [ index, value ].extend( OrderedHash )
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# poor man's Hash.keys
|
188
|
+
def keys
|
189
|
+
map(&:first)
|
190
|
+
end
|
191
|
+
|
192
|
+
# poor man's Hash.values
|
193
|
+
def values
|
194
|
+
map(&:last)
|
195
|
+
end
|
196
|
+
end # OrderedHash
|
197
|
+
end
|