http_router 0.3.13 → 0.3.14

Sign up to get free protection for your applications and to get access to all the features.
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