ellington 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/Blast.pdf ADDED
Binary file
data/Gemfile CHANGED
@@ -1,7 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem "hero", "~> 0.1.7"
4
- gem "state_jacket", "~> 0.0.7"
4
+ gem "state_jacket", "~> 0.0.9"
5
+ gem "ruby-graphviz", "~> 1.0.8", :require => "graphviz"
5
6
 
6
7
  group :development, :test do
7
8
  gem "yell"
data/Gemfile.lock CHANGED
@@ -21,8 +21,9 @@ GEM
21
21
  pry-stack_explorer (0.4.8)
22
22
  binding_of_caller (~> 0.6.8)
23
23
  pry (~> 0.9.11)
24
+ ruby-graphviz (1.0.8)
24
25
  slop (3.4.3)
25
- state_jacket (0.0.7)
26
+ state_jacket (0.0.9)
26
27
  yell (1.2.3)
27
28
 
28
29
  PLATFORMS
@@ -36,5 +37,6 @@ DEPENDENCIES
36
37
  pry
37
38
  pry-exception_explorer
38
39
  pry-stack_explorer
39
- state_jacket (~> 0.0.7)
40
+ ruby-graphviz (~> 1.0.8)
41
+ state_jacket (~> 0.0.9)
40
42
  yell
@@ -17,11 +17,21 @@ module Ellington
17
17
  end
18
18
  alias_method :errored, :error_target
19
19
 
20
+ def state_names(states)
21
+ names = []
22
+ names << "PASS" if (states & passed).length == passed.length
23
+ names << "FAIL" if (states & failed).length == failed.length
24
+ names << "ERROR" if (states & errored).length == errored.length
25
+ names
26
+ end
27
+
20
28
  def state(passenger)
21
29
  case
22
30
  when passed.satisfied?(passenger) then "PASS"
23
31
  when failed.satisfied?(passenger) then "FAIL"
24
32
  when errored.satisfied?(passenger) then "ERROR"
33
+ else
34
+ nil
25
35
  end
26
36
  end
27
37
 
@@ -68,7 +68,10 @@ module Ellington
68
68
  @states ||= begin
69
69
  catalog = StateJacket::Catalog.new
70
70
  stations.each_with_index do |station, index|
71
- catalog.merge! station.states
71
+ station.states.each do |state, transitions|
72
+ catalog[state] = transitions.nil? ? nil : transitions.dup
73
+ end
74
+
72
75
  if index < stations.length - 1
73
76
  next_station = stations[index + 1]
74
77
  catalog[station.passed] = next_station.states.keys
@@ -26,9 +26,12 @@ module Ellington
26
26
  message << "[#{station.state(passenger)}]"
27
27
  message << "[#{station_full_name}]"
28
28
  end
29
- line.route.log_passenger_attrs.each do |attr|
29
+ line.route.log_options[:passenger].each do |attr|
30
30
  message << "[#{attr}:#{passenger.send(attr)}]"
31
31
  end
32
+ line.route.log_options[:options].each do |attr|
33
+ message << "[#{attr}:#{self.options[attr]}]"
34
+ end
32
35
  message.join " "
33
36
  end
34
37
  end
@@ -1,4 +1,5 @@
1
1
  require "delegate"
2
+ require "fileutils"
2
3
 
3
4
  module Ellington
4
5
  class Route < SimpleDelegator
@@ -35,6 +36,17 @@ module Ellington
35
36
  include HasTargets
36
37
  attr_reader :initialized
37
38
 
39
+ def inherited(subclass)
40
+ (@subclasses ||= []) << subclass
41
+ end
42
+
43
+ def generate_graphs(dir)
44
+ FileUtils.mkdir_p(dir)
45
+ @subclasses.each do |subclass|
46
+ Ellington::Visualizer.new(subclass.new, dir).graph
47
+ end
48
+ end
49
+
38
50
  def initialized?
39
51
  @initialized
40
52
  end
@@ -53,7 +65,9 @@ module Ellington
53
65
  catalog.add initial_state => lines.first.stations.first.states.keys
54
66
 
55
67
  lines.each do |line|
56
- catalog.merge! line.states
68
+ line.states.each do |state, transitions|
69
+ catalog[state] = transitions.nil? ? nil : transitions.dup
70
+ end
57
71
  end
58
72
 
59
73
  connections.each do |connection|
@@ -88,8 +102,12 @@ module Ellington
88
102
  connections << Ellington::Connection.new(line, type, states)
89
103
  end
90
104
 
91
- def log_passenger_attrs(*attrs)
92
- @log_passenger_attrs ||= attrs
105
+ def log_options(options={})
106
+ @log_options ||= begin
107
+ options[:passenger] ||= []
108
+ options[:options] ||= []
109
+ options
110
+ end
93
111
  end
94
112
 
95
113
  def line_completed(line_info)
@@ -15,9 +15,12 @@ module Ellington
15
15
  message << "[ROUTE COMPLETED]"
16
16
  message << "[#{route.state(passenger)}]"
17
17
  message << "[#{route.name}]"
18
- line.route.log_passenger_attrs.each do |attr|
18
+ route.log_options[:passenger].each do |attr|
19
19
  message << "[#{attr}:#{passenger.send(attr)}]"
20
20
  end
21
+ route.log_options[:options].each do |attr|
22
+ message << "[#{attr}:#{self.options[attr]}]"
23
+ end
21
24
  message.join " "
22
25
  end
23
26
  end
@@ -1,3 +1,3 @@
1
1
  module Ellington
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -0,0 +1,254 @@
1
+ require "delegate"
2
+
3
+ # options: http://www.graphviz.org/doc/info/attrs.html
4
+ # colors: http://www.graphviz.org/doc/info/colors.html
5
+ module Ellington
6
+ class Visualizer
7
+
8
+ class Node < SimpleDelegator
9
+ attr_reader :base, :viz, :children
10
+
11
+ def initialize(base, viz)
12
+ @base = base
13
+ @viz = viz
14
+ @children = []
15
+ super children
16
+ end
17
+
18
+ def name
19
+ viz.id
20
+ end
21
+
22
+ def find(context)
23
+ if context.is_a?(String)
24
+ to_a.select{ |node| node.name == context }.first
25
+ else
26
+ to_a.select{ |node| node.base == context }.first
27
+ end
28
+ end
29
+ end
30
+
31
+ attr_reader :route, :dir
32
+
33
+ def initialize(route, dir)
34
+ @route = route
35
+ @dir = dir
36
+ end
37
+
38
+ def graph
39
+ graph_route_basic
40
+ graph_route
41
+ graph_lines_basic
42
+ graph_lines
43
+ end
44
+
45
+ def graph_lines_basic
46
+ g = Node.new(nil, GraphViz.new("G"))
47
+ g.viz["label"] = "#{route.name} Lines - basic"
48
+ g.viz["ranksep"] = 0.4
49
+ g.viz["fontname"] = "Helvetica"
50
+ g.viz.node["fontname"] = "Helvetica"
51
+ g.viz.node["shape"] = "box"
52
+ g.viz.node["style"] = "rounded"
53
+
54
+ route.lines.each_with_index do |line, index|
55
+ line_cluster = Node.new(line, g.viz.add_graph("cluster#{index}"))
56
+ line_cluster.viz["label"] = line.class.name
57
+ line_cluster.viz["style"] = "filled"
58
+ g << line_cluster
59
+
60
+ line.stations.each do |station|
61
+ station_node = Node.new(station, line_cluster.viz.add_nodes(station.class.name))
62
+ station_node.viz["style"] = "filled,rounded"
63
+ station_node.viz["color"] = "white"
64
+ line_cluster << station_node
65
+ end
66
+
67
+ line_cluster.each_with_index.each do |node, node_index|
68
+ next_node = line_cluster[node_index + 1]
69
+ line_cluster.viz.add_edges node.viz, next_node.viz if next_node
70
+ end
71
+ end
72
+
73
+ file_name = "#{route.name.downcase.gsub("::", "_")}-lines-basic.pdf"
74
+ g.viz.output(:pdf => File.join(dir, file_name))
75
+ end
76
+
77
+ def graph_lines
78
+ g = Node.new(nil, GraphViz.new("G"))
79
+ g.viz["label"] = "#{route.name} Lines"
80
+ g.viz["ranksep"] = 0.4
81
+ g.viz["fontname"] = "Helvetica"
82
+ g.viz.node["fontname"] = "Helvetica"
83
+ g.viz.node["shape"] = "box"
84
+ g.viz.node["style"] = "rounded"
85
+
86
+ route.lines.each_with_index do |line, index|
87
+ line_cluster = Node.new(line, g.viz.add_graph("cluster#{index}"))
88
+ line_cluster.viz["label"] = line.class.name
89
+ line_cluster.viz["style"] = "filled"
90
+ g << line_cluster
91
+
92
+ line.states.keys.each do |state|
93
+ state_node = Node.new(state, line_cluster.viz.add_nodes(state))
94
+ state_node.viz["style"] = "filled,rounded"
95
+ state_node.viz["color"] = "white"
96
+ if line.goal.include?(state)
97
+ state_node.viz["color"] = "green2"
98
+ end
99
+ if route.goal.include?(state)
100
+ state_node.viz["color"] = "gold"
101
+ end
102
+ line_cluster << state_node
103
+ end
104
+
105
+ line.states.each do |state, transitions|
106
+ a = line_cluster.find(state)
107
+ (transitions || []).each do |transition|
108
+ b = line_cluster.find(transition)
109
+ line_cluster.viz.add_edges a.viz, b.viz
110
+ end
111
+ end
112
+ end
113
+
114
+ file_name = "#{route.name.downcase.gsub("::", "_")}-lines.pdf"
115
+ g.viz.output(:pdf => File.join(dir, file_name))
116
+ end
117
+
118
+ def graph_route_basic
119
+ g = Node.new(nil, GraphViz.new("G"))
120
+ g.viz["label"] = "#{route.name} Route - basic"
121
+ g.viz["compound"] = true
122
+ g.viz["ranksep"] = 1.2
123
+ g.viz["fontname"] = "Helvetica"
124
+ g.viz.node["fontname"] = "Helvetica"
125
+ g.viz.node["shape"] = "box"
126
+ g.viz.node["style"] = "rounded"
127
+
128
+ route.lines.each_with_index do |line, index|
129
+ line_cluster = Node.new(line, g.viz.add_graph("cluster#{index}"))
130
+ line_cluster.viz["label"] = line.class.name
131
+ line_cluster.viz["style"] = "filled"
132
+ g << line_cluster
133
+
134
+ %w{PASS FAIL ERROR}.each do |state|
135
+ state_node = Node.new(state, line_cluster.viz.add_nodes("#{line.class.name}#{state}", "label" => state))
136
+ state_node.viz["style"] = "filled,rounded"
137
+ state_node.viz["color"] = "white"
138
+ if !(route.goal & line.stations.map{ |s| "#{state} #{s.name}" }).empty?
139
+ state_node.viz["color"] = "gold"
140
+ end
141
+ line_cluster << state_node
142
+ end
143
+ end
144
+
145
+ route.connections.each do |connection|
146
+ to_node = g.find(connection.line)
147
+ from_line = to_node.base
148
+
149
+ combos = {}
150
+ g.to_a.each do |node|
151
+ states = node.base.state_names(connection.states)
152
+ states.each do |state|
153
+ (combos[state] ||= []) << node
154
+ end
155
+ end
156
+
157
+ if connection.type == :if_any
158
+ combos.each do |state, nodes|
159
+ nodes.each do |node|
160
+ to_line = node.base
161
+ g.viz.add_edges(
162
+ node.viz.get_node("#{to_line.class.name}#{state}"),
163
+ to_node.viz.get_node("#{from_line.class.name}PASS"),
164
+ "lhead" => to_node.viz.id
165
+ )
166
+ end
167
+ end
168
+ end
169
+
170
+ if connection.type == :if_all
171
+ combos.each do |state, nodes|
172
+ node_name = nodes.map{ |n| n.base.class.name }.join + state
173
+ node_label = nodes.map{ |n| "#{n.base.class.name} #{state}"}.join("\n")
174
+ viz = g.viz.add_nodes(node_name, "label" => node_label)
175
+ viz["style"] = "rounded"
176
+ viz["color"] = "gray50"
177
+ viz["fontcolor"] = "gray40"
178
+
179
+ nodes.each do |node|
180
+ from_viz = node.viz.get_node("#{node.base.class.name}#{state}")
181
+ g.viz.add_edges from_viz, viz
182
+ end
183
+
184
+ g.viz.add_edges(
185
+ viz,
186
+ to_node.viz.get_node("#{connection.line.class.name}#{state}"),
187
+ "lhead" => to_node.viz.id
188
+ )
189
+ end
190
+ end
191
+ end
192
+
193
+ file_name = "#{route.name.downcase.gsub("::", "_")}-route-basic.pdf"
194
+ g.viz.output(:pdf => File.join(dir, file_name))
195
+ end
196
+
197
+ def graph_route
198
+ g = Node.new(nil, GraphViz.new("G"))
199
+ g.viz["label"] = "#{route.name} Lines"
200
+ g.viz["compound"] = true
201
+ g.viz["ranksep"] = 0.8
202
+ g.viz["fontname"] = "Helvetica"
203
+ g.viz.node["fontname"] = "Helvetica"
204
+ g.viz.node["shape"] = "box"
205
+ g.viz.node["style"] = "rounded"
206
+
207
+ route.lines.each_with_index do |line, index|
208
+ line_cluster = Node.new(line, g.viz.add_graph("cluster#{index}"))
209
+ line_cluster.viz["label"] = line.class.name
210
+ line_cluster.viz["style"] = "filled"
211
+ g << line_cluster
212
+
213
+ line.states.keys.each do |state|
214
+ state_node = Node.new(state, line_cluster.viz.add_nodes(state))
215
+ state_node.viz["style"] = "filled,rounded"
216
+ state_node.viz["color"] = "white"
217
+ if line.goal.include?(state)
218
+ state_node.viz["color"] = "green2"
219
+ end
220
+ if route.goal.include?(state)
221
+ state_node.viz["color"] = "gold"
222
+ end
223
+ line_cluster << state_node
224
+ end
225
+ end
226
+
227
+ viz = g.viz.add_nodes(route.initial_state)
228
+
229
+ route.states.each do |from_state, to_states|
230
+ (to_states || []).each do |to_state|
231
+ from_line = route.lines.to_a.select{ |l| l.states.keys.include?(from_state) }.first
232
+ from_node = g.find(from_line) if from_line
233
+ from_viz = from_node.viz.get_node(from_state) if from_node
234
+ from_viz ||= g.viz.get_node(from_state)
235
+ to_line = route.lines.to_a.select{ |l| l.states.keys.include?(to_state) }.first
236
+ to_node = g.find(to_line) if to_line
237
+ to_viz = to_node.viz.get_node(to_state) if to_node
238
+ to_viz ||= g.viz.get_node(to_state)
239
+
240
+ if from_viz && to_viz
241
+ g.viz.add_edges(
242
+ from_viz,
243
+ to_viz
244
+ )
245
+ end
246
+ end
247
+ end
248
+
249
+ file_name = "#{route.name.downcase.gsub("::", "_")}-route.pdf"
250
+ g.viz.output(:pdf => File.join(dir, file_name))
251
+ end
252
+
253
+ end
254
+ end
data/test/example.rb CHANGED
@@ -187,7 +187,7 @@ class BasicMath < Ellington::Route
187
187
  connect_to division, :if_any => addition.passed
188
188
  connect_to multiplication, :if_any => addition.failed
189
189
 
190
- log_passenger_attrs :original_value, :current_value
190
+ log_options :passenger => [:original_value, :current_value]
191
191
  end
192
192
 
193
193
  # conductor ---------------------------------------------------------------
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ellington
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-25 00:00:00.000000000 Z
12
+ date: 2013-02-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hero
@@ -72,7 +72,9 @@ files:
72
72
  - lib/ellington/transition_info.rb
73
73
  - lib/ellington/unique_type_array.rb
74
74
  - lib/ellington/version.rb
75
+ - lib/ellington/visualizer.rb
75
76
  - lib/ellington.rb
77
+ - Blast.pdf
76
78
  - Gemfile
77
79
  - Gemfile.lock
78
80
  - LICENSE.txt
@@ -116,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
118
  version: '0'
117
119
  requirements: []
118
120
  rubyforge_project:
119
- rubygems_version: 1.8.23
121
+ rubygems_version: 1.8.25
120
122
  signing_key:
121
123
  specification_version: 3
122
124
  summary: A micro framework to ensure your projects are easy to manage, develop, &
@@ -139,4 +141,3 @@ test_files:
139
141
  - test/ticket_test.rb
140
142
  - test/transition_info_test.rb
141
143
  - test/unique_type_array_test.rb
142
- has_rdoc: