http_router 0.8.9 → 0.8.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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