usher 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- variable_type = variable.slice!(0).chr.to_sym
130
- variable_class = case variable_type
131
- when :'!' then Usher::Route::Variable::Greedy
132
- when :* then Usher::Route::Variable::Glob
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, regex, requirements && requirements[variable_name])
134
+ current_group << variable_class.new(variable_name, Regexp.new(pattern), requirements && requirements[variable_name])
137
135
  elsif simple
138
- current_group << Usher::Route::Static::Greedy.new(Regexp.new(pattern))
136
+ static = Usher::Route::Static::Greedy.new(pattern)
137
+ static.generate_with = pattern
138
+ current_group << static
139
139
  else
140
- current_group << regex
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, :priority_lookups,
16
- :parent_route, :generator, :grapher
27
+ :delimiters, :delimiters_regex, :parent_route, :generator, :grapher, :parser
17
28
  attr_accessor :route_class
18
29
 
19
- alias_method :priority_lookups?, :priority_lookups
20
-
21
- # Returns whether the route set is empty
22
- #
23
- # set = Usher.new
24
- # set.empty? => true
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
- # Returns the number of routes
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
- # set = Usher.new
40
- # set.add_route('/test')
41
- # set.empty? => false
42
- # set.reset!
43
- # set.empty? => true
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
- # <tt>:delimiters</tt>: Array of Strings. (default <tt>['/', '.']</tt>). Delimiters used in path separation. Array must be single character strings.
56
- #
57
- # <tt>:valid_regex</tt>: String. (default <tt>'[0-9A-Za-z\$\-_\+!\*\',]+'</tt>). String that can be interpolated into regex to match
58
- # valid character sequences within path.
59
- #
60
- # <tt>:request_methods</tt>: Array of Symbols. (default <tt>[:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]</tt>)
61
- # Array of methods called against the request object for the purposes of matching route requirements.
62
- #
63
- # <tt>:generator</tt>: +nil+ or Generator instance. (default: +nil+) Take a look at <tt>Usher::Util::Generators for examples.</tt>.
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
- def can_generate?
103
- !@generator.nil?
103
+ # @return [Boolean] State of priority_lookups feature.
104
+ def priority_lookups?
105
+ @priority_lookups
104
106
  end
105
107
 
106
- def generator
107
- @generator
108
+ # @return [Boolean] Able to generate
109
+ def can_generate?
110
+ !generator.nil?
108
111
  end
109
112
 
110
- # Adds a route referencable by +name+. See add_route for format +path+ and +options+.
111
- #
112
- # set = Usher.new
113
- # set.add_named_route(:test_route, '/test')
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 +name+. At least the path and conditions have to match the route you intend to delete.
119
- #
120
- # set = Usher.new
121
- # set.delete_named_route(:test_route, '/test')
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 +route+ to a +name+
128
- #
129
- # set = Usher.new
130
- # route = set.add_route('/test')
131
- # set.name(:test, route)
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 +path+ and +options+
138
- #
139
- # === +path+
140
- # A path consists a mix of dynamic and static parts delimited by <tt>/</tt>
141
- #
142
- # ==== Dynamic
143
- # Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or
144
- # more parts.
145
- #
146
- # <b>Example:</b>
147
- # <tt>/path/:variable/path</tt> would match
148
- #
149
- # * <tt>/path/test/path</tt>
150
- # * <tt>/path/something_else/path</tt>
151
- # * <tt>/path/one_more/path</tt>
152
- #
153
- # In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key <tt>:variable</tt>.
154
- # However, <tt>/path/test/one_more/path</tt> would not be matched.
155
- #
156
- # <b>Example:</b>
157
- # <tt>/path/*variable/path</tt> would match
158
- #
159
- # * <tt>/path/one/two/three/path</tt>
160
- # * <tt>/path/four/five/path</tt>
161
- #
162
- # In the above examples, ['one', 'two', 'three'] and ['four', 'five'] respectively would be bound to the key :variable.
163
- #
164
- # As well, variables can have a regex matcher.
165
- #
166
- # <b>Example:</b>
167
- # <tt>/product/{:id,\d+}</tt> would match
168
- #
169
- # * <tt>/product/123</tt>
170
- # * <tt>/product/4521</tt>
171
- #
172
- # But not
173
- # * <tt>/product/AE-35</tt>
174
- #
175
- # As well, the same logic applies for * variables as well, where only parts matchable by the supplied regex will
176
- # actually be bound to the variable
177
- #
178
- # Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
179
- # regex allows.
180
- #
181
- # <b>Example:</b>
182
- # <tt>/product/{!id,hello/world|hello}</tt> would match
183
- #
184
- # * <tt>/product/hello/world</tt>
185
- # * <tt>/product/hello</tt>
186
- #
187
- # ==== Static
188
- #
189
- # Static parts of literal character sequences. For instance, <tt>/path/something.html</tt> would match only the same path.
190
- # As well, static parts can have a regex pattern in them as well, such as <tt>/path/something.{html|xml}</tt> which would match only
191
- # <tt>/path/something.html</tt> and <tt>/path/something.xml</tt>
192
- #
193
- # ==== Optional sections
194
- #
195
- # Sections of a route can be marked as optional by surrounding it with brackets. For instance, in the above static example, <tt>/path/something(.html)</tt> would match both <tt>/path/something</tt> and <tt>/path/something.html</tt>.
196
- #
197
- # ==== One and only one sections
198
- #
199
- # 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.
200
- # For instance, the path, <tt>/path/something(.xml|.html)</tt> would only match <tt>/path/something.xml</tt> and
201
- # <tt>/path/something.html</tt>. Generally its more efficent to use one and only sections over using regex.
202
- #
203
- # === +options+
204
- # * +requirements+ - After transformation, tests the condition using ===. If it returns false, it raises an <tt>Usher::ValidationException</tt>
205
- # * +conditions+ - Accepts any of the +request_methods+ specificied in the construction of Usher. This can be either a <tt>string</tt> or a regular expression.
206
- # * +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.
207
- # * +priority+ - If there are two routes which equally match, the route with the highest priority will match first.
208
- # * Any other key is interpreted as a requirement for the variable of its name.
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
- # set = Usher.new
221
- # set.delete_route('/test')
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
- @routes = root.unique_routes
226
- rebuild_grapher!
242
+ routes.replace(root.unique_routes)
243
+ build_grapher!
227
244
  route
228
245
  end
229
246
 
230
- # 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.
231
- #
232
- # Request = Struct.new(:path)
233
- # set = Usher.new
234
- # route = set.add_route('/test')
235
- # set.recognize(Request.new('/test')).path.route == route => true
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
- root.find(request, path, splitter.split(path))
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 +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.
241
- #
242
- # Request = Struct.new(:path)
243
- # set = Usher.new
244
- # route = set.add_route('/test')
245
- # set.recognize_path('/test').path.route == route => true
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 +parameters+ and gets the closest matching Usher::Route::Path or +nil+ if no route exists.
251
- #
252
- # set = Usher.new
253
- # route = set.add_route('/:controller/:action')
254
- # set.path_for_options({:controller => 'test', :action => 'action'}) == path.route => true
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
- rebuild_grapher!
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.inspect, consider_destination_keys.inspect, can_generate?.inspect, priority_lookups.inspect]
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.select{|k| request_methods.include?(k)}.join(", ")}")
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
- def rebuild_grapher!
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'})).should == Usher::Node::Response.new(target_route.paths.first, ['html'], nil, "/sample.html")
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/{^o{2,3}$}')
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'})).should == Usher::Node::Response.new(target_route.paths.first, ['sample'], nil, "/sample.html")
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'})).should == Usher::Node::Response.new(target_route.paths.first, ['sample', 'html'], nil, '/sample.html')
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.clear!
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.clear!
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