http_router 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,14 +4,25 @@ class HttpRouter
4
4
  attr_accessor :trailing_slash_ignore, :partially_match, :default_values
5
5
 
6
6
  def initialize(base, path)
7
- @base = base
7
+ @router = base
8
8
  @path = path
9
9
  @original_path = path.dup
10
10
  @partially_match = extract_partial_match(path)
11
11
  @trailing_slash_ignore = extract_trailing_slash(path)
12
12
  @variable_store = {}
13
13
  @matches_with = {}
14
+ @additional_matchers = {}
14
15
  @conditions = {}
16
+ @default_values = {}
17
+ end
18
+
19
+ def significant_variable_names
20
+ unless @significant_variable_names
21
+ @significant_variable_names = @paths.map { |p| p.variable_names }
22
+ @significant_variable_names.flatten!
23
+ @significant_variable_names.uniq!
24
+ end
25
+ @significant_variable_names
15
26
  end
16
27
 
17
28
  def method_missing(method, *args, &block)
@@ -22,9 +33,27 @@ class HttpRouter
22
33
  end
23
34
  end
24
35
 
36
+ def with_options(options)
37
+ if options && options[:matching]
38
+ default(options[:matching])
39
+ end
40
+ if options && options[:conditions]
41
+ condition(options[:conditions])
42
+ end
43
+ if options && options[:default_values]
44
+ default(options[:default_values])
45
+ end
46
+ self
47
+ end
48
+
25
49
  def name(name)
26
50
  @name = name
27
- @base.named_routes[name] = self
51
+ router.named_routes[name] = self
52
+ end
53
+
54
+ def default(v)
55
+ @default_values.merge!(v)
56
+ self
28
57
  end
29
58
 
30
59
  def get
@@ -36,7 +65,7 @@ class HttpRouter
36
65
  end
37
66
 
38
67
  def head
39
- request_method('head')
68
+ request_method('HEAD')
40
69
  end
41
70
 
42
71
  def put
@@ -62,11 +91,19 @@ class HttpRouter
62
91
  end
63
92
  alias_method :conditions, :condition
64
93
 
65
- def matching(*match)
94
+ def matching(match)
66
95
  guard_compiled
67
- @matches_with.merge!(match.pop) if match.last.is_a?(Hash)
68
- match.each_slice(2) do |(k,v)|
69
- @matches_with[k] = v
96
+ match.each do |var_name, matchers|
97
+ matchers = Array(matchers)
98
+ 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
106
+ end
70
107
  end
71
108
  self
72
109
  end
@@ -93,9 +130,14 @@ class HttpRouter
93
130
  def compile
94
131
  unless @paths
95
132
  @paths = compile_paths
133
+ @paths.each_with_index do |p1, i|
134
+ @paths[i+1, @paths.size].each do |p2|
135
+ raise AmbiguousRouteException.new if p1 === p2
136
+ end
137
+ end
96
138
  @paths.each do |path|
97
139
  path.route = self
98
- current_node = @base.root.add_path(path)
140
+ current_node = router.root.add_path(path)
99
141
  working_set = current_node.add_request_methods(@conditions)
100
142
  working_set.each do |current_node|
101
143
  current_node.value = path
@@ -138,25 +180,31 @@ class HttpRouter
138
180
 
139
181
  def url(*args)
140
182
  options = args.last.is_a?(Hash) ? args.pop : nil
183
+ options ||= {} if default_values
141
184
  options = default_values.merge(options) if default_values && options
142
- path = matching_path(args.empty? ? options : args)
185
+ path = if args.empty?
186
+ matching_path(options)
187
+ else
188
+ matching_path(args, options)
189
+ end
143
190
  raise UngeneratableRouteException.new unless path
144
191
  path.url(args, options)
145
192
  end
146
193
 
147
194
  private
195
+
196
+ attr_reader :router
148
197
 
149
- def matching_path(params)
198
+ def matching_path(params, other_hash = nil)
150
199
  if @paths.size == 1
151
200
  @paths.first
152
201
  else
153
202
  if params.is_a?(Array)
154
- @paths.each do |path|
155
- if path.variables.size == params.size
156
- return path
157
- end
158
- end
159
- nil
203
+ significant_keys = other_hash && significant_variable_names & other_hash.keys
204
+ @paths.find { |path|
205
+ var_count = significant_keys ? params.size + significant_keys.size : params.size
206
+ path.variables.size == var_count
207
+ }
160
208
  else
161
209
  @paths.reverse_each do |path|
162
210
  if params && !params.empty?
@@ -181,7 +229,8 @@ class HttpRouter
181
229
  def extract_extension(path)
182
230
  if match = path.match(/^(.*)(\.:([a-zA-Z_]+))$/)
183
231
  path.replace(match[1])
184
- Variable.new(@base, match[3].to_sym)
232
+ v_name = match[3].to_sym
233
+ router.variable(match[3].to_sym, @matches_with[v_name], @additional_matchers && @additional_matchers[v_name])
185
234
  elsif match = path.match(/^(.*)(\.([a-zA-Z_]+))$/)
186
235
  path.replace(match[1])
187
236
  match[3]
@@ -221,14 +270,14 @@ class HttpRouter
221
270
  paths.map do |path|
222
271
  original_path = path.dup
223
272
  extension = extract_extension(path)
224
- new_path = @base.split(path).map do |part|
273
+ new_path = router.split(path).map do |part|
225
274
  case part[0]
226
275
  when ?:
227
276
  v_name = part[1, part.size].to_sym
228
- @variable_store[v_name] ||= Variable.new(@base, v_name, @matches_with[v_name])
277
+ @variable_store[v_name] ||= router.variable(v_name, @matches_with[v_name], @additional_matchers && @additional_matchers[v_name])
229
278
  when ?*
230
279
  v_name = part[1, part.size].to_sym
231
- @variable_store[v_name] ||= Glob.new(@base, v_name, @matches_with[v_name])
280
+ @variable_store[v_name] ||= router.glob(v_name, @matches_with[v_name], @additional_matchers && @additional_matchers[v_name])
232
281
  else
233
282
  generate_interstitial_parts(part)
234
283
  end
@@ -245,13 +294,14 @@ class HttpRouter
245
294
  part_segments.map do |seg|
246
295
  new_seg = if seg[0] == ?:
247
296
  next_index = index + 1
297
+ v_name = seg[1, seg.size].to_sym
298
+ matcher = @matches_with[v_name]
248
299
  scan_regex = if next_index == part_segments.size
249
- /^[^\/]+/
300
+ matcher || /^[^\/]+/
250
301
  else
251
- /^.*?(?=#{Regexp.quote(part_segments[next_index])})/
302
+ /^#{matcher || '.*?'}(?=#{Regexp.quote(part_segments[next_index])})/
252
303
  end
253
- v_name = seg[1, seg.size].to_sym
254
- @variable_store[v_name] ||= Variable.new(@base, v_name, scan_regex)
304
+ @variable_store[v_name] ||= router.variable(v_name, scan_regex, @additional_matchers && @additional_matchers[v_name])
255
305
  else
256
306
  /^#{Regexp.quote(seg)}/
257
307
  end
@@ -2,25 +2,32 @@ class HttpRouter
2
2
  class Variable
3
3
  attr_reader :name, :matches_with
4
4
 
5
- def initialize(base, name, matches_with = nil)
6
- @base = base
5
+ def initialize(base, name, matches_with = nil, additional_matchers = nil)
6
+ @router = base
7
7
  @name = name
8
8
  @matches_with = matches_with
9
+ @additional_matchers = additional_matchers
9
10
  end
10
-
11
- def matches(parts, whole_path)
11
+
12
+ def matches(env, parts, whole_path)
12
13
  if @matches_with.nil?
13
- parts.first
14
- elsif @matches_with && match = @matches_with.match(whole_path)
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)
15
16
  whole_path.slice!(0, match[0].size)
16
- parts.replace(@base.split(whole_path))
17
+ parts.replace(router.split(whole_path))
17
18
  match[0]
18
19
  end
19
20
  end
20
-
21
+
22
+ def additional_matchers(env, test)
23
+ @additional_matchers.nil? || @additional_matchers.all?{|m| m.call(env, test)}
24
+ end
25
+
21
26
  def ===(part)
22
27
  @matches_with.nil?
23
28
  end
24
-
29
+
30
+ protected
31
+ attr_reader :router
25
32
  end
26
33
  end
data/lib/http_router.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  $LOAD_PATH << File.dirname(__FILE__)
2
2
  require 'rack'
3
- require 'rack/uri_escape'
3
+ require 'ext/rack/uri_escape'
4
4
 
5
5
  class HttpRouter
6
6
  autoload :Node, 'http_router/node'
@@ -11,21 +11,24 @@ class HttpRouter
11
11
  autoload :Response, 'http_router/response'
12
12
  autoload :Path, 'http_router/path'
13
13
 
14
- UngeneratableRouteException = Class.new(RuntimeError)
15
- MissingParameterException = Class.new(RuntimeError)
16
- TooManyParametersException = Class.new(RuntimeError)
17
- AlreadyCompiledException = Class.new(RuntimeError)
18
- RoutingError = Struct.new(:status, :headers)
14
+ UngeneratableRouteException = Class.new(RuntimeError)
15
+ MissingParameterException = Class.new(RuntimeError)
16
+ TooManyParametersException = Class.new(RuntimeError)
17
+ AlreadyCompiledException = Class.new(RuntimeError)
18
+ AmbiguousRouteException = Class.new(RuntimeError)
19
+ UnsupportedRequestConditionError = Class.new(RuntimeError)
20
+ AmbiguousVariableException = Class.new(RuntimeError)
19
21
 
20
22
  attr_reader :named_routes, :routes, :root
21
23
 
22
- def initialize(options = nil)
24
+ def initialize(options = nil, &block)
23
25
  @default_app = options && options[:default_app] || proc{|env| ::Rack::Response.new("Not Found", 404).finish }
24
26
  @ignore_trailing_slash = options && options.key?(:ignore_trailing_slash) ? options[:ignore_trailing_slash] : true
25
27
  @redirect_trailing_slash = options && options.key?(:redirect_trailing_slash) ? options[:redirect_trailing_slash] : false
26
28
  @routes = []
27
29
  @named_routes = {}
28
30
  reset!
31
+ instance_eval(&block) if block
29
32
  end
30
33
 
31
34
  def ignore_trailing_slash?
@@ -46,41 +49,41 @@ class HttpRouter
46
49
  @default_app = app
47
50
  end
48
51
 
49
- def split(path, with_delimiter = false)
50
- path.slice!(0) if path[0] == ?/
51
- with_delimiter ? path.split('(/)') : path.split('/')
52
+ def split(path)
53
+ (path[0] == ?/ ? path[1, path.size] : path).split('/')
52
54
  end
53
55
 
54
- def add(path)
55
- route = Route.new(self, path.dup)
56
+ def add(path, options = nil)
57
+ route = Route.new(self, path.dup).with_options(options)
56
58
  @routes << route
57
59
  route
58
60
  end
59
61
 
60
- def get(path)
61
- add(path).get
62
+ def get(path, options = nil)
63
+ add(path, options).get
62
64
  end
63
65
 
64
- def post(path)
65
- add(path).post
66
+ def post(path, options = nil)
67
+ add(path, options).post
66
68
  end
67
69
 
68
- def put(path)
69
- add(path).put
70
+ def put(path, options = nil)
71
+ add(path, options).put
70
72
  end
71
73
 
72
- def delete(path)
73
- add(path).delete
74
+ def delete(path, options = nil)
75
+ add(path, options).delete
74
76
  end
75
77
 
76
- def only_get(path)
77
- add(path).only_get
78
+ def only_get(path, options = nil)
79
+ add(path, options).only_get
78
80
  end
79
81
 
80
82
  def recognize(env)
81
83
  response = @root.find(env.is_a?(Hash) ? Rack::Request.new(env) : env)
82
84
  end
83
85
 
86
+ # Generate a URL for a specified route.
84
87
  def url(route, *args)
85
88
  case route
86
89
  when Symbol
@@ -92,6 +95,7 @@ class HttpRouter
92
95
  end
93
96
  end
94
97
 
98
+ # Allow the router to be called via Rake / Middleware.
95
99
  def call(env)
96
100
  request = Rack::Request.new(env)
97
101
  if redirect_trailing_slash? && (request.head? || request.get?) && request.path_info[-1] == ?/
@@ -99,19 +103,39 @@ class HttpRouter
99
103
  response.redirect(request.path_info[0, request.path_info.size - 1], 302)
100
104
  response.finish
101
105
  else
102
- response = recognize(request)
103
106
  env['router'] = self
104
- if response.is_a?(RoutingError)
105
- [response.status, response.headers, []]
106
- elsif response && response.route.dest && response.route.dest.respond_to?(:call)
107
- process_params(env, response)
108
- consume_path!(request, response) if response.partial_match?
109
- response.route.dest.call(env)
110
- else
111
- @default_app.call(env)
107
+ if response = recognize(request)
108
+ if response.matched? && response.route.dest && response.route.dest.respond_to?(:call)
109
+ process_params(env, response)
110
+ consume_path!(request, response) if response.partial_match?
111
+ return response.route.dest.call(env)
112
+ elsif !response.matched?
113
+ return [response.status, response.headers, []]
114
+ end
112
115
  end
116
+ @default_app.call(env)
113
117
  end
114
118
  end
119
+
120
+ # Returns a new node
121
+ def node(*args)
122
+ Node.new(self, *args)
123
+ end
124
+
125
+ # Returns a new request node
126
+ def request_node(*args)
127
+ RequestNode.new(self, *args)
128
+ end
129
+
130
+ # Returns a new variable
131
+ def variable(*args)
132
+ Variable.new(self, *args)
133
+ end
134
+
135
+ # Returns a new glob
136
+ def glob(*args)
137
+ Glob.new(self, *args)
138
+ end
115
139
 
116
140
  private
117
141
 
@@ -32,21 +32,59 @@ describe "HttpRouter#generate" do
32
32
  @router.add("/:var").name(:test).compile
33
33
  @router.url(:test, 'test', :query => 'string').should == '/test?query=string'
34
34
  end
35
-
36
- it "should generate with a format" do
37
- @router.add("/test.:format").name(:test).compile
38
- @router.url(:test, 'html').should == '/test.html'
35
+
36
+ it "should generate with multiple dynamics" do
37
+ @router.add("/:var/:baz").name(:test).compile
38
+ @router.url(:test, 'one', 'two').should == '/one/two'
39
+ @router.url(:test, :var => 'one', :baz => 'two').should == '/one/two'
39
40
  end
40
41
 
41
- it "should generate with a format as a hash" do
42
- @router.add("/test.:format").name(:test).compile
43
- @router.url(:test, :format => 'html').should == '/test.html'
44
- end
42
+ context "with a :format" do
43
+ it "should generate with a format" do
44
+ @router.add("/test.:format").name(:test).compile
45
+ @router.url(:test, 'html').should == '/test.html'
46
+ end
45
47
 
46
- it "should generate with an optional format" do
47
- @router.add("/test(.:format)").name(:test).compile
48
- @router.url(:test, 'html').should == '/test.html'
49
- @router.url(:test).should == '/test'
48
+ it "should generate with a format as a hash" do
49
+ @router.add("/test.:format").name(:test).compile
50
+ @router.url(:test, :format => 'html').should == '/test.html'
51
+ end
52
+
53
+ it "should generate with format as a symbol" do
54
+ @router.add("/test.:format").name(:test).compile
55
+ @router.url(:test, :format => :html).should == '/test.html'
56
+ end
57
+
58
+ it "should generate with an optional format" do
59
+ @router.add("/test(.:format)").name(:test).compile
60
+ @router.url(:test, 'html').should == '/test.html'
61
+ @router.url(:test).should == '/test'
62
+ end
63
+
64
+ it "should generate a dynamic path and a format" do
65
+ @router.add("/:var1.:format").name(:test).compile
66
+ @router.url(:test, 'var', :format => 'html').should == '/var.html'
67
+ end
68
+
69
+ it "should generate a dynamic path and an optional format" do
70
+ @router.add("/:var1(.:format)").name(:test).compile
71
+ @router.url(:test, 'var').should == '/var'
72
+ @router.url(:test, 'var', :format => 'html').should == '/var.html'
73
+ end
74
+
75
+ it "should generate multiple dynamics and a format" do
76
+ @router.add("/:foo/:bar.:format").name(:test).compile
77
+ @router.url(:test, 'var', 'baz', 'html').should == '/var/baz.html'
78
+ @router.url(:test, :foo => 'var', :bar => 'baz', :format => 'html').should == '/var/baz.html'
79
+ end
80
+
81
+ it "should generate multiple dynamics and an optional format" do
82
+ @router.add("/:foo/:bar(.:format)").name(:test).compile
83
+ @router.url(:test, 'var', 'baz').should == '/var/baz'
84
+ @router.url(:test, 'var', 'baz', 'html').should == '/var/baz.html'
85
+ @router.url(:test, :foo => 'var', :bar => 'baz').should == '/var/baz'
86
+ @router.url(:test, :foo => 'var', :bar => 'baz', :format => 'html').should == '/var/baz.html'
87
+ end
50
88
  end
51
89
 
52
90
  context "with optional parts" do
@@ -58,7 +96,38 @@ describe "HttpRouter#generate" do
58
96
  @router.url(:test, :var1 => 'var', :var2 => 'fooz').should == '/var/fooz'
59
97
  proc{@router.url(:test, :var2 => 'fooz').should == '/var/fooz'}.should raise_error(HttpRouter::UngeneratableRouteException)
60
98
  end
99
+ it "should generate with a format" do
100
+ @router.add("/:var1(/:var2.:format)").name(:test).compile
101
+ @router.url(:test, 'var').should == '/var'
102
+ @router.url(:test, 'var', 'fooz', 'html').should == '/var/fooz.html'
103
+ @router.url(:test, :var1 => 'var').should == '/var'
104
+ @router.url(:test, :var1 => 'var', :var2 => 'fooz', :format => 'html').should == '/var/fooz.html'
105
+ end
106
+ it "should generate with an embeded optional" do
107
+ @router.add("/:var1(/:var2(/:var3))").name(:test).compile
108
+ @router.url(:test, 'var').should == '/var'
109
+ @router.url(:test, 'var', 'fooz').should == '/var/fooz'
110
+ @router.url(:test, 'var', 'fooz', 'baz').should == '/var/fooz/baz'
111
+ @router.url(:test, :var1 => 'var').should == '/var'
112
+ @router.url(:test, :var1 => 'var', :var2 => 'fooz', :var3 => 'baz').should == '/var/fooz/baz'
113
+ end
114
+
115
+ it "should support optional plus optional format" do
116
+ @router.add("/:var1(/:var2)(.:format)").name(:test).compile
117
+ @router.url(:test, 'var').should == '/var'
118
+ @router.url(:test, 'var', 'fooz').should == '/var/fooz'
119
+ @router.url(:test, 'var', 'fooz', 'html').should == '/var/fooz.html'
120
+ @router.url(:test, :var1 => 'var').should == '/var'
121
+ @router.url(:test, :var1 => 'var', :var2 => 'fooz', :format => 'html').should == '/var/fooz.html'
122
+ @router.url(:test, :var1 => 'var', :format => 'html').should == '/var.html'
123
+ end
61
124
  end
62
125
 
126
+ context "with default values" do
127
+ it "should generate with all params" do
128
+ @router.add("/:var").default(:page => 1).name(:test).compile
129
+ @router.url(:test, 123).should == "/123?page=1"
130
+ end
131
+ end
63
132
  end
64
133
  end
data/spec/misc_spec.rb ADDED
@@ -0,0 +1,38 @@
1
+ describe "HttpRouter" do
2
+ before(:each) do
3
+ @router = HttpRouter.new
4
+ end
5
+
6
+ context "route adding" do
7
+ it "should work with options too" do
8
+ route = @router.add('/:test', :conditions => {:request_method => %w{HEAD GET}, :host => 'host1'}, :default_values => {:page => 1}, :matching => {:test => /^\d+/}).to :test
9
+ @router.recognize(Rack::MockRequest.env_for('http://host2/variable', :method => 'POST')).matched?.should be_false
10
+ @router.recognize(Rack::MockRequest.env_for('http://host1/variable', :method => 'POST')).matched?.should be_false
11
+ @router.recognize(Rack::MockRequest.env_for('http://host2/123', :method => 'POST')).matched?.should be_false
12
+ @router.recognize(Rack::MockRequest.env_for('http://host1/123', :method => 'POST')).matched?.should be_false
13
+ @router.recognize(Rack::MockRequest.env_for('http://host1/123', :method => 'GET')).route.dest.should == :test
14
+ end
15
+ end
16
+
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
+
22
+ end
23
+
24
+ context "exceptions" do
25
+ it "should be smart about multiple optionals" do
26
+ proc {@router.add("/:var1(/:var2)(/:var3)").compile}.should raise_error(HttpRouter::AmbiguousRouteException)
27
+ end
28
+
29
+ it "should raise on identical variable name" do
30
+ proc {@router.add("/:var1(/:var1)(/:var1)").compile}.should raise_error(HttpRouter::AmbiguousVariableException)
31
+ end
32
+
33
+ it "should raise on unsupported request methods" do
34
+ proc {@router.add("/").condition(:flibberty => 'gibet').compile}.should raise_error(HttpRouter::UnsupportedRequestConditionError)
35
+ end
36
+
37
+ end
38
+ end
@@ -1,7 +1,7 @@
1
1
  route_set = HttpRouter.new
2
2
  route_set.extend(CallWithMockRequestMixin)
3
3
 
4
- describe "Usher (for rack) route dispatching with redirect_on_trailing_delimiters" do
4
+ describe "HttpRouter route dispatching with redirect_on_trailing_delimiters" do
5
5
  before(:each) do
6
6
  @route_set = HttpRouter.new(:redirect_trailing_slash => true)
7
7
  @route_set.extend(CallWithMockRequestMixin)
@@ -16,7 +16,7 @@ describe "Usher (for rack) route dispatching with redirect_on_trailing_delimiter
16
16
 
17
17
  end
18
18
 
19
- describe "Usher (for rack) route dispatching" do
19
+ describe "HttpRouter route dispatching" do
20
20
  before(:each) do
21
21
  route_set.reset!
22
22
  @app = MockApp.new("Hello World!")
@@ -1,7 +1,7 @@
1
1
  route_set = HttpRouter.new
2
2
  route_set.extend(CallWithMockRequestMixin)
3
3
 
4
- describe "Usher (for rack) route generation" do
4
+ describe "HttpRouter route generation" do
5
5
  before(:each) do
6
6
  route_set.reset!
7
7
  @app = MockApp.new("Hello World!")
@@ -29,6 +29,15 @@ describe "HttpRouter#recognize" do
29
29
  end
30
30
  end
31
31
 
32
+ context("proc acceptance") do
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'
38
+ end
39
+ end
40
+
32
41
  context("trailing slashes") do
33
42
  it "should ignore a trailing slash" do
34
43
  route = @router.add("/test").to(:test)
@@ -75,11 +84,17 @@ describe "HttpRouter#recognize" do
75
84
 
76
85
  it "should move an endpoint to the non-specific request method when a more specific route gets added" do
77
86
  @router.add("/test").name(:test_catchall).to(:test1)
78
- @router.post("/test").request_method('POST').name(:test_post).to(:test2)
87
+ @router.post("/test").name(:test_post).to(:test2)
79
88
  @router.recognize(Rack::MockRequest.env_for('/test', :method => 'POST')).route.named.should == :test_post
80
89
  @router.recognize(Rack::MockRequest.env_for('/test', :method => 'PUT')).route.named.should == :test_catchall
81
90
  end
82
91
 
92
+ it "should try both specific and non-specifc routes" do
93
+ @router.post("/test").host('host1').to(:post_host1)
94
+ @router.add("/test").host('host2').to(:any_post2)
95
+ @router.recognize(Rack::MockRequest.env_for('http://host2/test', :method => 'POST')).dest.should == :any_post2
96
+ end
97
+
83
98
  end
84
99
 
85
100
  context("dynamic paths") do
@@ -161,11 +176,19 @@ describe "HttpRouter#recognize" do
161
176
  response.route.should == route
162
177
  response.params_as_hash[:variable].should == 'value'
163
178
  end
179
+
180
+ it "should recognize interstitial variables with a regex" do
181
+ route = @router.add('/one-:variable-time').matching(:variable => /^\d+/).to(:test)
182
+ @router.recognize(Rack::MockRequest.env_for('/one-value-time')).should be_nil
183
+ response = @router.recognize(Rack::MockRequest.env_for('/one-123-time'))
184
+ response.route.should == route
185
+ response.params_as_hash[:variable].should == '123'
186
+ end
164
187
  end
165
188
 
166
189
  context("dynamic greedy paths") do
167
190
  it "should recognize greedy variables" do
168
- route = @router.add('/:variable').matching(:variable, /\d+/).to(:test)
191
+ route = @router.add('/:variable').matching(:variable => /\d+/).to(:test)
169
192
  response = @router.recognize(Rack::MockRequest.env_for('/123'))
170
193
  response.route.should == route
171
194
  response.params.should == ['123']
@@ -1,7 +1,7 @@
1
1
  require "sinatra"
2
- require "http_router/sinatra"
2
+ require "http_router/interface/sinatra"
3
3
 
4
- describe "Usher (for Sinatra) route recognition" do
4
+ describe "HttpRouter (for Sinatra) route recognition" do
5
5
  before(:each) do
6
6
  @app = Sinatra.new { register HttpRouter::Interface::Sinatra::Extension }
7
7
  @app.extend(CallWithMockRequestMixin)