http_router 0.8.9 → 0.8.10

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.
@@ -3,14 +3,14 @@ require 'rbench'
3
3
  #require 'lib/usher'
4
4
  $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
5
5
 
6
- require 'lib/http_router'
6
+ require 'http_router'
7
7
 
8
8
  u = HttpRouter.new
9
- u.add('/simple') .name(:simple).send(:compile)
10
- u.add('/simple/:variable') .name(:one_variable).send(:compile)
11
- u.add('/simple/:var1/:var2/:var3') .name(:three_variables).send(:compile)
12
- u.add('/simple/:v1/:v2/:v3/:v4/:v5/:v6/:v7/:v8') .name(:eight_variables).send(:compile)
13
- u.add('/with_condition/:cond1/:cond2').matching(:cond1 => /^\d+$/, :cond2 => /^[a-z]+$/) .name(:two_conditions).send(:compile)
9
+ u.add('/simple') .name(:simple).to{}
10
+ u.add('/simple/:variable') .name(:one_variable).to{}
11
+ u.add('/simple/:var1/:var2/:var3') .name(:three_variables).to{}
12
+ u.add('/simple/:v1/:v2/:v3/:v4/:v5/:v6/:v7/:v8') .name(:eight_variables).to{}
13
+ u.add('/with_condition/:cond1/:cond2').matching(:cond1 => /^\d+$/, :cond2 => /^[a-z]+$/) .name(:two_conditions).to{}
14
14
 
15
15
  TIMES = 50_000
16
16
 
@@ -196,10 +196,6 @@ class HttpRouter
196
196
  cloned_router
197
197
  end
198
198
 
199
- def compile
200
- @root.compile
201
- end
202
-
203
199
  def next_counter
204
200
  @counter += 1
205
201
  end
@@ -65,18 +65,13 @@ class HttpRouter
65
65
 
66
66
  private
67
67
  def inject_root_methods(code = nil, &blk)
68
- if code
69
- root.methods_module.module_eval(code)
70
- else
71
- root.methods_module.module_eval(&blk)
72
- end
68
+ code ? root.methods_module.module_eval(code) : root.methods_module.module_eval(&blk)
73
69
  end
74
70
 
75
71
  def inject_root_ivar(name, val)
76
72
  root.instance_variable_set(name, val)
77
73
  end
78
74
 
79
-
80
75
  def add(matcher)
81
76
  @matchers << matcher unless matcher.usable?(@matchers.last)
82
77
  @matchers.last
@@ -25,9 +25,7 @@ class HttpRouter
25
25
  #{@map[k].map{|n| n.to_code} * "\n"}
26
26
  request.path.unshift part
27
27
  end"}.join("\n")
28
- code = "
29
- send(\"#{method_prefix}\#{request.path.first}\", request) if !request.path_finished? && #{lookup_ivar}.key?(request.path.first)
30
- "
28
+ "send(\"#{method_prefix}\#{request.path.first}\", request) if !request.path_finished? && #{lookup_ivar}.key?(request.path.first)"
31
29
  end
32
30
  end
33
31
  end
@@ -2,10 +2,14 @@ class HttpRouter
2
2
  class Node
3
3
  class Regex < Node
4
4
  alias_method :node_to_code, :to_code
5
- attr_reader :matcher, :splitting_indicies, :capturing_indicies
5
+ attr_reader :matcher, :splitting_indicies, :capturing_indicies, :ordered_indicies
6
6
 
7
7
  def initialize(router, parent, matcher, capturing_indicies, splitting_indicies = nil)
8
8
  @matcher, @capturing_indicies, @splitting_indicies = matcher, capturing_indicies, splitting_indicies
9
+ @ordered_indicies = []
10
+ @ordered_indicies.concat(capturing_indicies.map{|i| [i, :capture]}) if capturing_indicies
11
+ @ordered_indicies.concat(splitting_indicies.map{|i| [i, :split]}) if splitting_indicies
12
+ @ordered_indicies.sort!
9
13
  super(router, parent)
10
14
  end
11
15
 
@@ -16,14 +20,21 @@ class HttpRouter
16
20
  def to_code
17
21
  params_size = @splitting_indicies.size + @capturing_indicies.size
18
22
  "if match = #{@matcher.inspect}.match(request.path.first) and match.begin(0).zero?
19
- part = request.path.shift\n" <<
20
- @splitting_indicies.map { |s| "request.params << match[#{s}].split(/\\//)\n" }.join <<
21
- @capturing_indicies.map { |c| "request.params << match[#{c}]\n" }.join << "
22
- #{super}
23
+ part = request.path.shift\n" << param_capturing_code <<
24
+ "#{super}
23
25
  request.path.unshift part
24
26
  #{params_size == 1 ? "request.params.pop" : "request.params.slice!(#{-params_size}, #{params_size})"}
25
27
  end"
26
28
  end
29
+
30
+ def param_capturing_code
31
+ @ordered_indicies.map{|(i, type)|
32
+ case type
33
+ when :capture then "request.params << match[#{i}]\n"
34
+ when :split then "request.params << match[#{i}].split(/\\//)\n"
35
+ end
36
+ }.join("")
37
+ end
27
38
  end
28
39
  end
29
40
  end
@@ -16,6 +16,7 @@ class HttpRouter
16
16
  def to_code
17
17
  code = "if "
18
18
  code << @opts.map do |k,v|
19
+ v = [v] unless v.is_a?(Array)
19
20
  case v.size
20
21
  when 1 then to_code_condition(k, v.first)
21
22
  else "(#{v.map{|vv| to_code_condition(k, vv)}.join(' or ')})"
@@ -2,13 +2,11 @@ class HttpRouter
2
2
  class Node
3
3
  class SpanningRegex < Regex
4
4
  def to_code
5
- params_count = (@splitting_indicies || []).size + @capturing_indicies.size
5
+ params_count = @ordered_indicies.size
6
6
  "whole_path#{depth} = request.joined_path
7
7
  if match = #{@matcher.inspect}.match(whole_path#{depth}) and match.begin(0).zero?
8
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 << "
9
+ " << param_capturing_code << "
12
10
  remaining_path = whole_path#{depth}[match[0].size + (whole_path#{depth}[match[0].size] == ?/ ? 1 : 0), whole_path#{depth}.size]
13
11
  request.path = remaining_path.split('/')
14
12
  #{node_to_code}
@@ -2,13 +2,17 @@ class HttpRouter
2
2
  class RegexRoute < Route
3
3
  def initialize(router, path, opts = {})
4
4
  @router, @original_path, @opts = router, path, opts
5
+ @param_names = @original_path.respond_to?(:names) ? @original_path.names.map(&:to_sym) : []
5
6
  process_opts
6
7
  end
7
8
 
8
- def compile
9
- @param_names = @original_path.respond_to?(:names) ? @original_path.names.map(&:to_sym) : []
9
+ def add_path_to_tree
10
+ @paths = [@original_path]
10
11
  add_non_path_to_tree(@router.root.add_free_match(@original_path), path, @param_names)
11
- @compiled = true
12
+ end
13
+
14
+ def significant_variable_names
15
+ @param_names
12
16
  end
13
17
 
14
18
  def match_partially?
@@ -1,13 +1,13 @@
1
1
  class HttpRouter
2
2
  class Route
3
- attr_reader :default_values, :matches_with, :router, :path, :conditions, :original_path
3
+ DoubleCompileError = Class.new(RuntimeError)
4
+
5
+ attr_reader :default_values, :router, :path, :conditions, :original_path, :match_partially, :dest, :regex, :named, :matches_with
6
+ alias_method :match_partially?, :match_partially
7
+ alias_method :regex?, :regex
4
8
 
5
9
  def initialize(router, path, opts = {})
6
- @router = router
7
- @original_path = path
8
- @opts = opts
9
- @matches_with = {}
10
- @default_values = opts[:default_values] || {}
10
+ @router, @original_path, @opts = router, path, opts
11
11
  if @original_path[-1] == ?*
12
12
  @match_partially = true
13
13
  path.slice!(-1)
@@ -16,15 +16,24 @@ class HttpRouter
16
16
  end
17
17
 
18
18
  def process_opts
19
- @arbitrary = @opts[:arbitrary] || @opts[:__arbitrary__]
20
- @conditions = @opts[:conditions] || @opts[:__conditions__] || {}
21
- @opts.merge!(@opts[:matching]) if @opts[:matching]
22
- @match_partially = @opts[:partial] if @match_partially.nil? && @opts.key?(:partial)
23
- name(@opts[:name]) if @opts.key?(:name)
19
+ @default_values = @opts[:__default_values__] || @opts[:default_values] || {}
20
+ @arbitrary = @opts[:__arbitrary__] || @opts[:arbitrary]
21
+ @matches_with = significant_variable_names.include?(:matching) ? @opts : @opts[:__matching__] || @opts[:matching] || {}
22
+ significant_variable_names.each do |name|
23
+ @matches_with[name] = @opts[name] if @opts.key?(name) && !@matches_with.key?(name)
24
+ end
25
+ @conditions = @opts[:__conditions__] || @opts[:conditions] || {}
26
+ @match_partially = @opts[:__partial__] if @match_partially.nil? && !@opts[:__partial__].nil?
27
+ @match_partially = @opts[:partial] if @match_partially.nil? && !@opts[:partial].nil?
28
+ name(@opts[:__name__] || @opts[:name]) if @opts.key?(:__name__) || @opts.key?(:name)
24
29
  end
25
30
 
26
31
  def as_options
27
- {:matching => @matches_with, :conditions => @conditions, :default_values => @default_values, :name => @name, :partial => @partially_match, :arbitrary => @arbitrary}
32
+ {:__matching__ => @matches_with, :__conditions__ => @conditions, :__default_values__ => @default_values, :__name__ => @named, :__partial__ => @partially_match, :__arbitrary__ => @arbitrary}
33
+ end
34
+
35
+ def compiled?
36
+ !@paths.nil?
28
37
  end
29
38
 
30
39
  def partial(match_partially = true)
@@ -32,30 +41,14 @@ class HttpRouter
32
41
  self
33
42
  end
34
43
 
35
- def match_partially?
36
- @match_partially
37
- end
38
-
39
- def dest
40
- @app
41
- end
42
-
43
- def regex?
44
- false
45
- end
46
-
47
44
  def to(dest = nil, &dest2)
48
- @app = dest || dest2
49
- compile
45
+ @dest = dest || dest2
46
+ add_path_to_tree
50
47
  self
51
48
  end
52
49
 
53
- def compiled?
54
- @compiled
55
- end
56
-
57
50
  def name(n)
58
- @name = n
51
+ @named = n
59
52
  @router.named_routes[n] = self
60
53
  self
61
54
  end
@@ -77,8 +70,7 @@ class HttpRouter
77
70
  end
78
71
 
79
72
  def matching(matchers)
80
- matchers = Hash[*matchers] if matchers.is_a?(Array)
81
- @opts.merge!(matchers)
73
+ @matches_with.merge!(matchers.is_a?(Array) ? Hash[*matchers] : matchers)
82
74
  self
83
75
  end
84
76
 
@@ -141,11 +133,7 @@ class HttpRouter
141
133
  options = args.last.is_a?(Hash) ? args.pop : nil
142
134
  options = options.nil? ? default_values.dup : default_values.merge(options) if default_values
143
135
  options.delete_if{ |k,v| v.nil? } if options
144
- path = if args.empty?
145
- matching_path(options)
146
- else
147
- matching_path(args, options)
148
- end
136
+ path = args.empty? ? matching_path(options) : matching_path(args, options)
149
137
  raise InvalidRouteException unless path
150
138
  result, params = path.url(args, options)
151
139
  mount_point = router.url_mount && router.url_mount.url(options)
@@ -157,118 +145,100 @@ class HttpRouter
157
145
  end
158
146
 
159
147
  def matching_path(params, other_hash = nil)
160
- if @paths.size == 1
161
- @paths.first
162
- else
163
- if params.is_a?(Array)
164
- significant_keys = other_hash && significant_variable_names & other_hash.keys
165
- @paths.find { |path|
166
- var_count = significant_keys ? params.size + significant_keys.size : params.size
167
- path.param_names.size == var_count
168
- }
169
- else
170
- @paths.each do |path|
171
- if params && !params.empty?
172
- return path if (path.param_names & params.keys).size == path.param_names.size
173
- elsif path.param_names.empty?
174
- return path
175
- end
176
- end
177
- nil
178
- end
148
+ return @paths.first if @paths.size == 1
149
+ case params
150
+ when Array
151
+ significant_keys = other_hash && significant_variable_names & other_hash.keys
152
+ @paths.find { |path| path.param_names.size == (significant_keys ? params.size + significant_keys.size : params.size) }
153
+ when Hash
154
+ @paths.find { |path| (params && !params.empty? && (path.param_names & params.keys).size == path.param_names.size) || path.param_names.empty? }
179
155
  end
180
156
  end
181
157
 
182
- def named
183
- @name
184
- end
185
-
186
158
  def to_s
187
159
  "#<HttpRouter:Route #{object_id} @original_path=#{@original_path.inspect} @conditions=#{@conditions.inspect} @arbitrary=#{@arbitrary.inspect}>"
188
160
  end
189
161
 
190
- def compile
191
- return if @compiled
192
- start_index, end_index = 0, 1
193
- raw_paths, chars = [""], @original_path.split('')
194
- until chars.empty?
195
- case fc = chars.first[0]
196
- when ?(
197
- chars.shift
198
- (start_index...end_index).each { |path_index| raw_paths << raw_paths[path_index].dup }
199
- start_index = end_index
200
- end_index = raw_paths.size
201
- when ?)
202
- chars.shift
203
- start_index -= end_index - start_index
204
- else
205
- c = if chars[0][0] == ?\\ && (chars[1][0] == ?( || chars[1][0] == ?)); chars.shift; chars.shift; else; chars.shift; end
206
- (start_index...end_index).each { |path_index| raw_paths[path_index] << c }
162
+ private
163
+ def raw_paths
164
+ @raw_paths ||= begin
165
+ start_index, end_index = 0, 1
166
+ @raw_paths, chars = [""], @original_path.split('')
167
+ until chars.empty?
168
+ case fc = chars.first[0]
169
+ when ?(
170
+ chars.shift
171
+ (start_index...end_index).each { |path_index| raw_paths << raw_paths[path_index].dup }
172
+ start_index = end_index
173
+ end_index = raw_paths.size
174
+ when ?)
175
+ chars.shift
176
+ start_index -= end_index - start_index
177
+ else
178
+ c = if chars[0][0] == ?\\ && (chars[1][0] == ?( || chars[1][0] == ?)); chars.shift; chars.shift; else; chars.shift; end
179
+ (start_index...end_index).each { |path_index| raw_paths[path_index] << c }
180
+ end
207
181
  end
182
+ @raw_paths.reverse!
183
+ end
184
+ end
185
+
186
+ def add_normal_part(node, part, param_names)
187
+ name = part[1, part.size]
188
+ node = case part[0]
189
+ when ?\\
190
+ node.add_lookup(part[1].chr)
191
+ when ?:
192
+ param_names << name.to_sym
193
+ @matches_with[name.to_sym] ? node.add_spanning_match(@matches_with[name.to_sym]) : node.add_variable
194
+ when ?*
195
+ param_names << name.to_sym
196
+ @matches_with[name.to_sym] ? node.add_glob_regexp(@matches_with[name.to_sym]) : node.add_glob
197
+ else
198
+ node.add_lookup(part)
208
199
  end
209
- raw_paths.reverse!
210
- @paths = raw_paths.map do |path|
211
- param_names = []
212
- node = @router.root
213
- path.split(/\//).each do |part|
214
- next if part == ''
215
- parts = part.scan(/\\.|[:*][a-z0-9_]+|[^:*\\]+/)
216
- if parts.size == 1
217
- name = part[1, part.size]
218
- node = case parts[0][0]
219
- when ?\\
220
- node.add_lookup(parts[0][1].chr)
221
- when ?:
222
- param_names << name.to_sym
223
- matches_with[name.to_sym] = @opts[name.to_sym]
224
- @opts[name.to_sym] ? node.add_spanning_match(@opts.delete(name.to_sym)) : node.add_variable
225
- when ?*
226
- param_names << name.to_sym
227
- matches_with[name.to_sym] = @opts[name.to_sym]
228
- @opts[name.to_sym] ? node.add_glob_regexp(@opts.delete(name.to_sym)) : node.add_glob
229
- else
230
- node.add_lookup(parts[0])
231
- end
200
+ end
201
+
202
+ def add_complex_part(node, parts, param_names)
203
+ capturing_indicies, splitting_indicies, captures, spans = [], [], 0, false
204
+ regex = parts.inject('') do |reg, part|
205
+ reg << case part[0]
206
+ when ?\\ then Regexp.quote(part[1].chr)
207
+ when ?:, ?*
208
+ spans = true if part[0] == ?*
209
+ captures += 1
210
+ (part[0] == ?* ? splitting_indicies : capturing_indicies) << captures
211
+ name = part[1, part.size].to_sym
212
+ param_names << name
213
+ if spans
214
+ @matches_with[name] ? "((?:#{@matches_with[name]}\\/?)+)" : '(.*?)'
232
215
  else
233
- capturing_indicies = []
234
- splitting_indicies = []
235
- captures = 0
236
- spans = false
237
- regex = parts.inject('') do |reg, part|
238
- reg << case part[0]
239
- when ?\\
240
- Regexp.quote(part[1].chr)
241
- when ?:
242
- captures += 1
243
- capturing_indicies << captures
244
- name = part[1, part.size].to_sym
245
- param_names << name
246
- matches_with[name] = @opts[name]
247
- "(#{(@opts[name] || '[^/]*?')})"
248
- when ?*
249
- spans = true
250
- captures += 1
251
- splitting_indicies << captures
252
- name = part[1, part.size].to_sym
253
- param_names << name
254
- matches_with[name] = @opts[name]
255
- @opts[name] ?
256
- "((?:#{@opts[name]}\\/?)+)" : '(.*?)'
257
- else
258
- Regexp.quote(part)
259
- end
260
- end
261
- node = spans ? node.add_spanning_match(Regexp.new("#{regex}$"), capturing_indicies, splitting_indicies) :
262
- node.add_match(Regexp.new("#{regex}$"), capturing_indicies, splitting_indicies)
216
+ "(#{(@matches_with[name] || '[^/]*?')})"
263
217
  end
218
+ else
219
+ Regexp.quote(part)
220
+ end
221
+ end
222
+ spans ? node.add_spanning_match(Regexp.new("#{regex}$"), capturing_indicies, splitting_indicies) :
223
+ node.add_match(Regexp.new("#{regex}$"), capturing_indicies, splitting_indicies)
224
+ end
225
+
226
+ def add_path_to_tree
227
+ raise DoubleCompileError if compiled?
228
+ @paths ||= begin
229
+ raw_paths.map do |path|
230
+ param_names = []
231
+ node = @router.root
232
+ path.split(/\//).each do |part|
233
+ next if part == ''
234
+ parts = part.scan(/\\.|[:*][a-z0-9_]+|[^:*\\]+/)
235
+ node = parts.size == 1 ? add_normal_part(node, part, param_names) : add_complex_part(node, parts, param_names)
236
+ end
237
+ add_non_path_to_tree(node, path, param_names)
264
238
  end
265
- add_non_path_to_tree(node, path, param_names)
266
239
  end
267
- @compiled = true
268
- self
269
240
  end
270
241
 
271
- private
272
242
  def add_non_path_to_tree(node, path, names)
273
243
  path_obj = Path.new(self, path, names)
274
244
  node = node.add_request(@conditions) unless @conditions.empty?
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  class HttpRouter #:nodoc
3
- VERSION = '0.8.9'
3
+ VERSION = '0.8.10'
4
4
  end
@@ -53,6 +53,7 @@ class TestRecognition < MiniTest::Unit::TestCase
53
53
  assert_route route, '/one/two/three'
54
54
  assert_route route, '/one/two/three/four'
55
55
  assert_route route, '/one/two/three/five'
56
+ assert_route route, '/one/two/three/four/five'
56
57
  end
57
58
 
58
59
  def test_escape_paren
@@ -7,11 +7,16 @@ class TestRequest < MiniTest::Unit::TestCase
7
7
  end
8
8
 
9
9
  def test_simple_case_with_array
10
- r = router { add('test', :request => {:request_methods => ['POST', 'GET']}) }
10
+ r = router { add('test', :conditions => {:request_method => ['POST', 'GET']}) }
11
11
  assert_route r, Rack::MockRequest.env_for('/test', :method => 'POST')
12
12
  assert_route r, Rack::MockRequest.env_for('/test', :method => 'GET')
13
13
  end
14
14
 
15
+ def test_simple_case_with_non_array
16
+ r = router { add('test', :conditions => {:request_method => 'GET'}) }
17
+ assert_route r, Rack::MockRequest.env_for('/test', :method => 'GET')
18
+ end
19
+
15
20
  def test_single_app_with_404
16
21
  r = router { add('test').post.to{|env| [404, {}, []]} }
17
22
  assert_route nil, Rack::MockRequest.env_for('/test', :method => 'POST')
@@ -94,6 +94,10 @@ class TestVariable < MiniTest::Unit::TestCase
94
94
  assert_route '/test/*variable/test/*variable2', '/test/one/two/three/test/four/five/six', {:variable => ['one', 'two', 'three'], :variable2 => ['four', 'five', 'six']}
95
95
  end
96
96
 
97
+ def test_var_glob_var
98
+ assert_route '/test/:test-*variable.:format', '/test/one-two/three/four/five.six', {:test=> 'one', :variable => ['two', 'three', 'four', 'five'], :format => 'six'}
99
+ end
100
+
97
101
  def test_glob_with_regexp
98
102
  r = router { add('/test/*variable', :variable => /[a-z]+/) }
99
103
  assert_route nil, '/test/asd/123'
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: 45
4
+ hash: 43
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 8
9
- - 9
10
- version: 0.8.9
9
+ - 10
10
+ version: 0.8.10
11
11
  platform: ruby
12
12
  authors:
13
13
  - Joshua Hull
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-06-19 00:00:00 -07:00
18
+ date: 2011-06-23 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency