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.
- data/README.rdoc +1 -1
- data/lib/rack/mount.rb +0 -2
- data/lib/rack/mount/analysis/frequency.rb +10 -3
- data/lib/rack/mount/analysis/splitting.rb +79 -62
- data/lib/rack/mount/generatable_regexp.rb +48 -45
- data/lib/rack/mount/generation/route.rb +10 -5
- data/lib/rack/mount/generation/route_set.rb +23 -31
- data/lib/rack/mount/prefix.rb +14 -15
- data/lib/rack/mount/recognition/code_generation.rb +51 -61
- data/lib/rack/mount/recognition/route.rb +7 -22
- data/lib/rack/mount/recognition/route_set.rb +40 -16
- data/lib/rack/mount/regexp_with_named_groups.rb +33 -7
- data/lib/rack/mount/route_set.rb +7 -5
- data/lib/rack/mount/strexp.rb +11 -40
- data/lib/rack/mount/strexp/parser.rb +158 -0
- data/lib/rack/mount/strexp/tokenizer.rb +84 -0
- data/lib/rack/mount/utils.rb +26 -163
- data/lib/rack/mount/vendor/multimap/multimap.rb +1 -0
- data/lib/rack/mount/vendor/reginald/reginald.rb +55 -0
- data/lib/rack/mount/vendor/reginald/reginald/alternation.rb +50 -0
- data/lib/rack/mount/vendor/reginald/reginald/anchor.rb +20 -0
- data/lib/rack/mount/vendor/reginald/reginald/character.rb +53 -0
- data/lib/rack/mount/vendor/reginald/reginald/character_class.rb +61 -0
- data/lib/rack/mount/vendor/reginald/reginald/expression.rb +75 -0
- data/lib/rack/mount/vendor/reginald/reginald/group.rb +61 -0
- data/lib/rack/mount/vendor/reginald/reginald/parser.rb +306 -0
- data/lib/rack/mount/vendor/reginald/reginald/tokenizer.rb +141 -0
- metadata +14 -15
- data/lib/rack/mount/const.rb +0 -45
- data/lib/rack/mount/meta_method.rb +0 -104
data/lib/rack/mount/prefix.rb
CHANGED
@@ -2,29 +2,28 @@ require 'rack/mount/utils'
|
|
2
2
|
|
3
3
|
module Rack::Mount
|
4
4
|
class Prefix #:nodoc:
|
5
|
-
|
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
|
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
|
-
|
14
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
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
|
-
|
10
|
+
optimize_recognize!
|
13
11
|
end
|
14
12
|
|
15
13
|
private
|
16
14
|
def expire!
|
17
15
|
class << self
|
18
|
-
|
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
|
-
|
26
|
-
m << 'env = req.env'
|
24
|
+
body = []
|
27
25
|
|
28
26
|
container.each_with_index { |route, i|
|
29
|
-
|
30
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
46
|
+
RUBY
|
57
47
|
}
|
58
48
|
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
65
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
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
|
-
|
95
|
-
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
RUBY
|
96
86
|
end
|
97
87
|
end
|
98
88
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'rack/mount/
|
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
|
28
|
-
|
29
|
-
|
30
|
-
routing_args = @defaults.dup
|
21
|
+
def recognize(obj)
|
22
|
+
params = @defaults.dup
|
31
23
|
if @conditions.all? { |method, condition|
|
32
|
-
value =
|
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
|
-
|
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
|
-
|
52
|
-
@app.call(env)
|
37
|
+
params
|
53
38
|
else
|
54
|
-
|
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) ||
|
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[
|
34
|
-
env[
|
60
|
+
set_expectation = env[EXPECT] != '100-continue'
|
61
|
+
env[EXPECT] = '100-continue' if set_expectation
|
35
62
|
|
36
|
-
env[
|
63
|
+
env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO])
|
37
64
|
|
38
|
-
cache = {}
|
39
65
|
req = @request_class.new(env)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
74
|
+
|
75
|
+
set_expectation ? [404, {'Content-Type' => 'text/html'}, ['Not Found']] : [417, {'Content-Type' => 'text/html'}, ['Expectation failed']]
|
52
76
|
ensure
|
53
|
-
env.delete(
|
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
|
-
|
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
|
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
|
-
|
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
|
data/lib/rack/mount/route_set.rb
CHANGED
@@ -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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
data/lib/rack/mount/strexp.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|
-
|
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
|