rack-mount 0.0.1 → 0.0.2

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.
Files changed (30) hide show
  1. data/README.rdoc +1 -1
  2. data/lib/rack/mount.rb +0 -2
  3. data/lib/rack/mount/analysis/frequency.rb +10 -3
  4. data/lib/rack/mount/analysis/splitting.rb +79 -62
  5. data/lib/rack/mount/generatable_regexp.rb +48 -45
  6. data/lib/rack/mount/generation/route.rb +10 -5
  7. data/lib/rack/mount/generation/route_set.rb +23 -31
  8. data/lib/rack/mount/prefix.rb +14 -15
  9. data/lib/rack/mount/recognition/code_generation.rb +51 -61
  10. data/lib/rack/mount/recognition/route.rb +7 -22
  11. data/lib/rack/mount/recognition/route_set.rb +40 -16
  12. data/lib/rack/mount/regexp_with_named_groups.rb +33 -7
  13. data/lib/rack/mount/route_set.rb +7 -5
  14. data/lib/rack/mount/strexp.rb +11 -40
  15. data/lib/rack/mount/strexp/parser.rb +158 -0
  16. data/lib/rack/mount/strexp/tokenizer.rb +84 -0
  17. data/lib/rack/mount/utils.rb +26 -163
  18. data/lib/rack/mount/vendor/multimap/multimap.rb +1 -0
  19. data/lib/rack/mount/vendor/reginald/reginald.rb +55 -0
  20. data/lib/rack/mount/vendor/reginald/reginald/alternation.rb +50 -0
  21. data/lib/rack/mount/vendor/reginald/reginald/anchor.rb +20 -0
  22. data/lib/rack/mount/vendor/reginald/reginald/character.rb +53 -0
  23. data/lib/rack/mount/vendor/reginald/reginald/character_class.rb +61 -0
  24. data/lib/rack/mount/vendor/reginald/reginald/expression.rb +75 -0
  25. data/lib/rack/mount/vendor/reginald/reginald/group.rb +61 -0
  26. data/lib/rack/mount/vendor/reginald/reginald/parser.rb +306 -0
  27. data/lib/rack/mount/vendor/reginald/reginald/tokenizer.rb +141 -0
  28. metadata +14 -15
  29. data/lib/rack/mount/const.rb +0 -45
  30. data/lib/rack/mount/meta_method.rb +0 -104
@@ -2,29 +2,28 @@ require 'rack/mount/utils'
2
2
 
3
3
  module Rack::Mount
4
4
  class Prefix #:nodoc:
5
- KEY = 'rack.mount.prefix'.freeze
5
+ EMPTY_STRING = ''.freeze
6
+ PATH_INFO = 'PATH_INFO'.freeze
7
+ SCRIPT_NAME = 'SCRIPT_NAME'.freeze
8
+ SLASH = '/'.freeze
6
9
 
7
- def initialize(app, prefix = nil)
10
+ def initialize(app, prefix)
8
11
  @app, @prefix = app, prefix.freeze
9
12
  freeze
10
13
  end
11
14
 
12
15
  def call(env)
13
- if prefix = env[KEY] || @prefix
14
- old_path_info = env[Const::PATH_INFO].dup
15
- old_script_name = env[Const::SCRIPT_NAME].dup
16
+ old_path_info = env[PATH_INFO].dup
17
+ old_script_name = env[SCRIPT_NAME].dup
16
18
 
17
- begin
18
- env[Const::PATH_INFO] = Utils.normalize_path(env[Const::PATH_INFO].sub(prefix, Const::EMPTY_STRING))
19
- env[Const::PATH_INFO] = Const::EMPTY_STRING if env[Const::PATH_INFO] == Const::SLASH
20
- env[Const::SCRIPT_NAME] = Utils.normalize_path(env[Const::SCRIPT_NAME].to_s + prefix)
21
- @app.call(env)
22
- ensure
23
- env[Const::PATH_INFO] = old_path_info
24
- env[Const::SCRIPT_NAME] = old_script_name
25
- end
26
- else
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)
27
23
  @app.call(env)
24
+ ensure
25
+ env[PATH_INFO] = old_path_info
26
+ env[SCRIPT_NAME] = old_script_name
28
27
  end
29
28
  end
30
29
  end
@@ -1,98 +1,88 @@
1
- require 'rack/mount/meta_method'
2
-
3
1
  module Rack::Mount
4
2
  module Recognition
5
3
  module CodeGeneration #:nodoc:
6
- def _expired_call(env) #:nodoc:
4
+ def _expired_recognize(env) #:nodoc:
7
5
  raise 'route set not finalized'
8
6
  end
9
7
 
10
8
  def rehash
11
9
  super
12
- optimize_call!
10
+ optimize_recognize!
13
11
  end
14
12
 
15
13
  private
16
14
  def expire!
17
15
  class << self
18
- alias_method :call, :_expired_call
16
+ undef :recognize
17
+ alias_method :recognize, :_expired_recognize
19
18
  end
20
19
 
21
20
  super
22
21
  end
23
22
 
24
23
  def optimize_container_iterator(container)
25
- m = MetaMethod.new(:optimized_each, :req)
26
- m << 'env = req.env'
24
+ body = []
27
25
 
28
26
  container.each_with_index { |route, i|
29
- path_info_unanchored = route.conditions[:path_info] &&
30
- !Utils.regexp_anchored?(route.conditions[:path_info])
31
- m << "route = self[#{i}]"
32
- m << 'routing_args = route.defaults.dup'
27
+ body << "route = self[#{i}]"
28
+ body << 'params = route.defaults.dup'
33
29
 
34
- m << matchers = MetaMethod::Condition.new do |body|
35
- body << "env[#{@parameters_key.inspect}] = routing_args"
36
- body << "response = route.app.call(env)"
37
- body << "return response unless response[0].to_i == 417"
30
+ conditions = []
31
+ route.conditions.each do |method, condition|
32
+ b = []
33
+ b << "if m = obj.#{method}.match(#{condition.inspect})"
34
+ b << 'matches = m.captures' if route.named_captures[method].any?
35
+ b << 'p = nil' if route.named_captures[method].any?
36
+ b << route.named_captures[method].map { |k, j| "params[#{k.inspect}] = p if p = matches[#{j}]" }.join('; ')
37
+ b << 'true'
38
+ b << 'end'
39
+ conditions << "(#{b.join('; ')})"
38
40
  end
39
41
 
40
- route.conditions.each do |method, condition|
41
- matchers << MetaMethod::Block.new do |matcher|
42
- matcher << c = MetaMethod::Condition.new("m = req.#{method}.match(#{condition.inspect})") do |b|
43
- b << "matches = m.captures" if route.named_captures[method].any?
44
- route.named_captures[method].each do |k, i|
45
- b << MetaMethod::Condition.new("p = matches[#{i}]") do |c2|
46
- c2 << "routing_args[#{k.inspect}] = Utils.unescape_uri(p)"
47
- end
48
- end
49
- if method == :path_info && !Utils.regexp_anchored?(condition)
50
- b << "env[Prefix::KEY] = m.to_s"
51
- end
52
- b << "true"
53
- end
54
- c.else = MetaMethod::Block.new("false")
42
+ body << <<-RUBY
43
+ if #{conditions.join(' && ')}
44
+ yield route, params
55
45
  end
56
- end
46
+ RUBY
57
47
  }
58
48
 
59
- m << 'nil'
60
- # puts "\n#{m.inspect}"
61
- container.instance_eval(m, __FILE__, __LINE__)
49
+ container.instance_eval(<<-RUBY, __FILE__, __LINE__)
50
+ def optimized_each(obj)
51
+ #{body.join("\n")}
52
+ nil
53
+ end
54
+ RUBY
62
55
  end
63
56
 
64
- def optimize_call!
65
- method = MetaMethod.new(:call, :env)
57
+ def optimize_recognize!
58
+ keys = @recognition_keys.map { |key|
59
+ if key.is_a?(Array)
60
+ key.call_source(:cache, :obj)
61
+ else
62
+ "obj.#{key}"
63
+ end
64
+ }.join(', ')
66
65
 
67
- if @routes.empty?
68
- method << 'env[Const::EXPECT] != Const::CONTINUE ? Const::NOT_FOUND_RESPONSE : Const::EXPECTATION_FAILED_RESPONSE'
69
- else
70
- method << 'begin'
71
- method << 'set_expectation = env[Const::EXPECT] != Const::CONTINUE'
72
- method << 'env[Const::EXPECT] = Const::CONTINUE if set_expectation'
66
+ instance_eval(<<-RUBY, __FILE__, __LINE__)
67
+ undef :recognize
68
+ def recognize(obj, &block)
69
+ cache = {}
70
+ container = @recognition_graph[#{keys}]
71
+ optimize_container_iterator(container) unless container.respond_to?(:optimized_each)
73
72
 
74
- method << 'env[Const::PATH_INFO] = Utils.normalize_path(env[Const::PATH_INFO])'
75
- method << "req = #{@request_class.name}.new(env)"
76
- cache = false
77
- keys = @recognition_keys.map { |key|
78
- if key.is_a?(Array)
79
- cache = true
80
- key.call_source(:cache, :req)
73
+ if block_given?
74
+ container.optimized_each(obj) do |route, params|
75
+ yield route, params
76
+ end
81
77
  else
82
- "req.#{key}"
78
+ container.optimized_each(obj) do |route, params|
79
+ return route, params
80
+ end
83
81
  end
84
- }.join(', ')
85
- method << 'cache = {}' if cache
86
- method << "container = @recognition_graph[#{keys}]"
87
- method << "optimize_container_iterator(container) unless container.respond_to?(:optimized_each)"
88
- method << "container.optimized_each(req) || (set_expectation ? Const::NOT_FOUND_RESPONSE : Const::EXPECTATION_FAILED_RESPONSE)"
89
- method << 'ensure'
90
- method << 'env.delete(Const::EXPECT) if set_expectation'
91
- method << 'end'
92
- end
93
82
 
94
- # puts "\n#{method.inspect}"
95
- instance_eval(method, __FILE__, __LINE__)
83
+ nil
84
+ end
85
+ RUBY
96
86
  end
97
87
  end
98
88
  end
@@ -1,4 +1,4 @@
1
- require 'rack/mount/prefix'
1
+ require 'rack/mount/utils'
2
2
 
3
3
  module Rack::Mount
4
4
  module Recognition
@@ -8,12 +8,6 @@ module Rack::Mount
8
8
  def initialize(*args)
9
9
  super
10
10
 
11
- # TODO: Don't explict check for :path_info condition
12
- if @conditions.has_key?(:path_info) &&
13
- !Utils.regexp_anchored?(@conditions[:path_info])
14
- @app = Prefix.new(@app)
15
- end
16
-
17
11
  @named_captures = {}
18
12
  @conditions.map { |method, condition|
19
13
  @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)|
@@ -24,34 +18,25 @@ module Rack::Mount
24
18
  @named_captures.freeze
25
19
  end
26
20
 
27
- def call(req)
28
- env = req.env
29
-
30
- routing_args = @defaults.dup
21
+ def recognize(obj)
22
+ params = @defaults.dup
31
23
  if @conditions.all? { |method, condition|
32
- value = req.send(method)
24
+ value = obj.send(method)
33
25
  if m = value.match(condition)
34
26
  matches = m.captures
35
27
  @named_captures[method].each { |k, i|
36
28
  if v = matches[i]
37
- # TODO: We only want to unescape params from
38
- # uri related methods
39
- routing_args[k] = Utils.unescape_uri(v)
29
+ params[k] = v
40
30
  end
41
31
  }
42
- # TODO: Don't explict check for :path_info condition
43
- if method == :path_info && !Utils.regexp_anchored?(condition)
44
- env[Prefix::KEY] = m.to_s
45
- end
46
32
  true
47
33
  else
48
34
  false
49
35
  end
50
36
  }
51
- env[@set.parameters_key] = routing_args
52
- @app.call(env)
37
+ params
53
38
  else
54
- Const::EXPECTATION_FAILED_RESPONSE
39
+ nil
55
40
  end
56
41
  end
57
42
  end
@@ -7,7 +7,7 @@ module Rack::Mount
7
7
 
8
8
  # Adds recognition related concerns to RouteSet.new.
9
9
  def initialize(options = {})
10
- @parameters_key = options.delete(:parameters_key) || Const::RACK_ROUTING_ARGS
10
+ @parameters_key = options.delete(:parameters_key) || 'rack.routing_args'
11
11
  @parameters_key.freeze
12
12
  @recognition_key_analyzer = Analysis::Frequency.new_with_module(Analysis::Splitting)
13
13
 
@@ -21,6 +21,33 @@ module Rack::Mount
21
21
  route
22
22
  end
23
23
 
24
+ def recognize(obj)
25
+ raise 'route set not finalized' unless @recognition_graph
26
+
27
+ cache = {}
28
+ keys = @recognition_keys.map { |key|
29
+ if key.is_a?(Array)
30
+ key.call(cache, obj)
31
+ else
32
+ obj.send(key)
33
+ end
34
+ }
35
+ @recognition_graph[*keys].each do |route|
36
+ if params = route.recognize(obj)
37
+ if block_given?
38
+ yield route, params
39
+ else
40
+ return route, params
41
+ end
42
+ end
43
+ end
44
+
45
+ nil
46
+ end
47
+
48
+ EXPECT = 'Expect'.freeze
49
+ PATH_INFO = 'PATH_INFO'.freeze
50
+
24
51
  # Rack compatible recognition and dispatching method. Routes are
25
52
  # tried until one returns a non-catch status code. If no routes
26
53
  # match, the catch status code is returned.
@@ -30,27 +57,24 @@ module Rack::Mount
30
57
  def call(env)
31
58
  raise 'route set not finalized' unless @recognition_graph
32
59
 
33
- set_expectation = env[Const::EXPECT] != Const::CONTINUE
34
- env[Const::EXPECT] = Const::CONTINUE if set_expectation
60
+ set_expectation = env[EXPECT] != '100-continue'
61
+ env[EXPECT] = '100-continue' if set_expectation
35
62
 
36
- env[Const::PATH_INFO] = Utils.normalize_path(env[Const::PATH_INFO])
63
+ env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO])
37
64
 
38
- cache = {}
39
65
  req = @request_class.new(env)
40
- keys = @recognition_keys.map { |key|
41
- if key.is_a?(Array)
42
- key.call(cache, req)
43
- else
44
- req.send(key)
45
- end
46
- }
47
- @recognition_graph[*keys].each do |route|
48
- result = route.call(req)
66
+ recognize(req) do |route, params|
67
+ # TODO: We only want to unescape params from uri related methods
68
+ params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) }
69
+
70
+ env[@parameters_key] = params
71
+ result = route.app.call(env)
49
72
  return result unless result[0].to_i == 417
50
73
  end
51
- set_expectation ? Const::NOT_FOUND_RESPONSE : Const::EXPECTATION_FAILED_RESPONSE
74
+
75
+ set_expectation ? [404, {'Content-Type' => 'text/html'}, ['Not Found']] : [417, {'Content-Type' => 'text/html'}, ['Expectation failed']]
52
76
  ensure
53
- env.delete(Const::EXPECT) if set_expectation
77
+ env.delete(EXPECT) if set_expectation
54
78
  end
55
79
 
56
80
  def rehash #:nodoc:
@@ -1,7 +1,15 @@
1
- require 'rack/mount/utils'
2
-
3
1
  module Rack::Mount
4
- unless Const::SUPPORTS_NAMED_CAPTURES
2
+ begin
3
+ eval('/(?<foo>.*)/').named_captures
4
+
5
+ class RegexpWithNamedGroups < Regexp
6
+ def self.supports_named_captures?
7
+ true
8
+ end
9
+ end
10
+ rescue SyntaxError, NoMethodError
11
+ require 'strscan'
12
+
5
13
  # A wrapper that adds shim named capture support to older
6
14
  # versions of Ruby.
7
15
  #
@@ -16,6 +24,10 @@ module Rack::Mount
16
24
  #
17
25
  # /(?:<foo>[a-z]+)/
18
26
  class RegexpWithNamedGroups < Regexp
27
+ def self.supports_named_captures?
28
+ false
29
+ end
30
+
19
31
  def self.new(regexp) #:nodoc:
20
32
  if regexp.is_a?(RegexpWithNamedGroups)
21
33
  regexp
@@ -26,9 +38,25 @@ module Rack::Mount
26
38
 
27
39
  # Wraps Regexp with named capture support.
28
40
  def initialize(regexp)
29
- regexp, @names = Utils.extract_named_captures(regexp)
41
+ regexp = Regexp.compile(regexp) unless regexp.is_a?(Regexp)
42
+ source, options = regexp.source, regexp.options
43
+ @names, scanner = [], StringScanner.new(source)
44
+
45
+ while scanner.skip_until(/\(/)
46
+ if scanner.scan(/\?:<([^>]+)>/)
47
+ @names << scanner[1]
48
+ elsif scanner.scan(/\?:/)
49
+ # ignore
50
+ else
51
+ @names << nil
52
+ end
53
+ end
54
+ source.gsub!(/\?:<([^>]+)>/, '')
55
+
56
+ @names = [] unless @names.any?
30
57
  @names.freeze
31
- super(regexp)
58
+
59
+ super(source, options)
32
60
  end
33
61
 
34
62
  def names
@@ -43,7 +71,5 @@ module Rack::Mount
43
71
  named_captures
44
72
  end
45
73
  end
46
- else
47
- RegexpWithNamedGroups = Regexp
48
74
  end
49
75
  end
@@ -40,7 +40,7 @@ module Rack::Mount
40
40
  # Conditions may be expressed as strings or
41
41
  # regexps to match against.
42
42
  # <tt>defaults</tt>:: A hash of values that always gets merged in
43
- # <tt>name</tt>:: Symbol identifier for the route used with named
43
+ # <tt>name</tt>:: Symbol identifier for the route used with named
44
44
  # route generations
45
45
  def add_route(app, conditions = {}, defaults = {}, name = nil)
46
46
  route = Route.new(self, app, conditions, defaults, name)
@@ -98,10 +98,12 @@ module Rack::Mount
98
98
  def build_nested_route_set(keys, &block)
99
99
  graph = Multimap.new
100
100
  @routes.each_with_index do |route, index|
101
- k = keys.map { |key| block.call(key, index) }
102
- Utils.pop_trailing_nils!(k)
103
- k.map! { |key| key || /.+/ }
104
- graph[*k] = route
101
+ catch(:skip) do
102
+ k = keys.map { |key| block.call(key, index) }
103
+ Utils.pop_trailing_nils!(k)
104
+ k.map! { |key| key || /.+/ }
105
+ graph[*k] = route
106
+ end
105
107
  end
106
108
  graph
107
109
  end
@@ -1,4 +1,4 @@
1
- require 'strscan'
1
+ require 'rack/mount/strexp/parser'
2
2
 
3
3
  module Rack::Mount
4
4
  class Strexp < Regexp
@@ -24,14 +24,19 @@ module Rack::Mount
24
24
  def initialize(str, requirements = {}, separators = [])
25
25
  return super(str) if str.is_a?(Regexp)
26
26
 
27
- re = Regexp.escape(str)
28
27
  requirements = requirements ? requirements.dup : {}
29
-
30
28
  normalize_requirements!(requirements, separators)
31
- parse_dynamic_segments!(re, requirements)
32
- parse_optional_segments!(re)
33
29
 
34
- super("\\A#{re}\\Z")
30
+ parser = StrexpParser.new
31
+ parser.requirements = requirements
32
+
33
+ begin
34
+ re = parser.scan_str(str)
35
+ rescue Racc::ParseError => e
36
+ raise RegexpError, e.message
37
+ end
38
+
39
+ super(re)
35
40
  end
36
41
 
37
42
  private
@@ -52,40 +57,6 @@ module Rack::Mount
52
57
  requirements
53
58
  end
54
59
 
55
- def parse_dynamic_segments!(str, requirements)
56
- re, pos, scanner = '', 0, StringScanner.new(str)
57
- while scanner.scan_until(/(:|\\\*)([a-zA-Z_]\w*)/)
58
- pre, pos = scanner.pre_match[pos..-1], scanner.pos
59
- if pre =~ /(.*)\\\\\Z/
60
- re << $1 + scanner.matched
61
- else
62
- name = scanner[2].to_sym
63
- requirement = scanner[1] == ':' ?
64
- requirements[name] : '.+'
65
- re << pre + Const::REGEXP_NAMED_CAPTURE % [name, requirement]
66
- end
67
- end
68
- re << scanner.rest
69
- str.replace(re)
70
- end
71
-
72
- def parse_optional_segments!(str)
73
- re, pos, scanner = '', 0, StringScanner.new(str)
74
- while scanner.scan_until(/\\\(|\\\)/)
75
- pre, pos = scanner.pre_match[pos..-1], scanner.pos
76
- if pre =~ /(.*)\\\\\Z/
77
- re << $1 + scanner.matched
78
- elsif scanner.matched == '\\('
79
- # re << pre + '(?:'
80
- re << pre + '('
81
- elsif scanner.matched == '\\)'
82
- re << pre + ')?'
83
- end
84
- end
85
- re << scanner.rest
86
- str.replace(re)
87
- end
88
-
89
60
  def regexp_has_modifiers?(regexp)
90
61
  regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
91
62
  end