http_router 0.8.1 → 0.8.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/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