http_router 0.1.1 → 0.1.2

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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ benchmarks
2
+ pkg
data/Rakefile CHANGED
@@ -1,18 +1,3 @@
1
- begin
2
- require 'jeweler'
3
- Jeweler::Tasks.new do |s|
4
- s.name = "http_router"
5
- s.description = s.summary = "A kick-ass HTTP router for use in Rack & Sinatra"
6
- s.email = "joshbuddy@gmail.com"
7
- s.homepage = "http://github.com/joshbuddy/http_router"
8
- s.authors = ["Joshua Hull"]
9
- s.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
10
- end
11
- Jeweler::GemcutterTasks.new
12
- rescue LoadError
13
- puts "Jeweler not available. Install it with: gem install jeweler"
14
- end
15
-
16
1
  require 'spec'
17
2
  require 'spec/rake/spectask'
18
3
  Spec::Rake::SpecTask.new(:spec) do |t|
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{http_router}
5
+ s.version = File.read(File.join(File.dirname(__FILE__), 'VERSION')).strip
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Joshua Hull"]
9
+ s.date = %q{2010-05-30}
10
+ s.description = %q{A kick-ass HTTP router for use in Rack & Sinatra}
11
+ s.email = %q{joshbuddy@gmail.com}
12
+ s.extra_rdoc_files = [
13
+ "README.rdoc"
14
+ ]
15
+ s.files = `git ls-files`.split("\n")
16
+ s.homepage = %q{http://github.com/joshbuddy/http_router}
17
+ s.rdoc_options = ["--charset=UTF-8"]
18
+ s.require_paths = ["lib"]
19
+ s.rubygems_version = %q{1.3.7}
20
+ s.summary = %q{A kick-ass HTTP router for use in Rack & Sinatra}
21
+ s.test_files = `git ls-files spec`.split("\n")
22
+
23
+ # dependencies
24
+ s.add_dependency "rack", ">= 1.0.0"
25
+
26
+ if s.respond_to? :specification_version then
27
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
28
+ s.specification_version = 3
29
+
30
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
31
+ else
32
+ end
33
+ else
34
+ end
35
+ end
36
+
@@ -6,7 +6,6 @@ class HttpRouter
6
6
  while !parts.empty? and match = @matches_with.match(parts.first)
7
7
  params << parts.shift
8
8
  end
9
- return unless additional_matchers(env, params)
10
9
  whole_path.replace(parts.join('/'))
11
10
  params
12
11
  else
@@ -1,7 +1,7 @@
1
1
  class HttpRouter
2
2
  class Node
3
3
  attr_accessor :value, :variable, :catchall
4
- attr_reader :linear, :lookup, :request_node, :extension_node
4
+ attr_reader :linear, :lookup, :request_node, :arbitrary_node
5
5
 
6
6
  def initialize(base)
7
7
  @router = base
@@ -36,11 +36,6 @@ class HttpRouter
36
36
  end
37
37
  end
38
38
 
39
- def add_extension(ext)
40
- @extension_node ||= router.node
41
- @extension_node.add(ext)
42
- end
43
-
44
39
  def add_request_methods(options)
45
40
  if !options.empty?
46
41
  generate_request_method_tree(options)
@@ -55,6 +50,24 @@ class HttpRouter
55
50
  end
56
51
  end
57
52
 
53
+ def add_arbitrary(procs)
54
+ target = self
55
+ if procs && !procs.empty?
56
+ @arbitrary_node ||= router.arbitrary_node
57
+ @arbitrary_node.create_linear
58
+ target = router.node
59
+ @arbitrary_node.linear << [procs, target]
60
+ if @value
61
+ @arbitrary_node.catchall = router.node
62
+ @arbitrary_node.catchall.value = @value
63
+ @value = nil
64
+ end
65
+ elsif @arbitrary_node
66
+ target = @arbitrary_node.catchall = router.node
67
+ end
68
+ target
69
+ end
70
+
58
71
  protected
59
72
 
60
73
  attr_reader :router
@@ -120,45 +133,44 @@ class HttpRouter
120
133
  current_nodes
121
134
  end
122
135
 
123
- def find_on_parts(request, parts, extension, params)
124
- if parts.empty? && extension_node && extension
125
- parts << extension
126
- extension_node.find_on_parts(request, parts, extension, params)
127
- else
128
- if @linear && !@linear.empty?
129
- whole_path = parts.join('/')
130
- next_node = @linear.find do |(tester, node)|
131
- if tester.is_a?(Regexp) and match = whole_path.match(tester) #and match.index == 0 TODO
132
- whole_path.slice!(0,match[0].size)
133
- parts.replace(router.split(whole_path))
134
- node
135
- elsif new_params = tester.matches(request.env, parts, whole_path)
136
- params << new_params
137
- node
138
- else
139
- nil
140
- end
136
+ def find_on_parts(request, parts, params)
137
+ if @linear && !@linear.empty?
138
+ whole_path = parts.join('/')
139
+ next_node = @linear.find do |(tester, node)|
140
+ if tester.is_a?(Regexp) and match = tester.match(whole_path) #and match.index == 0 TODO
141
+ whole_path.slice!(0,match[0].size)
142
+ parts.replace(router.split(whole_path))
143
+ node
144
+ elsif tester.respond_to?(:matches) and new_params = tester.matches(request.env, parts, whole_path)
145
+ params << new_params
146
+ node
147
+ else
148
+ nil
141
149
  end
142
- return next_node.last.find_on_parts(request, parts, extension, params) if next_node
143
150
  end
144
- if match = @lookup && @lookup[parts.first]
145
- parts.shift
146
- match.find_on_parts(request, parts, extension, params)
147
- elsif @catchall
148
- params << @catchall.variable.matches(request.env, parts, whole_path)
149
- parts.shift
150
- @catchall.find_on_parts(request, parts, extension, params)
151
- elsif parts.size == 1 && parts.first == '' && (value && value.route.trailing_slash_ignore?)
152
- parts.shift
153
- find_on_parts(request, parts, extension, params)
154
- elsif request_node
155
- request_node.find_on_request_methods(request)
156
- elsif @value
157
- self
158
- else
159
- nil
151
+ if next_node and match = next_node.last.find_on_parts(request, parts, params)
152
+ return match
160
153
  end
161
154
  end
155
+ if match = @lookup && @lookup[parts.first]
156
+ parts.shift
157
+ match.find_on_parts(request, parts, params)
158
+ elsif @catchall
159
+ params << @catchall.variable.matches(request.env, parts, whole_path)
160
+ parts.shift
161
+ @catchall.find_on_parts(request, parts, params)
162
+ elsif parts.size == 1 && parts.first == '' && (value && value.route.trailing_slash_ignore? || router.ignore_trailing_slash?)
163
+ parts.shift
164
+ find_on_parts(request, parts, params)
165
+ elsif request_node
166
+ request_node.find_on_request_methods(request)
167
+ elsif arbitrary_node
168
+ arbitrary_node.find_on_arbitrary(request)
169
+ elsif @value
170
+ self
171
+ else
172
+ nil
173
+ end
162
174
  end
163
175
 
164
176
  def create_linear
@@ -169,6 +181,18 @@ class HttpRouter
169
181
  @lookup ||= {}
170
182
  end
171
183
  end
184
+
185
+ class ArbitraryNode < Node
186
+ def find_on_arbitrary(request)
187
+ if @linear && !@linear.empty?
188
+ next_node = @linear.find do |(procs, node)|
189
+ procs.all?{|p| p.call(request)}
190
+ end
191
+ return next_node.last if next_node
192
+ end
193
+ @catchall
194
+ end
195
+ end
172
196
 
173
197
  class RequestNode < Node
174
198
  RequestMethods = [:request_method, :host, :port, :scheme]
@@ -191,7 +215,9 @@ class HttpRouter
191
215
  end
192
216
  end
193
217
 
194
- if @value
218
+ if @arbitrary_node
219
+ @arbitrary_node.find_on_arbitrary(request)
220
+ elsif @value
195
221
  self
196
222
  else
197
223
  current_node = request_method == :request_method ? Response.unmatched(405, {"Allow" => @lookup.keys.join(", ")}) : nil
@@ -1,10 +1,10 @@
1
1
  require 'cgi'
2
2
  class HttpRouter
3
3
  class Path
4
- attr_reader :parts, :extension
4
+ attr_reader :parts
5
5
  attr_accessor :route
6
- def initialize(path, parts, extension)
7
- @path, @parts, @extension = path, parts, extension
6
+ def initialize(path, parts)
7
+ @path, @parts = path, parts
8
8
  if duplicate_variable_names = variable_names.dup.uniq!
9
9
  raise AmbiguousVariableException.new("You have duplicate variable name present: #{duplicate_variable_names.join(', ')}")
10
10
  end
@@ -22,7 +22,7 @@ class HttpRouter
22
22
  @parts.each_with_index {|p,i|
23
23
  return unless compare_parts(p, other_path.parts[i])
24
24
  }
25
- compare_parts(@extension, other_path.extension)
25
+ true
26
26
  end
27
27
 
28
28
  def compare_parts(p1, p2)
@@ -60,7 +60,6 @@ class HttpRouter
60
60
  def variables
61
61
  unless @variables
62
62
  @variables = @parts.select{|p| p.is_a?(Variable)}
63
- @variables << @extension if @extension.is_a?(Variable)
64
63
  end
65
64
  @variables
66
65
  end
@@ -68,9 +67,5 @@ class HttpRouter
68
67
  def variable_names
69
68
  @variable_names ||= variables.map{|v| v.name}
70
69
  end
71
-
72
- def matches_extension?(extension)
73
- @extension.nil? || @extension === (extension)
74
- end
75
70
  end
76
71
  end
@@ -15,14 +15,13 @@ class HttpRouter
15
15
  end
16
16
  end
17
17
 
18
- class Matched < Struct.new(:path, :params, :extension, :matched_path, :remaining_path)
18
+ class Matched < Struct.new(:path, :params, :matched_path, :remaining_path)
19
19
  attr_reader :params_as_hash, :route
20
20
 
21
- def initialize(path, params, extension, matched_path, remaining_path)
21
+ def initialize(path, params, matched_path, remaining_path = nil)
22
22
  raise if matched_path.nil?
23
23
  super
24
24
  @params_as_hash = path.variable_names.zip(params).inject({}) {|h, (k,v)| h[k] = v; h }
25
- @params_as_hash[path.extension.name] = extension if path.extension && path.extension.is_a?(Variable)
26
25
  end
27
26
 
28
27
  def matched?
@@ -2,38 +2,32 @@ class HttpRouter
2
2
  class Root < Node
3
3
  def add_path(path)
4
4
  node = path.parts.inject(self) { |node, part| node.add(part) }
5
- if path.extension
6
- node = node.add_extension(path.extension)
7
- end
8
5
  node
9
6
  end
10
7
 
11
8
  def find(request)
12
9
  path = request.path_info.dup
13
- path.slice!(-1) if router.ignore_trailing_slash? && path[-1] == ?/
14
- extension = extract_extension(path)
15
10
  parts = router.split(path)
16
11
  parts << '' if path[path.size - 1] == ?/
17
12
  params = []
18
13
  process_response(
19
- find_on_parts(request, parts, extension, params),
14
+ find_on_parts(request, parts, params),
20
15
  parts,
21
- extension,
22
16
  params,
23
17
  request
24
18
  )
25
19
  end
26
20
 
27
21
  private
28
- def process_response(node, parts, extension, params, request)
22
+ def process_response(node, parts, params, request)
29
23
  if node.respond_to?(:matched?) && !node.matched?
30
24
  node
31
25
  elsif node && node.value
32
26
  if parts.empty?
33
- post_match(node.value, params, extension, request.path_info)
27
+ Response.matched(node.value, params, request.path_info)
34
28
  elsif node.value.route.partially_match?
35
- rest = '/' << parts.join('/') << (extension ? ".#{extension}" : '')
36
- post_match(node.value, params, nil, request.path_info[0, request.path_info.size - rest.size], rest)
29
+ rest = '/' << parts.join('/')
30
+ Response.matched(node.value, params, request.path_info[0, request.path_info.size - rest.size], rest)
37
31
  else
38
32
  nil
39
33
  end
@@ -41,21 +35,5 @@ class HttpRouter
41
35
  nil
42
36
  end
43
37
  end
44
-
45
- def extract_extension(path)
46
- if path.gsub!(/\.([^\/\.]+)$/, '')
47
- extension = $1
48
- else
49
- nil
50
- end
51
- end
52
-
53
- def post_match(path, params, extension, matched_path, remaining_path = nil)
54
- if path.route.partially_match? || path.matches_extension?(extension)
55
- Response.matched(path, params, extension, matched_path, remaining_path)
56
- else
57
- nil
58
- end
59
- end
60
38
  end
61
39
  end
@@ -9,9 +9,8 @@ class HttpRouter
9
9
  @original_path = path.dup
10
10
  @partially_match = extract_partial_match(path)
11
11
  @trailing_slash_ignore = extract_trailing_slash(path)
12
- @variable_store = {}
13
12
  @matches_with = {}
14
- @additional_matchers = {}
13
+ @arbitrary = []
15
14
  @conditions = {}
16
15
  @default_values = {}
17
16
  end
@@ -96,13 +95,9 @@ class HttpRouter
96
95
  match.each do |var_name, matchers|
97
96
  matchers = Array(matchers)
98
97
  matchers.each do |m|
99
- if m.respond_to?(:call)
100
- (@additional_matchers[var_name] ||= []) << m
101
- else
102
- @matches_with.key?(var_name) ?
103
- raise :
104
- @matches_with[var_name] = m
105
- end
98
+ @matches_with.key?(var_name) ?
99
+ raise :
100
+ @matches_with[var_name] = m
106
101
  end
107
102
  end
108
103
  self
@@ -122,6 +117,11 @@ class HttpRouter
122
117
  @partially_match = match
123
118
  self
124
119
  end
120
+
121
+ def arbitrary(proc = nil, &block)
122
+ @arbitrary << (proc || block)
123
+ self
124
+ end
125
125
 
126
126
  def compiled?
127
127
  !@paths.nil?
@@ -139,6 +139,7 @@ class HttpRouter
139
139
  path.route = self
140
140
  current_node = router.root.add_path(path)
141
141
  working_set = current_node.add_request_methods(@conditions)
142
+ working_set.map!{|node| node.add_arbitrary(@arbitrary)}
142
143
  working_set.each do |current_node|
143
144
  current_node.value = path
144
145
  end
@@ -226,18 +227,6 @@ class HttpRouter
226
227
  path[-2, 2] == '/?' && path.slice!(-2, 2)
227
228
  end
228
229
 
229
- def extract_extension(path)
230
- if match = path.match(/^(.*)(\.:([a-zA-Z_]+))$/)
231
- path.replace(match[1])
232
- v_name = match[3].to_sym
233
- router.variable(match[3].to_sym, @matches_with[v_name], @additional_matchers && @additional_matchers[v_name])
234
- elsif match = path.match(/^(.*)(\.([a-zA-Z_]+))$/)
235
- path.replace(match[1])
236
- match[3]
237
- end
238
- end
239
-
240
-
241
230
  def compile_optionals(path)
242
231
  start_index = 0
243
232
  end_index = 1
@@ -269,26 +258,28 @@ class HttpRouter
269
258
  paths = compile_optionals(@path)
270
259
  paths.map do |path|
271
260
  original_path = path.dup
272
- extension = extract_extension(path)
273
- new_path = router.split(path).map do |part|
274
- case part[0]
275
- when ?:
276
- v_name = part[1, part.size].to_sym
277
- @variable_store[v_name] ||= router.variable(v_name, @matches_with[v_name], @additional_matchers && @additional_matchers[v_name])
278
- when ?*
279
- v_name = part[1, part.size].to_sym
280
- @variable_store[v_name] ||= router.glob(v_name, @matches_with[v_name], @additional_matchers && @additional_matchers[v_name])
261
+ index = -1
262
+ split_path = router.split(path)
263
+ new_path = split_path.map do |part|
264
+ index += 1
265
+ case part
266
+ when /^:([a-zA-Z_0-9]+)$/
267
+ v_name = $1.to_sym
268
+ router.variable(v_name, @matches_with[v_name])
269
+ when /^\*([a-zA-Z_0-9]+)$/
270
+ v_name = $1.to_sym
271
+ router.glob(v_name, @matches_with[v_name])
281
272
  else
282
273
  generate_interstitial_parts(part)
283
274
  end
284
275
  end
285
276
  new_path.flatten!
286
- Path.new(original_path, new_path, extension)
277
+ Path.new(original_path, new_path)
287
278
  end
288
279
  end
289
280
 
290
281
  def generate_interstitial_parts(part)
291
- part_segments = part.split(/(:[a-zA-Z_]+)/)
282
+ part_segments = part.scan(/:[a-zA-Z_0-9]+|[^:]+/)
292
283
  if part_segments.size > 1
293
284
  index = 0
294
285
  part_segments.map do |seg|
@@ -301,7 +292,7 @@ class HttpRouter
301
292
  else
302
293
  /^#{matcher || '.*?'}(?=#{Regexp.quote(part_segments[next_index])})/
303
294
  end
304
- @variable_store[v_name] ||= router.variable(v_name, scan_regex, @additional_matchers && @additional_matchers[v_name])
295
+ router.variable(v_name, scan_regex)
305
296
  else
306
297
  /^#{Regexp.quote(seg)}/
307
298
  end
@@ -2,27 +2,22 @@ class HttpRouter
2
2
  class Variable
3
3
  attr_reader :name, :matches_with
4
4
 
5
- def initialize(base, name, matches_with = nil, additional_matchers = nil)
5
+ def initialize(base, name, matches_with = nil)
6
6
  @router = base
7
7
  @name = name
8
8
  @matches_with = matches_with
9
- @additional_matchers = additional_matchers
10
9
  end
11
10
 
12
11
  def matches(env, parts, whole_path)
13
12
  if @matches_with.nil?
14
- additional_matchers(env, parts.first) ? parts.first : nil
15
- elsif @matches_with and match = @matches_with.match(whole_path) and additional_matchers(env, parts.first)
13
+ parts.first
14
+ elsif @matches_with and match = @matches_with.match(whole_path)
16
15
  whole_path.slice!(0, match[0].size)
17
16
  parts.replace(router.split(whole_path))
18
17
  match[0]
19
18
  end
20
19
  end
21
20
 
22
- def additional_matchers(env, test)
23
- @additional_matchers.nil? || @additional_matchers.all?{|m| m.call(env, test)}
24
- end
25
-
26
21
  def ===(part)
27
22
  @matches_with.nil?
28
23
  end
data/lib/http_router.rb CHANGED
@@ -127,6 +127,10 @@ class HttpRouter
127
127
  RequestNode.new(self, *args)
128
128
  end
129
129
 
130
+ def arbitrary_node(*args)
131
+ ArbitraryNode.new(self, *args)
132
+ end
133
+
130
134
  # Returns a new variable
131
135
  def variable(*args)
132
136
  Variable.new(self, *args)
data/spec/misc_spec.rb CHANGED
@@ -15,12 +15,11 @@ describe "HttpRouter" do
15
15
  end
16
16
 
17
17
  context "instance_eval block" do
18
- HttpRouter.new {
19
- add('/test').to :test
20
- }.recognize(Rack::MockRequest.env_for('/test', :method => 'GET')).dest.should == :test
21
-
18
+ #HttpRouter.new {
19
+ # add('/test').to :test
20
+ #}.recognize(Rack::MockRequest.env_for('/test', :method => 'GET')).dest.should == :test
22
21
  end
23
-
22
+
24
23
  context "exceptions" do
25
24
  it "should be smart about multiple optionals" do
26
25
  proc {@router.add("/:var1(/:var2)(/:var3)").compile}.should raise_error(HttpRouter::AmbiguousRouteException)
@@ -31,11 +31,37 @@ describe "HttpRouter#recognize" do
31
31
 
32
32
  context("proc acceptance") do
33
33
  it "should match optionally with a proc" do
34
- route = @router.add("/:test").matching(:test => /^\d+/).matching(:test => proc{|env, val| val == '123' or raise}).to(:test)
35
- response = @router.recognize(Rack::MockRequest.env_for('/123'))
36
- response.route.should == route
37
- response.params_as_hash[:test].should == '123'
34
+ @router.add("/test").arbitrary(Proc.new{|req| req.host == 'hellodooly' }).to(:test1)
35
+ @router.add("/test").arbitrary(Proc.new{|req| req.host == 'lovelove' }).arbitrary{|req| req.port == 80}.to(:test2)
36
+ @router.add("/test").arbitrary(Proc.new{|req| req.host == 'lovelove' }).arbitrary{|req| req.port == 8080}.to(:test3)
37
+ response = @router.recognize(Rack::MockRequest.env_for('http://lovelove:8080/test'))
38
+ response.dest.should == :test3
39
+ end
40
+
41
+ it "should still use an existing less specific node if possible" do
42
+ @router.add("/test").to(:test4)
43
+ @router.add("/test").arbitrary(Proc.new{|req| req.host == 'hellodooly' }).to(:test1)
44
+ @router.add("/test").arbitrary(Proc.new{|req| req.host == 'lovelove' }).arbitrary{|req| req.port == 80}.to(:test2)
45
+ @router.add("/test").arbitrary(Proc.new{|req| req.host == 'lovelove' }).arbitrary{|req| req.port == 8080}.to(:test3)
46
+ response = @router.recognize(Rack::MockRequest.env_for('http://lovelove:8081/test'))
47
+ response.dest.should == :test4
48
+ end
49
+
50
+ it "should match optionally with a proc and request conditions" do
51
+ @router.add("/test").get.arbitrary(Proc.new{|req| req.host == 'lovelove' }).arbitrary{|req| req.port == 80}.to(:test1)
52
+ @router.add("/test").get.arbitrary(Proc.new{|req| req.host == 'lovelove' }).arbitrary{|req| req.port == 8080}.to(:test2)
53
+ response = @router.recognize(Rack::MockRequest.env_for('http://lovelove:8080/test'))
54
+ response.dest.should == :test2
38
55
  end
56
+
57
+ it "should still use an existing less specific node if possible with request conditions" do
58
+ @router.add("/test").get.arbitrary(Proc.new{|req| req.host == 'lovelove' }).arbitrary{|req| req.port == 80}.to(:test1)
59
+ @router.add("/test").get.arbitrary(Proc.new{|req| req.host == 'lovelove' }).arbitrary{|req| req.port == 8080}.to(:test2)
60
+ @router.add("/test").get.to(:test3)
61
+ response = @router.recognize(Rack::MockRequest.env_for('http://lovelove:8081/test'))
62
+ response.dest.should == :test3
63
+ end
64
+
39
65
  end
40
66
 
41
67
  context("trailing slashes") do
@@ -55,6 +81,10 @@ describe "HttpRouter#recognize" do
55
81
  route = @router.add("/test").to(:test)
56
82
  @router.recognize(Rack::MockRequest.env_for('/test/')).should be_nil
57
83
  end
84
+ it "should not capture the trailing slash in a variable normally" do
85
+ route = @router.add("/:test").to(:test)
86
+ @router.recognize(Rack::MockRequest.env_for('/test/')).params.first.should == 'test'
87
+ end
58
88
  end
59
89
 
60
90
  end
@@ -110,7 +140,6 @@ describe "HttpRouter#recognize" do
110
140
  route = @router.add('/test.:format').to(:test)
111
141
  response = @router.recognize(Rack::MockRequest.env_for('/test.html'))
112
142
  response.route.should == route
113
- response.extension.should == 'html'
114
143
  response.params_as_hash[:format].should == 'html'
115
144
  @router.recognize(Rack::MockRequest.env_for('/test')).should be_nil
116
145
  end
@@ -119,11 +148,9 @@ describe "HttpRouter#recognize" do
119
148
  route = @router.add('/test(.:format)').to(:test)
120
149
  response = @router.recognize(Rack::MockRequest.env_for('/test.html'))
121
150
  response.route.should == route
122
- response.extension.should == 'html'
123
151
  response.params_as_hash[:format].should == 'html'
124
152
  response = @router.recognize(Rack::MockRequest.env_for('/test'))
125
153
  response.route.should == route
126
- response.extension.should be_nil
127
154
  response.params_as_hash[:format].should be_nil
128
155
  end
129
156
 
@@ -131,7 +158,6 @@ describe "HttpRouter#recognize" do
131
158
  route = @router.add('/:test.:format').to(:test)
132
159
  response = @router.recognize(Rack::MockRequest.env_for('/hey.html'))
133
160
  response.route.should == route
134
- response.extension.should == 'html'
135
161
  response.params_as_hash[:format].should == 'html'
136
162
  response.params_as_hash[:test].should == 'hey'
137
163
  end
@@ -140,12 +166,10 @@ describe "HttpRouter#recognize" do
140
166
  route = @router.add('/:test(.:format)').to(:test)
141
167
  response = @router.recognize(Rack::MockRequest.env_for('/hey.html'))
142
168
  response.route.should == route
143
- response.extension.should == 'html'
144
169
  response.params_as_hash[:format].should == 'html'
145
170
  response.params_as_hash[:test].should == 'hey'
146
171
  response = @router.recognize(Rack::MockRequest.env_for('/hey'))
147
172
  response.route.should == route
148
- response.extension.should be_nil
149
173
  response.params_as_hash[:format].should be_nil
150
174
  response.params_as_hash[:test].should == 'hey'
151
175
  end
@@ -184,6 +208,14 @@ describe "HttpRouter#recognize" do
184
208
  response.route.should == route
185
209
  response.params_as_hash[:variable].should == '123'
186
210
  end
211
+
212
+ it "should recognize interstitial variable when there is an extension" do
213
+ route = @router.add('/hey.:greed.html').to(:test)
214
+ response = @router.recognize(Rack::MockRequest.env_for('/hey.greedyboy.html'))
215
+ response.route.should == route
216
+ response.params_as_hash[:greed].should == 'greedyboy'
217
+ end
218
+
187
219
  end
188
220
 
189
221
  context("dynamic greedy paths") do
@@ -196,5 +228,16 @@ describe "HttpRouter#recognize" do
196
228
  response = @router.recognize(Rack::MockRequest.env_for('/asd'))
197
229
  response.should be_nil
198
230
  end
231
+
232
+ it "should capture the trailing slash in a greedy variable" do
233
+ route = @router.add("/:test").matching(:test => /.*/).to(:test)
234
+ @router.recognize(Rack::MockRequest.env_for('/test/')).params.first.should == 'test/'
235
+ end
236
+
237
+ it "should capture the extension in a greedy variable" do
238
+ route = @router.add("/:test").matching(:test => /.*/).to(:test)
239
+ @router.recognize(Rack::MockRequest.env_for('/test.html')).params.first.should == 'test.html'
240
+ end
241
+
199
242
  end
200
243
  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: 25
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Joshua Hull
@@ -17,8 +17,23 @@ cert_chain: []
17
17
 
18
18
  date: 2010-05-30 00:00:00 -04:00
19
19
  default_executable:
20
- dependencies: []
21
-
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 23
30
+ segments:
31
+ - 1
32
+ - 0
33
+ - 0
34
+ version: 1.0.0
35
+ type: :runtime
36
+ version_requirements: *id001
22
37
  description: A kick-ass HTTP router for use in Rack & Sinatra
23
38
  email: joshbuddy@gmail.com
24
39
  executables: []
@@ -28,9 +43,11 @@ extensions: []
28
43
  extra_rdoc_files:
29
44
  - README.rdoc
30
45
  files:
46
+ - .gitignore
31
47
  - README.rdoc
32
48
  - Rakefile
33
49
  - VERSION
50
+ - http_router.gemspec
34
51
  - lib/ext/rack/rack_mapper.rb
35
52
  - lib/ext/rack/uri_escape.rb
36
53
  - lib/http_router.rb
@@ -93,4 +110,5 @@ test_files:
93
110
  - spec/rack/route_spec.rb
94
111
  - spec/recognize_spec.rb
95
112
  - spec/sinatra/recognize_spec.rb
113
+ - spec/spec.opts
96
114
  - spec/spec_helper.rb