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.
Files changed (85) hide show
  1. data/LICENSE +40 -0
  2. data/README.textile +293 -0
  3. data/Rakefile +114 -0
  4. data/lib/binding.rb +292 -0
  5. data/lib/event.rb +192 -0
  6. data/lib/executioner.rb +120 -0
  7. data/lib/hooks.rb +39 -0
  8. data/lib/interface.rb +132 -0
  9. data/lib/lathe.rb +538 -0
  10. data/lib/machine.rb +184 -0
  11. data/lib/method_factory.rb +243 -0
  12. data/lib/persistence.rb +116 -0
  13. data/lib/persistence/active_record.rb +34 -0
  14. data/lib/persistence/attribute.rb +47 -0
  15. data/lib/persistence/base.rb +100 -0
  16. data/lib/persistence/relaxdb.rb +23 -0
  17. data/lib/persistence/session.rb +7 -0
  18. data/lib/sprocket.rb +58 -0
  19. data/lib/state-fu.rb +56 -0
  20. data/lib/state.rb +48 -0
  21. data/lib/support/active_support_lite/array.rb +9 -0
  22. data/lib/support/active_support_lite/array/access.rb +60 -0
  23. data/lib/support/active_support_lite/array/conversions.rb +202 -0
  24. data/lib/support/active_support_lite/array/extract_options.rb +21 -0
  25. data/lib/support/active_support_lite/array/grouping.rb +109 -0
  26. data/lib/support/active_support_lite/array/random_access.rb +13 -0
  27. data/lib/support/active_support_lite/array/wrapper.rb +25 -0
  28. data/lib/support/active_support_lite/blank.rb +67 -0
  29. data/lib/support/active_support_lite/cattr_reader.rb +57 -0
  30. data/lib/support/active_support_lite/keys.rb +57 -0
  31. data/lib/support/active_support_lite/misc.rb +59 -0
  32. data/lib/support/active_support_lite/module.rb +1 -0
  33. data/lib/support/active_support_lite/module/delegation.rb +130 -0
  34. data/lib/support/active_support_lite/object.rb +9 -0
  35. data/lib/support/active_support_lite/string.rb +38 -0
  36. data/lib/support/active_support_lite/symbol.rb +16 -0
  37. data/lib/support/applicable.rb +41 -0
  38. data/lib/support/arrays.rb +197 -0
  39. data/lib/support/core_ext.rb +90 -0
  40. data/lib/support/exceptions.rb +106 -0
  41. data/lib/support/has_options.rb +16 -0
  42. data/lib/support/logger.rb +165 -0
  43. data/lib/support/methodical.rb +17 -0
  44. data/lib/support/no_stdout.rb +55 -0
  45. data/lib/support/plotter.rb +62 -0
  46. data/lib/support/vizier.rb +300 -0
  47. data/lib/tasks/spec_last.rake +55 -0
  48. data/lib/tasks/state_fu.rake +57 -0
  49. data/lib/transition.rb +338 -0
  50. data/lib/transition_query.rb +224 -0
  51. data/spec/custom_formatter.rb +49 -0
  52. data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
  53. data/spec/features/method_missing_only_once_spec.rb +28 -0
  54. data/spec/features/not_requirements_spec.rb +118 -0
  55. data/spec/features/plotter_spec.rb +97 -0
  56. data/spec/features/shared_log_spec.rb +7 -0
  57. data/spec/features/singleton_machine_spec.rb +39 -0
  58. data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
  59. data/spec/features/transition_boolean_comparison_spec.rb +101 -0
  60. data/spec/helper.rb +13 -0
  61. data/spec/integration/active_record_persistence_spec.rb +202 -0
  62. data/spec/integration/binding_extension_spec.rb +41 -0
  63. data/spec/integration/class_accessor_spec.rb +117 -0
  64. data/spec/integration/event_definition_spec.rb +74 -0
  65. data/spec/integration/example_01_document_spec.rb +133 -0
  66. data/spec/integration/example_02_string_spec.rb +88 -0
  67. data/spec/integration/instance_accessor_spec.rb +97 -0
  68. data/spec/integration/lathe_extension_spec.rb +67 -0
  69. data/spec/integration/machine_duplication_spec.rb +101 -0
  70. data/spec/integration/relaxdb_persistence_spec.rb +97 -0
  71. data/spec/integration/requirement_reflection_spec.rb +270 -0
  72. data/spec/integration/state_definition_spec.rb +163 -0
  73. data/spec/integration/transition_spec.rb +1033 -0
  74. data/spec/spec.opts +9 -0
  75. data/spec/spec_helper.rb +132 -0
  76. data/spec/state_fu_spec.rb +948 -0
  77. data/spec/units/binding_spec.rb +192 -0
  78. data/spec/units/event_spec.rb +214 -0
  79. data/spec/units/exceptions_spec.rb +82 -0
  80. data/spec/units/lathe_spec.rb +570 -0
  81. data/spec/units/machine_spec.rb +229 -0
  82. data/spec/units/method_factory_spec.rb +366 -0
  83. data/spec/units/sprocket_spec.rb +69 -0
  84. data/spec/units/state_spec.rb +59 -0
  85. 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,9 @@
1
+ class Object #:nodoc:all
2
+
3
+ #:nodoc
4
+ def extended_by #:nodoc
5
+ ancestors = class << self; ancestors end
6
+ ancestors.select { |mod| mod.class == Module } - [ Object, Kernel ]
7
+ end
8
+
9
+ 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