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,202 @@
1
+ module ActiveSupport #:nodoc
2
+ module CoreExtensions #:nodoc
3
+ module Array #:nodoc
4
+ module Conversions
5
+ # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
6
+ # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
7
+ # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
8
+ # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
9
+ #:nodoc
10
+ def to_sentence(options = {})
11
+ default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
12
+ default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
13
+ default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
14
+
15
+ # Try to emulate to_senteces previous to 2.3
16
+ if options.has_key?(:connector) || options.has_key?(:skip_last_comma)
17
+ ::ActiveSupport::Deprecation.warn(":connector has been deprecated. Use :words_connector instead", caller) if options.has_key? :connector
18
+ ::ActiveSupport::Deprecation.warn(":skip_last_comma has been deprecated. Use :last_word_connector instead", caller) if options.has_key? :skip_last_comma
19
+
20
+ skip_last_comma = options.delete :skip_last_comma
21
+ if connector = options.delete(:connector)
22
+ options[:last_word_connector] ||= skip_last_comma ? connector : ", #{connector}"
23
+ else
24
+ options[:last_word_connector] ||= skip_last_comma ? default_two_words_connector : default_last_word_connector
25
+ end
26
+ end
27
+
28
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
29
+ options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector
30
+
31
+ case length
32
+ when 0
33
+ ""
34
+ when 1
35
+ self[0].to_s
36
+ when 2
37
+ "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
38
+ else
39
+ "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
40
+ end
41
+ end
42
+
43
+
44
+ # Calls <tt>to_param</tt> on all its elements and joins the result with
45
+ # slashes. This is used by <tt>url_for</tt> in Action Pack.
46
+ #:nodoc
47
+ def to_param
48
+ collect { |e| e.to_param }.join '/'
49
+ end
50
+
51
+ # Converts an array into a string suitable for use as a URL query string,
52
+ # using the given +key+ as the param name.
53
+ #
54
+ # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
55
+ #:nodoc
56
+ def to_query(key)
57
+ prefix = "#{key}[]"
58
+ collect { |value| value.to_query(prefix) }.join '&'
59
+ end
60
+
61
+ #:nodoc
62
+ def self.included(base) #:nodoc
63
+ base.class_eval do
64
+ alias_method :to_default_s, :to_s
65
+ alias_method :to_s, :to_formatted_s
66
+ end
67
+ end
68
+
69
+ # Converts a collection of elements into a formatted string by calling
70
+ # <tt>to_s</tt> on all elements and joining them:
71
+ #
72
+ # Blog.find(:all).to_formatted_s # => "First PostSecond PostThird Post"
73
+ #
74
+ # Adding in the <tt>:db</tt> argument as the format yields a prettier
75
+ # output:
76
+ #
77
+ # Blog.find(:all).to_formatted_s(:db) # => "First Post,Second Post,Third Post"
78
+ #:nodoc
79
+ def to_formatted_s(format = :default)
80
+ case format
81
+ when :db
82
+ if respond_to?(:empty?) && self.empty?
83
+ "null"
84
+ else
85
+ collect { |element| element.id }.join(",")
86
+ end
87
+ else
88
+ to_default_s
89
+ end
90
+ end
91
+
92
+ # Returns a string that represents this array in XML by sending +to_xml+
93
+ # to each element. Active Record collections delegate their representation
94
+ # in XML to this method.
95
+ #
96
+ # All elements are expected to respond to +to_xml+, if any of them does
97
+ # not an exception is raised.
98
+ #
99
+ # The root node reflects the class name of the first element in plural
100
+ # if all elements belong to the same type and that's not Hash:
101
+ #
102
+ # customer.projects.to_xml
103
+ #
104
+ # <?xml version="1.0" encoding="UTF-8"?>
105
+ # <projects type="array">
106
+ # <project>
107
+ # <amount type="decimal">20000.0</amount>
108
+ # <customer-id type="integer">1567</customer-id>
109
+ # <deal-date type="date">2008-04-09</deal-date>
110
+ # ...
111
+ # </project>
112
+ # <project>
113
+ # <amount type="decimal">57230.0</amount>
114
+ # <customer-id type="integer">1567</customer-id>
115
+ # <deal-date type="date">2008-04-15</deal-date>
116
+ # ...
117
+ # </project>
118
+ # </projects>
119
+ #
120
+ # Otherwise the root element is "records":
121
+ #
122
+ # [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml
123
+ #
124
+ # <?xml version="1.0" encoding="UTF-8"?>
125
+ # <records type="array">
126
+ # <record>
127
+ # <bar type="integer">2</bar>
128
+ # <foo type="integer">1</foo>
129
+ # </record>
130
+ # <record>
131
+ # <baz type="integer">3</baz>
132
+ # </record>
133
+ # </records>
134
+ #
135
+ # If the collection is empty the root element is "nil-classes" by default:
136
+ #
137
+ # [].to_xml
138
+ #
139
+ # <?xml version="1.0" encoding="UTF-8"?>
140
+ # <nil-classes type="array"/>
141
+ #
142
+ # To ensure a meaningful root element use the <tt>:root</tt> option:
143
+ #
144
+ # customer_with_no_projects.projects.to_xml(:root => "projects")
145
+ #
146
+ # <?xml version="1.0" encoding="UTF-8"?>
147
+ # <projects type="array"/>
148
+ #
149
+ # By default root children have as node name the one of the root
150
+ # singularized. You can change it with the <tt>:children</tt> option.
151
+ #
152
+ # The +options+ hash is passed downwards:
153
+ #
154
+ # Message.all.to_xml(:skip_types => true)
155
+ #
156
+ # <?xml version="1.0" encoding="UTF-8"?>
157
+ # <messages>
158
+ # <message>
159
+ # <created-at>2008-03-07T09:58:18+01:00</created-at>
160
+ # <id>1</id>
161
+ # <name>1</name>
162
+ # <updated-at>2008-03-07T09:58:18+01:00</updated-at>
163
+ # <user-id>1</user-id>
164
+ # </message>
165
+ # </messages>
166
+ #
167
+ #:nodoc
168
+ def to_xml(options = {})
169
+ raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml }
170
+ require 'builder' unless defined?(Builder)
171
+
172
+ options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize : "records"
173
+ options[:children] ||= options[:root].singularize
174
+ options[:indent] ||= 2
175
+ options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
176
+
177
+ root = options.delete(:root).to_s
178
+ children = options.delete(:children)
179
+
180
+ if !options.has_key?(:dasherize) || options[:dasherize]
181
+ root = root.dasherize
182
+ end
183
+
184
+ options[:builder].instruct! unless options.delete(:skip_instruct)
185
+
186
+ opts = options.merge({ :root => children })
187
+
188
+ xml = options[:builder]
189
+ if empty?
190
+ xml.tag!(root, options[:skip_types] ? {} : {:type => "array"})
191
+ else
192
+ xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) {
193
+ yield xml if block_given?
194
+ each { |e| e.to_xml(opts.merge({ :skip_instruct => true })) }
195
+ }
196
+ end
197
+ end
198
+
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveSupport #:nodoc
2
+ module CoreExtensions #:nodoc
3
+ module Array #:nodoc
4
+ module ExtractOptions #:nodoc:all
5
+ # Extracts options from a set of arguments. Removes and returns the last
6
+ # element in the array if it's a hash, otherwise returns a blank hash.
7
+ #
8
+ # def options(*args)
9
+ # args.extract_options!
10
+ # end
11
+ #
12
+ # options(1, 2) # => {}
13
+ # options(1, 2, :a => :b) # => {:a=>:b}
14
+ #:nodoc
15
+ def extract_options!
16
+ last.is_a?(::Hash) ? pop : {}
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,109 @@
1
+ require 'enumerator'
2
+
3
+ module ActiveSupport #:nodoc
4
+ module CoreExtensions #:nodoc
5
+ module Array #:nodoc
6
+ module Grouping #:nodoc:all
7
+ # Splits or iterates over the array in groups of size +number+,
8
+ # padding any remaining slots with +fill_with+ unless it is +false+.
9
+ #
10
+ # %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group}
11
+ # ["1", "2", "3"]
12
+ # ["4", "5", "6"]
13
+ # ["7", nil, nil]
14
+ #
15
+ # %w(1 2 3).in_groups_of(2, '&nbsp;') {|group| p group}
16
+ # ["1", "2"]
17
+ # ["3", "&nbsp;"]
18
+ #
19
+ # %w(1 2 3).in_groups_of(2, false) {|group| p group}
20
+ # ["1", "2"]
21
+ # ["3"]
22
+ #:nodoc
23
+ def in_groups_of(number, fill_with = nil)
24
+ if fill_with == false
25
+ collection = self
26
+ else
27
+ # size % number gives how many extra we have;
28
+ # subtracting from number gives how many to add;
29
+ # modulo number ensures we don't add group of just fill.
30
+ padding = (number - size % number) % number
31
+ collection = dup.concat([fill_with] * padding)
32
+ end
33
+
34
+ if block_given?
35
+ collection.each_slice(number) { |slice| yield(slice) }
36
+ else
37
+ returning [] do |groups|
38
+ collection.each_slice(number) { |group| groups << group }
39
+ end
40
+ end
41
+ end
42
+
43
+ # Splits or iterates over the array in +number+ of groups, padding any
44
+ # remaining slots with +fill_with+ unless it is +false+.
45
+ #
46
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
47
+ # ["1", "2", "3", "4"]
48
+ # ["5", "6", "7", nil]
49
+ # ["8", "9", "10", nil]
50
+ #
51
+ # %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|group| p group}
52
+ # ["1", "2", "3"]
53
+ # ["4", "5", "&nbsp;"]
54
+ # ["6", "7", "&nbsp;"]
55
+ #
56
+ # %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
57
+ # ["1", "2", "3"]
58
+ # ["4", "5"]
59
+ # ["6", "7"]
60
+ #:nodoc
61
+ def in_groups(number, fill_with = nil)
62
+ # size / number gives minor group size;
63
+ # size % number gives how many objects need extra accomodation;
64
+ # each group hold either division or division + 1 items.
65
+ division = size / number
66
+ modulo = size % number
67
+
68
+ # create a new array avoiding dup
69
+ groups = []
70
+ start = 0
71
+
72
+ number.times do |index|
73
+ length = division + (modulo > 0 && modulo > index ? 1 : 0)
74
+ padding = fill_with != false &&
75
+ modulo > 0 && length == division ? 1 : 0
76
+ groups << slice(start, length).concat([fill_with] * padding)
77
+ start += length
78
+ end
79
+
80
+ if block_given?
81
+ groups.each{|g| yield(g) }
82
+ else
83
+ groups
84
+ end
85
+ end
86
+
87
+ # Divides the array into one or more subarrays based on a delimiting +value+
88
+ # or the result of an optional block.
89
+ #
90
+ # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
91
+ # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
92
+ #:nodoc
93
+ def split(value = nil)
94
+ using_block = block_given?
95
+
96
+ inject([[]]) do |results, element|
97
+ if (using_block && yield(element)) || (value == element)
98
+ results << []
99
+ else
100
+ results.last << element
101
+ end
102
+
103
+ results
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveSupport #:nodoc
2
+ module CoreExtensions #:nodoc
3
+ module Array #:nodoc
4
+ module RandomAccess
5
+ # Returns a random element from the array.
6
+ #:nodoc
7
+ def rand
8
+ self[Kernel.rand(length)]
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveSupport #:nodoc
2
+ module CoreExtensions #:nodoc
3
+ module Array #:nodoc
4
+ module Wrapper
5
+ # Wraps the object in an Array unless it's an Array. Converts the
6
+ # object to an Array using #to_ary if it implements that.
7
+ #:nodoc
8
+ def wrap(object)
9
+ case object
10
+ when nil
11
+ []
12
+ when self
13
+ object
14
+ else
15
+ if object.respond_to?(:to_ary)
16
+ object.to_ary
17
+ else
18
+ [object]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ class Object #:nodoc:all
2
+ # An object is blank if it's false, empty, or a whitespace string.
3
+ # For example, "", " ", +nil+, [], and {} are blank.
4
+ #
5
+ # This simplifies
6
+ #
7
+ # if !address.nil? && !address.empty?
8
+ #
9
+ # to
10
+ #
11
+ # if !address.blank?
12
+ #:nodoc
13
+ def blank?
14
+ respond_to?(:empty?) ? empty? : !self
15
+ end
16
+
17
+ # An object is present if it's not blank.
18
+ #:nodoc
19
+ def present?
20
+ !blank?
21
+ end
22
+ end
23
+
24
+ class NilClass #:nodoc
25
+ #:nodoc
26
+ def blank?
27
+ true
28
+ end
29
+ end
30
+
31
+ class FalseClass #:nodoc
32
+ #:nodoc
33
+ def blank?
34
+ true
35
+ end
36
+ end
37
+
38
+ class TrueClass #:nodoc
39
+ #:nodoc
40
+ def blank?
41
+ false
42
+ end
43
+ end
44
+
45
+ class Array #:nodoc
46
+ #:nodoc
47
+ alias_method :blank?, :empty?
48
+ end
49
+
50
+ class Hash #:nodoc
51
+ #:nodoc
52
+ alias_method :blank?, :empty?
53
+ end
54
+
55
+ class String #:nodoc
56
+ #:nodoc
57
+ def blank?
58
+ self !~ /\S/
59
+ end
60
+ end
61
+
62
+ class Numeric #:nodoc
63
+ #:nodoc
64
+ def blank?
65
+ false
66
+ end
67
+ end
@@ -0,0 +1,57 @@
1
+ # Extends the class object with class and instance accessors for class attributes,
2
+ # just like the native attr* accessors for instance attributes.
3
+ #
4
+ # class Person
5
+ # cattr_accessor :hair_colors
6
+ # end
7
+ #
8
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
9
+ class Class #:nodoc:all
10
+ #:nodoc
11
+ def cattr_reader(*syms)
12
+ syms.flatten.each do |sym|
13
+ next if sym.is_a?(Hash)
14
+ class_eval(<<-EOS, __FILE__, __LINE__)
15
+ unless defined? @@#{sym} # unless defined? @@hair_colors
16
+ @@#{sym} = nil # @@hair_colors = nil
17
+ end # end
18
+ #
19
+ def self.#{sym} # def self.hair_colors
20
+ @@#{sym} # @@hair_colors
21
+ end # end
22
+ #
23
+ def #{sym} # def hair_colors
24
+ @@#{sym} # @@hair_colors
25
+ end # end
26
+ EOS
27
+ end
28
+ end
29
+
30
+ #:nodoc
31
+ def cattr_writer(*syms)
32
+ options = syms.extract_options!
33
+ syms.flatten.each do |sym|
34
+ class_eval(<<-EOS, __FILE__, __LINE__)
35
+ unless defined? @@#{sym} # unless defined? @@hair_colors
36
+ @@#{sym} = nil # @@hair_colors = nil
37
+ end # end
38
+ #
39
+ def self.#{sym}=(obj) # def self.hair_colors=(obj)
40
+ @@#{sym} = obj # @@hair_colors = obj
41
+ end # end
42
+ #
43
+ #{" #
44
+ def #{sym}=(obj) # def hair_colors=(obj)
45
+ @@#{sym} = obj # @@hair_colors = obj
46
+ end # end
47
+ " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
48
+ EOS
49
+ end
50
+ end
51
+
52
+ #:nodoc
53
+ def cattr_accessor(*syms)
54
+ cattr_reader(*syms)
55
+ cattr_writer(*syms)
56
+ end
57
+ end