ellington 0.0.1 → 0.0.2
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/Gemfile +14 -2
- data/Gemfile.lock +40 -0
- data/README.md +66 -17
- data/lib/ellington.rb +6 -4
- data/lib/ellington/attendant.rb +21 -0
- data/lib/ellington/conductor.rb +59 -0
- data/lib/ellington/connection.rb +11 -0
- data/lib/ellington/connection_list.rb +29 -0
- data/lib/ellington/errros.rb +22 -0
- data/lib/ellington/has_targets.rb +29 -0
- data/lib/ellington/line.rb +95 -0
- data/lib/ellington/line_info.rb +34 -0
- data/lib/ellington/line_list.rb +24 -0
- data/lib/ellington/logger.rb +11 -0
- data/lib/ellington/passenger.rb +67 -0
- data/lib/ellington/route.rb +114 -0
- data/lib/ellington/route_info.rb +25 -0
- data/lib/ellington/station.rb +88 -0
- data/lib/ellington/station_info.rb +11 -0
- data/lib/ellington/station_list.rb +24 -0
- data/lib/ellington/target.rb +25 -0
- data/lib/ellington/ticket.rb +22 -0
- data/lib/ellington/transition_info.rb +4 -0
- data/lib/ellington/unique_type_array.rb +39 -0
- data/lib/ellington/version.rb +1 -1
- data/test/attendant_test.rb +42 -0
- data/test/conductor_test.rb +32 -0
- data/test/example.rb +212 -0
- data/test/line_test.rb +95 -0
- data/test/passenger_test.rb +73 -0
- data/test/route_test.rb +51 -0
- data/test/station_test.rb +115 -0
- data/test/target_test.rb +54 -0
- data/test/test_helper.rb +6 -0
- data/test/ticket_test.rb +39 -0
- metadata +92 -14
- data/.gitignore +0 -17
- data/ellington.gemspec +0 -19
@@ -0,0 +1,34 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Ellington
|
4
|
+
class LineInfo < SimpleDelegator
|
5
|
+
attr_reader :line, :station_info
|
6
|
+
|
7
|
+
def initialize(line, station_info)
|
8
|
+
@line = line
|
9
|
+
@station_info = station_info
|
10
|
+
super station_info
|
11
|
+
end
|
12
|
+
|
13
|
+
def station_full_name
|
14
|
+
@station_full_name ||= "#{station.class.name}::#{line.class.name}::#{line.route.name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def log_message(options={})
|
18
|
+
message = []
|
19
|
+
if options[:line_completed]
|
20
|
+
message << "[LINE COMPLETED]"
|
21
|
+
message << "[#{line.state(passenger)}]"
|
22
|
+
end
|
23
|
+
if options[:station_completed]
|
24
|
+
message << "[STATION COMPLETED]"
|
25
|
+
message << "[#{station.state(passenger)}]"
|
26
|
+
end
|
27
|
+
message << "[#{station_full_name}]"
|
28
|
+
line.route.log_passenger_attrs.each do |attr|
|
29
|
+
message << "[#{attr}:#{passenger.send(attr)}]"
|
30
|
+
end
|
31
|
+
message.join " "
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Ellington
|
4
|
+
class LineList < SimpleDelegator
|
5
|
+
attr_reader :route
|
6
|
+
|
7
|
+
def initialize(route)
|
8
|
+
@route = route
|
9
|
+
@inner_list = UniqueTypeArray.new
|
10
|
+
super @inner_list
|
11
|
+
end
|
12
|
+
|
13
|
+
def push(line)
|
14
|
+
value = inner_list << line
|
15
|
+
line.route = route
|
16
|
+
value
|
17
|
+
end
|
18
|
+
alias_method :<<, :push
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
attr_reader :inner_list
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "observer"
|
2
|
+
|
3
|
+
module Ellington
|
4
|
+
class Passenger < SimpleDelegator
|
5
|
+
include Observable
|
6
|
+
attr_accessor :context, :ticket
|
7
|
+
attr_reader :route
|
8
|
+
|
9
|
+
def initialize(context, route, ticket=nil)
|
10
|
+
ticket ||= Ellington::Ticket.new
|
11
|
+
@context = context
|
12
|
+
@route = route
|
13
|
+
@ticket = ticket
|
14
|
+
super context
|
15
|
+
end
|
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
|
+
def current_state
|
33
|
+
return context.current_state if context.respond_to?(:current_state)
|
34
|
+
@current_state
|
35
|
+
end
|
36
|
+
|
37
|
+
def current_state=(value)
|
38
|
+
return context.current_state=(value) if context.respond_to?(:current_state=)
|
39
|
+
@current_state = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def transition_to(new_state)
|
43
|
+
if !locked?
|
44
|
+
message = "Cannot transition an unlocked #{self.class.name}'s state"
|
45
|
+
raise Ellington::InvalidStateTransition.new(message)
|
46
|
+
end
|
47
|
+
|
48
|
+
if !route.states.can_transition?(current_state => new_state)
|
49
|
+
message = "Cannot transition #{self.class.name} from:#{current_state} to:#{new_state}"
|
50
|
+
raise Ellington::InvalidStateTransition.new(message)
|
51
|
+
end
|
52
|
+
|
53
|
+
old_state = current_state
|
54
|
+
|
55
|
+
if context.respond_to?(:transition_to)
|
56
|
+
return_value = context.transition_to(new_state)
|
57
|
+
else
|
58
|
+
self.current_state = new_state
|
59
|
+
end
|
60
|
+
|
61
|
+
changed
|
62
|
+
notify_observers Ellington::TransitionInfo.new(self, old_state, new_state)
|
63
|
+
return_value || new_state
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Ellington
|
4
|
+
class Route < SimpleDelegator
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super self.class
|
8
|
+
init unless initialized?
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
include HasTargets
|
13
|
+
attr_reader :initialized
|
14
|
+
|
15
|
+
def initialized?
|
16
|
+
@initialized
|
17
|
+
end
|
18
|
+
|
19
|
+
def init
|
20
|
+
initialize_lines
|
21
|
+
states
|
22
|
+
@initialized = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize_lines
|
26
|
+
lines.each do |line|
|
27
|
+
line.add_observer self, :line_completed
|
28
|
+
line.stations.each do |station|
|
29
|
+
station.add_observer line, :station_completed
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def board(passenger, options={})
|
35
|
+
lines.first.board passenger, options
|
36
|
+
end
|
37
|
+
|
38
|
+
def lines
|
39
|
+
@lines ||= Ellington::LineList.new(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def states
|
43
|
+
@states ||= begin
|
44
|
+
catalog = StateJacket::Catalog.new
|
45
|
+
catalog.add initial_state => lines.first.stations.first.states.keys
|
46
|
+
|
47
|
+
lines.each do |line|
|
48
|
+
catalog.merge! line.states
|
49
|
+
end
|
50
|
+
|
51
|
+
connections.each do |connection|
|
52
|
+
connection.states.each do |state|
|
53
|
+
catalog[state] ||= []
|
54
|
+
catalog[state].concat connection.line.stations.first.states.keys
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
catalog.lock
|
59
|
+
catalog
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def initial_state
|
64
|
+
@initial_state ||= "PRE #{name}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def pass_target(*line_goals)
|
68
|
+
@goal ||= Ellington::Target.new(*line_goals.flatten)
|
69
|
+
end
|
70
|
+
alias_method :passed, :pass_target
|
71
|
+
alias_method :goal, :pass_target
|
72
|
+
|
73
|
+
def connections
|
74
|
+
@connections ||= Ellington::ConnectionList.new
|
75
|
+
end
|
76
|
+
|
77
|
+
def connect_to(line, options)
|
78
|
+
connections << Ellington::Connection.new(line, options[:if])
|
79
|
+
end
|
80
|
+
|
81
|
+
def log_passenger_attrs(*attrs)
|
82
|
+
@log_passenger_attrs ||= attrs
|
83
|
+
end
|
84
|
+
|
85
|
+
def line_completed(line_info)
|
86
|
+
route_info = Ellington::RouteInfo.new(self, line_info)
|
87
|
+
|
88
|
+
required_connections = connections.select do |connection|
|
89
|
+
connection.states.satisfied?(route_info.passenger)
|
90
|
+
end
|
91
|
+
|
92
|
+
if required_connections.empty?
|
93
|
+
if passed.satisfied?(route_info.passenger) || failed.satisfied?(route_info.passenger)
|
94
|
+
log route_info
|
95
|
+
end
|
96
|
+
Ellington.logger.info "\n" if Ellington.logger
|
97
|
+
end
|
98
|
+
|
99
|
+
required_connections.each do |connection|
|
100
|
+
connection.line.board route_info.passenger, route_info.options
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def log(route_info)
|
107
|
+
return unless Ellington.logger
|
108
|
+
Ellington.logger.info route_info.log_message
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Ellington
|
4
|
+
class RouteInfo < SimpleDelegator
|
5
|
+
attr_reader :route, :line_info
|
6
|
+
|
7
|
+
def initialize(route, line_info)
|
8
|
+
@route = route
|
9
|
+
@line_info = line_info
|
10
|
+
super line_info
|
11
|
+
end
|
12
|
+
|
13
|
+
def log_message(options={})
|
14
|
+
message = []
|
15
|
+
message << "[ROUTE COMPLETED]"
|
16
|
+
message << "[#{route.state(passenger)}]"
|
17
|
+
message << "[#{station_full_name}]"
|
18
|
+
line.route.log_passenger_attrs.each do |attr|
|
19
|
+
message << "[#{attr}:#{passenger.send(attr)}]"
|
20
|
+
end
|
21
|
+
message.join " "
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require "observer"
|
2
|
+
require "forwardable"
|
3
|
+
|
4
|
+
module Ellington
|
5
|
+
|
6
|
+
class Station
|
7
|
+
extend Forwardable
|
8
|
+
include Observable
|
9
|
+
attr_accessor :line
|
10
|
+
def_delegators :line, :route
|
11
|
+
|
12
|
+
def name
|
13
|
+
@name ||= "#{self.class.name}::#{line.name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def state_name(state)
|
17
|
+
"#{state.to_s.upcase} #{name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def passed
|
21
|
+
@pass_state ||= state_name(:pass)
|
22
|
+
end
|
23
|
+
|
24
|
+
def failed
|
25
|
+
@fail_state ||= state_name(:fail)
|
26
|
+
end
|
27
|
+
|
28
|
+
def errored
|
29
|
+
@error_state ||= state_name(:error)
|
30
|
+
end
|
31
|
+
|
32
|
+
def states
|
33
|
+
@states ||= begin
|
34
|
+
catalog = StateJacket::Catalog.new
|
35
|
+
catalog.add passed
|
36
|
+
catalog.add failed
|
37
|
+
catalog.add errored => [ passed, failed, errored ]
|
38
|
+
catalog.lock
|
39
|
+
catalog
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def can_engage?(passenger, options={})
|
44
|
+
passenger.locked? &&
|
45
|
+
route.states.can_transition?(passenger.current_state => states.keys)
|
46
|
+
end
|
47
|
+
|
48
|
+
def engage(passenger, options={})
|
49
|
+
raise Ellington::NotImplementedError
|
50
|
+
end
|
51
|
+
|
52
|
+
def call(passenger, options={})
|
53
|
+
if can_engage?(passenger)
|
54
|
+
attendant = Ellington::Attendant.new(self)
|
55
|
+
passenger.add_observer attendant
|
56
|
+
engage passenger, options
|
57
|
+
passenger.delete_observer attendant
|
58
|
+
raise Ellington::AttendandDisapproves unless attendant.approve?
|
59
|
+
changed
|
60
|
+
notify_observers Ellington::StationInfo.new(self, passenger, attendant.passenger_transitions.first, options)
|
61
|
+
end
|
62
|
+
|
63
|
+
passenger
|
64
|
+
end
|
65
|
+
|
66
|
+
def pass(passenger)
|
67
|
+
passenger.transition_to passed
|
68
|
+
end
|
69
|
+
|
70
|
+
def fail(passenger)
|
71
|
+
passenger.transition_to failed
|
72
|
+
end
|
73
|
+
|
74
|
+
def error(passenger)
|
75
|
+
passenger.transition_to errored
|
76
|
+
end
|
77
|
+
|
78
|
+
def state(passenger)
|
79
|
+
case passenger.current_state
|
80
|
+
when passed then "PASS"
|
81
|
+
when failed then "FAIL"
|
82
|
+
when errored then "ERROR"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Ellington
|
2
|
+
class StationInfo
|
3
|
+
attr_reader :station, :passenger, :transition, :options
|
4
|
+
def initialize(station, passenger, transition, options)
|
5
|
+
@station = station
|
6
|
+
@passenger = passenger
|
7
|
+
@transition = transition
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Ellington
|
4
|
+
class StationList < SimpleDelegator
|
5
|
+
attr_reader :line
|
6
|
+
|
7
|
+
def initialize(line)
|
8
|
+
@line = line
|
9
|
+
@inner_list = UniqueTypeArray.new
|
10
|
+
super @inner_list
|
11
|
+
end
|
12
|
+
|
13
|
+
def push(station)
|
14
|
+
value = inner_list << station
|
15
|
+
station.line = line
|
16
|
+
value
|
17
|
+
end
|
18
|
+
alias_method :<<, :push
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
attr_reader :inner_list
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Ellington
|
4
|
+
class Target < SimpleDelegator
|
5
|
+
|
6
|
+
def initialize(*states)
|
7
|
+
@inner_list = states.flatten
|
8
|
+
super inner_list
|
9
|
+
end
|
10
|
+
|
11
|
+
def include?(state)
|
12
|
+
inner_list.include? state
|
13
|
+
end
|
14
|
+
|
15
|
+
def satisfied?(passenger)
|
16
|
+
return false if passenger.nil?
|
17
|
+
include? passenger.current_state
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
attr_reader :inner_list
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "digest"
|
2
|
+
require "delegate"
|
3
|
+
|
4
|
+
module Ellington
|
5
|
+
|
6
|
+
class Ticket < SimpleDelegator
|
7
|
+
attr_reader :passenger, :goal
|
8
|
+
|
9
|
+
def initialize(goal=nil, hash={})
|
10
|
+
goal ||= Ellington::Target.new
|
11
|
+
@goal = goal
|
12
|
+
super hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def id
|
16
|
+
Digest::SHA256.hexdigest values.map(&:to_s).join
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|