http_router 0.1.1 → 0.1.2

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