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,62 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module StateFu
|
4
|
+
class Plotter
|
5
|
+
attr_reader :machine, :dot, :graph, :states, :events
|
6
|
+
|
7
|
+
OUTPUT_HELPER = Module.new do
|
8
|
+
|
9
|
+
def save!
|
10
|
+
Tempfile.new(['state_fu_graph','.dot']) do |fh|
|
11
|
+
fh.write( self )
|
12
|
+
end.path
|
13
|
+
end
|
14
|
+
|
15
|
+
def save_as( filename )
|
16
|
+
File.open(filename, 'w') { |fh| fh.write( self ) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def save_png(filename)
|
20
|
+
raise NotImplementedError
|
21
|
+
# dot graph.dot -Tpng -O
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def output
|
27
|
+
generate
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize( machine, options={} )
|
31
|
+
raise RuntimeError, machine.class.to_s unless machine.is_a?(StateFu::Machine)
|
32
|
+
@machine = machine
|
33
|
+
@options = options.symbolize_keys!
|
34
|
+
@states = {}
|
35
|
+
@events = {}
|
36
|
+
# generate
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate
|
40
|
+
@dot ||= generate_dot!.extend( OUTPUT_HELPER )
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_dot!
|
44
|
+
@graph = Vizier::Graph.new(:state_machine) do |g|
|
45
|
+
g.node :shape => 'doublecircle'
|
46
|
+
machine.state_names.map.each do |s|
|
47
|
+
@states[s] = g.add_node(s.to_s)
|
48
|
+
end
|
49
|
+
machine.events.map.each do |e|
|
50
|
+
e.origins.map(&:name).each do |from|
|
51
|
+
e.targets.map(&:name).each do |to|
|
52
|
+
g.connect( @states[from], @states[to], :label => e.name.to_s )
|
53
|
+
end
|
54
|
+
end
|
55
|
+
# @events[s] = g.add_node(s.to_s)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@graph.generate!
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require File.join(File.dirname(__FILE__), '/state_fu/core_ext' )
|
5
|
+
rescue LoadError
|
6
|
+
require 'activesupport'
|
7
|
+
end
|
8
|
+
|
9
|
+
# Vizier is a simple library to help generate dot output for graphviz. It is used by StateFu's rake
|
10
|
+
# tasks to generate graphs of state machines.
|
11
|
+
#
|
12
|
+
# Sorry, there's only Heisendocumentation (if I realize anyone's looking for docs, I might write some)
|
13
|
+
#
|
14
|
+
module Vizier #:nodoc:all
|
15
|
+
|
16
|
+
module Support
|
17
|
+
LEGAL_CHARS = 'a-zA-Z0-9_'
|
18
|
+
|
19
|
+
def attributes=( attrs )
|
20
|
+
@attributes = attrs.symbolize_keys!.extend( Attributes )
|
21
|
+
end
|
22
|
+
|
23
|
+
def attributes
|
24
|
+
(@attributes ||= {}).extend( Attributes )
|
25
|
+
end
|
26
|
+
|
27
|
+
def legal?( str )
|
28
|
+
str =~ /^[#{LEGAL_CHARS}]+$/ && str == str.split
|
29
|
+
end
|
30
|
+
|
31
|
+
def sanitize(str)
|
32
|
+
sanitize( str )
|
33
|
+
end
|
34
|
+
|
35
|
+
def quote( str )
|
36
|
+
return str if legal?( str )
|
37
|
+
'"' + str.to_s.gsub(/"/,'\"') + '"'
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.included( klass )
|
41
|
+
klass.extend( ClassMethods )
|
42
|
+
end
|
43
|
+
|
44
|
+
module Finder
|
45
|
+
def []( idx )
|
46
|
+
begin
|
47
|
+
super( idx )
|
48
|
+
rescue TypeError => e
|
49
|
+
if idx.is_a?( String ) || idx.is_a?( Symbol )
|
50
|
+
self.detect { |i| i.name.to_s == idx.to_s }
|
51
|
+
elsif idx.class.respond_to?(:table_name)
|
52
|
+
self.detect { |i| i.name.to_s == Vizier::Node.make_name( idx ) }
|
53
|
+
else
|
54
|
+
raise e
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module ClassMethods
|
61
|
+
def sanitize( str )
|
62
|
+
str.to_s.gsub(/[^#{LEGAL_CHARS}]/,'_').gsub(/__+/,'_')
|
63
|
+
end
|
64
|
+
|
65
|
+
def finder( name )
|
66
|
+
class_eval do
|
67
|
+
define_method name do
|
68
|
+
instance_variable_get( "@#{name}" ).extend( Finder )
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module Attributes
|
76
|
+
include Support
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
return '[]' if empty?
|
80
|
+
'[ ' + self.map do |k,v|
|
81
|
+
"#{quote k} = #{quote v}"
|
82
|
+
end.join(" ") + ' ]'
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
class Base
|
88
|
+
def [](k)
|
89
|
+
attributes[k.to_sym]
|
90
|
+
end
|
91
|
+
|
92
|
+
def []=(k,v)
|
93
|
+
attributes[k.to_sym] = v
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Link < Base
|
98
|
+
include Support
|
99
|
+
attr_accessor :from
|
100
|
+
attr_accessor :to
|
101
|
+
|
102
|
+
def initialize( from, to, attrs={} )
|
103
|
+
self.attributes = attrs
|
104
|
+
@from = extract_name( from )
|
105
|
+
@to = extract_name( to )
|
106
|
+
end
|
107
|
+
|
108
|
+
def extract_name( o )
|
109
|
+
o.is_a?(String) ? o : o.name
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_str
|
113
|
+
"#{quote from} -> #{quote to} #{attributes};"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# TODO ..
|
118
|
+
module Label
|
119
|
+
def []( i )
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class Node < Base
|
125
|
+
include Support
|
126
|
+
|
127
|
+
attr_accessor :object
|
128
|
+
attr_accessor :fields
|
129
|
+
attr_accessor :name
|
130
|
+
|
131
|
+
def initialize( name = nil, attrs={} )
|
132
|
+
self.attributes = attrs
|
133
|
+
if name.is_a?( String )
|
134
|
+
self.name = name
|
135
|
+
@label = attrs.delete(:label) || name
|
136
|
+
else
|
137
|
+
@object = name
|
138
|
+
self.name = Node.make_name( @object )
|
139
|
+
@label = attrs.delete(:label) || Node.first_response( @object, :name, :identifier, :label ) || name
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.make_name( obj )
|
144
|
+
sanitize [ obj.class, first_response( obj, :name, :identifier, :id, :hash)].join('_')
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.first_response obj, *method_names
|
148
|
+
responder = method_names.flatten.detect { |m| obj.respond_to?(m) }
|
149
|
+
obj.send( responder ) unless responder.nil?
|
150
|
+
end
|
151
|
+
|
152
|
+
def name=( str )
|
153
|
+
@name = str.to_s.gsub(/[^a-zA-Z0-9_]/,'_').gsub(/__+/,'_')
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_str
|
157
|
+
"#{quote name} #{attributes.to_s};"
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_s
|
161
|
+
quote( name )
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class SubGraph < Base
|
166
|
+
include Support
|
167
|
+
|
168
|
+
finder :nodes
|
169
|
+
|
170
|
+
attr_accessor :links
|
171
|
+
attr_accessor :name
|
172
|
+
|
173
|
+
def initialize( name, attrs={} )
|
174
|
+
self.attributes = attrs
|
175
|
+
@node = {}
|
176
|
+
@edge = {}
|
177
|
+
|
178
|
+
@name = name
|
179
|
+
@nodes = []
|
180
|
+
@links = []
|
181
|
+
end
|
182
|
+
|
183
|
+
def node(attrs={})
|
184
|
+
(@node ||= {}).merge!(attrs).extend(Attributes)
|
185
|
+
end
|
186
|
+
|
187
|
+
def graph(attrs={})
|
188
|
+
self.attributes.merge!(attrs).extend(Attributes)
|
189
|
+
end
|
190
|
+
|
191
|
+
def edge(attrs={})
|
192
|
+
(@edge ||= {}).merge!(attrs).extend(Attributes)
|
193
|
+
end
|
194
|
+
|
195
|
+
def add_node( n, a={} )
|
196
|
+
returning Node.new(n,a) do |n|
|
197
|
+
@nodes << n
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_link(from, to, a={})
|
202
|
+
returning Link.new( from, to, a) do |l|
|
203
|
+
@links << l
|
204
|
+
end
|
205
|
+
end
|
206
|
+
alias_method :connect, :add_link
|
207
|
+
alias_method :add_edge, :add_link
|
208
|
+
|
209
|
+
def build(lines = [], indent = 0)
|
210
|
+
lines.map do |line|
|
211
|
+
if line.is_a?( Array )
|
212
|
+
build( line, indent + 1)
|
213
|
+
else
|
214
|
+
(" " * (indent * 4) ) + line.to_s
|
215
|
+
end
|
216
|
+
end.join("\n")
|
217
|
+
end
|
218
|
+
|
219
|
+
def write_comment( str, j = 0 )
|
220
|
+
l = 40 - (j * 4)
|
221
|
+
i = ' ' * (j * 4)
|
222
|
+
"\n#{i}/*#{'*'*(l-2)}\n#{i}** #{ str.ljust((l - (6) - (j*4)),' ') }#{i} **\n#{i}#{'*'*(l-1)}/"
|
223
|
+
end
|
224
|
+
|
225
|
+
def comment(str)
|
226
|
+
write_comment(str, 2)
|
227
|
+
end
|
228
|
+
|
229
|
+
def to_str
|
230
|
+
build( ["subgraph #{quote name} {",
|
231
|
+
[ # attributes.map {|k,v| "#{quote k} = #{quote v};" },
|
232
|
+
["graph #{attributes};",
|
233
|
+
"node #{node};",
|
234
|
+
"edge #{edge};"
|
235
|
+
],
|
236
|
+
nodes.map(&:to_str),
|
237
|
+
links.map(&:to_str),
|
238
|
+
"}"
|
239
|
+
],
|
240
|
+
])
|
241
|
+
end
|
242
|
+
alias_method :generate!, :to_str
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
class Graph < SubGraph
|
247
|
+
finder :subgraphs
|
248
|
+
|
249
|
+
def comment( str )
|
250
|
+
write_comment( str, 1 )
|
251
|
+
end
|
252
|
+
|
253
|
+
def to_str
|
254
|
+
build(["digraph #{quote name} {",
|
255
|
+
[
|
256
|
+
comment("global options"),
|
257
|
+
"graph #{graph};",
|
258
|
+
"node #{node};",
|
259
|
+
"edge #{edge};"
|
260
|
+
],
|
261
|
+
comment("nodes"),
|
262
|
+
nodes.map(&:to_str),
|
263
|
+
comment("links"),
|
264
|
+
links.map(&:to_str),
|
265
|
+
comment("subgraphs"),
|
266
|
+
subgraphs.map(&:to_str),
|
267
|
+
"}"])
|
268
|
+
end
|
269
|
+
alias_method :generate!, :to_str
|
270
|
+
|
271
|
+
def publish!( a = {} )
|
272
|
+
generate! # -> png
|
273
|
+
end
|
274
|
+
|
275
|
+
def subgraph(name, a = {})
|
276
|
+
returning( SubGraph.new(name, a)) do |g|
|
277
|
+
@subgraphs << g
|
278
|
+
yield g if block_given?
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def cluster(name = nil, a = {}, &block)
|
283
|
+
if name && name = "cluster_#{name}"
|
284
|
+
subgraph( name, a, &block )
|
285
|
+
else
|
286
|
+
clusters
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def clusters
|
291
|
+
@subgraphs.select {|s| s.name =~ /^cluster_/ }.extend( Finder )
|
292
|
+
end
|
293
|
+
|
294
|
+
def initialize(name = 'my_graph', attrs = {})
|
295
|
+
@subgraphs = []
|
296
|
+
super( name, attrs )
|
297
|
+
yield self if block_given?
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
unless Object.const_defined?('STATE_FU_APP_PATH')
|
4
|
+
STATE_FU_APP_PATH = Object.const_defined?('RAILS_ROOT') ? RAILS_ROOT : File.join( File.dirname(__FILE__), '/../..')
|
5
|
+
end
|
6
|
+
|
7
|
+
unless Object.const_defined?('STATE_FU_PLUGIN_PATH')
|
8
|
+
STATE_FU_PLUGIN_PATH = Object.const_defined?('RAILS_ROOT') ? File.join( RAILS_ROOT, '/vendor/plugins/state-fu' ) : STATE_FU_APP_PATH
|
9
|
+
end
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'rake'
|
13
|
+
require 'spec'
|
14
|
+
require 'spec/rake/spectask'
|
15
|
+
|
16
|
+
namespace :spec do
|
17
|
+
def find_last_modified_spec
|
18
|
+
require 'find'
|
19
|
+
specs = []
|
20
|
+
Find.find( File.expand_path(File.join(STATE_FU_APP_PATH,'spec'))) do |f|
|
21
|
+
next unless f !~ /\.#/ && f =~ /_spec.rb$/
|
22
|
+
specs << f
|
23
|
+
end
|
24
|
+
spec = specs.sort_by { |spec| File.stat( spec ).mtime }.last
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "runs the last modified spec; L=n runs only that line"
|
28
|
+
Spec::Rake::SpecTask.new(:last) do |t|
|
29
|
+
specfile = find_last_modified_spec || return
|
30
|
+
t.verbose = true
|
31
|
+
t.spec_opts = ["-c","-b","-u"]
|
32
|
+
if ENV['L']
|
33
|
+
t.spec_opts += ["-l", ENV["L"],"-f", "specdoc"]
|
34
|
+
else
|
35
|
+
t.spec_opts += ["-f", "profile"]
|
36
|
+
end
|
37
|
+
t.spec_files = FileList[specfile]
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "runs all specs, or those which last failed"
|
41
|
+
Spec::Rake::SpecTask.new(:faily) do |t|
|
42
|
+
specfile = find_last_modified_spec || return
|
43
|
+
faily = 'spec.fail'
|
44
|
+
t.verbose = true
|
45
|
+
t.spec_opts = ["-f","failing_examples:#{faily}", "-f","n","-c","-b","-u"]
|
46
|
+
if File.exists?(faily) && File.read(faily).split("\n")[0] != ""
|
47
|
+
t.spec_opts << ["-e",faily]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
rescue LoadError
|
53
|
+
# fail quietly if rspec is not installed
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
unless Object.const_defined?('STATE_FU_APP_PATH')
|
4
|
+
STATE_FU_APP_PATH = Object.const_defined?('RAILS_ROOT') ? RAILS_ROOT : File.join( File.dirname(__FILE__), '/../..')
|
5
|
+
end
|
6
|
+
|
7
|
+
unless Object.const_defined?('STATE_FU_PLUGIN_PATH')
|
8
|
+
STATE_FU_PLUGIN_PATH = Object.const_defined?('RAILS_ROOT') ? File.join( RAILS_ROOT, '/vendor/plugins/state-fu' ) : STATE_FU_APP_PATH
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :state_fu do
|
12
|
+
|
13
|
+
task :update do
|
14
|
+
path = STATE_FU_PLUGIN_PATH
|
15
|
+
pwd = FileUtils.pwd
|
16
|
+
FileUtils.cd( path )
|
17
|
+
system('git pull')
|
18
|
+
FileUtils.cd pwd
|
19
|
+
end
|
20
|
+
|
21
|
+
def graph_name( klass, machine, doc_path = false )
|
22
|
+
parts = ["#{klass}_#{machine}"]
|
23
|
+
if doc_path
|
24
|
+
folder = parts.unshift( File.join( STATE_FU_APP_PATH, "doc/") )
|
25
|
+
FileUtils.mkdir_p( folder )
|
26
|
+
parts.push( '.png' )
|
27
|
+
end
|
28
|
+
parts.join
|
29
|
+
end
|
30
|
+
|
31
|
+
def graph( klass, machine )
|
32
|
+
name = graph_name( klass, machine )
|
33
|
+
graphviz = `which dot`.strip || raise("Graphviz not installed? Can't find dot executable!")
|
34
|
+
puts graphviz
|
35
|
+
tmp_dot = "/tmp/#{name}.dot"
|
36
|
+
klass.machine( machine.to_sym ).graphviz.save_as( tmp_dot )
|
37
|
+
tmp_png = tmp_dot + '.png'
|
38
|
+
doc_png = graph_name( klass, machine, true )
|
39
|
+
puts( "#{graphviz} -Tpng -O #{tmp_dot}" )
|
40
|
+
system( "#{graphviz} -Tpng -O #{tmp_dot}" )
|
41
|
+
FileUtils.cp tmp_png, doc_png
|
42
|
+
doc_png
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Graph workflows with dot"
|
46
|
+
task :graph => :environment do |t|
|
47
|
+
state_fu_classes = ObjectSpace.each_object { |o| x << o if o.respond_to? :machines }
|
48
|
+
state_fu_classes.each do |klass|
|
49
|
+
klass.state_fu_machines.each do |machine_name, machine|
|
50
|
+
STDERR.puts "#{klass} -> #{machine_name.inspect}"
|
51
|
+
doc_png = graph( klass, machine_name )
|
52
|
+
# yield doc_png if block_given?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
# `open #{doc_png}`
|
56
|
+
end
|
57
|
+
end
|