http_router 0.3.13 → 0.3.14

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 CHANGED
@@ -1,13 +1,5 @@
1
1
  = HTTP Router
2
2
 
3
- == Introduction
4
-
5
- When I wrote Usher, I made a few compromises in design that I wasn't totally happy with. More and more features got added to it, and eventually, it became harder to maintain. I took a few moments to work in Node.js, and wrote a router there called Sherpa, which I was happier with. But I felt that by losing more abstraction, and tackling just the problem of HTTP routing, I could come up with something even better.
6
-
7
- == Warning
8
-
9
- This is very new code. Lots of stuff probably doesn't work right. I will likely never support all the features I had in Usher. Documentation is super-sparse.
10
-
11
3
  == Features
12
4
 
13
5
  * Supports variables, and globbing, both named and unnamed.
data/benchmarks/rec2.rb CHANGED
@@ -2,9 +2,10 @@ require 'rubygems'
2
2
  require 'rbench'
3
3
 
4
4
  $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
5
-
6
5
  require 'lib/http_router'
7
6
 
7
+ #require 'http_router'
8
+
8
9
  u = HttpRouter.new
9
10
  u.add('/simple').to {|env| [200, {'Content-type'=>'text/html'}, []]}
10
11
  u.add('/simple/again').compile.to {|env| [200, {'Content-type'=>'text/html'}, []]}
@@ -1,7 +1,8 @@
1
1
  class HttpRouter
2
2
  class Glob < Variable
3
3
  def matches?(parts)
4
- @matches_with.nil? or (!parts.empty? and match = @matches_with.match(parts.first) and match.begin(0)) ? match : nil
4
+ return if @matches_with.nil? or parts.empty? or !match.begin(0)
5
+ @matches_with.match(parts.first)
5
6
  end
6
7
 
7
8
  def consume(match, parts)
@@ -1,4 +1,3 @@
1
- $LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..')
2
1
  require 'http_router'
3
2
 
4
3
  class HttpRouter
@@ -88,7 +88,7 @@ class HttpRouter
88
88
  end
89
89
 
90
90
  def generate_request_method_tree(request_options)
91
- raise(UnsupportedRequestConditionError.new) if (request_options.keys & RequestNode::RequestMethods).size != request_options.size
91
+ raise UnsupportedRequestConditionError if (request_options.keys & RequestNode::RequestMethods).size != request_options.size
92
92
  current_nodes = [self]
93
93
  RequestNode::RequestMethods.each do |method|
94
94
  if request_options.key?(method) # so, the request method we care about it ..
@@ -136,47 +136,44 @@ class HttpRouter
136
136
 
137
137
  def find_on_parts(request, parts, params)
138
138
  if parts and !parts.empty?
139
- if parts.size == 1 and parts.first == ''
140
- potential_match = find_on_parts(request, [], params)
141
- if potential_match and (router.ignore_trailing_slash? or potential_match.value && potential_match.value.route.trailing_slash_ignore?)
142
- parts.shift
143
- return potential_match
144
- end
145
- end
139
+ return potential if potential = potential_match(request, parts, params)
146
140
  if @linear && !@linear.empty?
147
141
  response = nil
148
142
  dupped_parts = nil
143
+ dupped_params = nil
149
144
  next_node = @linear.find do |(tester, node)|
150
145
  if tester.respond_to?(:matches?) and match = tester.matches?(parts)
151
146
  dupped_parts = parts.dup
152
- params.push((val = tester.consume(match, dupped_parts) and val.is_a?(Array)) ? val.map{|v| HttpRouter.uri_unescape(v)} : HttpRouter.uri_unescape(val))
153
- parts.replace(dupped_parts) if response = node.find_on_parts(request, dupped_parts, params)
147
+ dupped_params = params.dup
148
+ dupped_params.push((val = tester.consume(match, dupped_parts) and val.is_a?(Array)) ? val.map{|v| HttpRouter.uri_unescape(v)} : HttpRouter.uri_unescape(val))
149
+ parts.replace(dupped_parts) if response = node.find_on_parts(request, dupped_parts, dupped_params)
154
150
  elsif tester.respond_to?(:match) and match = tester.match(parts.whole_path) and match.begin(0) == 0
155
151
  dupped_parts = router.split(parts.whole_path[match[0].size, parts.whole_path.size])
156
- parts.replace(dupped_parts) if response = node.find_on_parts(request, dupped_parts, params)
152
+ dupped_params = params.dup
153
+ parts.replace(dupped_parts) if response = node.find_on_parts(request, dupped_parts, dupped_params)
157
154
  else
158
155
  nil
159
156
  end
160
157
  end
161
- return response if response
158
+ if response
159
+ params.replace(dupped_params)
160
+ return response
161
+ end
162
162
  end
163
163
  if match = @lookup && @lookup[parts.first]
164
- parts.shift
165
- return match.find_on_parts(request, parts, params)
166
- elsif @catchall
164
+ part = parts.shift
165
+ if match = match.find_on_parts(request, parts, params)
166
+ return match
167
+ else
168
+ parts.unshift(part)
169
+ end
170
+ end
171
+ if @catchall
167
172
  params.push((val = @catchall.variable.consume(nil, parts) and val.is_a?(Array)) ? val.map{|v| HttpRouter.uri_unescape(v)} : HttpRouter.uri_unescape(val))
168
173
  return @catchall.find_on_parts(request, parts, params)
169
174
  end
170
175
  end
171
- if request_node
172
- request_node.find_on_request_methods(request)
173
- elsif arbitrary_node
174
- arbitrary_node.find_on_arbitrary(request)
175
- elsif @value
176
- self
177
- else
178
- nil
179
- end
176
+ request_node and request_node.find_on_request_methods(request) or resolve_node(request)
180
177
  end
181
178
 
182
179
  def create_linear
@@ -186,57 +183,60 @@ class HttpRouter
186
183
  def create_lookup
187
184
  @lookup ||= {}
188
185
  end
186
+
187
+ protected
188
+ def resolve_node(request)
189
+ if arbitrary_node
190
+ arbitrary_node.find_on_arbitrary(request)
191
+ elsif @value
192
+ self
193
+ else
194
+ nil
195
+ end
196
+ end
197
+
198
+ def potential_match(request, parts, params)
199
+ if parts.size == 1 and parts.first == ''
200
+ potential = find_on_parts(request, [], params)
201
+ if potential and (router.ignore_trailing_slash? or potential.value && potential.value.route.trailing_slash_ignore?)
202
+ parts.shift
203
+ potential
204
+ end
205
+ end
206
+ nil
207
+ end
189
208
  end
190
209
 
191
210
  class ArbitraryNode < Node
192
211
  def find_on_arbitrary(request)
193
- if @linear && !@linear.empty?
194
- next_node = @linear.find do |(procs, node)|
195
- procs.all?{|p| p.call(request)}
196
- end
197
- return next_node.last if next_node
198
- end
199
- @catchall
212
+ next_node = @linear && !@linear.empty? && @linear.find { |(procs, node)| procs.all?{|p| p.call(request)} }
213
+ next_node && next_node.last || @catchall
200
214
  end
201
215
  end
202
216
 
203
217
  class RequestNode < Node
204
- RequestMethods = [:request_method, :host, :port, :scheme, :user_agent, :ip, :fullpath, :query_string]
218
+ RequestMethods = [:request_method, :host, :port, :scheme, :user_agent, :ip, :fullpath, :query_string].freeze
205
219
  attr_accessor :request_method
206
-
207
- def find_on_request_methods(request)
208
- if @request_method
209
- request_method_satisfied = false
220
+ def find_on_request_methods(request)
221
+ next_node = if @request_method
210
222
  request_value = request.send(request_method)
223
+ linear_node(request, request_value) or lookup_node(request, request_value) or catchall_node(request)
224
+ end
225
+ next_node or resolve_node(request)
226
+ end
227
+ private
228
+ def linear_node(request, request_value)
211
229
  if @linear && !@linear.empty?
212
- next_node = @linear.find do |(regexp, node)|
213
- regexp === request_value
214
- end
215
- request_method_satisfied = true if next_node
216
- next_node &&= next_node.last.find_on_request_methods(request)
217
- return next_node if next_node
218
- end
219
- if @lookup and next_node = @lookup[request_value]
220
- request_method_satisfied = true
221
- next_node = next_node.find_on_request_methods(request)
222
- return next_node if next_node
223
- end
224
- if @catchall
225
- request_method_satisfied = true
226
- next_node = @catchall.find_on_request_methods(request)
227
- return next_node if next_node
230
+ node = @linear.find { |(regexp, node)| regexp === request_value }
231
+ node.last.find_on_request_methods(request) if node
228
232
  end
229
233
  end
230
-
231
- if @arbitrary_node
232
- @arbitrary_node.find_on_arbitrary(request)
233
- elsif @value
234
- self
235
- else
236
- nil
234
+ def lookup_node(request, request_value)
235
+ @lookup[request_value].find_on_request_methods(request) if @lookup and @lookup[request_value]
236
+ end
237
+ def catchall_node(request)
238
+ @catchall.find_on_request_methods(request) if @catchall
237
239
  end
238
- end
239
-
240
240
  end
241
241
 
242
242
  end
@@ -3,9 +3,9 @@ class HttpRouter
3
3
  attr_reader :parts, :route, :splitting_indexes
4
4
  def initialize(route, path, parts, splitting_indexes)
5
5
  @route, @path, @parts, @splitting_indexes = route, path, parts, splitting_indexes
6
- if duplicate_variable_names = variable_names.dup.uniq!
7
- raise AmbiguousVariableException.new("You have duplicate variable name present: #{duplicate_variable_names.join(', ')}")
8
- end
6
+
7
+ duplicate_variable_names = variable_names.dup.uniq!
8
+ raise AmbiguousVariableException, "You have duplicate variable name present: #{duplicate_variable_names.join(', ')}" if duplicate_variable_names
9
9
 
10
10
  @path_validation_regex = path.split(/([:\*][a-zA-Z0-9_]+)/).map{ |part|
11
11
  case part[0]
@@ -17,7 +17,7 @@ class HttpRouter
17
17
  }.join
18
18
  @path_validation_regex = Regexp.new("^#{@path_validation_regex}$")
19
19
 
20
- eval_path = path.gsub(/[:\*]([a-zA-Z0-9_]+)/) {"\#{args.shift || (options && options.delete(:#{$1})) || raise(MissingParameterException.new(\"missing parameter #{$1}\"))}" }
20
+ eval_path = path.gsub(/[:\*]([a-zA-Z0-9_]+)/) {"\#{args.shift || (options && options.delete(:#{$1})) || raise(MissingParameterException, \"missing parameter #{$1}\")}" }
21
21
  instance_eval "
22
22
  def raw_url(args,options)
23
23
  \"#{eval_path}\"
@@ -35,17 +35,16 @@ class HttpRouter
35
35
 
36
36
  def compare_parts(p1, p2)
37
37
  case p1
38
- when Glob then p2.is_a?(Glob)
38
+ when Glob then p2.is_a?(Glob)
39
39
  when Variable then p2.is_a?(Variable)
40
- else
41
- p1 == p2
40
+ else p1 == p2
42
41
  end
43
42
  end
44
43
 
45
44
  def url(args, options)
46
45
  path = raw_url(args, options)
47
- raise InvalidRouteException.new if path !~ @path_validation_regex
48
- raise TooManyParametersException.new unless args.empty?
46
+ raise InvalidRouteException if path !~ @path_validation_regex
47
+ raise TooManyParametersException unless args.empty?
49
48
  HttpRouter.uri_escape!(path)
50
49
  generate_querystring(path, options)
51
50
  path
@@ -190,7 +190,7 @@ class HttpRouter
190
190
  @paths = compile_paths
191
191
  @paths.each_with_index do |p1, i|
192
192
  @paths[i+1, @paths.size].each do |p2|
193
- raise AmbiguousRouteException.new if p1 === p2
193
+ raise AmbiguousRouteException if p1 === p2
194
194
  end
195
195
  end
196
196
  @paths.each do |path|
@@ -207,7 +207,7 @@ class HttpRouter
207
207
 
208
208
  # Sets the destination of this route to redirect to an arbitrary URL.
209
209
  def redirect(path, status = 302)
210
- raise(ArgumentError, "Status has to be an integer between 300 and 399") unless (300..399).include?(status)
210
+ raise ArgumentError, "Status has to be an integer between 300 and 399" unless (300..399).include?(status)
211
211
  to { |env|
212
212
  params = env['router.params']
213
213
  response = ::Rack::Response.new
@@ -247,7 +247,7 @@ class HttpRouter
247
247
  else
248
248
  matching_path(args, options)
249
249
  end
250
- raise UngeneratableRouteException.new unless path
250
+ raise UngeneratableRouteException unless path
251
251
 
252
252
  mount_point = nil
253
253
  if !router.url_mount.nil?
@@ -363,7 +363,7 @@ class HttpRouter
363
363
  end
364
364
 
365
365
  def guard_compiled
366
- raise AlreadyCompiledException.new if compiled?
366
+ raise AlreadyCompiledException if compiled?
367
367
  end
368
368
 
369
369
  def significant_variable_names
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  class HttpRouter #:nodoc
3
- VERSION = '0.3.13'
3
+ VERSION = '0.3.14'
4
4
  end
data/lib/http_router.rb CHANGED
@@ -170,7 +170,7 @@ class HttpRouter
170
170
  when Symbol
171
171
  url(@named_routes[route], *args)
172
172
  when nil
173
- raise UngeneratableRouteException.new
173
+ raise UngeneratableRouteException
174
174
  else
175
175
  route.url(*args)
176
176
  end
@@ -223,6 +223,13 @@ describe "HttpRouter#recognize" do
223
223
  @router.recognize(Rack::MockRequest.env_for('/foo/id')).params_as_hash[:$1].should == 'id'
224
224
  end
225
225
 
226
+ it "should use a static part as a variable if no further match is available" do
227
+ @router.add("/foo/foo").to(:test1)
228
+ @router.add("/:foo/foo2").to(:test2)
229
+ @router.recognize(Rack::MockRequest.env_for('/foo/foo')).dest.should == :test1
230
+ @router.recognize(Rack::MockRequest.env_for('/foo/foo2')).dest.should == :test2
231
+ end
232
+
226
233
  it "should recognize /foo/:/: and map it to $1 and $2" do
227
234
  @router.add("/foo/:/:").to(:test2)
228
235
  @router.recognize(Rack::MockRequest.env_for('/foo/id/what')).dest.should == :test2
@@ -400,5 +407,43 @@ describe "HttpRouter#recognize" do
400
407
  @router.recognize(Rack::MockRequest.env_for('/test.html')).params.first.should == 'test.html'
401
408
  end
402
409
 
410
+ # BUG: http://gist.github.com/554909
411
+ context "when there is an additional route for the case of regex matching failure" do
412
+ before :each do
413
+ @matched = @router.add("/:common_variable/:matched").matching(:matched => /\d+/).to(:something)
414
+ @unmatched = @router.add("/:common_variable/:unmatched").to(:something_unmatched)
415
+ end
416
+
417
+ it "should use main route if pattern is matched" do
418
+ response = @router.recognize(Rack::MockRequest.env_for('/common/123'))
419
+ response.route.should == @matched
420
+ response.params.should == ['common', '123']
421
+ end
422
+
423
+ it "should use additional route if pattern is not matched" do
424
+ response = @router.recognize(Rack::MockRequest.env_for('/common/other'))
425
+ response.route.should == @unmatched
426
+ response.params.should == ['common', 'other']
427
+ end
428
+
429
+ context "when delimiter is not a slash" do
430
+ before :each do
431
+ @matched = @router.add("/:common_variable.:matched").matching(:matched => /\d+/).to(:something)
432
+ @unmatched = @router.add("/:common_variable.:unmatched").to(:something_unmatched)
433
+ end
434
+
435
+ it "should use main route if pattern is matched" do
436
+ response = @router.recognize(Rack::MockRequest.env_for('/common.123'))
437
+ response.route.should == @matched
438
+ response.params.should == ['common', '123']
439
+ end
440
+
441
+ it "should use additional route if pattern is not matched" do
442
+ response = @router.recognize(Rack::MockRequest.env_for('/common.other'))
443
+ response.route.should == @unmatched
444
+ response.params.should == ['common', 'other']
445
+ end
446
+ end
447
+ end
403
448
  end
404
449
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http_router
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 13
10
- version: 0.3.13
9
+ - 14
10
+ version: 0.3.14
11
11
  platform: ruby
12
12
  authors:
13
13
  - Joshua Hull
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-20 00:00:00 -07:00
18
+ date: 2010-09-01 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency