ellington 0.1.2 → 0.1.3
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.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/lib/ellington/conductor.rb +4 -51
- data/lib/ellington/line.rb +14 -10
- data/lib/ellington/passenger.rb +5 -29
- data/lib/ellington/route.rb +17 -22
- data/lib/ellington/station.rb +4 -2
- data/lib/ellington/version.rb +1 -1
- data/lib/ellington/visualizer.rb +66 -104
- data/test/attendant_test.rb +0 -1
- data/test/conductor_test.rb +4 -36
- data/test/example.rb +0 -24
- data/test/passenger_test.rb +7 -26
- data/test/station_test.rb +5 -1
- data/test/target_test.rb +4 -4
- data/test/test_helper.rb +11 -2
- data/test/ticket_test.rb +1 -1
- metadata +32 -4
- data/Gemfile.lock +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b39e107345c1fad43d8eb115683b52627b71978f
|
4
|
+
data.tar.gz: f0406c1407a63cfc8bcbd48b6265ed24d2e4ad76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a272385be445c2afd786c7ec4eba7b2d75381b833d58771ce7298d8fa3a80694df58229347e92745fdeb59744a2b54aaf40a1606f30a9713c6c6c060aa0c8715
|
7
|
+
data.tar.gz: 4c2ef0b65ed7d78c481b2573dd8d9ebf555d845654eb6e176508cd2f103b0ed537c806fb88614fefe2d580dfcecda92605e04e7ccb0e54f40a83d606eca06155
|
data/README.md
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
[](https://travis-ci.org/hopsoft/ellington)
|
4
4
|
[](https://gemnasium.com/hopsoft/ellington)
|
5
5
|
[](https://codeclimate.com/github/hopsoft/ellington)
|
6
|
+
[](https://coveralls.io/r/hopsoft/ellington?branch=master)
|
6
7
|
|
7
8
|
Named after [Duke Ellington](http://www.dukeellington.com/) whose signature tune was ["Take the 'A' Train"](http://en.wikipedia.org/wiki/Take_the_%22A%22_Train).
|
8
9
|
The song was written about [New York City's A train](http://en.wikipedia.org/wiki/A_%28New_York_City_Subway_service%29).
|
@@ -29,7 +30,7 @@ We've found that using consistent and cohesive physical metaphors helps people r
|
|
29
30
|
|
30
31
|
## Lexicon
|
31
32
|
|
32
|
-
- **[Conductor](https://github.com/hopsoft/ellington/wiki/Conductor)** - A supervisor responsible for
|
33
|
+
- **[Conductor](https://github.com/hopsoft/ellington/wiki/Conductor)** - A supervisor responsible for putting `passengers` on a `route`.
|
33
34
|
- **[Passenger](https://github.com/hopsoft/ellington/wiki/Passenger)** - The stateful context that will be riding the virtual subway.
|
34
35
|
- **[Route](https://github.com/hopsoft/ellington/wiki/Route)** - A collection of `lines` and their `connections`.
|
35
36
|
- **[Line](https://github.com/hopsoft/ellington/wiki/Line)** - A linear track that moves the `passenger` from point A to point B.
|
data/lib/ellington/conductor.rb
CHANGED
@@ -1,67 +1,20 @@
|
|
1
|
-
require "thread"
|
2
|
-
|
3
1
|
module Ellington
|
4
2
|
class Conductor
|
5
3
|
attr_reader :route
|
6
4
|
|
7
5
|
def initialize(route)
|
8
6
|
@route = route
|
9
|
-
@conducting = false
|
10
|
-
end
|
11
|
-
|
12
|
-
def conducting?
|
13
|
-
@conducting
|
14
|
-
end
|
15
|
-
|
16
|
-
def start
|
17
|
-
return if conducting?
|
18
|
-
|
19
|
-
mutex.synchronize do
|
20
|
-
@stop = false
|
21
|
-
@conducting = true
|
22
|
-
end
|
23
|
-
|
24
|
-
@thread = Thread.new do
|
25
|
-
loop do
|
26
|
-
if @stop
|
27
|
-
mutex.synchronize { @conducting = false }
|
28
|
-
Thread.current.exit
|
29
|
-
end
|
30
|
-
gather_passengers.each do |passenger|
|
31
|
-
escort(passenger)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def stop
|
38
|
-
mutex.synchronize { @stop = true }
|
39
|
-
end
|
40
|
-
|
41
|
-
def wait
|
42
|
-
thread.join
|
43
7
|
end
|
44
8
|
|
9
|
+
# override this method in a subclass
|
10
|
+
# to perform actual passenger verification
|
45
11
|
def verify(passenger)
|
46
12
|
true
|
47
13
|
end
|
48
14
|
|
49
|
-
def
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
def escort(passenger)
|
54
|
-
return unless verify(passenger) && passenger.locked?
|
15
|
+
def conduct(passenger)
|
16
|
+
return unless verify(passenger)
|
55
17
|
route.lines.first.board passenger
|
56
18
|
end
|
57
|
-
|
58
|
-
protected
|
59
|
-
|
60
|
-
attr_reader :thread
|
61
|
-
|
62
|
-
def mutex
|
63
|
-
@mutex ||= Mutex.new
|
64
|
-
end
|
65
|
-
|
66
19
|
end
|
67
20
|
end
|
data/lib/ellington/line.rb
CHANGED
@@ -91,19 +91,23 @@ module Ellington
|
|
91
91
|
line_info = Ellington::LineInfo.new(self, station_info)
|
92
92
|
if line_info.station == stations.last ||
|
93
93
|
line_info.passenger.current_state == line_info.station.failed
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
if line_info.passenger.current_state == line_info.station.errored
|
101
|
-
Ellington.logger.info "\n" if Ellington.logger
|
102
|
-
end
|
94
|
+
return complete_line(line_info)
|
95
|
+
end
|
96
|
+
|
97
|
+
log line_info, :station_completed => true
|
98
|
+
if line_info.passenger.current_state == line_info.station.errored
|
99
|
+
Ellington.logger.info "\n" if Ellington.logger
|
103
100
|
end
|
104
101
|
end
|
105
102
|
|
106
|
-
|
103
|
+
protected
|
104
|
+
|
105
|
+
def complete_line(line_info)
|
106
|
+
log line_info, :station_completed => true
|
107
|
+
log line_info, :line_completed => true
|
108
|
+
changed
|
109
|
+
notify_observers line_info
|
110
|
+
end
|
107
111
|
|
108
112
|
def log(line_info, options={})
|
109
113
|
return unless Ellington.logger
|
data/lib/ellington/passenger.rb
CHANGED
@@ -4,31 +4,16 @@ module Ellington
|
|
4
4
|
class Passenger < SimpleDelegator
|
5
5
|
include Observable
|
6
6
|
attr_accessor :context, :ticket
|
7
|
-
attr_reader :route
|
7
|
+
attr_reader :route, :state_history
|
8
8
|
|
9
|
-
def initialize(context, route,
|
10
|
-
ticket ||= Ellington::Ticket.new
|
9
|
+
def initialize(context, route, options={})
|
11
10
|
@context = context
|
12
11
|
@route = route
|
13
|
-
@ticket = ticket
|
12
|
+
@ticket = options[:ticket] || Ellington::Ticket.new
|
13
|
+
@state_history = options[:state_history] || []
|
14
14
|
super context
|
15
15
|
end
|
16
16
|
|
17
|
-
def lock
|
18
|
-
return context.lock if context.respond_to?(:lock)
|
19
|
-
@locked = true
|
20
|
-
end
|
21
|
-
|
22
|
-
def unlock
|
23
|
-
return context.unlock if context.respond_to?(:unlock)
|
24
|
-
@locked = false
|
25
|
-
end
|
26
|
-
|
27
|
-
def locked?
|
28
|
-
return context.locked? if context.respond_to?(:locked?)
|
29
|
-
@locked
|
30
|
-
end
|
31
|
-
|
32
17
|
def current_state
|
33
18
|
return context.current_state if context.respond_to?(:current_state)
|
34
19
|
@current_state
|
@@ -39,20 +24,11 @@ module Ellington
|
|
39
24
|
@current_state = value
|
40
25
|
end
|
41
26
|
|
42
|
-
def state_history
|
43
|
-
@state_history ||= []
|
44
|
-
end
|
45
|
-
|
46
27
|
def state_history_includes?(*states)
|
47
28
|
(state_history & states).length == states.length
|
48
29
|
end
|
49
30
|
|
50
31
|
def transition_to(new_state)
|
51
|
-
if !locked?
|
52
|
-
message = "Cannot transition an unlocked #{self.class.name}'s state"
|
53
|
-
raise Ellington::InvalidStateTransition.new(message)
|
54
|
-
end
|
55
|
-
|
56
32
|
if !route.states.can_transition?(current_state => new_state)
|
57
33
|
message = "Cannot transition #{self.class.name} from:#{current_state} to:#{new_state}"
|
58
34
|
raise Ellington::InvalidStateTransition.new(message)
|
@@ -61,7 +37,7 @@ module Ellington
|
|
61
37
|
old_state = current_state
|
62
38
|
|
63
39
|
if context.respond_to?(:transition_to)
|
64
|
-
return_value = context.transition_to(new_state)
|
40
|
+
return_value = context.transition_to(new_state)
|
65
41
|
else
|
66
42
|
self.current_state = new_state
|
67
43
|
end
|
data/lib/ellington/route.rb
CHANGED
@@ -43,14 +43,6 @@ module Ellington
|
|
43
43
|
(@subclasses ||= []) << subclass
|
44
44
|
end
|
45
45
|
|
46
|
-
def generate_graphs(dir, options={})
|
47
|
-
options[:format] ||= :svg
|
48
|
-
FileUtils.mkdir_p(dir)
|
49
|
-
@subclasses.each do |subclass|
|
50
|
-
Ellington::Visualizer.new(subclass.new, dir, options[:format]).graph_all
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
46
|
def initialized?
|
55
47
|
@initialized
|
56
48
|
end
|
@@ -116,26 +108,29 @@ module Ellington
|
|
116
108
|
|
117
109
|
def line_completed(line_info)
|
118
110
|
route_info = Ellington::RouteInfo.new(self, line_info)
|
119
|
-
|
120
|
-
|
121
|
-
|
111
|
+
connections = required_connections(route_info.passenger)
|
112
|
+
return complete_route(route_info) if connections.empty?
|
113
|
+
connections.each do |connection|
|
114
|
+
connection.line.board route_info.passenger, route_info.options
|
122
115
|
end
|
116
|
+
end
|
123
117
|
|
124
|
-
|
125
|
-
if passed.satisfied?(route_info.passenger) || failed.satisfied?(route_info.passenger)
|
126
|
-
log route_info
|
127
|
-
changed
|
128
|
-
notify_observers route_info
|
129
|
-
end
|
130
|
-
Ellington.logger.info "\n" if Ellington.logger
|
131
|
-
end
|
118
|
+
protected
|
132
119
|
|
133
|
-
|
134
|
-
|
120
|
+
def complete_route(route_info)
|
121
|
+
if passed.satisfied?(route_info.passenger) || failed.satisfied?(route_info.passenger)
|
122
|
+
log route_info
|
123
|
+
changed
|
124
|
+
notify_observers route_info
|
135
125
|
end
|
126
|
+
Ellington.logger.info "\n" if Ellington.logger
|
136
127
|
end
|
137
128
|
|
138
|
-
|
129
|
+
def required_connections(passenger)
|
130
|
+
connections.select do |connection|
|
131
|
+
connection.required?(passenger)
|
132
|
+
end
|
133
|
+
end
|
139
134
|
|
140
135
|
def log(route_info)
|
141
136
|
return unless Ellington.logger
|
data/lib/ellington/station.rb
CHANGED
@@ -47,8 +47,9 @@ module Ellington
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def can_engage?(passenger, options={})
|
50
|
-
passenger.
|
51
|
-
|
50
|
+
return false unless route.states.can_transition?(passenger.current_state => states.keys)
|
51
|
+
return false if passenger.state_history_includes?(passed)
|
52
|
+
true
|
52
53
|
end
|
53
54
|
|
54
55
|
def engage(passenger, options={})
|
@@ -92,3 +93,4 @@ module Ellington
|
|
92
93
|
end
|
93
94
|
|
94
95
|
end
|
96
|
+
|
data/lib/ellington/version.rb
CHANGED
data/lib/ellington/visualizer.rb
CHANGED
@@ -15,6 +15,11 @@ module Ellington
|
|
15
15
|
super children
|
16
16
|
end
|
17
17
|
|
18
|
+
def add(node)
|
19
|
+
self << node
|
20
|
+
node
|
21
|
+
end
|
22
|
+
|
18
23
|
def name
|
19
24
|
viz.id
|
20
25
|
end
|
@@ -28,11 +33,10 @@ module Ellington
|
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
31
|
-
attr_reader :route, :
|
36
|
+
attr_reader :route, :format
|
32
37
|
|
33
|
-
def initialize(route,
|
38
|
+
def initialize(route, format=:svg)
|
34
39
|
@route = route
|
35
|
-
@dir = dir
|
36
40
|
@format = format
|
37
41
|
end
|
38
42
|
|
@@ -59,51 +63,31 @@ module Ellington
|
|
59
63
|
CLUSTER_FILLCOLOR = "gray70"
|
60
64
|
CLUSTER_PENCOLOR = "gray50"
|
61
65
|
|
62
|
-
def graph_all(passenger=nil)
|
63
|
-
graph_route_basic passenger
|
64
|
-
graph_route passenger
|
65
|
-
graph_lines_basic passenger
|
66
|
-
graph_lines passenger
|
67
|
-
end
|
68
|
-
|
69
66
|
def graph_lines_basic(passenger=nil)
|
70
67
|
g = Node.new(nil, GraphViz.new("GraphLinesBasic"))
|
71
68
|
set_graph_defaults g.viz
|
72
69
|
g.viz["label"] = "#{route.name} Lines - basic"
|
73
70
|
|
74
71
|
route.lines.each_with_index do |line, index|
|
75
|
-
line_cluster = Node.new(line, g.viz.add_graph("cluster#{index}"))
|
72
|
+
line_cluster = g.add(Node.new(line, g.viz.add_graph("cluster#{index}")))
|
76
73
|
set_cluster_defaults line_cluster.viz
|
77
74
|
line_cluster.viz["label"] = line.class.name
|
78
|
-
g << line_cluster
|
79
75
|
|
80
76
|
line.stations.each do |station|
|
81
|
-
|
82
|
-
line_cluster
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
station_node.viz["fillcolor"] = NODE_COLOR_LINE_GOAL
|
87
|
-
end
|
88
|
-
|
89
|
-
if !(route.goal & station.states.keys).empty?
|
90
|
-
station_node.viz["color"] = NODE_COLOR_ROUTE_GOAL
|
91
|
-
station_node.viz["fillcolor"] = NODE_COLOR_ROUTE_GOAL
|
92
|
-
end
|
93
|
-
|
94
|
-
if passenger && !(passenger.state_history & station.states.keys).empty?
|
95
|
-
station_node.viz["color"] = NODE_COLOR_PASSENGER_HIT
|
96
|
-
station_node.viz["penwidth"] = NODE_PENWIDTH_PASSENGER_HIT
|
97
|
-
end
|
77
|
+
states = station.states.keys
|
78
|
+
station_node = line_cluster.add(Node.new(station, line_cluster.viz.add_nodes(station.class.name)))
|
79
|
+
style_node_for_line(station_node, line, *states)
|
80
|
+
style_node_for_route(station_node, route, *states)
|
81
|
+
style_node_for_passenger(station_node, passenger, *states)
|
98
82
|
end
|
99
83
|
|
100
84
|
line_cluster.each_with_index.each do |node, node_index|
|
101
85
|
next_node = line_cluster[node_index + 1]
|
102
86
|
if next_node
|
103
|
-
station = node.base
|
104
|
-
next_station = next_node.base
|
87
|
+
#station = node.base
|
88
|
+
#next_station = next_node.base
|
105
89
|
edge = line_cluster.viz.add_edges(node.viz, next_node.viz)
|
106
|
-
if passenger
|
90
|
+
if passenger
|
107
91
|
if color_name(node.viz["color"]) == NODE_COLOR_PASSENGER_HIT &&
|
108
92
|
color_name(next_node.viz["color"]) == NODE_COLOR_PASSENGER_HIT
|
109
93
|
edge["color"] = EDGE_COLOR_PASSENGER_HIT
|
@@ -116,8 +100,7 @@ module Ellington
|
|
116
100
|
end
|
117
101
|
end
|
118
102
|
|
119
|
-
|
120
|
-
g.viz.output(format => File.join(dir, file_name))
|
103
|
+
g.viz.output(format => String)
|
121
104
|
end
|
122
105
|
|
123
106
|
def graph_lines(passenger=nil)
|
@@ -126,34 +109,17 @@ module Ellington
|
|
126
109
|
g.viz["label"] = "#{route.name} Lines"
|
127
110
|
|
128
111
|
route.lines.each_with_index do |line, index|
|
129
|
-
line_cluster = Node.new(line, g.viz.add_graph("cluster#{index}"))
|
112
|
+
line_cluster = g.add(Node.new(line, g.viz.add_graph("cluster#{index}")))
|
130
113
|
set_cluster_defaults line_cluster.viz
|
131
114
|
line_cluster.viz["label"] = line.class.name
|
132
|
-
|
133
|
-
|
134
|
-
line.states.keys.each do |state|
|
135
|
-
state_node = Node.new(state, line_cluster.viz.add_nodes(state))
|
136
|
-
if line.goal.include?(state)
|
137
|
-
state_node.viz["color"] = NODE_COLOR_LINE_GOAL
|
138
|
-
state_node.viz["fillcolor"] = NODE_FILLCOLOR_LINE_GOAL
|
139
|
-
end
|
140
|
-
if route.goal.include?(state)
|
141
|
-
state_node.viz["color"] = NODE_COLOR_ROUTE_GOAL
|
142
|
-
state_node.viz["fillcolor"] = NODE_FILLCOLOR_ROUTE_GOAL
|
143
|
-
end
|
144
|
-
if passenger && passenger.state_history_includes?(state)
|
145
|
-
state_node.viz["color"] = NODE_COLOR_PASSENGER_HIT
|
146
|
-
state_node.viz["penwidth"] = NODE_PENWIDTH_PASSENGER_HIT
|
147
|
-
end
|
148
|
-
line_cluster << state_node
|
149
|
-
end
|
115
|
+
add_state_nodes_for_line line_cluster, line, passenger
|
150
116
|
|
151
117
|
line.states.each do |state, transitions|
|
152
118
|
a = line_cluster.find(state)
|
153
119
|
(transitions || []).each do |transition|
|
154
120
|
b = line_cluster.find(transition)
|
155
121
|
edge = line_cluster.viz.add_edges(a.viz, b.viz)
|
156
|
-
if passenger
|
122
|
+
if passenger
|
157
123
|
if passenger.state_history_includes?(state, transition)
|
158
124
|
edge["color"] = EDGE_COLOR_PASSENGER_HIT
|
159
125
|
edge["penwidth"] = EDGE_PENWIDTH_PASSENGER_HIT
|
@@ -165,8 +131,7 @@ module Ellington
|
|
165
131
|
end
|
166
132
|
end
|
167
133
|
|
168
|
-
|
169
|
-
g.viz.output(format => File.join(dir, file_name))
|
134
|
+
g.viz.output(format => String)
|
170
135
|
end
|
171
136
|
|
172
137
|
def graph_route_basic(passenger=nil)
|
@@ -176,34 +141,17 @@ module Ellington
|
|
176
141
|
g.viz["label"] = "#{route.name} Route - basic"
|
177
142
|
|
178
143
|
route.lines.each_with_index do |line, index|
|
179
|
-
line_cluster = Node.new(line, g.viz.add_graph("cluster#{index}"))
|
144
|
+
line_cluster = g.add(Node.new(line, g.viz.add_graph("cluster#{index}")))
|
180
145
|
set_cluster_defaults line_cluster.viz
|
181
146
|
line_cluster.viz["label"] = line.class.name
|
182
|
-
g << line_cluster
|
183
147
|
|
184
148
|
passenger_hit = false
|
185
149
|
%w{PASS FAIL ERROR}.each do |state|
|
186
|
-
state_node = Node.new(state, line_cluster.viz.add_nodes("#{line.class.name}#{state}", "label" => state))
|
187
|
-
line_cluster << state_node
|
150
|
+
state_node = line_cluster.add(Node.new(state, line_cluster.viz.add_nodes("#{line.class.name}#{state}", "label" => state)))
|
188
151
|
states = line.stations.map{ |s| "#{state} #{s.name}" }
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
state_node.viz["fillcolor"] = NODE_COLOR_LINE_GOAL
|
193
|
-
end
|
194
|
-
|
195
|
-
if !(route.goal & states).empty?
|
196
|
-
state_node.viz["color"] = NODE_COLOR_ROUTE_GOAL
|
197
|
-
state_node.viz["fillcolor"] = NODE_COLOR_ROUTE_GOAL
|
198
|
-
end
|
199
|
-
|
200
|
-
if passenger
|
201
|
-
if !passenger_hit && !(passenger.state_history & line.send("#{state.downcase}ed")).empty?
|
202
|
-
passenger_hit = true
|
203
|
-
state_node.viz["color"] = NODE_COLOR_PASSENGER_HIT
|
204
|
-
state_node.viz["penwidth"] = NODE_PENWIDTH_PASSENGER_HIT
|
205
|
-
end
|
206
|
-
end
|
152
|
+
style_node_for_line(state_node, line, *states)
|
153
|
+
style_node_for_route(state_node, route, *states)
|
154
|
+
passenger_hit ||= style_node_for_passenger(state_node, passenger, *line.send("#{state.downcase}ed"))
|
207
155
|
end
|
208
156
|
end
|
209
157
|
|
@@ -255,8 +203,7 @@ module Ellington
|
|
255
203
|
end
|
256
204
|
end
|
257
205
|
|
258
|
-
|
259
|
-
g.viz.output(format => File.join(dir, file_name))
|
206
|
+
g.viz.output(format => String)
|
260
207
|
end
|
261
208
|
|
262
209
|
def graph_route(passenger=nil)
|
@@ -266,35 +213,18 @@ module Ellington
|
|
266
213
|
g.viz["ranksep"] = 0.8
|
267
214
|
|
268
215
|
route.lines.each_with_index do |line, index|
|
269
|
-
line_cluster = Node.new(line, g.viz.add_graph("cluster#{index}"))
|
216
|
+
line_cluster = g.add(Node.new(line, g.viz.add_graph("cluster#{index}")))
|
270
217
|
set_cluster_defaults line_cluster.viz
|
271
218
|
line_cluster.viz["label"] = line.class.name
|
272
|
-
|
273
|
-
|
274
|
-
line.states.keys.each do |state|
|
275
|
-
state_node = Node.new(state, line_cluster.viz.add_nodes(state))
|
276
|
-
if line.goal.include?(state)
|
277
|
-
state_node.viz["color"] = NODE_COLOR_LINE_GOAL
|
278
|
-
state_node.viz["fillcolor"] = NODE_FILLCOLOR_LINE_GOAL
|
279
|
-
end
|
280
|
-
if route.goal.include?(state)
|
281
|
-
state_node.viz["color"] = NODE_COLOR_ROUTE_GOAL
|
282
|
-
state_node.viz["fillcolor"] = NODE_FILLCOLOR_ROUTE_GOAL
|
283
|
-
end
|
284
|
-
if passenger && passenger.state_history_includes?(state)
|
285
|
-
state_node.viz["color"] = NODE_COLOR_PASSENGER_HIT
|
286
|
-
state_node.viz["penwidth"] = NODE_PENWIDTH_PASSENGER_HIT
|
287
|
-
end
|
288
|
-
line_cluster << state_node
|
289
|
-
end
|
219
|
+
add_state_nodes_for_line line_cluster, line, passenger
|
290
220
|
end
|
291
221
|
|
292
222
|
viz = g.viz.add_nodes(route.initial_state)
|
293
223
|
rendered_edges = {}
|
294
224
|
|
295
225
|
if passenger
|
296
|
-
passenger_nodes = g.reduce([]) do |memo, line_cluster|
|
297
|
-
line_cluster.children.each do |node|
|
226
|
+
passenger_nodes = g.reduce([]) do |memo, line_cluster|
|
227
|
+
line_cluster.children.each do |node|
|
298
228
|
if node.viz["color"].to_s.gsub(/\W/, "") == NODE_COLOR_PASSENGER_HIT
|
299
229
|
memo << node
|
300
230
|
end
|
@@ -334,11 +264,43 @@ module Ellington
|
|
334
264
|
end
|
335
265
|
end
|
336
266
|
|
337
|
-
|
338
|
-
g.viz.output(format => File.join(dir, file_name))
|
267
|
+
g.viz.output(format => String)
|
339
268
|
end
|
340
269
|
|
341
|
-
|
270
|
+
protected
|
271
|
+
|
272
|
+
def add_state_nodes_for_line(cluster, line, passenger)
|
273
|
+
line.states.keys.each do |state|
|
274
|
+
node = cluster.add(Node.new(state, cluster.viz.add_nodes(state)))
|
275
|
+
style_node_for_line(node, line, state)
|
276
|
+
style_node_for_route(node, line.route, state)
|
277
|
+
style_node_for_passenger(node, passenger, state)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def style_node(node, color)
|
282
|
+
node.viz["color"] = color
|
283
|
+
node.viz["fillcolor"] = color
|
284
|
+
true
|
285
|
+
end
|
286
|
+
|
287
|
+
def style_node_for_line(node, line, *states)
|
288
|
+
return false if (line.goal & states).empty?
|
289
|
+
style_node node, NODE_COLOR_LINE_GOAL
|
290
|
+
end
|
291
|
+
|
292
|
+
def style_node_for_route(node, route, *states)
|
293
|
+
return false if (route.goal & states).empty?
|
294
|
+
style_node node, NODE_COLOR_ROUTE_GOAL
|
295
|
+
end
|
296
|
+
|
297
|
+
def style_node_for_passenger(node, passenger, *states)
|
298
|
+
return false if passenger.nil?
|
299
|
+
return false if (passenger.state_history & states).empty?
|
300
|
+
node.viz["color"] = NODE_COLOR_PASSENGER_HIT
|
301
|
+
node.viz["penwidth"] = NODE_PENWIDTH_PASSENGER_HIT
|
302
|
+
true
|
303
|
+
end
|
342
304
|
|
343
305
|
def color_name(graphviz_color)
|
344
306
|
graphviz_color.to_s.gsub("\"", "")
|
data/test/attendant_test.rb
CHANGED
@@ -8,7 +8,6 @@ class AttendantTest < MicroTest::Test
|
|
8
8
|
@station = line.stations.first
|
9
9
|
@passenger = Ellington::Passenger.new(NumberWithHistory.new(0), route)
|
10
10
|
@passenger.current_state = route.initial_state
|
11
|
-
@passenger.lock
|
12
11
|
@attendant = Ellington::Attendant.new(@station)
|
13
12
|
@passenger.add_observer @attendant
|
14
13
|
end
|
data/test/conductor_test.rb
CHANGED
@@ -7,55 +7,23 @@ class ConductorTest < MicroTest::Test
|
|
7
7
|
@conductor = Ellington::Conductor.new(@route)
|
8
8
|
@passenger = Ellington::Passenger.new(NumberWithHistory.new(0), @route)
|
9
9
|
@passenger.current_state = @route.initial_state
|
10
|
-
@passenger.lock
|
11
|
-
end
|
12
|
-
|
13
|
-
test "not conducting" do
|
14
|
-
assert !@conductor.conducting?
|
15
10
|
end
|
16
11
|
|
17
12
|
test "verify" do
|
18
13
|
assert @conductor.verify(@passenger)
|
19
14
|
end
|
20
15
|
|
21
|
-
test "
|
22
|
-
|
23
|
-
begin
|
24
|
-
@conductor.gather_passengers
|
25
|
-
rescue Ellington::NotImplementedError => e
|
26
|
-
error = e
|
27
|
-
end
|
28
|
-
assert !error.nil?
|
29
|
-
end
|
30
|
-
|
31
|
-
test "escort" do
|
32
|
-
@conductor.escort(@passenger)
|
16
|
+
test "conduct" do
|
17
|
+
@conductor.conduct(@passenger)
|
33
18
|
assert @passenger.current_state != @route.initial_state
|
34
19
|
end
|
35
20
|
|
36
|
-
test "
|
21
|
+
test "conduct prevented when passenger doesn't verify" do
|
37
22
|
def @conductor.verify(passenger)
|
38
23
|
false
|
39
24
|
end
|
40
|
-
@conductor.
|
25
|
+
@conductor.conduct(@passenger)
|
41
26
|
assert @passenger.current_state == @route.initial_state
|
42
27
|
end
|
43
28
|
|
44
|
-
test "escort prevented when passenger is not locked" do
|
45
|
-
@passenger.unlock
|
46
|
-
assert @passenger.current_state == @route.initial_state
|
47
|
-
end
|
48
|
-
|
49
|
-
test "start/stop with conducting? checks" do
|
50
|
-
def @conductor.gather_passengers
|
51
|
-
sleep 0.1
|
52
|
-
[]
|
53
|
-
end
|
54
|
-
@conductor.start
|
55
|
-
assert @conductor.conducting?
|
56
|
-
@conductor.stop
|
57
|
-
sleep 0.2
|
58
|
-
puts !@conductor.conducting?
|
59
|
-
end
|
60
|
-
|
61
29
|
end
|
data/test/example.rb
CHANGED
@@ -190,27 +190,3 @@ class BasicMath < Ellington::Route
|
|
190
190
|
log_options :passenger => [:original_value, :current_value]
|
191
191
|
end
|
192
192
|
|
193
|
-
# conductor ---------------------------------------------------------------
|
194
|
-
class NumberConductor < Ellington::Conductor
|
195
|
-
|
196
|
-
def gather_passengers
|
197
|
-
sleep 1
|
198
|
-
(0..999).to_a.sample(10).map do |num|
|
199
|
-
num = NumberWithHistory.new(num)
|
200
|
-
passenger = Ellington::Passenger.new(num, route)
|
201
|
-
passenger.current_state = route.initial_state
|
202
|
-
passenger.lock
|
203
|
-
passenger
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
end
|
208
|
-
|
209
|
-
if ENV["START"]
|
210
|
-
route = BasicMath.new
|
211
|
-
conductor = NumberConductor.new(route)
|
212
|
-
conductor.start
|
213
|
-
#conductor.wait
|
214
|
-
sleep 5
|
215
|
-
conductor.stop
|
216
|
-
end
|
data/test/passenger_test.rb
CHANGED
@@ -8,41 +8,26 @@ class PassengerTest < MicroTest::Test
|
|
8
8
|
@passenger = Ellington::Passenger.new(@number, @route)
|
9
9
|
end
|
10
10
|
|
11
|
-
test "
|
12
|
-
|
13
|
-
|
11
|
+
test "construct with ticket option" do
|
12
|
+
ticket = Ellington::Ticket.new
|
13
|
+
passenger = Ellington::Passenger.new(@number, @route, :ticket => ticket)
|
14
|
+
assert passenger.ticket == ticket
|
14
15
|
end
|
15
16
|
|
16
|
-
test "
|
17
|
-
|
18
|
-
|
19
|
-
assert !@passenger.locked?
|
20
|
-
end
|
21
|
-
|
22
|
-
test "transition_to fails when unlocked" do
|
23
|
-
error = nil
|
24
|
-
begin
|
25
|
-
@passenger.current_state = @route.initial_state
|
26
|
-
@passenger.transition_to @route.lines.first.states.keys.first
|
27
|
-
rescue Ellington::InvalidStateTransition => e
|
28
|
-
error = e
|
29
|
-
end
|
30
|
-
assert !error.nil?
|
31
|
-
assert error.message == "Cannot transition an unlocked Ellington::Passenger's state"
|
17
|
+
test "construct with state_history option" do
|
18
|
+
passenger = Ellington::Passenger.new(@number, @route, :state_history => [:foo, :bar])
|
19
|
+
assert passenger.state_history == [:foo, :bar]
|
32
20
|
end
|
33
21
|
|
34
22
|
test "transition_to valid state" do
|
35
|
-
@passenger.lock
|
36
23
|
@passenger.current_state = @route.initial_state
|
37
24
|
@passenger.transition_to @route.lines.first.states.keys.first
|
38
|
-
@passenger.unlock
|
39
25
|
assert @passenger.current_state == @route.lines.first.states.keys.first
|
40
26
|
end
|
41
27
|
|
42
28
|
test "transition_to invalid state" do
|
43
29
|
error = nil
|
44
30
|
begin
|
45
|
-
@passenger.lock
|
46
31
|
@passenger.current_state = :error
|
47
32
|
@passenger.transition_to :happy
|
48
33
|
rescue Ellington::InvalidStateTransition => e
|
@@ -60,10 +45,8 @@ class PassengerTest < MicroTest::Test
|
|
60
45
|
end
|
61
46
|
|
62
47
|
@passenger.add_observer(watcher)
|
63
|
-
@passenger.lock
|
64
48
|
@passenger.current_state = @route.initial_state
|
65
49
|
@passenger.transition_to @route.lines.first.states.keys.first
|
66
|
-
@passenger.unlock
|
67
50
|
|
68
51
|
assert watcher.info.passenger == @passenger
|
69
52
|
assert watcher.info.old_state == "PRE BasicMath"
|
@@ -71,11 +54,9 @@ class PassengerTest < MicroTest::Test
|
|
71
54
|
end
|
72
55
|
|
73
56
|
test "state_history" do
|
74
|
-
@passenger.lock
|
75
57
|
@passenger.current_state = @route.initial_state
|
76
58
|
@passenger.transition_to @route.lines[0].states.keys.first
|
77
59
|
@passenger.transition_to @route.lines[2].states.keys.first
|
78
|
-
@passenger.unlock
|
79
60
|
assert @passenger.state_history == ["PASS Addition Add10", "PASS Multiplication MultiplyBy10"]
|
80
61
|
end
|
81
62
|
|
data/test/station_test.rb
CHANGED
@@ -8,7 +8,6 @@ class StationTest < MicroTest::Test
|
|
8
8
|
@station = @line.stations.first
|
9
9
|
@passenger = Ellington::Passenger.new(NumberWithHistory.new(0), @route)
|
10
10
|
@passenger.current_state = @route.initial_state
|
11
|
-
@passenger.lock
|
12
11
|
end
|
13
12
|
|
14
13
|
test "name" do
|
@@ -89,6 +88,11 @@ class StationTest < MicroTest::Test
|
|
89
88
|
assert !@station.can_engage?(@passenger)
|
90
89
|
end
|
91
90
|
|
91
|
+
test "can_engage? returns false if the passed state exists in state_history" do
|
92
|
+
@passenger.current_state = @station.passed
|
93
|
+
assert !@station.can_engage?(@passenger)
|
94
|
+
end
|
95
|
+
|
92
96
|
test "engage" do
|
93
97
|
@passenger.current_state = @route.initial_state
|
94
98
|
@station.engage(@passenger, nil)
|
data/test/target_test.rb
CHANGED
@@ -24,28 +24,28 @@ class TargetTest < MicroTest::Test
|
|
24
24
|
end
|
25
25
|
|
26
26
|
test "not satisfied for passenger in the wrong state" do
|
27
|
-
passenger = Ellington::Passenger.new(@number,
|
27
|
+
passenger = Ellington::Passenger.new(@number, nil)
|
28
28
|
passenger.current_state = :open
|
29
29
|
goal = Ellington::Target.new(:closed)
|
30
30
|
assert !goal.satisfied?(passenger)
|
31
31
|
end
|
32
32
|
|
33
33
|
test "satisfied for passenger in the right state" do
|
34
|
-
passenger = Ellington::Passenger.new(@number,
|
34
|
+
passenger = Ellington::Passenger.new(@number, nil)
|
35
35
|
passenger.current_state = :closed
|
36
36
|
goal = Ellington::Target.new(:closed)
|
37
37
|
assert goal.satisfied?(passenger)
|
38
38
|
end
|
39
39
|
|
40
40
|
test "satisfied for passenger in an expected state" do
|
41
|
-
passenger = Ellington::Passenger.new(@number,
|
41
|
+
passenger = Ellington::Passenger.new(@number, nil)
|
42
42
|
passenger.current_state = :two
|
43
43
|
goal = Ellington::Target.new(:one, :two, :three)
|
44
44
|
assert goal.satisfied?(passenger)
|
45
45
|
end
|
46
46
|
|
47
47
|
test "not satisfied for passenger not in an expected state" do
|
48
|
-
passenger = Ellington::Passenger.new(@number,
|
48
|
+
passenger = Ellington::Passenger.new(@number, nil)
|
49
49
|
passenger.current_state = :four
|
50
50
|
goal = Ellington::Target.new(:one, :two, :three)
|
51
51
|
assert !goal.satisfied?(passenger)
|
data/test/test_helper.rb
CHANGED
@@ -1,7 +1,16 @@
|
|
1
|
-
require "micro_test"
|
2
|
-
require "micro_mock"
|
3
1
|
require "logger"
|
4
2
|
require "delegate"
|
3
|
+
require "micro_test"
|
4
|
+
require "micro_mock"
|
5
|
+
require 'simplecov'
|
6
|
+
require "coveralls"
|
7
|
+
SimpleCov.formatter = Coveralls::SimpleCov::Formatter
|
8
|
+
SimpleCov.command_name 'micro_test'
|
9
|
+
SimpleCov.start do
|
10
|
+
add_filter "/test/"
|
11
|
+
end
|
12
|
+
Coveralls.wear!
|
13
|
+
|
5
14
|
require_relative "../lib/ellington"
|
6
15
|
require_relative "example"
|
7
16
|
|
data/test/ticket_test.rb
CHANGED
@@ -10,7 +10,7 @@ class TicketTest < MicroTest::Test
|
|
10
10
|
:debit_id => "b834fb69-2eb3-4e57-b313-16b863e91f74",
|
11
11
|
:credit_id => "9a670fce-4826-4a90-aaf7-a58738a4ce5b"
|
12
12
|
)
|
13
|
-
@passenger = Ellington::Passenger.new(0, @route, @ticket)
|
13
|
+
@passenger = Ellington::Passenger.new(0, @route, :ticket => @ticket)
|
14
14
|
end
|
15
15
|
|
16
16
|
test "basic ticket" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ellington
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Hopkins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hero
|
@@ -150,6 +150,34 @@ dependencies:
|
|
150
150
|
- - '>='
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: simplecov
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - '>='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: coveralls
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - '>='
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
153
181
|
description: An opinionated framework for modeling complex business processes.
|
154
182
|
email:
|
155
183
|
- natehop@gmail.com
|
@@ -181,7 +209,6 @@ files:
|
|
181
209
|
- lib/ellington/visualizer.rb
|
182
210
|
- lib/ellington.rb
|
183
211
|
- Gemfile
|
184
|
-
- Gemfile.lock
|
185
212
|
- LICENSE.txt
|
186
213
|
- Rakefile
|
187
214
|
- README.md
|
@@ -222,7 +249,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
222
249
|
version: '0'
|
223
250
|
requirements: []
|
224
251
|
rubyforge_project:
|
225
|
-
rubygems_version: 2.0.
|
252
|
+
rubygems_version: 2.0.3
|
226
253
|
signing_key:
|
227
254
|
specification_version: 4
|
228
255
|
summary: An opinionated framework for modeling complex business processes.
|
@@ -244,3 +271,4 @@ test_files:
|
|
244
271
|
- test/ticket_test.rb
|
245
272
|
- test/transition_info_test.rb
|
246
273
|
- test/unique_type_array_test.rb
|
274
|
+
has_rdoc:
|
data/Gemfile.lock
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
ellington (0.1.2)
|
5
|
-
hero (~> 0.1.9)
|
6
|
-
ruby-graphviz (~> 1.0.8)
|
7
|
-
state_jacket (~> 0.1.0)
|
8
|
-
|
9
|
-
GEM
|
10
|
-
remote: https://rubygems.org/
|
11
|
-
specs:
|
12
|
-
awesome_print (1.1.0)
|
13
|
-
binding_of_caller (0.7.2)
|
14
|
-
debug_inspector (>= 0.0.1)
|
15
|
-
coderay (1.0.9)
|
16
|
-
debug_inspector (0.0.2)
|
17
|
-
hero (0.1.9)
|
18
|
-
interception (0.3)
|
19
|
-
method_source (0.8.1)
|
20
|
-
micro_mock (1.1.0)
|
21
|
-
micro_test (0.3.8)
|
22
|
-
os
|
23
|
-
os (0.9.6)
|
24
|
-
pry (0.9.12.2)
|
25
|
-
coderay (~> 1.0.5)
|
26
|
-
method_source (~> 0.8)
|
27
|
-
slop (~> 3.4)
|
28
|
-
pry-rescue (1.1.1)
|
29
|
-
interception (>= 0.3)
|
30
|
-
pry
|
31
|
-
pry-stack_explorer (0.4.9)
|
32
|
-
binding_of_caller (>= 0.7)
|
33
|
-
pry (~> 0.9.11)
|
34
|
-
rake (10.0.4)
|
35
|
-
ruby-graphviz (1.0.9)
|
36
|
-
slop (3.4.5)
|
37
|
-
state_jacket (0.1.0)
|
38
|
-
yell (1.3.0)
|
39
|
-
|
40
|
-
PLATFORMS
|
41
|
-
ruby
|
42
|
-
|
43
|
-
DEPENDENCIES
|
44
|
-
awesome_print
|
45
|
-
ellington!
|
46
|
-
micro_mock
|
47
|
-
micro_test
|
48
|
-
pry-rescue
|
49
|
-
pry-stack_explorer
|
50
|
-
rake
|
51
|
-
yell
|