state-fu 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
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