enum_state_machine 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -12
  3. data/.ruby-version +1 -1
  4. data/.ruby-version.orig +5 -0
  5. data/Gemfile +0 -1
  6. data/Rakefile +0 -18
  7. data/enum_state_machine.gemspec +35 -0
  8. data/enum_state_machine.gemspec.orig +43 -0
  9. data/lib/enum_state_machine/assertions.rb +36 -0
  10. data/lib/enum_state_machine/branch.rb +225 -0
  11. data/lib/enum_state_machine/callback.rb +232 -0
  12. data/lib/enum_state_machine/core.rb +12 -0
  13. data/lib/enum_state_machine/core_ext/class/state_machine.rb +5 -0
  14. data/lib/enum_state_machine/core_ext.rb +2 -0
  15. data/lib/enum_state_machine/error.rb +13 -0
  16. data/lib/enum_state_machine/eval_helpers.rb +87 -0
  17. data/lib/enum_state_machine/event.rb +257 -0
  18. data/lib/enum_state_machine/event_collection.rb +141 -0
  19. data/lib/enum_state_machine/extensions.rb +149 -0
  20. data/lib/enum_state_machine/graph.rb +93 -0
  21. data/lib/enum_state_machine/helper_module.rb +17 -0
  22. data/lib/enum_state_machine/initializers/rails.rb +22 -0
  23. data/lib/enum_state_machine/initializers.rb +4 -0
  24. data/lib/enum_state_machine/integrations/active_model/locale.rb +11 -0
  25. data/lib/enum_state_machine/integrations/active_model/observer.rb +33 -0
  26. data/lib/enum_state_machine/integrations/active_model/observer_update.rb +42 -0
  27. data/lib/enum_state_machine/integrations/active_model/versions.rb +31 -0
  28. data/lib/enum_state_machine/integrations/active_model.rb +585 -0
  29. data/lib/enum_state_machine/integrations/active_record/locale.rb +20 -0
  30. data/lib/enum_state_machine/integrations/active_record/versions.rb +123 -0
  31. data/lib/enum_state_machine/integrations/active_record.rb +548 -0
  32. data/lib/enum_state_machine/integrations/base.rb +100 -0
  33. data/lib/enum_state_machine/integrations.rb +97 -0
  34. data/lib/enum_state_machine/machine.rb +2292 -0
  35. data/lib/enum_state_machine/machine_collection.rb +86 -0
  36. data/lib/enum_state_machine/macro_methods.rb +518 -0
  37. data/lib/enum_state_machine/matcher.rb +123 -0
  38. data/lib/enum_state_machine/matcher_helpers.rb +54 -0
  39. data/lib/enum_state_machine/node_collection.rb +222 -0
  40. data/lib/enum_state_machine/path.rb +120 -0
  41. data/lib/enum_state_machine/path_collection.rb +90 -0
  42. data/lib/enum_state_machine/state.rb +297 -0
  43. data/lib/enum_state_machine/state_collection.rb +112 -0
  44. data/lib/enum_state_machine/state_context.rb +138 -0
  45. data/lib/enum_state_machine/state_enum.rb +23 -0
  46. data/lib/enum_state_machine/transition.rb +470 -0
  47. data/lib/enum_state_machine/transition_collection.rb +245 -0
  48. data/lib/enum_state_machine/version.rb +3 -0
  49. data/lib/enum_state_machine/yard/handlers/base.rb +32 -0
  50. data/lib/enum_state_machine/yard/handlers/event.rb +25 -0
  51. data/lib/enum_state_machine/yard/handlers/machine.rb +344 -0
  52. data/lib/enum_state_machine/yard/handlers/state.rb +25 -0
  53. data/lib/enum_state_machine/yard/handlers/transition.rb +47 -0
  54. data/lib/enum_state_machine/yard/handlers.rb +12 -0
  55. data/lib/enum_state_machine/yard/templates/default/class/html/setup.rb +30 -0
  56. data/lib/enum_state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
  57. data/lib/enum_state_machine/yard/templates.rb +3 -0
  58. data/lib/enum_state_machine/yard.rb +8 -0
  59. data/lib/enum_state_machine.rb +9 -0
  60. data/lib/tasks/enum_state_machine.rake +1 -0
  61. data/lib/tasks/enum_state_machine.rb +24 -0
  62. data/lib/yard-enum_state_machine.rb +2 -0
  63. data/test/functional/state_machine_test.rb +1066 -0
  64. data/test/unit/graph_test.rb +9 -5
  65. data/test/unit/integrations/active_model_test.rb +1245 -0
  66. data/test/unit/integrations/active_record_test.rb +2551 -0
  67. data/test/unit/integrations/base_test.rb +104 -0
  68. data/test/unit/integrations_test.rb +71 -0
  69. data/test/unit/invalid_event_test.rb +20 -0
  70. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  71. data/test/unit/invalid_transition_test.rb +115 -0
  72. data/test/unit/machine_collection_test.rb +603 -0
  73. data/test/unit/machine_test.rb +3395 -0
  74. data/test/unit/state_machine_test.rb +31 -0
  75. metadata +212 -44
  76. data/Appraisals +0 -28
  77. data/gemfiles/active_model_4.0.4.gemfile +0 -9
  78. data/gemfiles/active_model_4.0.4.gemfile.lock +0 -51
  79. data/gemfiles/active_record_4.0.4.gemfile +0 -11
  80. data/gemfiles/active_record_4.0.4.gemfile.lock +0 -61
  81. data/gemfiles/default.gemfile +0 -7
  82. data/gemfiles/default.gemfile.lock +0 -27
  83. data/gemfiles/graphviz_1.0.9.gemfile +0 -7
  84. data/gemfiles/graphviz_1.0.9.gemfile.lock +0 -30
@@ -0,0 +1,3 @@
1
+ module EnumStateMachine
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,32 @@
1
+ module EnumStateMachine
2
+ module YARD
3
+ module Handlers
4
+ # Handles and processes nodes
5
+ class Base < ::YARD::Handlers::Ruby::Base
6
+ private
7
+ # Extracts the value from the node as either a string or symbol
8
+ def extract_node_name(ast)
9
+ case ast.type
10
+ when :symbol_literal
11
+ ast.jump(:ident).source.to_sym
12
+ when :string_literal
13
+ ast.jump(:tstring_content).source
14
+ else
15
+ nil
16
+ end
17
+ end
18
+
19
+ # Extracts the values from the node as either strings or symbols.
20
+ # If the node isn't an array, it'll be converted to an array.
21
+ def extract_node_names(ast, convert_to_array = true)
22
+ if [nil, :array].include?(ast.type)
23
+ ast.children.map {|child| extract_node_name(child)}
24
+ else
25
+ node_name = extract_node_name(ast)
26
+ convert_to_array ? [node_name] : node_name
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ module EnumStateMachine
2
+ module YARD
3
+ module Handlers
4
+ # Handles and processes #event
5
+ class Event < Base
6
+ handles method_call(:event)
7
+
8
+ def process
9
+ if owner.is_a?(EnumStateMachine::Machine)
10
+ handler = self
11
+ statement = self.statement
12
+ names = extract_node_names(statement.parameters(false))
13
+
14
+ names.each do |name|
15
+ owner.event(name) do
16
+ # Parse the block
17
+ handler.parse_block(statement.last.last, :owner => self)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,344 @@
1
+ require 'tempfile'
2
+
3
+ module EnumStateMachine
4
+ module YARD
5
+ module Handlers
6
+ # Handles and processes #state_machine
7
+ class Machine < Base
8
+ handles method_call(:state_machine)
9
+ namespace_only
10
+
11
+ # The generated state machine
12
+ attr_reader :machine
13
+
14
+ def process
15
+ # Cross-file storage for state machines
16
+ globals.state_machines ||= Hash.new {|h, k| h[k] = {}}
17
+ namespace['state_machines'] ||= {}
18
+
19
+ # Create new machine
20
+ klass = inherited_machine ? Class.new(inherited_machine.owner_class) : Class.new { extend EnumStateMachine::MacroMethods }
21
+ @machine = klass.state_machine(name, options) {}
22
+
23
+ # Track the state machine
24
+ globals.state_machines[namespace.name][name] = machine
25
+ namespace['state_machines'][name] = {:name => name, :description => statement.docstring}
26
+
27
+ # Parse the block
28
+ parse_block(statement.last.last, :owner => machine)
29
+
30
+ # Draw the machine for reference in the template
31
+ file = Tempfile.new(['enum_state_machine', '.png'])
32
+ begin
33
+ if machine.draw(:name => File.basename(file.path, '.png'), :path => File.dirname(file.path), :orientation => 'landscape')
34
+ namespace['state_machines'][name][:image] = file.read
35
+ end
36
+ ensure
37
+ # Clean up tempfile
38
+ file.close
39
+ file.unlink
40
+ end
41
+
42
+ # Define auto-generated methods
43
+ define_macro_methods
44
+ define_state_methods
45
+ define_event_methods
46
+ end
47
+
48
+ protected
49
+ # Extracts the machine name's
50
+ def name
51
+ @name ||= begin
52
+ ast = statement.parameters.first
53
+ if ast && [:symbol_literal, :string_literal].include?(ast.type)
54
+ extract_node_name(ast)
55
+ else
56
+ :state
57
+ end
58
+ end
59
+ end
60
+
61
+ # Extracts the machine options. Note that this will only extract a
62
+ # subset of the options supported.
63
+ def options
64
+ @options ||= begin
65
+ options = {}
66
+ ast = statement.parameters(false).last
67
+
68
+ if !inherited_machine && ast && ![:symbol_literal, :string_literal].include?(ast.type)
69
+ ast.children.each do |assoc|
70
+ # Only extract important options
71
+ key = extract_node_name(assoc[0])
72
+ next unless [:initial, :attribute, :namespace, :action].include?(key)
73
+
74
+ value = extract_node_name(assoc[1])
75
+ options[key] = value
76
+ end
77
+ end
78
+
79
+ options
80
+ end
81
+ end
82
+
83
+ # Gets the machine that was inherited from a superclass. This also
84
+ # ensures each ancestor has been loaded prior to looking up their definitions.
85
+ def inherited_machine
86
+ @inherited_machine ||= begin
87
+ namespace.inheritance_tree.each do |ancestor|
88
+ begin
89
+ ensure_loaded!(ancestor)
90
+ rescue ::YARD::Handlers::NamespaceMissingError
91
+ # Ignore: just means that we can't access an ancestor
92
+ end
93
+ end
94
+
95
+ # Find the first ancestor that has the machine
96
+ loaded_superclasses.detect do |superclass|
97
+ if superclass != namespace
98
+ machine = globals.state_machines[superclass.name][name]
99
+ break machine if machine
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ # Gets members of this class's superclasses have already been loaded
106
+ # by YARD
107
+ def loaded_superclasses
108
+ namespace.inheritance_tree.select {|ancestor| ancestor.is_a?(::YARD::CodeObjects::ClassObject)}
109
+ end
110
+
111
+ # Gets a list of all attributes for the current class, including those
112
+ # that are inherited
113
+ def instance_attributes
114
+ attributes = {}
115
+ loaded_superclasses.each {|superclass| attributes.merge!(superclass.instance_attributes)}
116
+ attributes
117
+ end
118
+
119
+ # Gets the type of ORM integration being used based on the list of
120
+ # ancestors (including mixins)
121
+ def integration
122
+ @integration ||= Integrations.match_ancestors(namespace.inheritance_tree(true).map {|ancestor| ancestor.path})
123
+ end
124
+
125
+ # Gets the class type being used to define states. Default is "Symbol".
126
+ def state_type
127
+ @state_type ||= machine.states.any? ? machine.states.map {|state| state.name}.compact.first.class.to_s : 'Symbol'
128
+ end
129
+
130
+ # Gets the class type being used to define events. Default is "Symbol".
131
+ def event_type
132
+ @event_type ||= machine.events.any? ? machine.events.first.name.class.to_s : 'Symbol'
133
+ end
134
+
135
+ # Defines auto-generated macro methods for the given machine
136
+ def define_macro_methods
137
+ return if inherited_machine
138
+
139
+ # Human state name lookup
140
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}", :class))
141
+ m.docstring = [
142
+ "Gets the humanized name for the given state.",
143
+ "@param [#{state_type}] state The state to look up",
144
+ "@return [String] The human state name"
145
+ ]
146
+ m.parameters = ["state"]
147
+
148
+ # Human event name lookup
149
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:event_name)}", :class))
150
+ m.docstring = [
151
+ "Gets the humanized name for the given event.",
152
+ "@param [#{event_type}] event The event to look up",
153
+ "@return [String] The human event name"
154
+ ]
155
+ m.parameters = ["event"]
156
+
157
+ # Only register attributes when the accessor isn't explicitly defined
158
+ # by the class / superclass *and* isn't defined by inference from the
159
+ # ORM being used
160
+ unless integration || instance_attributes.include?(machine.attribute.to_sym)
161
+ attribute = machine.attribute
162
+ namespace.attributes[:instance][attribute] = {}
163
+
164
+ # Machine attribute getter
165
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute))
166
+ namespace.attributes[:instance][attribute][:read] = m
167
+ m.docstring = [
168
+ "Gets the current attribute value for the machine",
169
+ "@return The attribute value"
170
+ ]
171
+
172
+ # Machine attribute setter
173
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}="))
174
+ namespace.attributes[:instance][attribute][:write] = m
175
+ m.docstring = [
176
+ "Sets the current value for the machine",
177
+ "@param new_#{attribute} The new value to set"
178
+ ]
179
+ m.parameters = ["new_#{attribute}"]
180
+ end
181
+
182
+ if integration && integration.defaults[:action] && !options.include?(:action) || options[:action]
183
+ attribute = "#{machine.name}_event"
184
+ namespace.attributes[:instance][attribute] = {}
185
+
186
+ # Machine event attribute getter
187
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute))
188
+ namespace.attributes[:instance][attribute][:read] = m
189
+ m.docstring = [
190
+ "Gets the current event attribute value for the machine",
191
+ "@return The event attribute value"
192
+ ]
193
+
194
+ # Machine event attribute setter
195
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}="))
196
+ namespace.attributes[:instance][attribute][:write] = m
197
+ m.docstring = [
198
+ "Sets the current value for the machine",
199
+ "@param new_#{attribute} The new value to set"
200
+ ]
201
+ m.parameters = ["new_#{attribute}"]
202
+ end
203
+
204
+ # Presence query
205
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{machine.name}?"))
206
+ m.docstring = [
207
+ "Checks the given state name against the current state.",
208
+ "@param [#{state_type}] state_name The name of the state to check",
209
+ "@return [Boolean] True if they are the same state, otherwise false",
210
+ "@raise [IndexError] If the state name is invalid"
211
+ ]
212
+ m.parameters = ["state_name"]
213
+
214
+ # Internal state name
215
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:name)))
216
+ m.docstring = [
217
+ "Gets the internal name of the state for the current value.",
218
+ "@return [#{state_type}] The internal name of the state"
219
+ ]
220
+
221
+ # Human state name
222
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}"))
223
+ m.docstring = [
224
+ "Gets the human-readable name of the state for the current value.",
225
+ "@return [String] The human-readable state name"
226
+ ]
227
+
228
+ # Available events
229
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:events)))
230
+ m.docstring = [
231
+ "Gets the list of events that can be fired on the current #{machine.name} (uses the *unqualified* event names)",
232
+ "@param [Hash] requirements The transition requirements to test against",
233
+ "@option requirements [#{state_type}] :from (the current state) One or more initial states",
234
+ "@option requirements [#{state_type}] :to One or more target states",
235
+ "@option requirements [#{event_type}] :on One or more events that fire the transition",
236
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
237
+ "@return [Array<#{event_type}>] The list of event names"
238
+ ]
239
+ m.parameters = [["requirements", "{}"]]
240
+
241
+ # Available transitions
242
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:transitions)))
243
+ m.docstring = [
244
+ "Gets the list of transitions that can be made for the current #{machine.name}",
245
+ "@param [Hash] requirements The transition requirements to test against",
246
+ "@option requirements [#{state_type}] :from (the current state) One or more initial states",
247
+ "@option requirements [#{state_type}] :to One or more target states",
248
+ "@option requirements [#{event_type}] :on One or more events that fire the transition",
249
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
250
+ "@return [Array<EnumStateMachine::Transition>] The available transitions"
251
+ ]
252
+ m.parameters = [["requirements", "{}"]]
253
+
254
+ # Available transition paths
255
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:paths)))
256
+ m.docstring = [
257
+ "Gets the list of sequences of transitions that can be run for the current #{machine.name}",
258
+ "@param [Hash] requirements The transition requirements to test against",
259
+ "@option requirements [#{state_type}] :from (the current state) The initial state",
260
+ "@option requirements [#{state_type}] :to The target state",
261
+ "@option requirements [Boolean] :deep Whether to enable deep searches for the target state",
262
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
263
+ "@return [EnumStateMachine::PathCollection] The collection of paths"
264
+ ]
265
+ m.parameters = [["requirements", "{}"]]
266
+
267
+ # Generic event fire
268
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "fire_#{machine.attribute(:event)}"))
269
+ m.docstring = [
270
+ "Fires an arbitrary #{machine.name} event with the given argument list",
271
+ "@param [#{event_type}] event The name of the event to fire",
272
+ "@param args Optional arguments to include in the transition",
273
+ "@return [Boolean] +true+ if the event succeeds, otherwise +false+"
274
+ ]
275
+ m.parameters = ["event", "*args"]
276
+ end
277
+
278
+ # Defines auto-generated event methods for the given machine
279
+ def define_event_methods
280
+ machine.events.each do |event|
281
+ next if inherited_machine && inherited_machine.events[event.name]
282
+
283
+ # Event query
284
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "can_#{event.qualified_name}?"))
285
+ m.docstring = [
286
+ "Checks whether #{event.name.inspect} can be fired.",
287
+ "@param [Hash] requirements The transition requirements to test against",
288
+ "@option requirements [#{state_type}] :from (the current state) One or more initial states",
289
+ "@option requirements [#{state_type}] :to One or more target states",
290
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
291
+ "@return [Boolean] +true+ if #{event.name.inspect} can be fired, otherwise +false+"
292
+ ]
293
+ m.parameters = [["requirements", "{}"]]
294
+
295
+ # Event transition
296
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}_transition"))
297
+ m.docstring = [
298
+ "Gets the next transition that would be performed if #{event.name.inspect} were to be fired.",
299
+ "@param [Hash] requirements The transition requirements to test against",
300
+ "@option requirements [#{state_type}] :from (the current state) One or more initial states",
301
+ "@option requirements [#{state_type}] :to One or more target states",
302
+ "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
303
+ "@return [EnumStateMachine::Transition] The transition that would be performed or +nil+"
304
+ ]
305
+ m.parameters = [["requirements", "{}"]]
306
+
307
+ # Fire event
308
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, event.qualified_name))
309
+ m.docstring = [
310
+ "Fires the #{event.name.inspect} event.",
311
+ "@param [Array] args Optional arguments to include in transition callbacks",
312
+ "@return [Boolean] +true+ if the transition succeeds, otherwise +false+"
313
+ ]
314
+ m.parameters = ["*args"]
315
+
316
+ # Fire event (raises exception)
317
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}!"))
318
+ m.docstring = [
319
+ "Fires the #{event.name.inspect} event, raising an exception if it fails.",
320
+ "@param [Array] args Optional arguments to include in transition callbacks",
321
+ "@return [Boolean] +true+ if the transition succeeds",
322
+ "@raise [EnumStateMachine::InvalidTransition] If the transition fails"
323
+ ]
324
+ m.parameters = ["*args"]
325
+ end
326
+ end
327
+
328
+ # Defines auto-generated state methods for the given machine
329
+ def define_state_methods
330
+ machine.states.each do |state|
331
+ next if inherited_machine && inherited_machine.states[state.name] || !state.name
332
+
333
+ # State query
334
+ register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{state.qualified_name}?"))
335
+ m.docstring = [
336
+ "Checks whether #{state.name.inspect} is the current state.",
337
+ "@return [Boolean] +true+ if this is the current state, otherwise +false+"
338
+ ]
339
+ end
340
+ end
341
+ end
342
+ end
343
+ end
344
+ end
@@ -0,0 +1,25 @@
1
+ module EnumStateMachine
2
+ module YARD
3
+ module Handlers
4
+ # Handles and processes #state
5
+ class State < Base
6
+ handles method_call(:state)
7
+
8
+ def process
9
+ if owner.is_a?(EnumStateMachine::Machine)
10
+ handler = self
11
+ statement = self.statement
12
+ names = extract_node_names(statement.parameters(false))
13
+
14
+ names.each do |name|
15
+ owner.state(name) do
16
+ # Parse the block
17
+ handler.parse_block(statement.last.last, :owner => self)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,47 @@
1
+ module EnumStateMachine
2
+ module YARD
3
+ module Handlers
4
+ # Handles and processes #transition
5
+ class Transition < Base
6
+ handles method_call(:transition)
7
+
8
+ def process
9
+ if [EnumStateMachine::Machine, EnumStateMachine::Event, EnumStateMachine::State].include?(owner.class)
10
+ options = {}
11
+
12
+ # Extract requirements
13
+ ast = statement.parameters.first
14
+ ast.children.each do |assoc|
15
+ # Skip conditionals
16
+ next if %w(if unless).include?(assoc[0].jump(:ident).source)
17
+
18
+ options[extract_requirement(assoc[0])] = extract_requirement(assoc[1])
19
+ end
20
+
21
+ owner.transition(options)
22
+ end
23
+ end
24
+
25
+ private
26
+ # Extracts the statement requirement from the given node
27
+ def extract_requirement(ast)
28
+ case ast.type
29
+ when :symbol_literal, :string_literal, :array
30
+ extract_node_names(ast, false)
31
+ when :binary
32
+ AllMatcher.instance - extract_node_names(ast.children.last)
33
+ when :var_ref, :vcall
34
+ case ast.source
35
+ when 'nil'
36
+ nil
37
+ when 'same'
38
+ LoopbackMatcher.instance
39
+ else
40
+ AllMatcher.instance
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,12 @@
1
+ module EnumStateMachine
2
+ module YARD
3
+ # YARD custom handlers for integrating the state_machine DSL with the
4
+ # YARD documentation system
5
+ module Handlers
6
+ end
7
+ end
8
+ end
9
+
10
+ Dir["#{File.dirname(__FILE__)}/handlers/*.rb"].sort.each do |path|
11
+ require "enum_state_machine/yard/handlers/#{File.basename(path)}"
12
+ end
@@ -0,0 +1,30 @@
1
+ require 'tempfile'
2
+
3
+ # Define where state machine descriptions will be rendered
4
+ def init
5
+ super
6
+ sections.place(:state_machine_details).before(:children)
7
+ end
8
+
9
+ # Renders state machine details in the main content of the class's documentation
10
+ def state_machine_details
11
+ erb(:state_machines) if state_machines
12
+ end
13
+
14
+ # Gets a list of state machines prased for this class
15
+ def state_machines
16
+ @state_machines ||= begin
17
+ if state_machines = object['state_machines']
18
+ state_machines.each do |name, machine|
19
+ serializer.serialize(state_machine_image_path(machine), machine[:image]) if machine[:image]
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # Generates the image path for the given machine's visualization
26
+ def state_machine_image_path(machine)
27
+ base_path = File.dirname(serializer.serialized_path(object))
28
+ image_name = "#{object.name}_#{machine[:name]}"
29
+ "#{File.join(base_path, image_name)}.png"
30
+ end
@@ -0,0 +1,12 @@
1
+ <h2>State Machines</h2>
2
+
3
+ This class contains <%= state_machines.count %> state machine(s).
4
+
5
+ <% state_machines.each do |name, machine| %>
6
+ <h3><%= h(machine[:name]) %></h3>
7
+ <p><%= h(machine[:description]) %></p>
8
+
9
+ <% if machine[:image] %>
10
+ <img alt="State machine diagram for <%= h(machine[:name]) %>" src="<%= url_for(state_machine_image_path(machine)) %>" />
11
+ <% end %>
12
+ <% end %>
@@ -0,0 +1,3 @@
1
+ require 'yard'
2
+
3
+ YARD::Templates::Engine.register_template_path File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
@@ -0,0 +1,8 @@
1
+ module EnumStateMachine
2
+ # YARD plugin for automated documentation
3
+ module YARD
4
+ end
5
+ end
6
+
7
+ require 'enum_state_machine/yard/handlers'
8
+ require 'enum_state_machine/yard/templates'
@@ -0,0 +1,9 @@
1
+ # By default, requiring "enum_state_machine" means that both the core implementation
2
+ # *and* extensions to the Ruby core (Class in particular) will be pulled in.
3
+ #
4
+ # If you want to skip the Ruby core extensions, simply require "enum_state_machine/core"
5
+ # and extend EnumStateMachine::MacroMethods in your class. See the README for more
6
+ # information.
7
+ require 'enum_state_machine/core'
8
+ require 'enum_state_machine/core_ext'
9
+ require 'enum_state_machine/state_enum'
@@ -0,0 +1 @@
1
+ require File.join("#{File.dirname(__FILE__)}/enum_state_machine")
@@ -0,0 +1,24 @@
1
+ namespace :enum_state_machine do
2
+ desc 'Draws state machines using GraphViz (options: CLASS=User,Vehicle; FILE=user.rb,vehicle.rb [not required in Rails]; FONT=Arial; FORMAT=png; ORIENTATION=portrait; HUMAN_NAMES=true'
3
+ task :draw do
4
+ # Build drawing options
5
+ options = {}
6
+ options[:file] = ENV['FILE'] if ENV['FILE']
7
+ options[:path] = ENV['TARGET'] if ENV['TARGET']
8
+ options[:format] = ENV['FORMAT'] if ENV['FORMAT']
9
+ options[:font] = ENV['FONT'] if ENV['FONT']
10
+ options[:orientation] = ENV['ORIENTATION'] if ENV['ORIENTATION']
11
+ options[:human_names] = ENV['HUMAN_NAMES'] == 'true' if ENV['HUMAN_NAMES']
12
+
13
+ if defined?(Rails)
14
+ puts "Files are automatically loaded in Rails; ignoring FILE option" if options.delete(:file)
15
+ Rake::Task['environment'].invoke
16
+ else
17
+ # Load the library
18
+ $:.unshift(File.dirname(__FILE__) + '/..')
19
+ require 'enum_state_machine'
20
+ end
21
+
22
+ EnumStateMachine::Machine.draw(ENV['CLASS'], options)
23
+ end
24
+ end
@@ -0,0 +1,2 @@
1
+ require 'enum_state_machine/core'
2
+ require 'enum_state_machine/yard'