ellington 0.0.3 → 0.0.4

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/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: