commute 0.2.0.rc.2 → 0.3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.todo +28 -12
- data/README.md +0 -1
- data/commute.gemspec +4 -5
- data/lib/commute/common/basic_auth.rb +10 -9
- data/lib/commute/common/caching.rb +208 -0
- data/lib/commute/common/chemicals.rb +47 -24
- data/lib/commute/common/eventmachine.rb +68 -0
- data/lib/commute/common/synchrony.rb +42 -0
- data/lib/commute/common/typhoeus.rb +64 -0
- data/lib/commute/core/api.rb +42 -29
- data/lib/commute/core/builder.rb +4 -15
- data/lib/commute/core/context.rb +156 -15
- data/lib/commute/core/http.rb +124 -0
- data/lib/commute/core/layer.rb +187 -0
- data/lib/commute/core/sequence.rb +83 -132
- data/lib/commute/core/stack.rb +63 -72
- data/lib/commute/core/status.rb +45 -0
- data/lib/commute/core/util/event_emitter.rb +58 -0
- data/lib/commute/core/util/path.rb +37 -0
- data/lib/commute/core/util/stream.rb +141 -0
- data/lib/commute/extensions/crud.rb +88 -0
- data/lib/commute/extensions/param.rb +20 -0
- data/lib/commute/extensions/url.rb +53 -0
- data/lib/commute/version.rb +1 -1
- data/spec/commute/common/caching_spec.rb +158 -0
- data/spec/commute/common/eventmachine_spec.rb +74 -0
- data/spec/commute/common/typhoeus_spec.rb +67 -0
- data/spec/commute/core/api_spec.rb +3 -1
- data/spec/commute/core/builder_spec.rb +8 -8
- data/spec/commute/core/http_spec.rb +39 -0
- data/spec/commute/core/layer_spec.rb +81 -0
- data/spec/commute/core/sequence_spec.rb +36 -150
- data/spec/commute/core/stack_spec.rb +33 -83
- data/spec/commute/core/util/event_emitter_spec.rb +35 -0
- data/spec/commute/core/util/path_spec.rb +29 -0
- data/spec/commute/core/util/stream_spec.rb +90 -0
- data/spec/commute/extensions/url_spec.rb +76 -0
- data/spec/spec_helper.rb +3 -1
- metadata +61 -48
- data/examples/gist_api.rb +0 -71
- data/examples/highrise_task_api.rb +0 -59
- data/examples/pastie_api.rb +0 -18
- data/lib/commute/aspects/caching.rb +0 -37
- data/lib/commute/aspects/crud.rb +0 -41
- data/lib/commute/aspects/pagination.rb +0 -16
- data/lib/commute/aspects/url.rb +0 -57
- data/lib/commute/common/cache.rb +0 -43
- data/lib/commute/common/conditional.rb +0 -27
- data/lib/commute/common/em-synchrony_adapter.rb +0 -29
- data/lib/commute/common/em_http_request_adapter.rb +0 -57
- data/lib/commute/common/typhoeus_adapter.rb +0 -40
- data/lib/commute/common/xml.rb +0 -7
- data/lib/commute/core/commuter.rb +0 -116
- data/lib/commute/core/processors/code_status_processor.rb +0 -40
- data/lib/commute/core/processors/hook.rb +0 -14
- data/lib/commute/core/processors/request_builder.rb +0 -26
- data/lib/commute/core/processors/sequencer.rb +0 -46
- data/lib/commute/core/request.rb +0 -58
- data/lib/commute/core/response.rb +0 -18
- data/spec/commute/aspects/caching_spec.rb +0 -12
- data/spec/commute/aspects/url_spec.rb +0 -61
- data/spec/commute/core/commuter_spec.rb +0 -64
- data/spec/commute/core/processors/code_status_processor_spec.rb +0 -5
- data/spec/commute/core/processors/hook_spec.rb +0 -25
- data/spec/commute/core/processors/request_builder_spec.rb +0 -25
- data/spec/commute/core/processors/sequencer_spec.rb +0 -33
@@ -1,16 +1,29 @@
|
|
1
|
-
require 'commute/core/
|
1
|
+
require 'commute/core/layer'
|
2
2
|
|
3
3
|
module Commute
|
4
4
|
|
5
|
-
# Internal: A Sequence is an ordered list of
|
6
|
-
#
|
5
|
+
# Internal: A Sequence is an ordered list of Layers.
|
6
|
+
# It is a thin layer around an Array that enables it to
|
7
|
+
# add/remove/replace Layers more easily.
|
7
8
|
#
|
8
|
-
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# Sequence.new do
|
12
|
+
# append Commute::Common::Auth::Basic, as: :auth
|
13
|
+
# append Commute::Common::Json::Render, before: :auth
|
14
|
+
# replace :auth, Commute::Common::Auth::OAuth
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
class Sequence < Array
|
9
18
|
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
19
|
+
# Internal: Name of this Sequence.
|
20
|
+
attr_reader :name
|
21
|
+
|
22
|
+
# Public: Initializes a sequence without layers.
|
23
|
+
# Alters the Sequence if a block is given.
|
24
|
+
def initialize name, &alter
|
25
|
+
super()
|
26
|
+
@name = name
|
14
27
|
self.alter &alter if block_given?
|
15
28
|
end
|
16
29
|
|
@@ -30,151 +43,89 @@ module Commute
|
|
30
43
|
return self
|
31
44
|
end
|
32
45
|
|
33
|
-
#
|
34
|
-
|
35
|
-
@processors.clone
|
36
|
-
end
|
46
|
+
# Alias Array#insert.
|
47
|
+
alias :index_insert :insert
|
37
48
|
|
38
|
-
#
|
49
|
+
# Public: Appends a layer to a the list of layers.
|
39
50
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
51
|
+
# options - Optional parameters for layer naming and placement (default: {}):
|
52
|
+
# :as - The name of the layer in the sequence (optional).
|
53
|
+
# :after - A reference layer id after which the processor needs to be appended.
|
43
54
|
#
|
44
|
-
# Returns
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
#
|
51
|
-
|
52
|
-
#
|
53
|
-
|
54
|
-
# Identify the processor.
|
55
|
-
# TODO: not very performant
|
56
|
-
processor_id = @index.key processor
|
57
|
-
# Immediately jumpt to the next processor if this one is disabled.
|
58
|
-
if commuter.enabled? processor_id
|
59
|
-
# Get the parameters.
|
60
|
-
parameters = commuter.parameters(processor_id) if processor_id
|
61
|
-
# Calling the processor.
|
62
|
-
if parameters
|
63
|
-
processor.call commuter, parameters
|
64
|
-
else
|
65
|
-
processor.call commuter
|
66
|
-
end
|
67
|
-
end
|
68
|
-
# Call self for the next processor.
|
69
|
-
call_next = Proc.new { call_one index + 1, commuter }
|
70
|
-
if commuter.delayed?
|
71
|
-
commuter.wait &call_next
|
72
|
-
else
|
73
|
-
call_next.call
|
74
|
-
end
|
55
|
+
# Returns the appended processor.
|
56
|
+
def append callable, options = {}
|
57
|
+
# Create a layer.
|
58
|
+
layer = Layer.new(callable, options[:as])
|
59
|
+
# Index to insert at.
|
60
|
+
index = (options[:after] && layer_index(options[:after])+1) || -1
|
61
|
+
# Append the layer.
|
62
|
+
index_insert index, layer
|
63
|
+
# Return the appended layer.
|
64
|
+
layer
|
75
65
|
end
|
76
66
|
|
77
|
-
# Public:
|
67
|
+
# Public: Inserts a layer to a the list of layers.
|
78
68
|
#
|
79
|
-
# options - Optional parameters for
|
80
|
-
# :as - The name of the
|
81
|
-
# :after - A reference
|
69
|
+
# options - Optional parameters for layer naming and placement (default: {}):
|
70
|
+
# :as - The name of the layer in the sequence (optional).
|
71
|
+
# :after - A reference layer id before which the processor needs to be inserted.
|
82
72
|
#
|
83
73
|
# Returns the appended processor.
|
84
|
-
def
|
85
|
-
#
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
#
|
92
|
-
|
93
|
-
# Add the processor to the index for direct access purposes.
|
94
|
-
@index[id] = processor if id
|
95
|
-
# Return the appended processor.
|
96
|
-
processor
|
97
|
-
end
|
98
|
-
|
99
|
-
# TODO
|
100
|
-
def insert processor, options = {}
|
101
|
-
# When a reference is given, insert before it.
|
102
|
-
if options[:before]
|
103
|
-
@processors.insert id_index(options[:before]) - 1, processor
|
104
|
-
else
|
105
|
-
@processors.unshift processor
|
106
|
-
end
|
107
|
-
# Identify the processor using different methods.
|
108
|
-
id = identify processor, options[:as]
|
109
|
-
# Add the processor to the index for direct access purposes.
|
110
|
-
@index[id] = processor if id
|
111
|
-
# Return the appended processor.
|
112
|
-
processor
|
74
|
+
def insert callable, options = {}
|
75
|
+
# Create a layer.
|
76
|
+
layer = Layer.new(callable, options[:as])
|
77
|
+
# Index to insert at.
|
78
|
+
index = (options[:before] && layer_index(options[:before])) || 0
|
79
|
+
# Append the layer.
|
80
|
+
index_insert index, layer
|
81
|
+
# Return the appended layer.
|
82
|
+
layer
|
113
83
|
end
|
114
84
|
|
115
|
-
#
|
116
|
-
def replace processor_id
|
117
|
-
processor = at processor_id
|
118
|
-
raise "processor does not exist #{processor_id}" unless processor
|
119
|
-
replacement = yield processor
|
120
|
-
@processors[@processors.index processor] = replacement
|
121
|
-
@index[processor_id] = replacement
|
122
|
-
end
|
123
|
-
|
124
|
-
# Public: Removes a processor from the sequence.
|
85
|
+
# Public: Removes a layer from the sequence.
|
125
86
|
#
|
126
|
-
#
|
127
|
-
# Symbol identifier is given, the associated processor is removed.
|
87
|
+
# name - The name of the layer to be removed.
|
128
88
|
#
|
129
|
-
# Returns the removed
|
130
|
-
def remove
|
131
|
-
|
132
|
-
|
133
|
-
processor = at(processor)
|
134
|
-
@processors.delete processor
|
135
|
-
@index.delete processor_id
|
136
|
-
else
|
137
|
-
# Remove from list of processors.
|
138
|
-
@processors.delete processor
|
139
|
-
# Remove from index (if necessary).
|
140
|
-
@index.delete_if { |id, pr| pr == processor}
|
141
|
-
end
|
142
|
-
# Return the deleted processor.
|
143
|
-
processor
|
89
|
+
# Returns the removed layer.
|
90
|
+
def remove name
|
91
|
+
# Remove the layer.
|
92
|
+
delete_at layer_index(name)
|
144
93
|
end
|
145
94
|
|
146
|
-
# Public
|
147
|
-
# Returns the processor with the given id.
|
95
|
+
# Public: Replaces a layer in the sequence.
|
148
96
|
#
|
149
|
-
#
|
150
|
-
|
151
|
-
|
97
|
+
# name - The name of the layer to be replaced.
|
98
|
+
# layer - The callable to replace it with.
|
99
|
+
#
|
100
|
+
# Returns the new layer.
|
101
|
+
def replace name, callable
|
102
|
+
self[layer_index name] = Layer.new callable, name
|
152
103
|
end
|
153
104
|
|
154
|
-
# Internal:
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
105
|
+
# Internal: Lazily adds the sequence (as a Layer)
|
106
|
+
# with this name to the router path.
|
107
|
+
#
|
108
|
+
# Lazily here means that when this sequence is added as a Layer
|
109
|
+
# it is not necessarely complete yet. Layers could be appended or
|
110
|
+
# removed later on. Therefor, the sequence is fetched from
|
111
|
+
# the current context.
|
112
|
+
#
|
113
|
+
def call router, request, options = {}
|
114
|
+
context = request.http.context
|
115
|
+
router << context.stack.sequence(name).reject { |layer|
|
116
|
+
!layer.callable?(context)
|
117
|
+
}.each
|
118
|
+
router.succ.call router, request
|
160
119
|
end
|
161
120
|
|
162
121
|
private
|
163
122
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
end
|
171
|
-
|
172
|
-
def id_index id
|
173
|
-
@processors.index at(id)
|
123
|
+
# Internal: Returns the index of a layer.
|
124
|
+
#
|
125
|
+
# name - The name of the layer.
|
126
|
+
#
|
127
|
+
def layer_index name
|
128
|
+
index { |layer| layer.name == name }
|
174
129
|
end
|
175
|
-
|
176
|
-
protected
|
177
|
-
|
178
|
-
attr_writer :processors, :index
|
179
130
|
end
|
180
131
|
end
|
data/lib/commute/core/stack.rb
CHANGED
@@ -1,76 +1,61 @@
|
|
1
1
|
require 'commute/core/sequence'
|
2
|
+
require 'commute/core/util/path'
|
2
3
|
|
3
4
|
module Commute
|
4
5
|
|
5
6
|
# Internal: A stack is a collection of sequences with
|
6
7
|
# A main sequence uses as a starting point for processing.
|
7
8
|
#
|
8
|
-
# When the stack is called with a
|
9
|
+
# When the stack is called with a request, it gets
|
9
10
|
# processed by the main sequence. That main sequence
|
10
11
|
# can easily "embed" other sequences by adding them as
|
11
|
-
# a
|
12
|
+
# a layer (a sequence is also a layer (composite)).
|
12
13
|
#
|
13
14
|
# Example:
|
14
15
|
#
|
15
|
-
# stack = Stack.new do
|
16
|
-
#
|
17
|
-
# append
|
18
|
-
# append
|
16
|
+
# stack = Stack.new do
|
17
|
+
# sequence(:request_body) do
|
18
|
+
# append Commute::Common::Json::Render
|
19
|
+
# append Commute::Common::Auth::Basic
|
19
20
|
# end
|
20
21
|
#
|
21
|
-
# sequence.append
|
22
|
-
# sequence.append Proc.new { |p| p.process { |n| "The result is #{n}" }}
|
22
|
+
# sequence.append sequence(:request_body)
|
23
23
|
# end
|
24
24
|
#
|
25
|
-
# stack.call
|
26
|
-
#
|
25
|
+
# request = stack.call Request.new(context) do |response, status|
|
26
|
+
# # Process the response.
|
27
|
+
# end
|
28
|
+
# request.write data
|
29
|
+
# request.end
|
27
30
|
#
|
28
31
|
class Stack
|
29
32
|
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# sequence - Sequence used as main sequence.
|
33
|
-
#
|
34
|
-
# Yields
|
35
|
-
# 1. The Stack itself. (If the arity of the block is 2)
|
36
|
-
# 2. a new sequence to use a main sequence when no
|
37
|
-
# sequence is explicitely given.
|
33
|
+
# Internal: A Router that routes http request to the next layer.
|
38
34
|
#
|
39
|
-
#
|
35
|
+
# The router wraps the Http Request in a Layer Request and
|
36
|
+
# attaches the correct handler to the response event.
|
40
37
|
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
38
|
+
class Router < Path
|
39
|
+
|
40
|
+
# Public: Call the next layer.
|
41
|
+
#
|
42
|
+
# http - Http Request to send to the next layer on the Path.
|
43
|
+
#
|
44
|
+
# Yields a response and a status.
|
45
|
+
# Returns the Layer Request.
|
46
|
+
def call http, &on_response
|
47
|
+
Layer::Request.new(http).tap do |request|
|
48
|
+
request.on :response, &on_response
|
49
|
+
succ.call self, request
|
53
50
|
end
|
54
51
|
end
|
55
52
|
end
|
56
53
|
|
57
|
-
# Public:
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# Returns the Sequence that was added.
|
63
|
-
def add sequence, name
|
64
|
-
@sequences[name] = sequence
|
65
|
-
end
|
66
|
-
|
67
|
-
# Public: Get a sequence from the stack.
|
68
|
-
#
|
69
|
-
# name - The name of the wanted Sequence.
|
70
|
-
#
|
71
|
-
# Returns the asked sequence.
|
72
|
-
def get name
|
73
|
-
@sequences[name]
|
54
|
+
# Public: Constructs a stack with a main sequence.
|
55
|
+
# Akter the Stack if a block is given.
|
56
|
+
def initialize &alter
|
57
|
+
@sequences = {}
|
58
|
+
self.alter &alter if block_given?
|
74
59
|
end
|
75
60
|
|
76
61
|
# Public: Manipulate an either existing or new sequence.
|
@@ -84,25 +69,10 @@ module Commute
|
|
84
69
|
#
|
85
70
|
# Returns the Sequence.
|
86
71
|
def sequence name = nil, &alter
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# Public: Creates a processor that lazily fetches a named sequence
|
95
|
-
# on a call.
|
96
|
-
#
|
97
|
-
# name - The name of the sequence this sequencer stands in for.
|
98
|
-
#
|
99
|
-
# Returns a Sequencer
|
100
|
-
def sequencer name = nil
|
101
|
-
if name
|
102
|
-
Sequencer.new.here(name)
|
103
|
-
else
|
104
|
-
Sequencer.new
|
105
|
-
end
|
72
|
+
name ||= :main
|
73
|
+
sequence = @sequences[name] || add(Sequence.new name)
|
74
|
+
sequence.alter &alter if block_given?
|
75
|
+
sequence
|
106
76
|
end
|
107
77
|
|
108
78
|
# Internal: Calls the stack aka call the main sequence.
|
@@ -110,8 +80,9 @@ module Commute
|
|
110
80
|
# processable - The thing to process.
|
111
81
|
#
|
112
82
|
# Returns the processable
|
113
|
-
def call
|
114
|
-
|
83
|
+
def call request, &on_response
|
84
|
+
router = Router.new([sequence].each)
|
85
|
+
router.call request, &on_response
|
115
86
|
end
|
116
87
|
|
117
88
|
# Public: Alters the stack.
|
@@ -124,19 +95,39 @@ module Commute
|
|
124
95
|
if alter.arity == 0
|
125
96
|
instance_eval &alter
|
126
97
|
else
|
127
|
-
yield self
|
98
|
+
yield self
|
128
99
|
end
|
129
100
|
# Return the Stack.
|
130
101
|
return self
|
131
102
|
end
|
132
103
|
|
133
|
-
# Internal:
|
104
|
+
# Internal: Duplicates the stack.
|
134
105
|
def dup
|
135
|
-
Stack.new
|
106
|
+
Stack.new.tap do |stack|
|
136
107
|
stack.sequences = Hash[@sequences.map { |k,v| [k, v.dup] }]
|
137
108
|
end
|
138
109
|
end
|
139
110
|
|
111
|
+
private
|
112
|
+
|
113
|
+
# Internal: Adds a sequence to the stack.
|
114
|
+
#
|
115
|
+
# sequence - The Sequence to be added.
|
116
|
+
#
|
117
|
+
# Returns the Sequence that was added.
|
118
|
+
def add sequence
|
119
|
+
@sequences[sequence.name] = sequence
|
120
|
+
end
|
121
|
+
|
122
|
+
# Internal: Get a sequence from the stack.
|
123
|
+
#
|
124
|
+
# name - The name of the wanted Sequence.
|
125
|
+
#
|
126
|
+
# Returns the asked sequence.
|
127
|
+
def get name
|
128
|
+
@sequences[name]
|
129
|
+
end
|
130
|
+
|
140
131
|
protected
|
141
132
|
|
142
133
|
attr_writer :sequences
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Commute
|
2
|
+
|
3
|
+
# Public: A Generic status object that can represent
|
4
|
+
# either a positive or negative status.
|
5
|
+
#
|
6
|
+
# When the status is negative, an additional error
|
7
|
+
# detail can be provided.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# Status.new false, { reason: 'No access' }
|
12
|
+
# Status.ok # Same as Status.new true
|
13
|
+
#
|
14
|
+
class Status
|
15
|
+
|
16
|
+
# Public: Creates a positive status.
|
17
|
+
#
|
18
|
+
def self.ok
|
19
|
+
self.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Public: Creates a new status object.
|
23
|
+
#
|
24
|
+
# success - Whether the status is positive or not (default: true).
|
25
|
+
# error - Optional error detail (can be anything).
|
26
|
+
#
|
27
|
+
def initialize success = true, error = nil
|
28
|
+
@success = success
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Check if a status is a success.
|
32
|
+
#
|
33
|
+
# Returns true if the status is positive.
|
34
|
+
def success?
|
35
|
+
@success
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Check if a status is a fail.
|
39
|
+
#
|
40
|
+
# Returns true if the status is negative.
|
41
|
+
def fail?
|
42
|
+
!success?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Commute
|
2
|
+
|
3
|
+
# Internal: A Simple EventEmitter modelled after node.js' EventEmitter.
|
4
|
+
# More info: http://nodejs.org/api/events.html
|
5
|
+
#
|
6
|
+
# Include this module in any model that wants to send events to its listeners.
|
7
|
+
#
|
8
|
+
# Listen to events using `on(:event)`. In the model, notify listeners
|
9
|
+
# by calling `emit(:event, ...)` with some arguments.
|
10
|
+
module EventEmitter
|
11
|
+
|
12
|
+
# Internal: Initializes an empty handler hash.
|
13
|
+
def initialize *args
|
14
|
+
super *args
|
15
|
+
@handlers = Hash.new { |h, k| h[k] = [] }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Internal: Subscribe a handler to an event.
|
19
|
+
#
|
20
|
+
# event - The name of the event to listen for.
|
21
|
+
# handler - The proc to call when this event occurs.
|
22
|
+
#
|
23
|
+
# Returns Nothing.
|
24
|
+
def on event, &handler
|
25
|
+
@handlers[event] << handler
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# Internal: Subscribe a handler to an event and
|
30
|
+
# unsuscribe it when the event fired once.
|
31
|
+
#
|
32
|
+
def once event, &handler
|
33
|
+
on event do |*args, &block|
|
34
|
+
handler.call *args, &block
|
35
|
+
remove event, handler
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Internal: Remove an event handler.
|
40
|
+
def remove event, handler
|
41
|
+
@handlers[event].delete handler
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
# Internal: Emit an event to all handlers.
|
47
|
+
#
|
48
|
+
# event - The name of the event.
|
49
|
+
# args - Arguments to send to the listeners.
|
50
|
+
#
|
51
|
+
# Returns the notified handlers.
|
52
|
+
def emit event, *args
|
53
|
+
@handlers[event].each do |handler|
|
54
|
+
handler.call *args
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Commute
|
2
|
+
|
3
|
+
# Internal: A Path is that wraps some enums and
|
4
|
+
# returns there values in the order that you added them.
|
5
|
+
#
|
6
|
+
class Path
|
7
|
+
|
8
|
+
# Internal: Initialize the path with a main route.
|
9
|
+
#
|
10
|
+
# main - An enumerable to start the path with.
|
11
|
+
#
|
12
|
+
def initialize main
|
13
|
+
@path = []
|
14
|
+
self << main
|
15
|
+
end
|
16
|
+
|
17
|
+
# Internal: Push a route on the path (detour).
|
18
|
+
#
|
19
|
+
# route - An enumerable to walk first before continuing.
|
20
|
+
def << route
|
21
|
+
@path << route
|
22
|
+
end
|
23
|
+
|
24
|
+
# Internal: Figures out the successor in the path.
|
25
|
+
#
|
26
|
+
# Returns the next stop on the path.
|
27
|
+
def succ
|
28
|
+
begin
|
29
|
+
return nil if @path.empty?
|
30
|
+
@path.last.next
|
31
|
+
rescue StopIteration
|
32
|
+
@path.pop
|
33
|
+
retry
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|