rack-mount 0.4.7 → 0.5.0

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.
@@ -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