usher 0.7.1 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -0
- data/README.markdown +158 -0
- data/Rakefile +47 -12
- data/benchmarks/rack_recognition_bm.rb +48 -0
- data/lib/usher/delimiters.rb +20 -8
- data/lib/usher/exceptions.rb +4 -0
- data/lib/usher/grapher.rb +38 -32
- data/lib/usher/interface/rack/builder.rb +39 -0
- data/lib/usher/interface/rack/middleware.rb +22 -0
- data/lib/usher/interface/rack.rb +6 -55
- data/lib/usher/interface/sinatra.rb +115 -56
- data/lib/usher/interface.rb +12 -22
- data/lib/usher/node/response.rb +3 -1
- data/lib/usher/node.rb +56 -75
- data/lib/usher/route/static.rb +2 -0
- data/lib/usher/route/variable.rb +1 -1
- data/lib/usher/route.rb +21 -18
- data/lib/usher/splitter.rb +4 -6
- data/lib/usher/util/generate.rb +50 -34
- data/lib/usher/util/parser.rb +12 -9
- data/lib/usher.rb +204 -162
- data/spec/private/generate_spec.rb +5 -0
- data/spec/private/recognize_spec.rb +12 -12
- data/spec/private/sinatra/recognize_spec.rb +102 -0
- data/spec/spec_helper.rb +1 -1
- data/usher.gemspec +8 -5
- metadata +75 -19
- data/README.rdoc +0 -158
- data/spec/rails2_2/compat.rb +0 -3
- data/spec/rails2_2/generate_spec.rb +0 -28
- data/spec/rails2_2/path_spec.rb +0 -16
- data/spec/rails2_2/recognize_spec.rb +0 -78
- data/spec/rails2_3/compat.rb +0 -2
- data/spec/rails2_3/generate_spec.rb +0 -28
- data/spec/rails2_3/path_spec.rb +0 -16
- data/spec/rails2_3/recognize_spec.rb +0 -78
data/lib/usher/util/parser.rb
CHANGED
@@ -124,20 +124,23 @@ class Usher
|
|
124
124
|
pattern << regex_part
|
125
125
|
end
|
126
126
|
pattern.slice!(pattern.length - 1)
|
127
|
-
regex = Regexp.new(pattern)
|
128
127
|
if variable
|
129
|
-
|
130
|
-
|
131
|
-
when
|
132
|
-
when
|
133
|
-
when :':' then Usher::Route::Variable::Single
|
128
|
+
variable_class = case variable.slice!(0)
|
129
|
+
when ?! then Usher::Route::Variable::Greedy
|
130
|
+
when ?* then Usher::Route::Variable::Glob
|
131
|
+
when ?: then Usher::Route::Variable::Single
|
134
132
|
end
|
135
133
|
variable_name = variable[0, variable.size - 1].to_sym
|
136
|
-
current_group << variable_class.new(variable_name,
|
134
|
+
current_group << variable_class.new(variable_name, Regexp.new(pattern), requirements && requirements[variable_name])
|
137
135
|
elsif simple
|
138
|
-
|
136
|
+
static = Usher::Route::Static::Greedy.new(pattern)
|
137
|
+
static.generate_with = pattern
|
138
|
+
current_group << static
|
139
139
|
else
|
140
|
-
|
140
|
+
simple_parts = pattern.split(',', 2)
|
141
|
+
static = Usher::Route::Static::Greedy.new(Regexp.new(simple_parts.last))
|
142
|
+
static.generate_with = simple_parts.first
|
143
|
+
current_group << static
|
141
144
|
end
|
142
145
|
when ?(
|
143
146
|
new_group = Usher::Route::Util::Group.new(:any, current_group)
|
data/lib/usher.rb
CHANGED
@@ -10,67 +10,66 @@ require File.join('usher', 'exceptions')
|
|
10
10
|
require File.join('usher', 'util')
|
11
11
|
require File.join('usher', 'delimiters')
|
12
12
|
|
13
|
+
# Main class for routing.
|
14
|
+
# If you're going to be routing for a specific context, like rails or rack, you probably want to use an interface. Otherwise, this
|
15
|
+
# is the main class that actually does all the work.
|
16
|
+
# @example
|
17
|
+
# u = Usher.new
|
18
|
+
# u.add_route('one/two').to(:one)
|
19
|
+
# u.add_route('two/three').to(:two)
|
20
|
+
# u.add_route('two/:variable').to(:variable)
|
21
|
+
# u.recognize_path('one/two').destination
|
22
|
+
# ==> :one
|
23
|
+
# u.recognize_path('two/whatwasthat').params_as_hash
|
24
|
+
# ==> {:variable => 'whatwasthat'}
|
13
25
|
class Usher
|
14
26
|
attr_reader :root, :named_routes, :routes, :splitter,
|
15
|
-
:delimiters, :delimiters_regex, :
|
16
|
-
:parent_route, :generator, :grapher
|
27
|
+
:delimiters, :delimiters_regex, :parent_route, :generator, :grapher, :parser
|
17
28
|
attr_accessor :route_class
|
18
29
|
|
19
|
-
|
20
|
-
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# set.add_route('/test')
|
26
|
-
# set.empty? => false
|
30
|
+
# @return [Boolean] Whether the route set is empty
|
31
|
+
# @example
|
32
|
+
# set = Usher.new
|
33
|
+
# set.empty? => true
|
34
|
+
# set.add_route('/test')
|
35
|
+
# set.empty? => false
|
27
36
|
def empty?
|
28
37
|
routes.empty?
|
29
38
|
end
|
30
39
|
|
31
|
-
#
|
40
|
+
# @return [Number] The number of routes currently mapped
|
32
41
|
#
|
33
42
|
def route_count
|
34
43
|
routes.size
|
35
44
|
end
|
36
45
|
|
37
46
|
# Resets the route set back to its initial state
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
47
|
+
# @example
|
48
|
+
# set = Usher.new
|
49
|
+
# set.add_route('/test')
|
50
|
+
# set.empty? => false
|
51
|
+
# set.reset!
|
52
|
+
# set.empty? => true
|
44
53
|
def reset!
|
45
54
|
@root = Node::Root.new(self, request_methods)
|
46
55
|
@named_routes = {}
|
47
56
|
@routes = []
|
48
57
|
@grapher = Grapher.new(self)
|
49
58
|
@priority_lookups = false
|
59
|
+
@parser = Util::Parser.for_delimiters(self, valid_regex)
|
50
60
|
end
|
51
|
-
alias clear! reset!
|
52
61
|
|
53
62
|
# Creates a route set, with options
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
# <
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
# <tt>:ignore_trailing_delimiters</tt>: +true+ or +false+. (default: +false+) Ignore trailing delimiters in recognizing paths.
|
66
|
-
#
|
67
|
-
# <tt>:consider_destination_keys</tt>: +true+ or +false+. (default: +false+) When generating, and using hash destinations, you can have
|
68
|
-
# Usher use the destination hash to match incoming params.
|
69
|
-
#
|
70
|
-
# <tt>:allow_identical_variable_names</tt>: +true+ or +false+. (default: +true+) When adding routes, allow identical variable names to be used.
|
71
|
-
#
|
72
|
-
# Example, you create a route with a destination of :controller => 'test', :action => 'action'. If you made a call to generator with :controller => 'test',
|
73
|
-
# :action => 'action', it would pick that route to use for generation.
|
63
|
+
# @param [Hash] options the options to create a router with
|
64
|
+
# @option options [Array<String>] :delimiters (['/', '.']) Delimiters used in path separation. Array must be single character strings.
|
65
|
+
# @option options [String] :valid_regex ('[0-9A-Za-z\$\-_\+!\*\',]+') String that can be interpolated into regex to match valid character sequences within path.
|
66
|
+
# @option options [Array<Symbol>] :request_methods ([:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]) Array of methods called against the request object for the purposes of matching route requirements.
|
67
|
+
# @option options [nil or Generator] :generator (nil) Take a look at `Usher::Util::Generators for examples.`.
|
68
|
+
# @option options [Boolean] :ignore_trailing_delimiters (false) Ignore trailing delimiters in recognizing paths.
|
69
|
+
# @option options [Boolean] :consider_destination_keys (false) When generating, and using hash destinations, you can have Usher use the destination hash to match incoming params.
|
70
|
+
# Example, you create a route with a destination of :controller => 'test', :action => 'action'. If you made a call to generator with :controller => 'test',
|
71
|
+
# :action => 'action', it would pick that route to use for generation.
|
72
|
+
# @option options [Boolean] :allow_identical_variable_names (true) When adding routes, allow identical variable names to be used.
|
74
73
|
def initialize(options = nil)
|
75
74
|
self.route_class = Usher::Route
|
76
75
|
self.generator = options && options.delete(:generator)
|
@@ -80,132 +79,148 @@ class Usher
|
|
80
79
|
self.ignore_trailing_delimiters = options && options.key?(:ignore_trailing_delimiters) ? options.delete(:ignore_trailing_delimiters) : false
|
81
80
|
self.consider_destination_keys = options && options.key?(:consider_destination_keys) ? options.delete(:consider_destination_keys) : false
|
82
81
|
self.allow_identical_variable_names = options && options.key?(:allow_identical_variable_names) ? options.delete(:allow_identical_variable_names) : true
|
82
|
+
unless options.nil? || options.empty?
|
83
|
+
raise "unrecognized options -- #{options.keys.join(', ')}"
|
84
|
+
end
|
83
85
|
reset!
|
84
86
|
end
|
85
|
-
|
87
|
+
|
88
|
+
# @return [Boolean] State of allow_identical_variable_names feature.
|
86
89
|
def allow_identical_variable_names?
|
87
90
|
@allow_identical_variable_names
|
88
91
|
end
|
89
92
|
|
93
|
+
# @return [Boolean] State of ignore_trailing_delimiters feature.
|
90
94
|
def ignore_trailing_delimiters?
|
91
95
|
@ignore_trailing_delimiters
|
92
96
|
end
|
93
97
|
|
98
|
+
# @return [Boolean] State of consider_destination_keys feature.
|
94
99
|
def consider_destination_keys?
|
95
100
|
@consider_destination_keys
|
96
101
|
end
|
97
|
-
|
98
|
-
def parser
|
99
|
-
@parser ||= Util::Parser.for_delimiters(self, valid_regex)
|
100
|
-
end
|
101
102
|
|
102
|
-
|
103
|
-
|
103
|
+
# @return [Boolean] State of priority_lookups feature.
|
104
|
+
def priority_lookups?
|
105
|
+
@priority_lookups
|
104
106
|
end
|
105
107
|
|
106
|
-
|
107
|
-
|
108
|
+
# @return [Boolean] Able to generate
|
109
|
+
def can_generate?
|
110
|
+
!generator.nil?
|
108
111
|
end
|
109
112
|
|
110
|
-
# Adds a route referencable by
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
113
|
+
# Adds a route referencable by `name`. See {#add_route} for format `path` and `options`.
|
114
|
+
# @param name Name of route
|
115
|
+
# @param path Path of route
|
116
|
+
# @param options Options for route
|
117
|
+
# @return (Route) Route added
|
118
|
+
# @example
|
119
|
+
# set = Usher.new
|
120
|
+
# set.add_named_route(:test_route, '/test')
|
114
121
|
def add_named_route(name, path, options = nil)
|
115
122
|
add_route(path, options).name(name)
|
116
123
|
end
|
117
124
|
|
118
|
-
# Deletes a route referencable by
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
125
|
+
# Deletes a route referencable by `name`. At least the path and conditions have to match the route you intend to delete.
|
126
|
+
# @param name Name of route
|
127
|
+
# @param path Path of route
|
128
|
+
# @param options Options for route
|
129
|
+
# @return (Route) Route added
|
130
|
+
# @example
|
131
|
+
# set = Usher.new
|
132
|
+
# set.delete_named_route(:test_route, '/test')
|
122
133
|
def delete_named_route(name, path, options = nil)
|
123
134
|
delete_route(path, options)
|
124
135
|
named_routes.delete(name)
|
125
136
|
end
|
126
137
|
|
127
|
-
# Attaches a
|
128
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
138
|
+
# Attaches a `route` to a `name`
|
139
|
+
# @param name Name of route
|
140
|
+
# @param route Route to attach to
|
141
|
+
# @return (Route) Route named
|
142
|
+
# @example
|
143
|
+
# set = Usher.new
|
144
|
+
# route = set.add_route('/test')
|
145
|
+
# set.name(:test, route)
|
132
146
|
def name(name, route)
|
133
147
|
named_routes[name.to_sym] = route
|
134
148
|
route
|
135
149
|
end
|
136
150
|
|
137
|
-
# Creates a route from
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
#
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
178
|
-
#
|
179
|
-
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
#
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
207
|
-
#
|
208
|
-
#
|
151
|
+
# Creates a route from `path` and `options`
|
152
|
+
# @param [String] path
|
153
|
+
#
|
154
|
+
# A path consists a mix of dynamic and static parts delimited by `/`
|
155
|
+
# ## Dynamic
|
156
|
+
# Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or
|
157
|
+
# more parts.
|
158
|
+
#
|
159
|
+
# ### Example
|
160
|
+
# `/path/:variable/path` would match
|
161
|
+
#
|
162
|
+
# * `/path/test/path`
|
163
|
+
# * `/path/something_else/path`
|
164
|
+
# * `/path/one_more/path`
|
165
|
+
#
|
166
|
+
# In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key `:variable`.
|
167
|
+
# However, `/path/test/one_more/path` would not be matched.
|
168
|
+
#
|
169
|
+
# ### example
|
170
|
+
# `/path/*variable/path` would match
|
171
|
+
#
|
172
|
+
# * `/path/one/two/three/path`
|
173
|
+
# * `/path/four/five/path`
|
174
|
+
#
|
175
|
+
# In the above examples, `['one', 'two', 'three']` and `['four', 'five']` respectively would be bound to the key `:variable`.
|
176
|
+
#
|
177
|
+
# As well, variables can have a regex matcher.
|
178
|
+
#
|
179
|
+
# ### Example
|
180
|
+
# `/product/{:id,\d+}` would match
|
181
|
+
#
|
182
|
+
# * `/product/123`
|
183
|
+
# * `/product/4521`
|
184
|
+
#
|
185
|
+
# But not
|
186
|
+
#
|
187
|
+
# * `/product/AE-35`
|
188
|
+
#
|
189
|
+
# As well, the same logic applies for * variables as well, where only parts matchable by the supplied regex will
|
190
|
+
# actually be bound to the variable
|
191
|
+
#
|
192
|
+
# Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
|
193
|
+
# regex allows.
|
194
|
+
#
|
195
|
+
# ### Example
|
196
|
+
# `/product/{!id,hello/world|hello}` would match
|
197
|
+
#
|
198
|
+
# * `/product/hello/world`
|
199
|
+
# * `/product/hello`
|
200
|
+
#
|
201
|
+
# ## Static
|
202
|
+
#
|
203
|
+
# Static parts of literal character sequences. For instance, `/path/something.html` would match only the same path.
|
204
|
+
# As well, static parts can have a regex pattern in them as well, such as `/path/something.{html|xml}` which would match only
|
205
|
+
# `/path/something.html` and `/path/something.xml`
|
206
|
+
#
|
207
|
+
# ## Optional sections
|
208
|
+
#
|
209
|
+
# Sections of a route can be marked as optional by surrounding it with brackets. For instance, in the above static example, `/path/something(.html)` would match both `/path/something` and `/path/something.html`.
|
210
|
+
#
|
211
|
+
# ## One and only one sections
|
212
|
+
#
|
213
|
+
# Sections of a route can be marked as "one and only one" by surrounding it with brackets and separating parts of the route with pipes.
|
214
|
+
# For instance, the path, `/path/something(.xml|.html)` would only match `/path/something.xml` and
|
215
|
+
# `/path/something.html`. Generally its more efficent to use one and only sections over using regex.
|
216
|
+
#
|
217
|
+
# @param [Hash] options
|
218
|
+
# Any other key is interpreted as a requirement for the variable of its name.
|
219
|
+
# @option options [Object] :requirements After transformation, tests the condition using ===. If it returns false, it raises an {ValidationException}
|
220
|
+
# @option options [String, Regexp] :conditions Accepts any of the `request_methods` specificied in the construction of Usher. This can be either a `String` or a regular expression.
|
221
|
+
# @option options [Hash<Symbol, String>] :default_values Provides values for variables in your route for generation. If you're using URL generation, then any values supplied here that aren't included in your path will be appended to the query string.
|
222
|
+
# @option options [Number] :priority If there are two routes which equally match, the route with the highest priority will match first.
|
223
|
+
# @return [Route] The route added
|
209
224
|
def add_route(path, options = nil)
|
210
225
|
route = get_route(path, options)
|
211
226
|
root.add(route)
|
@@ -216,57 +231,78 @@ class Usher
|
|
216
231
|
end
|
217
232
|
|
218
233
|
# Deletes a route. At least the path and conditions have to match the route you intend to delete.
|
219
|
-
#
|
220
|
-
#
|
221
|
-
#
|
234
|
+
# @param path [String] The path to delete
|
235
|
+
# @param options [Hash] The options used to identify the path
|
236
|
+
# @example
|
237
|
+
# set.delete_route('/test')
|
238
|
+
# @return [Route] The route deleted
|
222
239
|
def delete_route(path, options = nil)
|
223
240
|
route = get_route(path, options)
|
224
241
|
root.delete(route)
|
225
|
-
|
226
|
-
|
242
|
+
routes.replace(root.unique_routes)
|
243
|
+
build_grapher!
|
227
244
|
route
|
228
245
|
end
|
229
246
|
|
230
|
-
# Recognizes a
|
231
|
-
#
|
232
|
-
#
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
247
|
+
# Recognizes a `request`
|
248
|
+
# @param request [#path] The request object. Must minimally respond to #path if no path argument is supplied here.
|
249
|
+
# @param path [String] The path to be recognized.
|
250
|
+
# @return [nil, Node::Response] The recognition response if the request object was recognized
|
251
|
+
# @example
|
252
|
+
# Request = Struct.new(:path)
|
253
|
+
# set = Usher.new
|
254
|
+
# route = set.add_route('/test')
|
255
|
+
# set.recognize(Request.new('/test')).path.route == route => true
|
236
256
|
def recognize(request, path = request.path)
|
237
|
-
|
257
|
+
if ignore_trailing_delimiters? and path.size > 1
|
258
|
+
path_size = path.size
|
259
|
+
path = path.gsub(/#{Regexp.quote(delimiters.first)}$/, '')
|
260
|
+
response = root.find(request, path, splitter.split(path))
|
261
|
+
response.only_trailing_delimiters = (path.size != path_size) if response
|
262
|
+
response
|
263
|
+
else
|
264
|
+
root.find(request, path, splitter.split(path))
|
265
|
+
end
|
266
|
+
|
238
267
|
end
|
239
268
|
|
240
|
-
# Recognizes a
|
241
|
-
#
|
242
|
-
#
|
243
|
-
#
|
244
|
-
#
|
245
|
-
#
|
269
|
+
# Recognizes a `path`
|
270
|
+
# @param path [String] The path to be recognized.
|
271
|
+
# @return [nil, Node::Response] The recognition response if the request object was recognized
|
272
|
+
# @example
|
273
|
+
# Request = Struct.new(:path)
|
274
|
+
# set = Usher.new
|
275
|
+
# route = set.add_route('/test')
|
276
|
+
# set.recognize_path('/test').path.route == route => true
|
246
277
|
def recognize_path(path)
|
247
278
|
recognize(nil, path)
|
248
279
|
end
|
249
280
|
|
250
|
-
# Recognizes a set of
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
281
|
+
# Recognizes a set of `parameters` and gets the closest matching Usher::Route::Path or `nil` if no route exists.
|
282
|
+
# @param options [Hash<Symbol, String>] A set of parameters
|
283
|
+
# @return [nil, Route::Path] A path matched or `nil` if not found.
|
284
|
+
# @example
|
285
|
+
# set = Usher.new
|
286
|
+
# route = set.add_route('/:controller/:action')
|
287
|
+
# set.path_for_options({:controller => 'test', :action => 'action'}) == path.route => true
|
255
288
|
def path_for_options(options)
|
256
289
|
grapher.find_matching_path(options)
|
257
290
|
end
|
258
291
|
|
292
|
+
# The assignes the parent route this router belongs to.
|
293
|
+
# @param route [Route] The route to use to assign as this routers parent route
|
259
294
|
def parent_route=(route)
|
260
295
|
@parent_route = route
|
261
296
|
routes.each{|r| r.parent_route = route}
|
262
297
|
end
|
263
298
|
|
299
|
+
# Duplicates the router.
|
300
|
+
# @return [Usher] The duplicated router
|
264
301
|
def dup
|
265
302
|
replacement = super
|
266
303
|
original = self
|
267
304
|
inverted_named_routes = original.named_routes.invert
|
268
305
|
replacement.instance_eval do
|
269
|
-
@parser = nil
|
270
306
|
reset!
|
271
307
|
original.routes.each do |route|
|
272
308
|
new_route = route.dup
|
@@ -278,13 +314,13 @@ class Usher
|
|
278
314
|
end
|
279
315
|
end
|
280
316
|
send(:generator=, original.generator.class.new) if original.can_generate?
|
281
|
-
|
317
|
+
build_grapher!
|
282
318
|
end
|
283
319
|
replacement
|
284
320
|
end
|
285
321
|
|
286
322
|
def inspect
|
287
|
-
"#<Usher:0x%x route_count=%d delimiters=%s request_methods=%s ignore_trailing_delimiters? %s consider_destination_keys? %s can_generate? %s priority_lookups? %s>" % [self.object_id, route_count, self.delimiters.inspect, request_methods.inspect, ignore_trailing_delimiters
|
323
|
+
"#<Usher:0x%x route_count=%d delimiters=%s request_methods=%s ignore_trailing_delimiters? %s consider_destination_keys? %s can_generate? %s priority_lookups? %s>" % [self.object_id, route_count, self.delimiters.inspect, request_methods.inspect, ignore_trailing_delimiters?.inspect, consider_destination_keys?.inspect, can_generate?.inspect, priority_lookups?.inspect]
|
288
324
|
end
|
289
325
|
|
290
326
|
def to_s
|
@@ -295,7 +331,8 @@ class Usher
|
|
295
331
|
|
296
332
|
attr_accessor :request_methods, :ignore_trailing_delimiters, :consider_destination_keys, :allow_identical_variable_names
|
297
333
|
attr_reader :valid_regex
|
298
|
-
|
334
|
+
attr_writer :parser
|
335
|
+
|
299
336
|
def generator=(generator)
|
300
337
|
if generator
|
301
338
|
@generator = generator
|
@@ -320,6 +357,10 @@ class Usher
|
|
320
357
|
@priority_lookups = true
|
321
358
|
end
|
322
359
|
|
360
|
+
# Returns the route this path, options belongs to. Used internally by add_route, delete_route.
|
361
|
+
# @see #add_route, #delete_route
|
362
|
+
# @param path [String] path
|
363
|
+
# @param options [Hash] options
|
323
364
|
def get_route(path, options = nil)
|
324
365
|
conditions = options && options.delete(:conditions) || nil
|
325
366
|
requirements = options && options.delete(:requirements) || nil
|
@@ -336,7 +377,7 @@ class Usher
|
|
336
377
|
end
|
337
378
|
|
338
379
|
if conditions && !conditions.empty?
|
339
|
-
conditions.keys.all?{|k| request_methods.include?(k)} or raise("You are trying to use request methods that don't exist in the request_methods supplied #{conditions.keys.join(', ')} -> #{conditions.keys
|
380
|
+
conditions.keys.all?{|k| request_methods.include?(k)} or raise("You are trying to use request methods that don't exist in the request_methods supplied #{conditions.keys.join(', ')} -> #{(conditions.keys - request_methods).join(", ")}")
|
340
381
|
end
|
341
382
|
|
342
383
|
if priority
|
@@ -349,9 +390,10 @@ class Usher
|
|
349
390
|
route
|
350
391
|
end
|
351
392
|
|
352
|
-
|
393
|
+
# Rebuilds the grapher
|
394
|
+
def build_grapher!
|
353
395
|
@grapher = Grapher.new(self)
|
354
396
|
routes.each{|r| grapher.add_route(r)}
|
355
397
|
end
|
356
|
-
|
398
|
+
|
357
399
|
end
|
@@ -44,6 +44,11 @@ describe "Usher URL generation" do
|
|
44
44
|
@route_set.generator.generate(:sample, ['maz', 'zoo']).should == '/sample/maz/zoo'
|
45
45
|
end
|
46
46
|
|
47
|
+
it "should generate a mutliple vairable URL from an array where the same variable name is repeated" do
|
48
|
+
@route_set.add_named_route(:sample, '/sample/:first/:first', :controller => 'sample')
|
49
|
+
@route_set.generator.generate(:sample, ['maz', 'zoo']).should == '/sample/maz/zoo'
|
50
|
+
end
|
51
|
+
|
47
52
|
it "should generate append extra hash variables to the end" do
|
48
53
|
@route_set.add_named_route(:sample, '/sample/:first/:second', :controller => 'sample')
|
49
54
|
@route_set.generator.generate(:sample, {:first => 'maz', :second => 'zoo', :third => 'zanz'}).should == '/sample/maz/zoo?third=zanz'
|
@@ -59,12 +59,6 @@ describe "Usher route recognition" do
|
|
59
59
|
@route_set.recognize(build_request({:path => '/'})).path.route.destination.should == :test
|
60
60
|
end
|
61
61
|
|
62
|
-
it "should allow adding a pure regex" do
|
63
|
-
@route_set.add_route(/\/test\/(testing|gold)/).to(:test)
|
64
|
-
@route_set.recognize(build_request({:path => '/test/testing'})).path.route.destination.should == :test
|
65
|
-
@route_set.recognize(build_request({:path => '/test/gold'})).path.route.destination.should == :test
|
66
|
-
end
|
67
|
-
|
68
62
|
it "should correctly fix that tree if conditionals are used later" do
|
69
63
|
noop_route = @route_set.add_route('/noop', :controller => 'products', :action => 'noop')
|
70
64
|
product_show_route = @route_set.add_route('/products/show/:id', :id => /\d+/, :conditions => {:method => 'get'})
|
@@ -101,7 +95,9 @@ describe "Usher route recognition" do
|
|
101
95
|
|
102
96
|
it "should recognize a format-style variable" do
|
103
97
|
target_route = @route_set.add_route('/sample.:format', :controller => 'sample', :action => 'action')
|
104
|
-
@route_set.recognize(build_request({:method => 'get', :path => '/sample.html', :domain => 'admin.host.com'}))
|
98
|
+
response = @route_set.recognize(build_request({:method => 'get', :path => '/sample.html', :domain => 'admin.host.com'}))
|
99
|
+
response.path.should == target_route.paths.first
|
100
|
+
response.params.should == [[:format, 'html']]
|
105
101
|
end
|
106
102
|
|
107
103
|
it "should recognize a glob-style variable" do
|
@@ -166,7 +162,7 @@ describe "Usher route recognition" do
|
|
166
162
|
end
|
167
163
|
|
168
164
|
it "should recgonize a regex static part containing {}'s" do
|
169
|
-
target_route = @route_set.add_route('/test/part/{
|
165
|
+
target_route = @route_set.add_route('/test/part/{oo,^o{2,3}$}')
|
170
166
|
@route_set.recognize(build_request({:method => 'get', :path => '/test/part/oo'})).path.route.should == target_route
|
171
167
|
@route_set.recognize(build_request({:method => 'get', :path => '/test/part/ooo'})).path.route.should == target_route
|
172
168
|
@route_set.recognize(build_request({:method => 'get', :path => '/test/part/oooo'})).should == nil
|
@@ -236,12 +232,16 @@ describe "Usher route recognition" do
|
|
236
232
|
|
237
233
|
it "should recognize a format-style literal" do
|
238
234
|
target_route = @route_set.add_route('/:action.html', :controller => 'sample', :action => 'action')
|
239
|
-
@route_set.recognize(build_request({:method => 'get', :path => '/sample.html', :domain => 'admin.host.com'}))
|
235
|
+
response = @route_set.recognize(build_request({:method => 'get', :path => '/sample.html', :domain => 'admin.host.com'}))
|
236
|
+
response.path.should == target_route.paths.first
|
237
|
+
response.params.should == [[:action, 'sample']]
|
240
238
|
end
|
241
239
|
|
242
240
|
it "should recognize a format-style variable along side another variable" do
|
243
241
|
target_route = @route_set.add_route('/:action.:format', :controller => 'sample', :action => 'action')
|
244
|
-
@route_set.recognize(build_request({:method => 'get', :path => '/sample.html', :domain => 'admin.host.com'}))
|
242
|
+
response = @route_set.recognize(build_request({:method => 'get', :path => '/sample.html', :domain => 'admin.host.com'}))
|
243
|
+
response.path.should == target_route.paths.first
|
244
|
+
response.params.should == [[:action, 'sample'], [:format, 'html']]
|
245
245
|
end
|
246
246
|
|
247
247
|
it "should use a requirement (proc) on incoming variables" do
|
@@ -315,14 +315,14 @@ describe "Usher route recognition" do
|
|
315
315
|
|
316
316
|
@route_set.recognize(build_request({:method => 'post', :protocol => 'https', :path => '/foo'})).path.route.should == route_higher
|
317
317
|
|
318
|
-
@route_set.
|
318
|
+
@route_set.reset!
|
319
319
|
|
320
320
|
route_higher = @route_set.add_route("/foo", :conditions => {:protocol => 'https'}, :priority => 2)
|
321
321
|
route_lower = @route_set.add_route("/foo", :conditions => {:method => 'post'}, :priority => 1)
|
322
322
|
|
323
323
|
@route_set.recognize(build_request({:method => 'post', :protocol => 'https', :path => '/foo'})).path.route.should == route_higher
|
324
324
|
|
325
|
-
@route_set.
|
325
|
+
@route_set.reset!
|
326
326
|
end
|
327
327
|
|
328
328
|
it "should only match the specified path of the route when a condition is specified" do
|