rack-mount 0.4.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,11 +5,7 @@ module Rack::Mount
5
5
  module Splitting
6
6
  NULL = "\0".freeze
7
7
 
8
- class Key < Array
9
- def initialize(method, index, separators)
10
- replace([method, index, separators])
11
- end
12
-
8
+ class Key < Struct.new(:method, :index, :separators)
13
9
  def self.split(value, separator_pattern)
14
10
  keys = value.split(separator_pattern)
15
11
  keys.shift if keys[0] == ''
@@ -18,11 +14,15 @@ module Rack::Mount
18
14
  end
19
15
 
20
16
  def call(cache, obj)
21
- (cache[self[0]] ||= self.class.split(obj.send(self[0]), self[2]))[self[1]]
17
+ (cache[method] ||= self.class.split(obj.send(method), separators))[index]
22
18
  end
23
19
 
24
20
  def call_source(cache, obj)
25
- "(#{cache}[:#{self[0]}] ||= Analysis::Splitting::Key.split(#{obj}.#{self[0]}, #{self[2].inspect}))[#{self[1]}]"
21
+ "(#{cache}[:#{method}] ||= Analysis::Splitting::Key.split(#{obj}.#{method}, #{separators.inspect}))[#{index}]"
22
+ end
23
+
24
+ def inspect
25
+ "#{method}[#{index}]"
26
26
  end
27
27
  end
28
28
 
@@ -4,6 +4,12 @@ module Rack::Mount
4
4
  # metaclass. This allows mixins to be stacked ontop of the instance
5
5
  # methods.
6
6
  module Mixover
7
+ def self.extended(klass)
8
+ klass.instance_eval do
9
+ @extended_modules = []
10
+ end
11
+ end
12
+
7
13
  module InstanceMethods #:nodoc:
8
14
  def dup
9
15
  obj = super
@@ -15,33 +21,40 @@ module Rack::Mount
15
21
 
16
22
  # Replaces include with a lazy version.
17
23
  def include(*mod)
18
- (@included_modules ||= []).push(*mod)
24
+ extended_modules.push(*mod)
25
+ end
26
+
27
+ def extended_modules
28
+ Thread.current[extended_modules_thread_local_key] || @extended_modules
19
29
  end
20
30
 
21
31
  def new(*args, &block) #:nodoc:
22
32
  obj = allocate
23
33
  obj.extend(InstanceMethods)
24
- (@included_modules ||= []).each { |mod| obj.extend(mod) }
34
+ extended_modules.each { |mod| obj.extend(mod) }
25
35
  obj.send(:initialize, *args, &block)
26
36
  obj
27
37
  end
28
38
 
29
39
  # Create a new class without an included module.
30
40
  def new_without_module(mod, *args, &block)
31
- old_included_modules = (@included_modules ||= []).dup
32
- @included_modules.delete(mod)
41
+ (Thread.current[extended_modules_thread_local_key] = extended_modules.dup).delete(mod)
33
42
  new(*args, &block)
34
43
  ensure
35
- @included_modules = old_included_modules
44
+ Thread.current[extended_modules_thread_local_key] = nil
36
45
  end
37
46
 
38
47
  # Create a new class temporarily with a module.
39
48
  def new_with_module(mod, *args, &block)
40
- old_included_modules = (@included_modules ||= []).dup
41
- include(mod)
49
+ (Thread.current[extended_modules_thread_local_key] = extended_modules.dup).push(*mod)
42
50
  new(*args, &block)
43
51
  ensure
44
- @included_modules = old_included_modules
52
+ Thread.current[extended_modules_thread_local_key] = nil
45
53
  end
54
+
55
+ private
56
+ def extended_modules_thread_local_key
57
+ "mixover_extended_modules_#{object_id}"
58
+ end
46
59
  end
47
60
  end
@@ -7,23 +7,29 @@ module Rack::Mount
7
7
  SCRIPT_NAME = 'SCRIPT_NAME'.freeze
8
8
  SLASH = '/'.freeze
9
9
 
10
- def initialize(app, prefix)
10
+ KEY = 'rack.mount.prefix'.freeze
11
+
12
+ def initialize(app, prefix = nil)
11
13
  @app, @prefix = app, prefix.freeze
12
14
  freeze
13
15
  end
14
16
 
15
17
  def call(env)
16
- old_path_info = env[PATH_INFO].dup
17
- old_script_name = env[SCRIPT_NAME].dup
18
+ if prefix = env[KEY] || @prefix
19
+ old_path_info = env[PATH_INFO].dup
20
+ old_script_name = env[SCRIPT_NAME].dup
18
21
 
19
- begin
20
- env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO].sub(@prefix, EMPTY_STRING))
21
- env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH
22
- env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + @prefix)
22
+ begin
23
+ env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO].sub(prefix, EMPTY_STRING))
24
+ env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH
25
+ env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + prefix)
26
+ @app.call(env)
27
+ ensure
28
+ env[PATH_INFO] = old_path_info
29
+ env[SCRIPT_NAME] = old_script_name
30
+ end
31
+ else
23
32
  @app.call(env)
24
- ensure
25
- env[PATH_INFO] = old_path_info
26
- env[SCRIPT_NAME] = old_script_name
27
33
  end
28
34
  end
29
35
  end
@@ -39,6 +39,7 @@ module Rack::Mount
39
39
 
40
40
  container.each_with_index { |route, i|
41
41
  body << "route = self[#{i}]"
42
+ body << 'matches = {}'
42
43
  body << 'params = route.defaults.dup'
43
44
 
44
45
  conditions = []
@@ -46,10 +47,11 @@ module Rack::Mount
46
47
  b = []
47
48
  if condition.is_a?(Regexp)
48
49
  b << "if m = obj.#{method}.match(#{condition.inspect})"
50
+ b << "matches[:#{method}] = m"
49
51
  if (named_captures = route.named_captures[method]) && named_captures.any?
50
- b << 'matches = m.captures'
52
+ b << 'captures = m.captures'
51
53
  b << 'p = nil'
52
- b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = matches[#{j}]" }.join('; ')
54
+ b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = captures[#{j}]" }.join('; ')
53
55
  end
54
56
  else
55
57
  b << "if m = obj.#{method} == route.conditions[:#{method}]"
@@ -61,7 +63,7 @@ module Rack::Mount
61
63
 
62
64
  body << <<-RUBY
63
65
  if #{conditions.join(' && ')}
64
- yield route, params
66
+ yield route, matches, params
65
67
  end
66
68
  RUBY
67
69
  }
@@ -76,7 +78,7 @@ module Rack::Mount
76
78
 
77
79
  def optimize_recognize!
78
80
  keys = @recognition_keys.map { |key|
79
- if key.is_a?(Array)
81
+ if key.respond_to?(:call_source)
80
82
  key.call_source(:cache, :obj)
81
83
  else
82
84
  "obj.#{key}"
@@ -94,12 +96,12 @@ module Rack::Mount
94
96
  optimize_container_iterator(container) unless container.respond_to?(:optimized_each)
95
97
 
96
98
  if block_given?
97
- container.optimized_each(obj) do |route, params|
98
- yield route, params
99
+ container.optimized_each(obj) do |route, matches, params|
100
+ yield route, matches, params
99
101
  end
100
102
  else
101
- container.optimized_each(obj) do |route, params|
102
- return route, params
103
+ container.optimized_each(obj) do |route, matches, params|
104
+ return route, matches, params
103
105
  end
104
106
  end
105
107
 
@@ -17,16 +17,31 @@ module Rack::Mount
17
17
  }.freeze
18
18
  }
19
19
  @named_captures.freeze
20
+
21
+ if @conditions.has_key?(:path_info) &&
22
+ !Utils.regexp_anchored?(@conditions[:path_info])
23
+ @prefix = true
24
+ @app = Prefix.new(@app)
25
+ else
26
+ @prefix = false
27
+ end
28
+ end
29
+
30
+ def prefix?
31
+ @prefix
20
32
  end
21
33
 
22
34
  def recognize(obj)
23
- params = @defaults.dup
35
+ matches = {}
36
+ params = @defaults.dup
37
+
24
38
  if @conditions.all? { |method, condition|
25
39
  value = obj.send(method)
26
40
  if condition.is_a?(Regexp) && (m = value.match(condition))
27
- matches = m.captures
41
+ matches[method] = m
42
+ captures = m.captures
28
43
  @named_captures[method].each { |k, i|
29
- if v = matches[i]
44
+ if v = captures[i]
30
45
  params[k] = v
31
46
  end
32
47
  }
@@ -37,7 +52,7 @@ module Rack::Mount
37
52
  false
38
53
  end
39
54
  }
40
- params
55
+ return matches, params
41
56
  else
42
57
  nil
43
58
  end
@@ -26,18 +26,19 @@ module Rack::Mount
26
26
 
27
27
  cache = {}
28
28
  keys = @recognition_keys.map { |key|
29
- if key.is_a?(Array)
29
+ if key.respond_to?(:call_source)
30
30
  key.call(cache, obj)
31
31
  else
32
32
  obj.send(key)
33
33
  end
34
34
  }
35
35
  @recognition_graph[*keys].each do |route|
36
- if params = route.recognize(obj)
36
+ matches, params = route.recognize(obj)
37
+ if matches && params
37
38
  if block_given?
38
- yield route, params
39
+ yield route, matches, params
39
40
  else
40
- return route, params
41
+ return route, matches, params
41
42
  end
42
43
  end
43
44
  end
@@ -62,10 +63,14 @@ module Rack::Mount
62
63
 
63
64
  request = nil
64
65
  req = @request_class.new(env)
65
- recognize(req) do |route, params|
66
+ recognize(req) do |route, matches, params|
66
67
  # TODO: We only want to unescape params from uri related methods
67
68
  params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) }
68
69
 
70
+ if route.prefix?
71
+ env[Prefix::KEY] = matches[:path_info].to_s
72
+ end
73
+
69
74
  env[@parameters_key] = params
70
75
  result = route.app.call(env)
71
76
  return result unless result[1][X_CASCADE] == PASS
@@ -81,6 +86,15 @@ module Rack::Mount
81
86
  super
82
87
  end
83
88
 
89
+ protected
90
+ def recognition_stats
91
+ { :keys => @recognition_keys,
92
+ :keys_size => @recognition_keys.size,
93
+ :graph_size => @recognition_graph.size,
94
+ :graph_height => @recognition_graph.height,
95
+ :graph_average_height => @recognition_graph.average_height }
96
+ end
97
+
84
98
  private
85
99
  def expire!
86
100
  @recognition_keys = @recognition_graph = nil
@@ -21,13 +21,14 @@ module Rack::Mount
21
21
  #
22
22
  # Strexp.compile('src/*files')
23
23
  # # => %r{\Asrc/(?<files>.+)\Z}
24
- def initialize(str, requirements = {}, separators = [])
24
+ def initialize(str, requirements = {}, separators = [], anchor = true)
25
25
  return super(str) if str.is_a?(Regexp)
26
26
 
27
27
  requirements = requirements ? requirements.dup : {}
28
28
  normalize_requirements!(requirements, separators)
29
29
 
30
30
  parser = StrexpParser.new
31
+ parser.anchor = anchor
31
32
  parser.requirements = requirements
32
33
 
33
34
  begin
@@ -20,7 +20,7 @@ else
20
20
  REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze
21
21
  end
22
22
 
23
- attr_accessor :requirements
23
+ attr_accessor :anchor, :requirements
24
24
  ##### State transition tables begin ###
25
25
 
26
26
  racc_action_table = [
@@ -114,7 +114,7 @@ Racc_debug_parser = false
114
114
  # reduce 0 omitted
115
115
 
116
116
  def _reduce_1(val, _values, result)
117
- result = "\\A#{val.join}\\Z"
117
+ result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}"
118
118
  result
119
119
  end
120
120
 
@@ -88,6 +88,17 @@ module Rack::Mount
88
88
  end
89
89
  module_function :build_nested_query
90
90
 
91
+ # Determines whether the regexp must match the entire string.
92
+ #
93
+ # regexp_anchored?(/^foo$/) # => true
94
+ # regexp_anchored?(/foo/) # => false
95
+ # regexp_anchored?(/^foo/) # => false
96
+ # regexp_anchored?(/foo$/) # => false
97
+ def regexp_anchored?(regexp)
98
+ regexp.source =~ /\A(\\A|\^).*(\\Z|\$)\Z/ ? true : false
99
+ end
100
+ module_function :regexp_anchored?
101
+
91
102
  def normalize_extended_expression(regexp)
92
103
  return regexp unless regexp.options & Regexp::EXTENDED != 0
93
104
  source = regexp.source
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-mount
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.7
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Peek