usher 0.5.4 → 0.5.5
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/README.rdoc +6 -6
- data/VERSION.yml +2 -2
- data/lib/usher.rb +45 -37
- data/lib/usher/interface.rb +3 -0
- data/lib/usher/interface/rack_interface.rb +48 -29
- data/lib/usher/interface/text_interface.rb +44 -0
- data/lib/usher/node.rb +12 -12
- data/lib/usher/route.rb +16 -14
- data/lib/usher/util/generate.rb +120 -79
- data/lib/usher/util/rack-mixins.rb +24 -0
- data/spec/private/generate_spec.rb +78 -25
- data/spec/private/rack/dispatch_spec.rb +52 -26
- data/spec/private/recognize_spec.rb +12 -4
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Usher
|
1
|
+
= Usher
|
2
2
|
|
3
3
|
Tree-based router library. Useful for (specifically) for Rails and Rack, but probably generally useful for anyone interested in doing routing. Based on Ilya Grigorik suggestion, turns out looking up in a hash and following a tree is faster than Krauter's massive regex approach.
|
4
4
|
|
@@ -19,13 +19,13 @@ Tree-based router library. Useful for (specifically) for Rails and Rack, but pro
|
|
19
19
|
From the rdoc:
|
20
20
|
|
21
21
|
Creates a route from +path+ and +options+
|
22
|
-
|
22
|
+
|
23
23
|
=== +path+
|
24
24
|
A path consists a mix of dynamic and static parts delimited by <tt>/</tt>
|
25
25
|
|
26
26
|
==== Dynamic
|
27
27
|
Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or
|
28
|
-
more parts.
|
28
|
+
more parts.
|
29
29
|
|
30
30
|
<b>Example:</b>
|
31
31
|
<tt>/path/:variable/path</tt> would match
|
@@ -35,7 +35,7 @@ more parts.
|
|
35
35
|
* <tt>/path/one_more/path</tt>
|
36
36
|
|
37
37
|
In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key <tt>:variable</tt>.
|
38
|
-
However, <tt>/path/test/one_more/path</tt> would not be matched.
|
38
|
+
However, <tt>/path/test/one_more/path</tt> would not be matched.
|
39
39
|
|
40
40
|
<b>Example:</b>
|
41
41
|
<tt>/path/*variable/path</tt> would match
|
@@ -59,7 +59,7 @@ But not
|
|
59
59
|
As well, the same logic applies for * variables as well, where only parts matchable by the supplied regex will
|
60
60
|
actually be bound to the variable
|
61
61
|
|
62
|
-
Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
|
62
|
+
Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
|
63
63
|
regex allows.
|
64
64
|
|
65
65
|
<b>Example:</b>
|
@@ -110,7 +110,7 @@ For instance, the path, <tt>/path/something(.xml|.html)</tt> would only match <t
|
|
110
110
|
[body] # Response body
|
111
111
|
]
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
routes = Usher::Interface.for(:rack) do
|
115
115
|
add('/hello/:name').to(app)
|
116
116
|
end
|
data/VERSION.yml
CHANGED
data/lib/usher.rb
CHANGED
@@ -7,12 +7,12 @@ require File.join(File.dirname(__FILE__), 'usher', 'exceptions')
|
|
7
7
|
require File.join(File.dirname(__FILE__), 'usher', 'util')
|
8
8
|
|
9
9
|
class Usher
|
10
|
-
attr_reader :root, :named_routes, :routes, :splitter,
|
11
|
-
:delimiters, :delimiter_chars, :delimiters_regex,
|
10
|
+
attr_reader :root, :named_routes, :routes, :splitter,
|
11
|
+
:delimiters, :delimiter_chars, :delimiters_regex,
|
12
12
|
:parent_route, :generator
|
13
|
-
|
13
|
+
|
14
14
|
# Returns whether the route set is empty
|
15
|
-
#
|
15
|
+
#
|
16
16
|
# set = Usher.new
|
17
17
|
# set.empty? => true
|
18
18
|
# set.add_route('/test')
|
@@ -20,13 +20,13 @@ class Usher
|
|
20
20
|
def empty?
|
21
21
|
@routes.empty?
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def route_count
|
25
25
|
@routes.size
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
# Resets the route set back to its initial state
|
29
|
-
#
|
29
|
+
#
|
30
30
|
# set = Usher.new
|
31
31
|
# set.add_route('/test')
|
32
32
|
# set.empty? => false
|
@@ -39,14 +39,14 @@ class Usher
|
|
39
39
|
@grapher = Grapher.new
|
40
40
|
end
|
41
41
|
alias clear! reset!
|
42
|
-
|
42
|
+
|
43
43
|
# Creates a route set, with options
|
44
|
-
#
|
44
|
+
#
|
45
45
|
# <tt>:delimiters</tt>: Array of Strings. (default <tt>['/', '.']</tt>). Delimiters used in path separation. Array must be single character strings.
|
46
|
-
#
|
46
|
+
#
|
47
47
|
# <tt>:valid_regex</tt>: String. (default <tt>'[0-9A-Za-z\$\-_\+!\*\',]+'</tt>). String that can be interpolated into regex to match
|
48
48
|
# valid character sequences within path.
|
49
|
-
#
|
49
|
+
#
|
50
50
|
# <tt>:request_methods</tt>: Array of Symbols. (default <tt>[:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]</tt>)
|
51
51
|
# Array of methods called against the request object for the purposes of matching route requirements.
|
52
52
|
def initialize(options = nil)
|
@@ -64,13 +64,13 @@ class Usher
|
|
64
64
|
def can_generate?
|
65
65
|
!@generator.nil?
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
def generator
|
69
69
|
@generator
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
# Adds a route referencable by +name+. See add_route for format +path+ and +options+.
|
73
|
-
#
|
73
|
+
#
|
74
74
|
# set = Usher.new
|
75
75
|
# set.add_named_route(:test_route, '/test')
|
76
76
|
def add_named_route(name, path, options = nil)
|
@@ -78,7 +78,7 @@ class Usher
|
|
78
78
|
end
|
79
79
|
|
80
80
|
# Deletes a route referencable by +name+. At least the path and conditions have to match the route you intend to delete.
|
81
|
-
#
|
81
|
+
#
|
82
82
|
# set = Usher.new
|
83
83
|
# set.delete_named_route(:test_route, '/test')
|
84
84
|
def delete_named_route(name, path, options = nil)
|
@@ -87,7 +87,7 @@ class Usher
|
|
87
87
|
end
|
88
88
|
|
89
89
|
# Attaches a +route+ to a +name+
|
90
|
-
#
|
90
|
+
#
|
91
91
|
# set = Usher.new
|
92
92
|
# route = set.add_route('/test')
|
93
93
|
# set.name(:test, route)
|
@@ -97,13 +97,13 @@ class Usher
|
|
97
97
|
end
|
98
98
|
|
99
99
|
# Creates a route from +path+ and +options+
|
100
|
-
#
|
100
|
+
#
|
101
101
|
# === +path+
|
102
102
|
# A path consists a mix of dynamic and static parts delimited by <tt>/</tt>
|
103
103
|
#
|
104
104
|
# ==== Dynamic
|
105
105
|
# Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or
|
106
|
-
# more parts.
|
106
|
+
# more parts.
|
107
107
|
#
|
108
108
|
# <b>Example:</b>
|
109
109
|
# <tt>/path/:variable/path</tt> would match
|
@@ -113,7 +113,7 @@ class Usher
|
|
113
113
|
# * <tt>/path/one_more/path</tt>
|
114
114
|
#
|
115
115
|
# In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key <tt>:variable</tt>.
|
116
|
-
# However, <tt>/path/test/one_more/path</tt> would not be matched.
|
116
|
+
# However, <tt>/path/test/one_more/path</tt> would not be matched.
|
117
117
|
#
|
118
118
|
# <b>Example:</b>
|
119
119
|
# <tt>/path/*variable/path</tt> would match
|
@@ -127,22 +127,22 @@ class Usher
|
|
127
127
|
#
|
128
128
|
# <b>Example:</b>
|
129
129
|
# <tt>/product/{:id,\d+}</tt> would match
|
130
|
-
#
|
130
|
+
#
|
131
131
|
# * <tt>/product/123</tt>
|
132
132
|
# * <tt>/product/4521</tt>
|
133
|
-
#
|
133
|
+
#
|
134
134
|
# But not
|
135
135
|
# * <tt>/product/AE-35</tt>
|
136
|
-
#
|
136
|
+
#
|
137
137
|
# As well, the same logic applies for * variables as well, where only parts matchable by the supplied regex will
|
138
138
|
# actually be bound to the variable
|
139
139
|
#
|
140
|
-
# Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
|
140
|
+
# Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
|
141
141
|
# regex allows.
|
142
142
|
#
|
143
143
|
# <b>Example:</b>
|
144
144
|
# <tt>/product/{!id,hello/world|hello}</tt> would match
|
145
|
-
#
|
145
|
+
#
|
146
146
|
# * <tt>/product/hello/world</tt>
|
147
147
|
# * <tt>/product/hello</tt>
|
148
148
|
#
|
@@ -176,7 +176,7 @@ class Usher
|
|
176
176
|
end
|
177
177
|
|
178
178
|
# Deletes a route. At least the path and conditions have to match the route you intend to delete.
|
179
|
-
#
|
179
|
+
#
|
180
180
|
# set = Usher.new
|
181
181
|
# set.delete_route('/test')
|
182
182
|
def delete_route(path, options = nil)
|
@@ -188,7 +188,7 @@ class Usher
|
|
188
188
|
end
|
189
189
|
|
190
190
|
# Recognizes a +request+ and returns +nil+ or an Usher::Node::Response, which is a struct containing a Usher::Route::Path and an array of arrays containing the extracted parameters.
|
191
|
-
#
|
191
|
+
#
|
192
192
|
# Request = Struct.new(:path)
|
193
193
|
# set = Usher.new
|
194
194
|
# route = set.add_route('/test')
|
@@ -198,7 +198,7 @@ class Usher
|
|
198
198
|
end
|
199
199
|
|
200
200
|
# Recognizes a +path+ and returns +nil+ or an Usher::Node::Response, which is a struct containing a Usher::Route::Path and an array of arrays containing the extracted parameters. Convenience method for when recognizing on the request object is unneeded.
|
201
|
-
#
|
201
|
+
#
|
202
202
|
# Request = Struct.new(:path)
|
203
203
|
# set = Usher.new
|
204
204
|
# route = set.add_route('/test')
|
@@ -208,35 +208,43 @@ class Usher
|
|
208
208
|
end
|
209
209
|
|
210
210
|
# Recognizes a set of +parameters+ and gets the closest matching Usher::Route::Path or +nil+ if no route exists.
|
211
|
-
#
|
211
|
+
#
|
212
212
|
# set = Usher.new
|
213
213
|
# route = set.add_route('/:controller/:action')
|
214
214
|
# set.path_for_options({:controller => 'test', :action => 'action'}) == path.route => true
|
215
215
|
def path_for_options(options)
|
216
216
|
@grapher.find_matching_path(options)
|
217
217
|
end
|
218
|
-
|
218
|
+
|
219
219
|
def parent_route=(route)
|
220
220
|
@parent_route = route
|
221
221
|
routes.each{|r| r.parent_route = route}
|
222
222
|
end
|
223
|
-
|
223
|
+
|
224
224
|
def dup
|
225
225
|
replacement = super
|
226
226
|
original = self
|
227
|
+
inverted_named_routes = original.named_routes.invert
|
227
228
|
replacement.instance_eval do
|
229
|
+
@parser = nil
|
228
230
|
reset!
|
229
231
|
original.routes.each do |route|
|
230
|
-
|
231
|
-
|
232
|
+
new_route = route.dup
|
233
|
+
new_route.router = self
|
234
|
+
@root.add(new_route)
|
235
|
+
@routes << new_route
|
236
|
+
if name = inverted_named_routes[route]
|
237
|
+
@named_routes[name] = new_route
|
238
|
+
end
|
232
239
|
end
|
240
|
+
send(:generator=, original.generator.class.new) if original.can_generate?
|
233
241
|
rebuild_grapher!
|
234
242
|
end
|
235
243
|
replacement
|
236
244
|
end
|
237
|
-
|
245
|
+
|
238
246
|
private
|
239
|
-
|
247
|
+
|
240
248
|
attr_accessor :request_methods
|
241
249
|
attr_reader :valid_regex
|
242
250
|
|
@@ -247,7 +255,7 @@ class Usher
|
|
247
255
|
end
|
248
256
|
@generator
|
249
257
|
end
|
250
|
-
|
258
|
+
|
251
259
|
def delimiters=(delimiters)
|
252
260
|
@delimiters = delimiters
|
253
261
|
@delimiter_chars = @delimiters.collect{|d| d[0]}
|
@@ -274,12 +282,12 @@ class Usher
|
|
274
282
|
end
|
275
283
|
end
|
276
284
|
end
|
277
|
-
|
285
|
+
|
278
286
|
route = parser.generate_route(path, conditions, requirements, default_values, generate_with)
|
279
287
|
route.to(options) if options && !options.empty?
|
280
288
|
route
|
281
289
|
end
|
282
|
-
|
290
|
+
|
283
291
|
def rebuild_grapher!
|
284
292
|
@grapher = Grapher.new
|
285
293
|
@routes.each{|r| @grapher.add_route(r)}
|
data/lib/usher/interface.rb
CHANGED
@@ -6,6 +6,7 @@ class Usher
|
|
6
6
|
autoload :RackInterface, File.join(File.dirname(__FILE__), 'interface', 'rack_interface')
|
7
7
|
autoload :EmailInterface, File.join(File.dirname(__FILE__), 'interface', 'email_interface')
|
8
8
|
autoload :Rails3Interface, File.join(File.dirname(__FILE__), 'interface', 'rails3_interface')
|
9
|
+
autoload :TextInterface, File.join(File.dirname(__FILE__), 'interface', 'text_interface')
|
9
10
|
|
10
11
|
def self.for(type, &blk)
|
11
12
|
class_for(type).new(&blk)
|
@@ -25,6 +26,8 @@ class Usher
|
|
25
26
|
EmailInterface
|
26
27
|
when :rails3
|
27
28
|
Rails3Interface
|
29
|
+
when :text
|
30
|
+
TextInterface
|
28
31
|
end
|
29
32
|
|
30
33
|
end
|
@@ -3,28 +3,34 @@ require 'rack'
|
|
3
3
|
class Usher
|
4
4
|
module Interface
|
5
5
|
class RackInterface
|
6
|
-
|
6
|
+
|
7
7
|
attr_reader :router
|
8
|
-
|
8
|
+
attr_accessor :app
|
9
|
+
|
10
|
+
DEFAULT_APPLICATION = lambda do |env|
|
11
|
+
Rack::Response.new("No route found", 404).finish
|
12
|
+
end
|
13
|
+
|
9
14
|
class Builder < Rack::Builder
|
10
|
-
|
15
|
+
|
11
16
|
def initialize(&block)
|
12
17
|
@usher = Usher::Interface::RackInterface.new
|
13
18
|
super
|
14
19
|
end
|
15
|
-
|
20
|
+
|
16
21
|
def map(path, options = nil, &block)
|
17
22
|
@usher.add(path, options).to(&block)
|
18
23
|
@ins << @usher unless @ins.last == @usher
|
19
24
|
end
|
20
|
-
|
25
|
+
|
21
26
|
end
|
22
|
-
|
23
|
-
def initialize(&blk)
|
27
|
+
|
28
|
+
def initialize(app = nil, &blk)
|
29
|
+
@app = app || DEFAULT_APPLICATION
|
24
30
|
@router = Usher.new(:request_methods => [:request_method, :host, :port, :scheme], :generator => Usher::Util::Generators::URL.new)
|
25
31
|
instance_eval(&blk) if blk
|
26
32
|
end
|
27
|
-
|
33
|
+
|
28
34
|
def dup
|
29
35
|
new_one = super
|
30
36
|
original = self
|
@@ -33,15 +39,15 @@ class Usher
|
|
33
39
|
end
|
34
40
|
new_one
|
35
41
|
end
|
36
|
-
|
42
|
+
|
37
43
|
def add(path, options = nil)
|
38
44
|
@router.add_route(path, options)
|
39
|
-
|
40
|
-
|
45
|
+
end
|
46
|
+
|
41
47
|
def parent_route=(route)
|
42
48
|
@router.parent_route = route
|
43
49
|
end
|
44
|
-
|
50
|
+
|
45
51
|
def parent_route
|
46
52
|
@router.parent_route
|
47
53
|
end
|
@@ -51,30 +57,43 @@ class Usher
|
|
51
57
|
end
|
52
58
|
|
53
59
|
def call(env)
|
54
|
-
env['usher.params'] ||= {}
|
55
60
|
response = @router.recognize(request = Rack::Request.new(env), request.path_info)
|
56
|
-
if response
|
57
|
-
|
58
|
-
headers = {"Content-Type" => "text/plain", "Content-Length" => body.length.to_s}
|
59
|
-
[404, headers, [body]]
|
60
|
-
else
|
61
|
-
params = response.path.route.default_values || {}
|
62
|
-
response.params.each{ |hk| params[hk.first] = hk.last}
|
63
|
-
|
64
|
-
# consume the path_info to the script_name response.remaining_path
|
65
|
-
env["SCRIPT_NAME"] << response.matched_path || ""
|
66
|
-
env["PATH_INFO"] = response.remaining_path || ""
|
67
|
-
|
68
|
-
env['usher.params'].merge!(params)
|
69
|
-
|
70
|
-
response.path.route.destination.call(env)
|
71
|
-
end
|
61
|
+
after_match(env, response) if response
|
62
|
+
determine_respondant(response).call(env)
|
72
63
|
end
|
73
64
|
|
74
65
|
def generate(route, params = nil, options = nil)
|
75
66
|
@usher.generator.generate(route, params, options)
|
76
67
|
end
|
77
68
|
|
69
|
+
# Allows a hook to be placed for sub classes to make use of between matching
|
70
|
+
# and calling the application
|
71
|
+
#
|
72
|
+
# @api plugin
|
73
|
+
def after_match(env, response)
|
74
|
+
env['usher.params'] ||= {}
|
75
|
+
params = response.path.route.default_values || {}
|
76
|
+
response.params.each{|hk| params[hk.first] = hk.last}
|
77
|
+
env['usher.params'].merge!(params)
|
78
|
+
|
79
|
+
# consume the path_info to the script_name response.remaining_path
|
80
|
+
env["SCRIPT_NAME"] << response.matched_path || ""
|
81
|
+
env["PATH_INFO"] = response.remaining_path || ""
|
82
|
+
end
|
83
|
+
|
84
|
+
# Determines which application to respond with.
|
85
|
+
#
|
86
|
+
# Within the request when determine respondant is called
|
87
|
+
# If there is a matching route to an application, that
|
88
|
+
# application is called, Otherwise the middleware application is called.
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
def determine_respondant(response)
|
92
|
+
return app if response.nil?
|
93
|
+
respondant = response.path.route.destination
|
94
|
+
respondant = app unless respondant.respond_to?(:call)
|
95
|
+
respondant
|
96
|
+
end
|
78
97
|
end
|
79
98
|
end
|
80
99
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Usher
|
2
|
+
module Interface
|
3
|
+
class TextInterface
|
4
|
+
|
5
|
+
def initialize(&blk)
|
6
|
+
@usher = Usher.new(:delimiters => [' '], :generator => Usher::Util::Generators::Generic.new)
|
7
|
+
instance_eval(&blk) if blk
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate(name, params = nil)
|
11
|
+
@usher.generator.generate(name, params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def on(text, name = nil, &blk)
|
15
|
+
r = @usher.add_route(text).to(:block => blk, :arg_type => :array)
|
16
|
+
r.name(name) if name
|
17
|
+
end
|
18
|
+
|
19
|
+
def unrecognized(&blk)
|
20
|
+
@unrecognize_block = blk
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_with_hash(text, name = nil, &blk)
|
24
|
+
r = @usher.add_route(text).to(:block => blk, :arg_type => :hash)
|
25
|
+
r.name(name) if name
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(text)
|
29
|
+
response = @usher.recognize_path(text.strip)
|
30
|
+
if response
|
31
|
+
case response.path.route.destination[:arg_type]
|
32
|
+
when :hash
|
33
|
+
response.path.route.destination[:block].call(response.params.inject({}){|h,(k,v)| h[k]=v; h })
|
34
|
+
when :array
|
35
|
+
response.path.route.destination[:block].call(*response.params.collect{|p| p.last})
|
36
|
+
end
|
37
|
+
else
|
38
|
+
@unrecognize_block ? @unrecognize_block.call(text) : nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/usher/node.rb
CHANGED
@@ -108,15 +108,7 @@ class Usher
|
|
108
108
|
end
|
109
109
|
|
110
110
|
def find(usher, request_object, original_path, path, params = [], position = 0)
|
111
|
-
if
|
112
|
-
if (specific_node = request[request_object.send(request_method_type)]) && (ret = specific_node.find(usher, request_object, original_path, path.dup, params.dup, position))
|
113
|
-
ret
|
114
|
-
elsif (general_node = request[nil]) && (ret = general_node.find(usher, request_object, original_path, path.dup, params.dup, position))
|
115
|
-
ret
|
116
|
-
else
|
117
|
-
nil
|
118
|
-
end
|
119
|
-
elsif terminates? && (path.empty? || terminates.route.partial_match?)
|
111
|
+
if terminates? && (path.empty? || terminates.route.partial_match?)
|
120
112
|
terminates.route.partial_match? ?
|
121
113
|
Response.new(terminates, params, original_path[position, original_path.size], original_path[0, position]) :
|
122
114
|
Response.new(terminates, params, nil, original_path)
|
@@ -130,7 +122,7 @@ class Usher
|
|
130
122
|
case next_part.value
|
131
123
|
when Route::Variable::Glob
|
132
124
|
params << [next_part.value.name, []] unless params.last && params.last.first == next_part.value.name
|
133
|
-
|
125
|
+
while true
|
134
126
|
if (next_part.value.look_ahead === part || (!usher.delimiter_chars.include?(part[0]) && next_part.value.regex_matcher && !next_part.value.regex_matcher.match(part)))
|
135
127
|
path.unshift(part)
|
136
128
|
position -= part.size
|
@@ -153,13 +145,21 @@ class Usher
|
|
153
145
|
var = next_part.value
|
154
146
|
var.valid!(part)
|
155
147
|
params << [var.name, part]
|
156
|
-
until (var.look_ahead === path.first)
|
148
|
+
until path.empty? || (var.look_ahead === path.first)
|
157
149
|
next_path_part = path.shift
|
158
150
|
position += next_path_part.size
|
159
151
|
params.last.last << next_path_part
|
160
|
-
end
|
152
|
+
end if var.look_ahead && usher.delimiter_chars.size > 1
|
161
153
|
end
|
162
154
|
next_part.find(usher, request_object, original_path, path, params, position)
|
155
|
+
elsif request_method_type
|
156
|
+
if (specific_node = request[request_object.send(request_method_type)]) && (ret = specific_node.find(usher, request_object, original_path, path.dup, params.dup, position))
|
157
|
+
ret
|
158
|
+
elsif (general_node = request[nil]) && (ret = general_node.find(usher, request_object, original_path, path.dup, params.dup, position))
|
159
|
+
ret
|
160
|
+
else
|
161
|
+
nil
|
162
|
+
end
|
163
163
|
else
|
164
164
|
nil
|
165
165
|
end
|
data/lib/usher/route.rb
CHANGED
@@ -8,16 +8,16 @@ class Usher
|
|
8
8
|
attr_reader :paths, :requirements, :conditions,
|
9
9
|
:destination, :named, :generate_with,
|
10
10
|
:default_values, :match_partially
|
11
|
-
attr_accessor :parent_route
|
12
|
-
|
11
|
+
attr_accessor :parent_route, :router
|
12
|
+
|
13
13
|
GenerateWith = Struct.new(:scheme, :port, :host)
|
14
|
-
|
15
|
-
def initialize(parsed_paths, router, conditions, requirements, default_values, generate_with, match_partially)
|
14
|
+
|
15
|
+
def initialize(parsed_paths, router, conditions, requirements, default_values, generate_with, match_partially)
|
16
16
|
@paths = parsed_paths.collect {|path| Path.new(self, path)}
|
17
17
|
@router, @requirements, @conditions, @default_values, @match_partially = router, requirements, conditions, default_values, match_partially
|
18
18
|
@generate_with = GenerateWith.new(generate_with[:scheme], generate_with[:port], generate_with[:host]) if generate_with
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def grapher
|
22
22
|
unless @grapher
|
23
23
|
@grapher = Grapher.new
|
@@ -28,7 +28,9 @@ class Usher
|
|
28
28
|
|
29
29
|
def dup
|
30
30
|
result = super
|
31
|
-
result.
|
31
|
+
result.instance_eval do
|
32
|
+
@grapher = nil
|
33
|
+
end
|
32
34
|
result
|
33
35
|
end
|
34
36
|
|
@@ -38,17 +40,17 @@ class Usher
|
|
38
40
|
else
|
39
41
|
matching_path = @paths.size == 1 ? @paths.first : grapher.find_matching_path(params)
|
40
42
|
end
|
41
|
-
|
43
|
+
|
42
44
|
if parent_route
|
43
45
|
matching_path = parent_route.find_matching_path(params).merge(matching_path)
|
44
46
|
matching_path.route = self
|
45
47
|
end
|
46
|
-
|
48
|
+
|
47
49
|
matching_path
|
48
50
|
end
|
49
|
-
|
51
|
+
|
50
52
|
# Sets +options+ on a route. Returns +self+.
|
51
|
-
#
|
53
|
+
#
|
52
54
|
# Request = Struct.new(:path)
|
53
55
|
# set = Usher.new
|
54
56
|
# route = set.add_route('/test')
|
@@ -66,7 +68,7 @@ class Usher
|
|
66
68
|
end
|
67
69
|
|
68
70
|
# Sets route as referenceable from +name+. Returns +self+.
|
69
|
-
#
|
71
|
+
#
|
70
72
|
# set = Usher.new
|
71
73
|
# route = set.add_route('/test').name(:route)
|
72
74
|
# set.generate_url(:route) => '/test'
|
@@ -75,16 +77,16 @@ class Usher
|
|
75
77
|
@router.name(name, self)
|
76
78
|
self
|
77
79
|
end
|
78
|
-
|
80
|
+
|
79
81
|
def match_partially!
|
80
82
|
@match_partially = true
|
81
83
|
self
|
82
84
|
end
|
83
|
-
|
85
|
+
|
84
86
|
def partial_match?
|
85
87
|
@match_partially
|
86
88
|
end
|
87
|
-
|
89
|
+
|
88
90
|
private
|
89
91
|
attr_writer :grapher
|
90
92
|
|
data/lib/usher/util/generate.rb
CHANGED
@@ -1,35 +1,46 @@
|
|
1
|
-
|
1
|
+
class Usher
|
2
|
+
module Util
|
3
|
+
class Generators
|
2
4
|
|
3
|
-
|
4
|
-
module Rack
|
5
|
+
class Generic
|
5
6
|
|
6
|
-
|
7
|
+
attr_accessor :usher
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
def generate(name, params)
|
10
|
+
generate_path_for_base_params(@usher.named_routes[name].find_matching_path(params), params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_path_for_base_params(path, params)
|
14
|
+
raise UnrecognizedException.new unless path
|
15
|
+
|
16
|
+
result = ''
|
17
|
+
path.parts.each do |part|
|
18
|
+
case part
|
19
|
+
when Route::Variable::Glob
|
20
|
+
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
21
|
+
value.each_with_index do |current_value, index|
|
22
|
+
part.valid!(current_value)
|
23
|
+
result << current_value.to_s
|
24
|
+
result << usher.delimiters.first if index != value.size - 1
|
25
|
+
end
|
26
|
+
when Route::Variable
|
27
|
+
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
28
|
+
part.valid!(value)
|
29
|
+
result << value.to_s
|
30
|
+
else
|
31
|
+
result << part
|
32
|
+
end
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
14
36
|
|
15
|
-
def uri_unescape(s)
|
16
|
-
gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
17
|
-
[$1.delete('%')].pack('H*')
|
18
|
-
}
|
19
37
|
end
|
20
|
-
module_function :uri_unescape
|
21
38
|
|
22
|
-
|
23
|
-
end
|
24
|
-
end
|
39
|
+
class URL < Generic
|
25
40
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
class URL
|
31
|
-
|
32
|
-
attr_accessor :usher
|
41
|
+
def initialize
|
42
|
+
require File.join(File.dirname(__FILE__), 'rack-mixins')
|
43
|
+
end
|
33
44
|
|
34
45
|
def generate_full(routing_lookup, request, params = nil)
|
35
46
|
path = path_for_routing_lookup(routing_lookup, params)
|
@@ -37,10 +48,75 @@ class Usher
|
|
37
48
|
result << generate_path(path, params)
|
38
49
|
end
|
39
50
|
|
51
|
+
# Generates a completed URL based on a +route+ or set of optional +params+
|
52
|
+
#
|
53
|
+
# set = Usher.new
|
54
|
+
# route = set.add_named_route(:test_route, '/:controller/:action')
|
55
|
+
# set.generator.generate(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
56
|
+
# set.generator.generate(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
57
|
+
# set.generator.generate(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
40
58
|
def generate(routing_lookup, params = nil)
|
41
59
|
generate_path(path_for_routing_lookup(routing_lookup, params), params)
|
42
60
|
end
|
43
61
|
|
62
|
+
def generate_path(path, params = nil)
|
63
|
+
params = Array(params) if params.is_a?(String)
|
64
|
+
if params.is_a?(Array)
|
65
|
+
given_size = params.size
|
66
|
+
extra_params = params.last.is_a?(Hash) ? params.pop : nil
|
67
|
+
params = Hash[*path.dynamic_parts.inject([]){|a, dynamic_part| a.concat([dynamic_part.name, params.shift || raise(MissingParameterException.new("got #{given_size}, expected #{path.dynamic_parts.size} parameters"))]); a}]
|
68
|
+
params.merge!(extra_params) if extra_params
|
69
|
+
end
|
70
|
+
|
71
|
+
result = Rack::Utils.uri_escape(generate_path_for_base_params(path, params))
|
72
|
+
unless params.nil? || params.empty?
|
73
|
+
extra_params = generate_extra_params(params, result[??])
|
74
|
+
result << extra_params
|
75
|
+
end
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def generation_module
|
80
|
+
build_module!
|
81
|
+
@generation_module
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_module!
|
85
|
+
unless @generation_module
|
86
|
+
@generation_module = Module.new
|
87
|
+
@generation_module.module_eval <<-END_EVAL
|
88
|
+
@@generator = nil
|
89
|
+
def self.generator=(generator)
|
90
|
+
@@generator = generator
|
91
|
+
end
|
92
|
+
END_EVAL
|
93
|
+
@generation_module.generator = self
|
94
|
+
|
95
|
+
@generation_module.module_eval <<-END_EVAL
|
96
|
+
def respond_to?(method_name)
|
97
|
+
if match = Regexp.new('^(.*?)_(path|url)$').match(method_name.to_s)
|
98
|
+
@@generator.usher.named_routes.key?(match.group(1))
|
99
|
+
else
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
END_EVAL
|
104
|
+
|
105
|
+
|
106
|
+
usher.named_routes.each do |name, route|
|
107
|
+
@generation_module.module_eval <<-END_EVAL
|
108
|
+
def #{name}_url(name, request, params = nil)
|
109
|
+
@@generator.generate_full(name, request, options)
|
110
|
+
end
|
111
|
+
|
112
|
+
def #{name}_path(name, params = nil)
|
113
|
+
@@generator.generate(name, options)
|
114
|
+
end
|
115
|
+
END_EVAL
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
44
120
|
def generate_start(path, request)
|
45
121
|
result = (path.route.generate_with && path.route.generate_with.scheme || request.scheme).dup
|
46
122
|
result << '://'
|
@@ -53,77 +129,42 @@ class Usher
|
|
53
129
|
end
|
54
130
|
result
|
55
131
|
end
|
56
|
-
|
132
|
+
|
57
133
|
def path_for_routing_lookup(routing_lookup, params = {})
|
58
134
|
path = case routing_lookup
|
59
135
|
when Symbol
|
60
|
-
route = @usher.named_routes[routing_lookup]
|
136
|
+
route = @usher.named_routes[routing_lookup]
|
137
|
+
raise UnrecognizedException unless route
|
61
138
|
route.find_matching_path(params || {})
|
62
139
|
when Route
|
63
|
-
routing_lookup.find_matching_path(params
|
140
|
+
routing_lookup.find_matching_path(params)
|
64
141
|
when nil
|
65
|
-
params.is_a?(Hash) ?
|
142
|
+
params.is_a?(Hash) ? usher.path_for_options(params) : raise
|
66
143
|
when Route::Path
|
67
144
|
routing_lookup
|
68
145
|
end
|
69
146
|
end
|
70
|
-
|
71
|
-
# Generates a completed URL based on a +route+ or set of optional +params+
|
72
|
-
#
|
73
|
-
# set = Usher.new
|
74
|
-
# route = set.add_named_route(:test_route, '/:controller/:action')
|
75
|
-
# set.generate_url(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
76
|
-
# set.generate_url(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
77
|
-
# set.generate_url(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
78
|
-
def generate_path(path, params = nil)
|
79
|
-
raise UnrecognizedException.new unless path
|
80
147
|
|
81
|
-
params = Array(params) if params.is_a?(String)
|
82
|
-
if params.is_a?(Array)
|
83
|
-
given_size = params.size
|
84
|
-
extra_params = params.last.is_a?(Hash) ? params.pop : nil
|
85
|
-
params = Hash[*path.dynamic_parts.inject([]){|a, dynamic_part| a.concat([dynamic_part.name, params.shift || raise(MissingParameterException.new("got #{given_size}, expected #{path.dynamic_parts.size} parameters"))]); a}]
|
86
|
-
params.merge!(extra_params) if extra_params
|
87
|
-
end
|
88
148
|
|
89
|
-
|
90
|
-
|
91
|
-
case part
|
92
|
-
when Route::Variable::Glob
|
93
|
-
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
94
|
-
value.each_with_index do |current_value, index|
|
95
|
-
part.valid!(current_value)
|
96
|
-
result << current_value.to_s
|
97
|
-
result << '/' if index != value.size - 1
|
98
|
-
end
|
99
|
-
when Route::Variable
|
100
|
-
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
101
|
-
part.valid!(value)
|
102
|
-
result << value.to_s
|
103
|
-
else
|
104
|
-
result << part
|
105
|
-
end
|
106
|
-
end
|
107
|
-
result = Rack::Utils.uri_escape(result)
|
149
|
+
def generate_extra_params(params, has_question_mark)
|
150
|
+
extra_params_result = ''
|
108
151
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
v.each do |v_part|
|
115
|
-
result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape("#{k.to_s}[]") << '=' << Rack::Utils.escape(v_part.to_s)
|
116
|
-
end
|
117
|
-
else
|
118
|
-
result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape(k.to_s) << '=' << Rack::Utils.escape(v.to_s)
|
152
|
+
params.each do |k,v|
|
153
|
+
case v
|
154
|
+
when Array
|
155
|
+
v.each do |v_part|
|
156
|
+
extra_params_result << (has_question_mark ? '&' : has_question_mark = true && '?') << Rack::Utils.escape("#{k.to_s}[]") << '=' << Rack::Utils.escape(v_part.to_s)
|
119
157
|
end
|
158
|
+
else
|
159
|
+
extra_params_result << (has_question_mark ? '&' : has_question_mark = true && '?') << Rack::Utils.escape(k.to_s) << '=' << Rack::Utils.escape(v.to_s)
|
120
160
|
end
|
121
161
|
end
|
122
|
-
|
162
|
+
extra_params_result
|
123
163
|
end
|
124
|
-
|
164
|
+
|
125
165
|
end
|
126
|
-
|
166
|
+
|
127
167
|
end
|
128
168
|
end
|
129
|
-
end
|
169
|
+
end
|
170
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
unless Rack::Utils.respond_to?(:uri_escape)
|
4
|
+
module Rack
|
5
|
+
|
6
|
+
module Utils
|
7
|
+
|
8
|
+
def uri_escape(s)
|
9
|
+
s.to_s.gsub(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
|
10
|
+
'%'<<$1.unpack('H2'*$1.size).join('%').upcase
|
11
|
+
}.tr(' ', '+')
|
12
|
+
end
|
13
|
+
module_function :uri_escape
|
14
|
+
|
15
|
+
def uri_unescape(s)
|
16
|
+
gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
17
|
+
[$1.delete('%')].pack('H*')
|
18
|
+
}
|
19
|
+
end
|
20
|
+
module_function :uri_unescape
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -2,37 +2,37 @@ require 'lib/usher'
|
|
2
2
|
require 'rack'
|
3
3
|
|
4
4
|
describe "Usher URL generation" do
|
5
|
-
|
5
|
+
|
6
6
|
before(:each) do
|
7
7
|
@route_set = Usher.new(:generator => Usher::Util::Generators::URL.new)
|
8
8
|
@route_set.reset!
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
it "should generate a simple URL" do
|
12
12
|
@route_set.add_named_route(:sample, '/sample', :controller => 'sample', :action => 'action')
|
13
13
|
@route_set.generator.generate(:sample, {}).should == '/sample'
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
it "should generate a simple URL with a single variable" do
|
17
17
|
@route_set.add_named_route(:sample, '/sample/:action', :controller => 'sample')
|
18
18
|
@route_set.generator.generate(:sample, {:action => 'action'}).should == '/sample/action'
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
it "should generate a simple URL with a single variable (and escape)" do
|
22
22
|
@route_set.add_named_route(:sample, '/sample/:action', :controller => 'sample')
|
23
23
|
@route_set.generator.generate(:sample, {:action => 'action time'}).should == '/sample/action%20time'
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
it "should generate a simple URL with a single variable (thats not a string)" do
|
27
27
|
@route_set.add_named_route(:sample, '/sample/:action/:id', :controller => 'sample')
|
28
28
|
@route_set.generator.generate(:sample, {:action => 'action', :id => 123}).should == '/sample/action/123'
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
it "should generate a simple URL with a glob variable" do
|
32
32
|
@route_set.add_named_route(:sample, '/sample/*action', :controller => 'sample')
|
33
33
|
@route_set.generator.generate(:sample, {:action => ['foo', 'baz']}).should == '/sample/foo/baz'
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
it "should generate a mutliple vairable URL from a hash" do
|
37
37
|
@route_set.add_named_route(:sample, '/sample/:first/:second', :controller => 'sample')
|
38
38
|
@route_set.generator.generate(:sample, {:first => 'zoo', :second => 'maz'}).should == '/sample/zoo/maz'
|
@@ -125,7 +125,7 @@ describe "Usher URL generation" do
|
|
125
125
|
@route_set.add_named_route(:name, '/:one/:two/:three', {:default_values => {:one => 'one', :two => 'two', :three => 'three'}})
|
126
126
|
@route_set.generator.generate(:name).should == '/one/two/three'
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
it "should generate a route using defaults and optionals using the last parameter" do
|
130
130
|
@route_set.add_named_route(:opts_with_defaults, '/:one(/:two(/:three))', {:default_values => {:one => '1', :two => '2', :three => '3'}})
|
131
131
|
@route_set.generator.generate(:opts_with_defaults, {:three => 'three'}).should == '/1/2/three'
|
@@ -135,61 +135,114 @@ describe "Usher URL generation" do
|
|
135
135
|
@route_set.add_named_route(:optionals, '/:controller(/:action(/:id))(.:format)')
|
136
136
|
@route_set.generator.generate(:optionals, {:controller => "foo", :action => "bar"}).should == '/foo/bar'
|
137
137
|
end
|
138
|
-
|
138
|
+
|
139
139
|
describe "nested generation" do
|
140
140
|
before do
|
141
141
|
@route_set2 = Usher.new(:generator => Usher::Util::Generators::URL.new)
|
142
142
|
@route_set3 = Usher.new(:generator => Usher::Util::Generators::URL.new)
|
143
143
|
@route_set4 = Usher.new(:generator => Usher::Util::Generators::URL.new)
|
144
|
-
|
144
|
+
|
145
145
|
@route_set.add_named_route(:simple, "/mount_point").match_partially!.to(@route_set2)
|
146
146
|
@route_set.add_route("/third/:foo", :default_values => {:foo => "foo"}).match_partially!.to(@route_set3)
|
147
147
|
@route_set.add_route("/fourth/:bar").match_partially!.to(@route_set4)
|
148
|
-
|
148
|
+
|
149
149
|
@route_set2.add_named_route(:nested_simple, "/nested/simple", :controller => "nested", :action => "simple")
|
150
150
|
@route_set2.add_named_route(:nested_complex, "/another_nested(/:complex)", :controller => "nested", :action => "complex")
|
151
|
-
|
151
|
+
|
152
152
|
@route_set3.add_named_route(:nested_simple, "/nested/simple", :controller => "nested", :action => "simple")
|
153
153
|
@route_set3.add_named_route(:nested_complex, "/another_nested(/:complex)", :controller => "nested", :action => "complex")
|
154
|
-
|
154
|
+
|
155
155
|
@route_set4.add_named_route(:nested_simple, "/nested/simple", :controller => "nested", :action => "simple")
|
156
156
|
end
|
157
|
-
|
157
|
+
|
158
158
|
it "should generate a route for the simple nested route" do
|
159
159
|
@route_set2.generator.generate(:nested_simple).should == "/mount_point/nested/simple"
|
160
160
|
end
|
161
|
-
|
161
|
+
|
162
162
|
it "should generate a simple route without optional segments" do
|
163
163
|
@route_set2.generator.generate(:nested_complex).should == "/mount_point/another_nested"
|
164
164
|
end
|
165
|
-
|
165
|
+
|
166
166
|
it "should generate a route with optional segements" do
|
167
167
|
@route_set2.generator.generate(:nested_complex, :complex => "foo").should == "/mount_point/another_nested/foo"
|
168
168
|
end
|
169
|
-
|
169
|
+
|
170
170
|
it "should genearte a route with the specified value for the parent route" do
|
171
171
|
@route_set3.generator.generate(:nested_simple, :foo => "bar").should == "/third/bar/nested/simple"
|
172
172
|
end
|
173
|
-
|
173
|
+
|
174
174
|
it "should generate a route with the default value from the parent route" do
|
175
175
|
@route_set3.generator.generate(:nested_simple).should == "/third/foo/nested/simple"
|
176
176
|
end
|
177
|
-
|
177
|
+
|
178
178
|
it "should generate a route with an optional segement in the parent and child" do
|
179
179
|
@route_set3.generator.generate(:nested_complex, :complex => "complex").should == "/third/foo/another_nested/complex"
|
180
180
|
end
|
181
|
-
|
181
|
+
|
182
182
|
it "should generate a route without the optional value from the child" do
|
183
183
|
@route_set3.generator.generate(:nested_complex).should == "/third/foo/another_nested"
|
184
184
|
end
|
185
|
-
|
185
|
+
|
186
186
|
it "should raise an exception when trying to generate a route where the parent variable is not defined and does not have a default value" do
|
187
187
|
lambda do
|
188
188
|
@route_set4.generator.generate(:nested_simple)
|
189
189
|
end.should raise_error(Usher::MissingParameterException)
|
190
190
|
end
|
191
|
-
|
192
|
-
|
193
|
-
|
194
191
|
end
|
195
|
-
|
192
|
+
|
193
|
+
describe "dupped generation" do
|
194
|
+
before(:each) do
|
195
|
+
@r1 = Usher.new(:generator => Usher::Util::Generators::URL.new)
|
196
|
+
@r2 = Usher.new(:generator => Usher::Util::Generators::URL.new)
|
197
|
+
@r3 = Usher.new(:generator => Usher::Util::Generators::URL.new)
|
198
|
+
|
199
|
+
@r1.add_route("/r1", :router => "r1").name(:route)
|
200
|
+
@r2.add_route("/r2", :router => "r2").name(:route)
|
201
|
+
@r3.add_route("/r3", :router => "r3").name(:route)
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should generate dupped routes" do
|
205
|
+
@r1.generator.generate(:route).should == "/r1"
|
206
|
+
r1 = @r1.dup
|
207
|
+
r1.generator.generate(:route).should == "/r1"
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should not generate new routes added to a dup on the original" do
|
211
|
+
r1 = @r1.dup
|
212
|
+
r1.add_route("/new_r1", :router => "r4").name(:new_route)
|
213
|
+
lambda do
|
214
|
+
@r1.generator.generate(:new_route).should be_nil
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should generate new routes added to a dup" do
|
219
|
+
r1 = @r1.dup
|
220
|
+
r1.add_route("/new_r1", :router => "r4").name(:new_route)
|
221
|
+
r1.generator.generate(:new_route).should == "/new_r1"
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should generate a route for a nested usher" do
|
225
|
+
@r1.add_route("/mounted").match_partially!.to(@r2)
|
226
|
+
@r2.generator.generate(:route).should == "/mounted/r2"
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should generate a route for a dupped nested usher" do
|
230
|
+
r3 = @r3.dup
|
231
|
+
@r1.add_route("/mounted").match_partially!.to(r3)
|
232
|
+
r3.generator.generate(:route).should == "/mounted/r3"
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should generate a route for 2 differently mounted dupped ushers" do
|
236
|
+
r21 = @r2.dup
|
237
|
+
r22 = @r2.dup
|
238
|
+
|
239
|
+
@r1.add_route("/mounted").match_partially!.to(r21)
|
240
|
+
@r1.add_route("/other_mount").match_partially!.to(r22)
|
241
|
+
|
242
|
+
r21.generator.generate(:route).should == "/mounted/r2"
|
243
|
+
r22.generator.generate(:route).should == "/other_mount/r2"
|
244
|
+
@r2.generator.generate(:route).should == "/r2"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
@@ -55,40 +55,40 @@ describe "Usher (for rack) route dispatching" do
|
|
55
55
|
response = route_set.call_with_mock_request("/not-existing-url")
|
56
56
|
response.status.should eql(404)
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
describe "mounted rack instances" do
|
60
60
|
before do
|
61
61
|
@bad_app = mock("bad_app")
|
62
|
-
|
62
|
+
|
63
63
|
@usher2 = Usher::Interface.for(:rack)
|
64
64
|
@usher2.add("/good" ).to(@app)
|
65
65
|
@usher2.add("/bad" ).match_partially!.to(@bad_app)
|
66
66
|
@usher2.add("/some(/:foo)").to(@app)
|
67
|
-
|
67
|
+
|
68
68
|
route_set.add("/foo/:bar", :default_values => {:foo => "foo"}).match_partially!.to(@usher2)
|
69
69
|
route_set.add("/foo", :default_values => {:controller => :foo}).to(@app)
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
it "should match the route without nesting" do
|
73
73
|
@app.should_receive(:call).once.with{ |e| e['usher.params'].should == {:controller => :foo}}
|
74
74
|
route_set.call(Rack::MockRequest.env_for("/foo"))
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
it "should route through the first route, and the second to the app" do
|
78
78
|
@app.should_receive(:call).once.with{|e| e['usher.params'].should == {:bar => "bar", :foo => "foo"}}
|
79
79
|
result = route_set.call(Rack::MockRequest.env_for("/foo/bar/good"))
|
80
80
|
end
|
81
|
-
|
81
|
+
|
82
82
|
it "should go through to the bad app" do
|
83
83
|
@bad_app.should_receive(:call).once.with{|e| e['usher.params'].should == {:bar => "some_bar", :foo => "foo"}}
|
84
84
|
result = route_set.call(Rack::MockRequest.env_for("/foo/some_bar/bad"))
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
it "should match optional routes paramters" do
|
88
88
|
@app.should_receive(:call).once.with{|e| e['usher.params'].should == {:bar => "bar", :foo => "a_different_foo"}}
|
89
89
|
route_set.call(Rack::MockRequest.env_for("/foo/bar/some/a_different_foo"))
|
90
90
|
end
|
91
|
-
|
91
|
+
|
92
92
|
describe "SCRIPT_NAME & PATH_INFO" do
|
93
93
|
it "should update the script name for a fully consumed route" do
|
94
94
|
@app.should_receive(:call).once.with do |e|
|
@@ -97,48 +97,48 @@ describe "Usher (for rack) route dispatching" do
|
|
97
97
|
end
|
98
98
|
route_set.call(Rack::MockRequest.env_for("/foo"))
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
it "should update the script name and path info for a partially consumed route" do
|
102
102
|
@app.should_receive(:call).once.with do |e|
|
103
103
|
e['SCRIPT_NAME'].should == "/partial"
|
104
104
|
e['PATH_INFO'].should == "/bar/baz"
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
route_set.add("/partial").match_partially!.to(@app)
|
108
108
|
route_set.call(Rack::MockRequest.env_for("/partial/bar/baz"))
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
it "should consume the path through a mounted usher" do
|
112
112
|
@bad_app.should_receive(:call).once.with do |e|
|
113
113
|
e['SCRIPT_NAME'].should == "/foo/bar/bad"
|
114
114
|
e['PATH_INFO'].should == "/leftovers"
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
route_set.call(Rack::MockRequest.env_for("/foo/bar/bad/leftovers"))
|
118
118
|
end
|
119
|
-
|
119
|
+
|
120
120
|
end
|
121
|
-
|
121
|
+
|
122
122
|
describe "dupping" do
|
123
|
-
before do
|
123
|
+
before do
|
124
124
|
@app = mock("app")
|
125
125
|
@u1 = Usher::Interface.for(:rack)
|
126
126
|
@u2 = Usher::Interface.for(:rack)
|
127
|
-
|
127
|
+
|
128
128
|
@u1.add("/one", :default_values => {:one => :one}).to(@app)
|
129
129
|
@u1.add("/mount").match_partially!.to(@u2)
|
130
|
-
|
130
|
+
|
131
131
|
@u2.add("/app", :default_values => {:foo => :bar}).to(@app)
|
132
|
-
|
132
|
+
|
133
133
|
end
|
134
|
-
|
134
|
+
|
135
135
|
it "should allow me to dup the router" do
|
136
136
|
@app.should_receive(:call).twice.with{|e| e['usher.params'].should == {:one => :one}}
|
137
137
|
@u1.call(Rack::MockRequest.env_for("/one"))
|
138
138
|
u1_dash = @u1.dup
|
139
139
|
u1_dash.call(Rack::MockRequest.env_for("/one"))
|
140
140
|
end
|
141
|
-
|
141
|
+
|
142
142
|
it "should allow me to dup the router and add a new route without polluting the original" do
|
143
143
|
@app.should_receive(:call).with{|e| e['usher.params'].should == {:foo => :bar}}
|
144
144
|
u1_dash = @u1.dup
|
@@ -147,27 +147,53 @@ describe "Usher (for rack) route dispatching" do
|
|
147
147
|
@app.should_not_receive(:call)
|
148
148
|
@u1.call(Rack::MockRequest.env_for("/foo"))
|
149
149
|
end
|
150
|
-
|
150
|
+
|
151
151
|
it "should allow me to dup the router and nested routers should remain intact" do
|
152
152
|
@app.should_receive(:call).with{|e| e['usher.params'].should == {:foo => :bar}}
|
153
153
|
u1_dash = @u1.dup
|
154
154
|
u1_dash.call(Rack::MockRequest.env_for("/mount/app"))
|
155
155
|
end
|
156
|
-
|
156
|
+
|
157
157
|
it "should allow me to dup the router and add more routes" do
|
158
158
|
@app.should_receive(:call).with{|e| e['usher.params'].should == {:another => :bar}}
|
159
|
-
|
159
|
+
|
160
160
|
u3 = Usher::Interface.for(:rack)
|
161
161
|
u1_dash = @u1.dup
|
162
|
-
|
162
|
+
|
163
163
|
u3.add("/another_bar", :default_values => {:another => :bar}).to(@app)
|
164
164
|
u1_dash.add("/some/mount").match_partially!.to(u3)
|
165
|
-
|
165
|
+
|
166
166
|
u1_dash.call(Rack::MockRequest.env_for("/some/mount/another_bar"))
|
167
|
-
|
167
|
+
|
168
168
|
@app.should_not_receive(:call)
|
169
169
|
@u1.call(Rack::MockRequest.env_for("/some/mount/another_bar"))
|
170
170
|
end
|
171
171
|
end
|
172
172
|
end
|
173
|
+
|
174
|
+
describe "use as middlware" do
|
175
|
+
it "should allow me to set a default application to use" do
|
176
|
+
@app.should_receive(:call).with{|e| e['usher.params'].should == {:middle => :ware}}
|
177
|
+
|
178
|
+
u = Usher::Interface::RackInterface.new(@app)
|
179
|
+
u.add("/foo", :default_values => {:middle => :ware}).name(:foo)
|
180
|
+
|
181
|
+
u.call(Rack::MockRequest.env_for("/foo"))
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should use the default application when no routes match" do
|
185
|
+
env = Rack::MockRequest.env_for("/not_a_route")
|
186
|
+
@app.should_receive(:call).with(env)
|
187
|
+
u = Usher::Interface::RackInterface.new(@app)
|
188
|
+
u.call(env)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should allow me to set the application after initialization" do
|
192
|
+
@app.should_receive(:call).with{|e| e['usher.params'].should == {:after => :stuff}}
|
193
|
+
u = Usher::Interface.for(:rack)
|
194
|
+
u.app = @app
|
195
|
+
u.add("/foo", :default_values => {:after => :stuff})
|
196
|
+
u.call(Rack::MockRequest.env_for("/foo"))
|
197
|
+
end
|
198
|
+
end
|
173
199
|
end
|
@@ -195,6 +195,14 @@ describe "Usher route recognition" do
|
|
195
195
|
route_set.recognize(build_request({:method => 'get', :path => '/testing/asd.qwe/testing2/poi.zxc/oiu.asd'})).params.should == [[:id, 'asd.qwe'], [:id2, 'poi.zxc'], [:id3, 'oiu.asd']]
|
196
196
|
end
|
197
197
|
|
198
|
+
it "should recognize a path with an optional compontnet" do
|
199
|
+
route_set.add_route("/:name(/:surname)", :conditions => {:method => 'get'})
|
200
|
+
result = route_set.recognize(build_request({:method => 'get', :path => '/homer'}))
|
201
|
+
result.params.should == [[:name, "homer"]]
|
202
|
+
result = route_set.recognize(build_request({:method => 'get', :path => "/homer/simpson"}))
|
203
|
+
result.params.should == [[:name, "homer"],[:surname, "simpson"]]
|
204
|
+
end
|
205
|
+
|
198
206
|
it "should should raise if malformed variables are used" do
|
199
207
|
route_set.add_route('/products/show/:id', :id => /\d+/, :conditions => {:method => 'get'})
|
200
208
|
proc {route_set.recognize(build_request({:method => 'get', :path => '/products/show/qweasd', :domain => 'admin.host.com'}))}.should raise_error
|
@@ -228,8 +236,8 @@ describe "Usher route recognition" do
|
|
228
236
|
end
|
229
237
|
|
230
238
|
it "should recognize the originals routes in the dup" do
|
231
|
-
route_set.recognize(build_request(:path => "/foo")).path.route.destination.should == {:foo =>"foo"}
|
232
|
-
@r2.recognize(
|
239
|
+
route_set.recognize( build_request(:path => "/foo")).path.route.destination.should == {:foo =>"foo"}
|
240
|
+
@r2.recognize( build_request(:path => "/foo")).path.route.destination.should == {:foo =>"foo"}
|
233
241
|
end
|
234
242
|
|
235
243
|
it "should not add routes added to the dup to the original" do
|
@@ -240,8 +248,8 @@ describe "Usher route recognition" do
|
|
240
248
|
|
241
249
|
it "should not delete routes added to the dup to the original" do
|
242
250
|
@r2.delete_route("/foo")
|
243
|
-
route_set.recognize(
|
244
|
-
@r2.recognize(
|
251
|
+
route_set.recognize( build_request(:path => "/foo")).path.route.destination.should == {:foo => "foo"}
|
252
|
+
@r2.recognize( build_request(:path => "/foo")).should == nil
|
245
253
|
end
|
246
254
|
|
247
255
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: usher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Hull
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-09-05 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- lib/usher/interface/rails2_2_interface/mapper.rb
|
50
50
|
- lib/usher/interface/rails2_3_interface.rb
|
51
51
|
- lib/usher/interface/rails3_interface.rb
|
52
|
+
- lib/usher/interface/text_interface.rb
|
52
53
|
- lib/usher/node.rb
|
53
54
|
- lib/usher/route.rb
|
54
55
|
- lib/usher/route/path.rb
|
@@ -59,6 +60,7 @@ files:
|
|
59
60
|
- lib/usher/util.rb
|
60
61
|
- lib/usher/util/generate.rb
|
61
62
|
- lib/usher/util/parser.rb
|
63
|
+
- lib/usher/util/rack-mixins.rb
|
62
64
|
- rails/init.rb
|
63
65
|
- spec/private/email/recognize_spec.rb
|
64
66
|
- spec/private/generate_spec.rb
|