http_router 0.1.0 → 0.1.1

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.
@@ -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)