rack-mount 0.0.1 → 0.0.2

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