rack-mount 0.6.13 → 0.8.3

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.
@@ -2,8 +2,8 @@ require 'rack/mount/utils'
2
2
 
3
3
  module Rack::Mount
4
4
  module Analysis
5
- class Splitting < Frequency
6
- NULL = "\0".freeze
5
+ class Splitting
6
+ NULL = "\0"
7
7
 
8
8
  class Key < Struct.new(:method, :index, :separators)
9
9
  def self.split(value, separator_pattern)
@@ -26,26 +26,44 @@ module Rack::Mount
26
26
  end
27
27
  end
28
28
 
29
+ def initialize(*keys)
30
+ clear
31
+ keys.each { |key| self << key }
32
+ end
33
+
29
34
  def clear
30
- @boundaries = {}
31
- super
35
+ @raw_keys = []
36
+ @key_frequency = Analysis::Histogram.new
37
+ self
32
38
  end
33
39
 
34
40
  def <<(key)
35
- super
36
- key.each_pair do |k, v|
37
- analyze_capture_boundaries(v, @boundaries[k] ||= Histogram.new)
41
+ raise ArgumentError unless key.is_a?(Hash)
42
+ @raw_keys << key
43
+ nil
44
+ end
45
+
46
+ def possible_keys
47
+ @possible_keys ||= begin
48
+ @raw_keys.map do |key|
49
+ key.inject({}) { |requirements, (method, requirement)|
50
+ process_key(requirements, method, requirement)
51
+ requirements
52
+ }
53
+ end
38
54
  end
39
55
  end
40
56
 
41
- def separators(key)
42
- @separators ||= {}
43
- @separators[key] ||= lookup_separators(key)
57
+ def report
58
+ @report ||= begin
59
+ possible_keys.each { |keys| keys.each_pair { |key, _| @key_frequency << key } }
60
+ return [] if @key_frequency.count <= 1
61
+ @key_frequency.keys_in_upper_quartile
62
+ end
44
63
  end
45
- attr_writer :separators
46
64
 
47
- def lookup_separators(key)
48
- @boundaries[key].keys_in_upper_quartile
65
+ def expire!
66
+ @possible_keys = @report = nil
49
67
  end
50
68
 
51
69
  def process_key(requirements, method, requirement)
@@ -55,40 +73,22 @@ module Rack::Mount
55
73
  requirements[Key.new(method, index, Regexp.union(*separators))] = value
56
74
  end
57
75
  else
58
- super
59
- end
60
- end
61
-
62
- private
63
- def analyze_capture_boundaries(regexp, boundaries) #:nodoc:
64
- return boundaries unless regexp.is_a?(Regexp)
65
-
66
- parts = Utils.parse_regexp(regexp)
67
- parts.each_with_index do |part, index|
68
- if part.is_a?(Regin::Group)
69
- if index > 0
70
- previous = parts[index-1]
71
- if previous.is_a?(Regin::Character) && previous.literal?
72
- boundaries << previous.to_s
73
- end
74
- end
75
-
76
- if inside = part.expression[0]
77
- if inside.is_a?(Regin::Character) && inside.literal?
78
- boundaries << inside.to_s
79
- end
80
- end
76
+ if requirement.is_a?(Regexp)
77
+ expression = Utils.parse_regexp(requirement)
81
78
 
82
- if index < parts.length
83
- following = parts[index+1]
84
- if following.is_a?(Regin::Character) && following.literal?
85
- boundaries << following.to_s
86
- end
87
- end
79
+ if expression.is_a?(Regin::Expression) && expression.anchored_to_line?
80
+ expression = Regin::Expression.new(expression.reject { |e| e.is_a?(Regin::Anchor) })
81
+ return requirements[method] = expression.to_s if expression.literal?
88
82
  end
89
83
  end
90
84
 
91
- boundaries
85
+ requirements[method] = requirement
86
+ end
87
+ end
88
+
89
+ private
90
+ def separators(key)
91
+ key == :path_info ? ["/", "."] : []
92
92
  end
93
93
 
94
94
  def generate_split_keys(regexp, separators) #:nodoc:
@@ -71,8 +71,11 @@ module Rack::Mount
71
71
  def optimize_recognize!
72
72
  Utils.debug "optimizing recognize"
73
73
 
74
+ uses_cache = false
75
+
74
76
  keys = @recognition_keys.map { |key|
75
77
  if key.respond_to?(:call_source)
78
+ uses_cache = true
76
79
  key.call_source(:cache, :obj)
77
80
  else
78
81
  "obj.#{key}"
@@ -85,7 +88,7 @@ module Rack::Mount
85
88
 
86
89
  instance_eval(<<-RUBY, __FILE__, __LINE__)
87
90
  def recognize(obj)
88
- cache = {}
91
+ #{"cache = {}" if uses_cache}
89
92
  container = @recognition_graph[#{keys}]
90
93
  optimize_container_iterator(container) unless container.respond_to?(:optimized_each)
91
94
 
@@ -110,7 +113,7 @@ module Rack::Mount
110
113
  def remove_metaclass_method(symbol)
111
114
  metaclass = class << self; self; end
112
115
  Utils.silence_debug { metaclass.send(:remove_method, symbol) }
113
- rescue NameError => e
116
+ rescue NameError
114
117
  nil
115
118
  end
116
119
  end
@@ -1,12 +1,16 @@
1
- begin
2
- require 'nested_multimap'
3
- rescue LoadError
4
- $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/multimap'))
5
- require 'nested_multimap'
6
- end
7
-
8
1
  module Rack::Mount
9
- class Multimap < NestedMultimap #:nodoc:
2
+ class Multimap #:nodoc:
3
+ def initialize(default = [])
4
+ @hash = Hash.new(default)
5
+ end
6
+
7
+ def initialize_copy(original)
8
+ @hash = Hash.new(original.default.dup)
9
+ original.hash.each_pair do |key, container|
10
+ @hash[key] = container.dup
11
+ end
12
+ end
13
+
10
14
  def store(*args)
11
15
  keys = args.dup
12
16
  value = keys.pop
@@ -26,7 +30,7 @@ module Rack::Mount
26
30
  @hash.each_pair { |k, _|
27
31
  if k =~ key
28
32
  args[0] = k
29
- super(*args)
33
+ _store(*args)
30
34
  end
31
35
  }
32
36
 
@@ -34,12 +38,19 @@ module Rack::Mount
34
38
  default[*keys.dup] = value
35
39
  end
36
40
  else
37
- super(*args)
41
+ _store(*args)
38
42
  end
39
43
  end
40
44
  alias_method :[]=, :store
41
45
 
42
- undef :index, :invert
46
+ def [](*keys)
47
+ i, l, r, k = 0, keys.length, self, self.class
48
+ while r.is_a?(k)
49
+ r = i < l ? r.hash[keys[i]] : r.default
50
+ i += 1
51
+ end
52
+ r
53
+ end
43
54
 
44
55
  def height
45
56
  containers_with_default.max { |a, b| a.length <=> b.length }.length
@@ -49,5 +60,78 @@ module Rack::Mount
49
60
  lengths = containers_with_default.map { |e| e.length }
50
61
  lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size
51
62
  end
63
+
64
+ def containers_with_default
65
+ containers = []
66
+ each_container_with_default { |container| containers << container }
67
+ containers
68
+ end
69
+
70
+ protected
71
+ def _store(*args)
72
+ keys = args
73
+ value = args.pop
74
+
75
+ raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
76
+
77
+ if keys.length > 1
78
+ update_container(keys.shift) do |container|
79
+ container = self.class.new(container) unless container.is_a?(self.class)
80
+ container[*keys] = value
81
+ container
82
+ end
83
+ elsif keys.length == 1
84
+ update_container(keys.first) do |container|
85
+ container << value
86
+ container
87
+ end
88
+ else
89
+ self << value
90
+ end
91
+ end
92
+
93
+ def <<(value)
94
+ @hash.each_value { |container| container << value }
95
+ self.default << value
96
+ self
97
+ end
98
+
99
+ def each_container_with_default(&block)
100
+ @hash.each_value do |container|
101
+ iterate_over_container(container, &block)
102
+ end
103
+ iterate_over_container(default, &block)
104
+ self
105
+ end
106
+
107
+ def default
108
+ @hash.default
109
+ end
110
+
111
+ def default=(value)
112
+ @hash.default = value
113
+ end
114
+
115
+ def hash
116
+ @hash
117
+ end
118
+
119
+ private
120
+ def update_container(key)
121
+ container = @hash[key]
122
+ container = container.dup if container.equal?(default)
123
+ container = yield(container)
124
+ @hash[key] = container
125
+ end
126
+
127
+ def iterate_over_container(container)
128
+ if container.respond_to?(:each_container_with_default)
129
+ container.each_container_with_default do |value|
130
+ yield value
131
+ end
132
+ else
133
+ yield container
134
+ end
135
+ end
52
136
  end
53
137
  end
@@ -10,7 +10,7 @@ module Rack::Mount
10
10
  KEY = 'rack.mount.prefix'.freeze
11
11
 
12
12
  def initialize(app, prefix = nil)
13
- @app, @prefix = app, prefix.freeze
13
+ @app, @prefix = app, prefix
14
14
  freeze
15
15
  end
16
16
 
@@ -54,10 +54,9 @@ module Rack::Mount
54
54
  @named_captures = {}
55
55
  @conditions.map { |method, condition|
56
56
  next unless condition.respond_to?(:named_captures)
57
- @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)|
58
- named_captures[k.to_sym] = v.last - 1
59
- named_captures
60
- }.freeze
57
+ @named_captures[method] = Hash[condition.named_captures.map { |k, v|
58
+ [k.to_sym, v.last - 1]
59
+ }].freeze
61
60
  }
62
61
  @named_captures.freeze
63
62
 
@@ -83,7 +82,7 @@ module Rack::Mount
83
82
 
84
83
 
85
84
  def generation_keys
86
- @conditions.inject({}) { |keys, (method, condition)|
85
+ @conditions.inject({}) { |keys, (_, condition)|
87
86
  if condition.respond_to?(:required_defaults)
88
87
  keys.merge!(condition.required_defaults)
89
88
  else
@@ -98,12 +97,9 @@ module Rack::Mount
98
97
 
99
98
  def generate(method, params = {}, recall = {}, options = {})
100
99
  if method.nil?
101
- result = @conditions.inject({}) { |h, (m, condition)|
102
- if condition.respond_to?(:generate)
103
- h[m] = condition.generate(params, recall, options)
104
- end
105
- h
106
- }
100
+ result = Hash[@conditions.map { |m, condition|
101
+ [m, condition.generate(params, recall, options)] if condition.respond_to?(:generate)
102
+ }]
107
103
  return nil if result.values.compact.empty?
108
104
  else
109
105
  if condition = @conditions[method]
@@ -25,7 +25,9 @@ module Rack::Mount
25
25
  @named_routes = {}
26
26
 
27
27
  @recognition_key_analyzer = Analysis::Splitting.new
28
- @generation_key_analyzer = Analysis::Frequency.new
28
+
29
+ @generation_keys = [:controller, :action]
30
+ @generation_route_keys = []
29
31
 
30
32
  @request_class = options.delete(:request_class) || Rack::Request
31
33
  @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym }
@@ -69,7 +71,7 @@ module Rack::Mount
69
71
  @recognition_key_analyzer << route.conditions
70
72
 
71
73
  @named_routes[route.name] = route if route.name
72
- @generation_key_analyzer << route.generation_keys
74
+ @generation_route_keys << route.generation_keys
73
75
 
74
76
  expire!
75
77
  route
@@ -125,7 +127,7 @@ module Rack::Mount
125
127
 
126
128
  # Rack compatible recognition and dispatching method. Routes are
127
129
  # tried until one returns a non-catch status code. If no routes
128
- # match, the catch status code is returned.
130
+ # match, then catch status code is returned.
129
131
  #
130
132
  # This method can only be invoked after the RouteSet has been
131
133
  # finalized.
@@ -144,9 +146,16 @@ module Rack::Mount
144
146
  env[Prefix::KEY] = matches[:path_info].to_s
145
147
  end
146
148
 
147
- env[@parameters_key] = params
149
+ old_params = env[@parameters_key]
150
+ env[@parameters_key] = (old_params || {}).merge(params)
151
+
148
152
  result = route.app.call(env)
149
- return result unless result[1][X_CASCADE] == PASS
153
+
154
+ if result[1][X_CASCADE] == PASS
155
+ env[@parameters_key] = old_params
156
+ else
157
+ return result
158
+ end
150
159
  end
151
160
 
152
161
  request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']]
@@ -160,7 +169,7 @@ module Rack::Mount
160
169
  # Additional parameters can be passed in as a hash
161
170
  # url(env, :people, :id => "1") # => "/people/1"
162
171
  #
163
- # If no name route is given, it will fall back to a slower
172
+ # If no named route is given, it will fall back to a slower
164
173
  # generation search.
165
174
  # url(env, :controller => "people", :action => "show", :id => "1")
166
175
  # # => "/people/1"
@@ -249,7 +258,6 @@ module Rack::Mount
249
258
 
250
259
  @recognition_keys = build_recognition_keys
251
260
  @recognition_graph = build_recognition_graph
252
- @generation_keys = build_generation_keys
253
261
  @generation_graph = build_generation_graph
254
262
 
255
263
  self
@@ -261,9 +269,10 @@ module Rack::Mount
261
269
  def freeze
262
270
  unless frozen?
263
271
  rehash
272
+ stubbed_request_class
264
273
 
265
274
  @recognition_key_analyzer = nil
266
- @generation_key_analyzer = nil
275
+ @generation_route_keys = nil
267
276
  @valid_conditions = nil
268
277
 
269
278
  @routes.each { |route| route.freeze }
@@ -273,26 +282,6 @@ module Rack::Mount
273
282
  super
274
283
  end
275
284
 
276
- def marshal_dump #:nodoc:
277
- hash = {}
278
-
279
- instance_variables_to_serialize.each do |ivar|
280
- hash[ivar] = instance_variable_get(ivar)
281
- end
282
-
283
- if graph = hash[:@recognition_graph]
284
- hash[:@recognition_graph] = graph.dup
285
- end
286
-
287
- hash
288
- end
289
-
290
- def marshal_load(hash) #:nodoc:
291
- hash.each do |ivar, value|
292
- instance_variable_set(ivar, value)
293
- end
294
- end
295
-
296
285
  protected
297
286
  def recognition_stats
298
287
  { :keys => @recognition_keys,
@@ -307,12 +296,7 @@ module Rack::Mount
307
296
  @recognition_keys = @recognition_graph = nil
308
297
  @recognition_key_analyzer.expire!
309
298
 
310
- @generation_keys = @generation_graph = nil
311
- @generation_key_analyzer.expire!
312
- end
313
-
314
- def instance_variables_to_serialize
315
- instance_variables.map { |ivar| ivar.to_sym } - [:@stubbed_request_class, :@optimized_recognize_defined]
299
+ @generation_graph = nil
316
300
  end
317
301
 
318
302
  # An internal helper method for constructing a nested set from
@@ -350,7 +334,7 @@ module Rack::Mount
350
334
  build_nested_route_set(@generation_keys) { |k, i|
351
335
  throw :skip unless @routes[i].significant_params?
352
336
 
353
- if k = @generation_key_analyzer.possible_keys[i][k]
337
+ if k = @generation_route_keys[i][k]
354
338
  k.to_s
355
339
  else
356
340
  nil
@@ -358,12 +342,6 @@ module Rack::Mount
358
342
  }
359
343
  end
360
344
 
361
- def build_generation_keys
362
- keys = @generation_key_analyzer.report
363
- Utils.debug "generation keys - #{keys.inspect}"
364
- keys
365
- end
366
-
367
345
  def extract_params!(*args)
368
346
  case args.length
369
347
  when 4
Binary file
@@ -116,7 +116,7 @@ module Rack::Mount
116
116
  module_function :regexp_anchored?
117
117
 
118
118
  def normalize_extended_expression(regexp)
119
- return regexp unless regexp.options & Regexp::EXTENDED != 0
119
+ return regexp if (regexp.options & Regexp::EXTENDED) == 0
120
120
  source = regexp.source
121
121
  source.gsub!(/#.+$/, '')
122
122
  source.gsub!(/\s+/, '')
@@ -1,6 +1,6 @@
1
1
  module Regin
2
2
  class Group
3
- attr_reader :expression, :quantifier, :capture, :index, :name
3
+ attr_reader :expression, :quantifier, :lookahead, :capture, :index, :name
4
4
 
5
5
  def initialize(expression, options = {})
6
6
  @quantifier = @index = @name = nil
@@ -8,6 +8,7 @@ module Regin
8
8
  @expression = expression.dup(options)
9
9
 
10
10
  @quantifier = options[:quantifier] if options.key?(:quantifier)
11
+ @lookahead = options[:lookahead] if options.key?(:lookahead)
11
12
  @capture = options[:capture] if options.key?(:capture)
12
13
  @index = options[:index] if options.key?(:index)
13
14
  @name = options[:name] if options.key?(:name)
@@ -21,11 +22,15 @@ module Regin
21
22
  #
22
23
  # A Group is literal if its expression is literal and it has no quantifier.
23
24
  def literal?
24
- quantifier.nil? && expression.literal?
25
+ quantifier.nil? && lookahead.nil? && expression.literal?
25
26
  end
26
27
 
27
28
  def to_s(parent = false)
28
- if !expression.options?
29
+ if lookahead == :postive
30
+ "(?=#{expression.to_s(parent)})#{quantifier}"
31
+ elsif lookahead == :negative
32
+ "(?!#{expression.to_s(parent)})#{quantifier}"
33
+ elsif !expression.options?
29
34
  "(#{capture ? '' : '?:'}#{expression.to_s(parent)})#{quantifier}"
30
35
  elsif capture == false
31
36
  "#{expression.to_s}#{quantifier}"