http_router 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/http_router.rb CHANGED
@@ -39,7 +39,7 @@ class HttpRouter
39
39
  @ignore_trailing_slash = options && options.key?(:ignore_trailing_slash) ? options[:ignore_trailing_slash] : true
40
40
  @redirect_trailing_slash = options && options.key?(:redirect_trailing_slash) ? options[:redirect_trailing_slash] : false
41
41
  @known_methods = Set.new(options && options[:known_methods] || [])
42
- @nodes = []
42
+ @counter = 0
43
43
  reset!
44
44
  instance_eval(&blk) if blk
45
45
  end
@@ -138,7 +138,7 @@ class HttpRouter
138
138
 
139
139
  # Resets the router to a clean state.
140
140
  def reset!
141
- @routes, @named_routes, @root = [], {}, Node.new(self, nil)
141
+ @routes, @named_routes, @root = [], {}, Node::Root.new(self)
142
142
  @default_app = Proc.new{ |env| ::Rack::Response.new("Your request couldn't be found", 404).finish }
143
143
  end
144
144
 
@@ -147,15 +147,6 @@ class HttpRouter
147
147
  @default_app = app
148
148
  end
149
149
 
150
- def register_node(n)
151
- @nodes << n
152
- @nodes.size - 1
153
- end
154
-
155
- def [](pos)
156
- @nodes.at(pos)
157
- end
158
-
159
150
  # Generate a URL for a specified route. This will accept a list of variable values plus any other variable names named as a hash.
160
151
  # This first value must be either the Route object or the name of the route.
161
152
  #
@@ -208,6 +199,10 @@ class HttpRouter
208
199
  @root.compile
209
200
  end
210
201
 
202
+ def next_counter
203
+ @counter += 1
204
+ end
205
+
211
206
  private
212
207
  def no_response(env, perform_call = true)
213
208
  supported_methods = (@known_methods - [env['REQUEST_METHOD']]).select do |m|
@@ -1,6 +1,8 @@
1
1
  class HttpRouter
2
2
  class Node
3
+ autoload :Root, 'http_router/node/root'
3
4
  autoload :Glob, 'http_router/node/glob'
5
+ autoload :GlobRegex, 'http_router/node/glob_regex'
4
6
  autoload :Variable, 'http_router/node/variable'
5
7
  autoload :Regex, 'http_router/node/regex'
6
8
  autoload :SpanningRegex, 'http_router/node/spanning_regex'
@@ -25,6 +27,10 @@ class HttpRouter
25
27
  add(Glob.new(@router, self))
26
28
  end
27
29
 
30
+ def add_glob_regexp(matcher)
31
+ add(GlobRegex.new(@router, self, matcher))
32
+ end
33
+
28
34
  def add_request(opts)
29
35
  add(Request.new(@router, self, opts))
30
36
  end
@@ -57,20 +63,15 @@ class HttpRouter
57
63
  false
58
64
  end
59
65
 
60
- def method_missing(m, *args, &blk)
61
- if m.to_s == '[]'
62
- compile
63
- send(:[], *args)
66
+ private
67
+ def inject_root_methods(code = nil, &blk)
68
+ if code
69
+ root.methods_module.module_eval(code, __FILE__, __LINE__)
64
70
  else
65
- super
71
+ root.methods_module.module_eval(&blk)
66
72
  end
67
73
  end
68
74
 
69
- def compile
70
- instance_eval "def [](request)\n#{to_code}\nnil\nend", __FILE__, __LINE__
71
- end
72
-
73
- private
74
75
  def add(matcher)
75
76
  @matchers << matcher unless matcher.usable?(@matchers.last)
76
77
  @matchers.last
@@ -80,14 +81,18 @@ class HttpRouter
80
81
  @matchers.map{ |m| "# #{m.class}\n" << m.to_code }.join("\n") << "\n"
81
82
  end
82
83
 
84
+ def root
85
+ @router.root
86
+ end
87
+
83
88
  def depth
84
- p = @parent
85
- d = 0
86
- until p.nil?
87
- d += 1
88
- p = p.parent
89
- end
89
+ d, p = 0, @parent
90
+ d, p = d + 1, p.parent until p.nil?
90
91
  d
91
92
  end
93
+
94
+ def use_named_captures?
95
+ //.respond_to?(:names)
96
+ end
92
97
  end
93
98
  end
@@ -5,7 +5,6 @@ class HttpRouter
5
5
 
6
6
  def initialize(router, parent, sallow_partial, blk, param_names)
7
7
  @allow_partial, @blk, @param_names = allow_partial, blk, param_names
8
- @node_position = router.register_node(blk)
9
8
  super(router, parent)
10
9
  end
11
10
 
@@ -14,13 +13,15 @@ class HttpRouter
14
13
  end
15
14
 
16
15
  def to_code
16
+ b, method_name = @blk, :"blk_#{router.next_counter}"
17
+ inject_root_methods { define_method(method_name) { b } }
17
18
  "#{"if request.path_finished?" unless @allow_partial}
18
19
  request.continue = proc { |state|
19
20
  if state
20
21
  #{super}
21
22
  end
22
23
  }
23
- router.nodes.at(#{node_position})[request, #{@param_names.nil? || @param_names.empty? ? '{}' : "Hash[#{@param_names.inspect}.zip(request.params)]"}]
24
+ #{method_name}[request, #{@param_names.nil? || @param_names.empty? ? '{}' : "Hash[#{@param_names.inspect}.zip(request.params)]"}]
24
25
  request.continue = nil
25
26
  #{"end" unless @allow_partial}"
26
27
  end
@@ -5,7 +5,6 @@ class HttpRouter
5
5
 
6
6
  def initialize(router, parent, blk, allow_partial)
7
7
  @blk, @allow_partial = blk, allow_partial
8
- @node_position = router.register_node(blk)
9
8
  super(router, parent)
10
9
  end
11
10
 
@@ -14,9 +13,11 @@ class HttpRouter
14
13
  end
15
14
 
16
15
  def to_code
16
+ b, method_name = @blk, :"blk_#{router.next_counter}"
17
+ inject_root_methods { define_method(method_name) { b } }
17
18
  "#{"if request.path_finished?" unless @allow_partial}
18
19
  request.passed_with = catch(:pass) do
19
- router.nodes.at(#{node_position})[request, #{@param_names.nil? || @param_names.empty? ? 'nil' : "Hash[#{@param_names.inspect}.zip(request.params)]"}]
20
+ #{method_name}[request, #{@param_names.nil? || @param_names.empty? ? 'nil' : "Hash[#{@param_names.inspect}.zip(request.params)]"}]
20
21
  end
21
22
  #{"end" unless @allow_partial}"
22
23
  end
@@ -8,17 +8,17 @@ class HttpRouter
8
8
  end
9
9
 
10
10
  def to_code
11
- "whole_path = \"/\#{request.joined_path}\"
12
- if match = #{matcher.inspect}.match(whole_path) and match[0].size == whole_path.size
11
+ "whole_path#{depth} = \"/\#{request.joined_path}\"
12
+ if match = #{matcher.inspect}.match(whole_path#{depth}) and match[0].size == whole_path#{depth}.size
13
13
  request.extra_env['router.regex_match'] = match
14
14
  old_path = request.path
15
15
  request.path = ['']
16
- " << (//.respond_to?(:names) ?
16
+ " << (use_named_captures? ?
17
17
  "match.names.size.times{|i| request.params << match[i + 1]} if match.respond_to?(:names) && match.names" : "") << "
18
18
  #{super}
19
19
  request.path = old_path
20
20
  request.extra_env.delete('router.regex_match')
21
- " << (//.respond_to?(:names) ?
21
+ " << (use_named_captures? ?
22
22
  "params.slice!(-match.names.size, match.names.size)" : ""
23
23
  ) << "
24
24
  end"
@@ -1,6 +1,7 @@
1
1
  class HttpRouter
2
2
  class Node
3
3
  class Glob < Node
4
+ alias_method :node_to_code, :to_code
4
5
  def usable?(other)
5
6
  other.class == self.class
6
7
  end
@@ -9,12 +10,11 @@ class HttpRouter
9
10
  "request.params << (globbed_params#{depth} = [])
10
11
  remaining_parts = request.path.dup
11
12
  until remaining_parts.empty?
12
- globbed_params#{depth} << URI.unescape(remaining_parts.shift)
13
+ globbed_params#{depth} << remaining_parts.shift
13
14
  request.path = remaining_parts
14
15
  #{super}
15
16
  end
16
- request.path[0,0] = request.params.pop
17
- "
17
+ request.path[0,0] = request.params.pop"
18
18
  end
19
19
  end
20
20
  end
@@ -1,18 +1,25 @@
1
1
  class HttpRouter
2
2
  class Node
3
- class GlobRegex < SpanningRegex
3
+ class GlobRegex < Glob
4
+ attr_reader :matcher
5
+ def initialize(router, parent, matcher)
6
+ @matcher = matcher
7
+ super router, parent
8
+ end
9
+
10
+ def usable?(other)
11
+ other.class == self.class && other.matcher == matcher
12
+ end
13
+
4
14
  def to_code
5
- "whole_path = request.joined_path
6
- if match = #{@matcher.inspect}.match(whole_path) and match.begin(0).zero?
7
- original_path = request.path.dup\n" <<
8
- @capturing_indicies.map { |c| "request.params << URI.unescape(match[#{c}].split(/\\//))\n" }.join << "
9
- remaining_path = whole_path[match[0].size + (whole_path[match[0].size] == ?/ ? 1 : 0), whole_path.size]
10
- request.path = remaining_path.split('/')
11
- #{super}
12
- request.path = original_path
13
- #{@capturing_indicies.size == 1 ? "request.params.pop" : "request.params.slice!(#{-@capturing_indicies.size}, #{@capturing_indicies.size})"}
14
- end
15
- "
15
+ "request.params << (globbed_params#{depth} = [])
16
+ remaining_parts = request.path.dup
17
+ while !remaining_parts.empty? and match = remaining_parts.first.match(#{@matcher.inspect}) and match[0] == remaining_parts.first
18
+ globbed_params#{depth} << remaining_parts.shift
19
+ request.path = remaining_parts
20
+ #{node_to_code}
21
+ end
22
+ request.path[0,0] = request.params.pop"
16
23
  end
17
24
  end
18
25
  end
@@ -15,15 +15,19 @@ class HttpRouter
15
15
  end
16
16
 
17
17
  def to_code
18
- code = "\ncase request.path.first\n"
19
- @map.keys.each do |k|
20
- code << "when #{k.inspect}\n
21
- part#{depth} = request.path.shift
22
- #{@map[k].map{|n| n.to_code} * "\n"}
23
- request.path.unshift part#{depth}
24
- "
18
+ inject_root_methods @map.keys.map {|k|
19
+ method = :"lookup_#{object_id}_#{k.hash}"
20
+ "define_method(#{method.inspect}) do |request|
21
+ part = request.path.shift
22
+ #{@map[k].map{|n| n.to_code} * "\n"}
23
+ request.path.unshift part
24
+ end"}.join("\n")
25
+ code = "
26
+ unless request.path_finished?
27
+ m = \"lookup_#{object_id}_\#{request.path.first.hash}\"
28
+ send(m, request) if respond_to?(m)
25
29
  end
26
- code << "\nend"
30
+ "
27
31
  end
28
32
  end
29
33
  end
@@ -17,8 +17,8 @@ class HttpRouter
17
17
  params_size = @splitting_indicies.size + @capturing_indicies.size
18
18
  "if match = #{@matcher.inspect}.match(request.path.first) and match.begin(0).zero?
19
19
  part = request.path.shift\n" <<
20
- @splitting_indicies.map { |s| "request.params << URI.unescape(match[#{s}]).split(/\\//)\n" }.join <<
21
- @capturing_indicies.map { |c| "request.params << URI.unescape(match[#{c}])\n" }.join << "
20
+ @splitting_indicies.map { |s| "request.params << match[#{s}].split(/\\//)\n" }.join <<
21
+ @capturing_indicies.map { |c| "request.params << match[#{c}]\n" }.join << "
22
22
  #{super}
23
23
  request.path.unshift part
24
24
  #{params_size == 1 ? "request.params.pop" : "request.params.slice!(#{-params_size}, #{params_size})"}
@@ -16,13 +16,21 @@ class HttpRouter
16
16
  def to_code
17
17
  code = "if "
18
18
  code << @opts.map do |k,v|
19
- case v
20
- when Array then "(#{v.map{|vv| "#{vv.inspect} === request.rack_request.#{k}"}.join(' or ')})"
21
- else "#{v.inspect} === request.rack_request.#{k.inspect}"
19
+ case v.size
20
+ when 1 then to_code_condition(k, v.first)
21
+ else "(#{v.map{|k, vv| to_code_condition(k, vv)}.join(' or ')})"
22
22
  end
23
23
  end * ' and '
24
24
  code << "\n #{super}\nend"
25
25
  end
26
+
27
+ private
28
+ def to_code_condition(k, v)
29
+ case v
30
+ when String then "#{v.inspect} == request.rack_request.#{k}"
31
+ else "#{v.inspect} === request.rack_request.#{k}"
32
+ end
33
+ end
26
34
  end
27
35
  end
28
36
  end
@@ -0,0 +1,22 @@
1
+ class HttpRouter
2
+ class Node
3
+ class Root < Node
4
+ attr_reader :methods_module
5
+ def initialize(router)
6
+ super(router, nil)
7
+ @methods_module = Module.new
8
+ end
9
+
10
+ def [](request)
11
+ compile
12
+ self[request]
13
+ end
14
+
15
+ private
16
+ def compile
17
+ root.extend(root.methods_module)
18
+ instance_eval "def [](request)\n#{to_code}\nnil\nend", __FILE__, __LINE__
19
+ end
20
+ end
21
+ end
22
+ end
@@ -3,18 +3,18 @@ class HttpRouter
3
3
  class SpanningRegex < Regex
4
4
  def to_code
5
5
  params_count = (@splitting_indicies || []).size + @capturing_indicies.size
6
- "whole_path = request.joined_path
7
- if match = #{@matcher.inspect}.match(whole_path) and match.begin(0).zero?
8
- original_path#{depth} = request.path.dup
9
- " <<
10
- (@splitting_indicies || []).map { |s| "request.params << URI.unescape(match[#{s}]).split(/\\//)\n" }.join <<
11
- @capturing_indicies.map { |c| "request.params << URI.unescape(match[#{c}])\n" }.join << "
12
- remaining_path = whole_path[match[0].size + (whole_path[match[0].size] == ?/ ? 1 : 0), whole_path.size]
13
- request.path = remaining_path.split('/')
14
- #{node_to_code}
15
- request.path = original_path#{depth}
16
- request.params.slice!(#{-params_count.size}, #{params_count})
17
- end
6
+ "whole_path#{depth} = request.joined_path
7
+ if match = #{@matcher.inspect}.match(whole_path#{depth}) and match.begin(0).zero?
8
+ original_path#{depth} = request.path.dup
9
+ " <<
10
+ (@splitting_indicies || []).map { |s| "request.params << match[#{s}].split(/\\//)\n" }.join <<
11
+ @capturing_indicies.map { |c| "request.params << match[#{c}]\n" }.join << "
12
+ remaining_path = whole_path#{depth}[match[0].size + (whole_path#{depth}[match[0].size] == ?/ ? 1 : 0), whole_path#{depth}.size]
13
+ request.path = remaining_path.split('/')
14
+ #{node_to_code}
15
+ request.path = original_path#{depth}
16
+ request.params.slice!(#{-params_count.size}, #{params_count})
17
+ end
18
18
  "
19
19
  end
20
20
  end
@@ -7,9 +7,9 @@ class HttpRouter
7
7
 
8
8
  def to_code
9
9
  "unless request.path_finished?
10
- request.params << URI.unescape(request.path.shift)
10
+ request.params << request.path.shift
11
11
  #{super}
12
- request.path.unshift URI.escape(request.params.pop)
12
+ request.path.unshift request.params.pop
13
13
  end"
14
14
  end
15
15
  end
@@ -5,8 +5,9 @@ class HttpRouter
5
5
  alias_method :rack, :rack_request
6
6
  def initialize(path, rack_request, perform_call, &acceptance_test)
7
7
  @rack_request, @perform_call, @acceptance_test = rack_request, perform_call, acceptance_test
8
- @path = (path[0] == ?/ ? path[1, path.size] : path).split(/\//)
9
- @path << '' if path.size > 1 && path[-1] == ?/
8
+ @path = URI.unescape(path).split(/\//)
9
+ @path.shift if @path.first == ''
10
+ @path.push('') if path[-1] == ?/
10
11
  @extra_env = {}
11
12
  @params = []
12
13
  end
@@ -210,9 +210,9 @@ class HttpRouter
210
210
  when ?*
211
211
  param_names << name.to_sym
212
212
  matches_with[name.to_sym] = @opts[name.to_sym]
213
- @opts[name.to_sym] ? node.add_spanning_match(@opts.delete(name.to_sym)) : node.add_glob
213
+ @opts[name.to_sym] ? node.add_glob_regexp(@opts.delete(name.to_sym)) : node.add_glob
214
214
  else
215
- node.add_lookup(URI.encode(parts[0]))
215
+ node.add_lookup(parts[0])
216
216
  end
217
217
  else
218
218
  capturing_indicies = []
@@ -237,9 +237,10 @@ class HttpRouter
237
237
  name = part[1, part.size].to_sym
238
238
  param_names << name
239
239
  matches_with[name] = @opts[name]
240
- "(#{(@opts[name] || '.*?')})"
240
+ @opts[name] ?
241
+ "((?:#{@opts[name]}\\/?)+)" : '(.*?)'
241
242
  else
242
- Regexp.quote(URI.encode(part))
243
+ Regexp.quote(part)
243
244
  end
244
245
  end
245
246
  node = spans ? node.add_spanning_match(Regexp.new("#{regex}$"), capturing_indicies, splitting_indicies) :
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  class HttpRouter #:nodoc
3
- VERSION = '0.8.1'
3
+ VERSION = '0.8.2'
4
4
  end
@@ -92,6 +92,16 @@ class TestRecognition < MiniTest::Unit::TestCase
92
92
  assert_body '/', router.call(Rack::MockRequest.env_for('/'))
93
93
  end
94
94
 
95
+ def test_request_mutation
96
+ got_this_far = false
97
+ non_matching, matching = router {
98
+ add("/test/:var/:var2/*glob").matching(:var2 => /123/, :glob => /[a-z]+/).get.arbitrary{|env, params| got_this_far = true; false}
99
+ add("/test/:var/:var2/*glob").matching(:var2 => /123/, :glob => /[a-z]+/).get
100
+ }
101
+ assert_route matching, '/test/123/123/asd/aasd/zxcqwe/asdzxc', {:var => '123', :var2 => '123', :glob => %w{asd aasd zxcqwe asdzxc}}
102
+ assert got_this_far, "matching should have gotten this far"
103
+ end
104
+
95
105
  def test_multiple_partial
96
106
  test, root = router {
97
107
  add("/test").partial.to{|env| [200, {}, ['/test',env['PATH_INFO']]]}
@@ -90,6 +90,19 @@ class TestVariable < MiniTest::Unit::TestCase
90
90
  assert_route '/test/*variable/test', '/test/one/two/three/test', {:variable => ['one', 'two', 'three']}
91
91
  end
92
92
 
93
+ def test_glob_with_regexp
94
+ r = router { add('/test/*variable', :variable => /[a-z]+/) }
95
+ assert_route nil, '/test/asd/123'
96
+ assert_route nil, '/test/asd/asd123'
97
+ assert_route r, '/test/asd/qwe', :variable => ['asd', 'qwe']
98
+ end
99
+
100
+ def test_glob_with_regexp_and_static
101
+ r = router { add('/test/*variable/test', :variable => /[a-z]+/) }
102
+ assert_route nil, '/test/asd/123/test'
103
+ assert_route r, '/test/asd/qwe/test', :variable => ['asd', 'qwe']
104
+ end
105
+
93
106
  def test_glob_with_variable
94
107
  assert_route '/test/*variable/:test', '/test/one/two/three', {:variable => ['one', 'two'], :test => 'three'}
95
108
  end
@@ -98,6 +111,13 @@ class TestVariable < MiniTest::Unit::TestCase
98
111
  assert_route '/test/*variable.:format', 'test/one/two/three.html', {:variable => ['one', 'two', 'three'], :format => 'html'}
99
112
  end
100
113
 
114
+ def test_glob_regexp_with_format
115
+ r = router { add('/test/*variable.:format', :variable => /[a-z]+/, :format => 'html') }
116
+ assert_route nil, 'test/one/2/three.html'
117
+ assert_route nil, 'test/one/two/three.xml'
118
+ assert_route r, 'test/one/two/three.html', {:variable => ['one', 'two', 'three'], :format => 'html'}
119
+ end
120
+
101
121
  def test_glob_with_optional_format
102
122
  assert_route '/test/*variable(.:format)', 'test/one/two/three.html', {:variable => ['one', 'two', 'three'], :format => 'html'}
103
123
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: http_router
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.8.1
5
+ version: 0.8.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Joshua Hull
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-06-01 00:00:00 -04:00
13
+ date: 2011-06-01 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -155,6 +155,7 @@ files:
155
155
  - lib/http_router/node/lookup.rb
156
156
  - lib/http_router/node/regex.rb
157
157
  - lib/http_router/node/request.rb
158
+ - lib/http_router/node/root.rb
158
159
  - lib/http_router/node/spanning_regex.rb
159
160
  - lib/http_router/node/variable.rb
160
161
  - lib/http_router/optional_compiler.rb