rack-mount 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +28 -0
- data/lib/rack/mount/analysis/frequency.rb +51 -0
- data/lib/rack/mount/analysis/histogram.rb +25 -0
- data/lib/rack/mount/analysis/splitting.rb +145 -0
- data/lib/rack/mount/const.rb +45 -0
- data/lib/rack/mount/exceptions.rb +3 -0
- data/lib/rack/mount/generatable_regexp.rb +163 -0
- data/lib/rack/mount/generation/route.rb +57 -0
- data/lib/rack/mount/generation/route_set.rb +163 -0
- data/lib/rack/mount/meta_method.rb +104 -0
- data/lib/rack/mount/mixover.rb +47 -0
- data/lib/rack/mount/multimap.rb +94 -0
- data/lib/rack/mount/prefix.rb +31 -0
- data/lib/rack/mount/recognition/code_generation.rb +99 -0
- data/lib/rack/mount/recognition/route.rb +59 -0
- data/lib/rack/mount/recognition/route_set.rb +88 -0
- data/lib/rack/mount/regexp_with_named_groups.rb +49 -0
- data/lib/rack/mount/route.rb +69 -0
- data/lib/rack/mount/route_set.rb +109 -0
- data/lib/rack/mount/strexp.rb +93 -0
- data/lib/rack/mount/utils.rb +271 -0
- data/lib/rack/mount/vendor/multimap/multimap.rb +466 -0
- data/lib/rack/mount/vendor/multimap/multiset.rb +153 -0
- data/lib/rack/mount/vendor/multimap/nested_multimap.rb +156 -0
- data/lib/rack/mount.rb +35 -0
- metadata +100 -0
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'rack/mount/utils'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Rack::Mount
|
5
|
+
module Generation
|
6
|
+
module RouteSet
|
7
|
+
# Adds generation related concerns to RouteSet.new.
|
8
|
+
def initialize(*args)
|
9
|
+
@named_routes = {}
|
10
|
+
@generation_key_analyzer = Analysis::Frequency.new
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
# Adds generation aspects to RouteSet#add_route.
|
16
|
+
def add_route(*args)
|
17
|
+
route = super
|
18
|
+
@named_routes[route.name] = route if route.name
|
19
|
+
@generation_key_analyzer << route.generation_keys
|
20
|
+
route
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generates path from identifiers or significant keys.
|
24
|
+
#
|
25
|
+
# To generate a url by named route, pass the name in as a +Symbol+.
|
26
|
+
# url(:dashboard) # => "/dashboard"
|
27
|
+
#
|
28
|
+
# Additional parameters can be passed in as a hash
|
29
|
+
# url(:people, :id => "1") # => "/people/1"
|
30
|
+
#
|
31
|
+
# If no name route is given, it will fall back to a slower
|
32
|
+
# generation search.
|
33
|
+
# url(:controller => "people", :action => "show", :id => "1")
|
34
|
+
# # => "/people/1"
|
35
|
+
def url(*args)
|
36
|
+
named_route, params, recall = extract_params!(*args)
|
37
|
+
|
38
|
+
params = URISegment.wrap_values(params)
|
39
|
+
recall = URISegment.wrap_values(recall)
|
40
|
+
|
41
|
+
unless result = generate(:path_info, named_route, params, recall)
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
uri, params = result
|
46
|
+
params.each do |k, v|
|
47
|
+
if v._value
|
48
|
+
params[k] = v._value
|
49
|
+
else
|
50
|
+
params.delete(k)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
uri << "?#{Utils.build_nested_query(params)}" if uri && params.any?
|
55
|
+
uri
|
56
|
+
end
|
57
|
+
|
58
|
+
def generate(method, *args) #:nodoc:
|
59
|
+
raise 'route set not finalized' unless @generation_graph
|
60
|
+
|
61
|
+
named_route, params, recall = extract_params!(*args)
|
62
|
+
merged = recall.merge(params)
|
63
|
+
route = nil
|
64
|
+
|
65
|
+
if named_route
|
66
|
+
if route = @named_routes[named_route.to_sym]
|
67
|
+
recall = route.defaults.merge(recall)
|
68
|
+
url = route.generate(method, params, recall)
|
69
|
+
[url, params]
|
70
|
+
else
|
71
|
+
raise RoutingError, "#{named_route} failed to generate from #{params.inspect}"
|
72
|
+
end
|
73
|
+
else
|
74
|
+
keys = @generation_keys.map { |key|
|
75
|
+
if k = merged[key]
|
76
|
+
k.to_s
|
77
|
+
else
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
}
|
81
|
+
@generation_graph[*keys].each do |r|
|
82
|
+
if url = r.generate(method, params, recall)
|
83
|
+
return [url, params]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
raise RoutingError, "No route matches #{params.inspect}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def rehash #:nodoc:
|
92
|
+
@generation_keys = build_generation_keys
|
93
|
+
@generation_graph = build_generation_graph
|
94
|
+
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def expire!
|
100
|
+
@generation_keys = @generation_graph = nil
|
101
|
+
super
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_generation_graph
|
105
|
+
build_nested_route_set(@generation_keys) { |k, i|
|
106
|
+
if k = @generation_key_analyzer.possible_keys[i][k]
|
107
|
+
k.to_s
|
108
|
+
else
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def build_generation_keys
|
115
|
+
@generation_key_analyzer.report
|
116
|
+
end
|
117
|
+
|
118
|
+
def extract_params!(*args)
|
119
|
+
case args.length
|
120
|
+
when 3
|
121
|
+
named_route, params, recall = args
|
122
|
+
when 2
|
123
|
+
if args[0].is_a?(Hash) && args[1].is_a?(Hash)
|
124
|
+
params, recall = args
|
125
|
+
else
|
126
|
+
named_route, params = args
|
127
|
+
end
|
128
|
+
when 1
|
129
|
+
if args[0].is_a?(Hash)
|
130
|
+
params = args[0]
|
131
|
+
else
|
132
|
+
named_route = args[0]
|
133
|
+
end
|
134
|
+
else
|
135
|
+
raise ArgumentError
|
136
|
+
end
|
137
|
+
|
138
|
+
named_route ||= nil
|
139
|
+
params ||= {}
|
140
|
+
recall ||= {}
|
141
|
+
|
142
|
+
[named_route, params.dup, recall.dup]
|
143
|
+
end
|
144
|
+
|
145
|
+
class URISegment < Struct.new(:_value)
|
146
|
+
def self.wrap_values(hash)
|
147
|
+
hash.inject({}) { |h, (k, v)| h[k] = new(v); h }
|
148
|
+
end
|
149
|
+
|
150
|
+
extend Forwardable
|
151
|
+
def_delegators :_value, :==, :eql?, :hash
|
152
|
+
|
153
|
+
def to_param
|
154
|
+
@to_param ||= begin
|
155
|
+
v = _value.respond_to?(:to_param) ? _value.to_param : _value
|
156
|
+
Utils.escape_uri(v)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
alias_method :to_s, :to_param
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Rack::Mount
|
2
|
+
class MetaMethod #:nodoc:
|
3
|
+
class Block < Array #:nodoc:
|
4
|
+
def initialize(*parts)
|
5
|
+
replace(parts)
|
6
|
+
yield(self) if block_given?
|
7
|
+
end
|
8
|
+
|
9
|
+
def multiline?
|
10
|
+
length > 1
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect(indented = 2)
|
14
|
+
return Const::EMPTY_STRING if empty?
|
15
|
+
space = ' ' * indented
|
16
|
+
space + map { |p|
|
17
|
+
if p.is_a?(Condition)
|
18
|
+
p.inspect(indented)
|
19
|
+
else
|
20
|
+
p
|
21
|
+
end
|
22
|
+
}.join("\n#{space}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_str
|
26
|
+
map { |p| p.to_str }.join('; ')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Condition #:nodoc:
|
31
|
+
attr_accessor :body, :else
|
32
|
+
|
33
|
+
def initialize(*conditions)
|
34
|
+
@conditions = conditions.map { |c| c.is_a?(Block) ? c : Block.new(c) }
|
35
|
+
@body = Block.new
|
36
|
+
@else = Block.new
|
37
|
+
yield(@body) if block_given?
|
38
|
+
end
|
39
|
+
|
40
|
+
def <<(condition)
|
41
|
+
@conditions << condition
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect(indented = 2)
|
45
|
+
return @body.inspect(indented) if @conditions.empty?
|
46
|
+
space = ' ' * indented
|
47
|
+
str = 'if '
|
48
|
+
str << @conditions.map { |b|
|
49
|
+
b.multiline? ?
|
50
|
+
"begin\n#{b.inspect(indented + 4)}\n#{space} end" :
|
51
|
+
b.inspect(0)
|
52
|
+
}.join(' && ')
|
53
|
+
str << "\n#{@body.inspect(indented + 2)}" if @body.any?
|
54
|
+
if @else.any?
|
55
|
+
str << "\n#{space}else\n#{@else.inspect(indented + 2)}"
|
56
|
+
end
|
57
|
+
str << "\n#{space}end"
|
58
|
+
str
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_str
|
62
|
+
return @body.to_str if @conditions.empty?
|
63
|
+
str = 'if '
|
64
|
+
str << @conditions.map { |b|
|
65
|
+
b.multiline? ? "(#{b.to_str})" : b.to_str
|
66
|
+
}.join(' && ')
|
67
|
+
str << "; #{@body.to_str}" if @body.any?
|
68
|
+
if @else.any?
|
69
|
+
str << "; else; #{@else.to_str}"
|
70
|
+
end
|
71
|
+
str << "; end"
|
72
|
+
str
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize(sym, *args)
|
77
|
+
@sym = sym
|
78
|
+
@args = args
|
79
|
+
@body = Block.new
|
80
|
+
end
|
81
|
+
|
82
|
+
def <<(line)
|
83
|
+
@body << line
|
84
|
+
end
|
85
|
+
|
86
|
+
def inspect
|
87
|
+
str = ""
|
88
|
+
str << "def #{@sym}"
|
89
|
+
str << "(#{@args.join(', ')})" if @args.any?
|
90
|
+
str << "\n#{@body.inspect}" if @body.any?
|
91
|
+
str << "\nend\n"
|
92
|
+
str
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_str
|
96
|
+
str = []
|
97
|
+
str << "def #{@sym}"
|
98
|
+
str << "(#{@args.join(', ')})" if @args.any?
|
99
|
+
str << "\n#{@body.to_str}" if @body.any?
|
100
|
+
str << "\nend"
|
101
|
+
str.join
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Rack::Mount
|
2
|
+
# A mixin that changes the behavior of +include+. Instead of modules
|
3
|
+
# being chained as a superclass, they are mixed into the objects
|
4
|
+
# metaclass. This allows mixins to be stacked ontop of the instance
|
5
|
+
# methods.
|
6
|
+
module Mixover
|
7
|
+
module InstanceMethods #:nodoc:
|
8
|
+
def dup
|
9
|
+
obj = super
|
10
|
+
included_modules = (class << self; included_modules; end) - (class << obj; included_modules; end)
|
11
|
+
included_modules.reverse.each { |mod| obj.extend(mod) }
|
12
|
+
obj
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Replaces include with a lazy version.
|
17
|
+
def include(*mod)
|
18
|
+
(@included_modules ||= []).push(*mod)
|
19
|
+
end
|
20
|
+
|
21
|
+
def new(*args, &block) #:nodoc:
|
22
|
+
obj = allocate
|
23
|
+
obj.extend(InstanceMethods)
|
24
|
+
(@included_modules ||= []).each { |mod| obj.extend(mod) }
|
25
|
+
obj.send(:initialize, *args, &block)
|
26
|
+
obj
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create a new class without an included module.
|
30
|
+
def new_without_module(mod, *args, &block)
|
31
|
+
old_included_modules = (@included_modules ||= []).dup
|
32
|
+
@included_modules.delete(mod)
|
33
|
+
new(*args, &block)
|
34
|
+
ensure
|
35
|
+
@included_modules = old_included_modules
|
36
|
+
end
|
37
|
+
|
38
|
+
# Create a new class temporarily with a module.
|
39
|
+
def new_with_module(mod, *args, &block)
|
40
|
+
old_included_modules = (@included_modules ||= []).dup
|
41
|
+
include(mod)
|
42
|
+
new(*args, &block)
|
43
|
+
ensure
|
44
|
+
@included_modules = old_included_modules
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
begin
|
2
|
+
require 'nested_multimap'
|
3
|
+
rescue LoadError
|
4
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/multimap'))
|
5
|
+
require 'nested_multimap'
|
6
|
+
end
|
7
|
+
|
8
|
+
module Rack::Mount
|
9
|
+
class Multimap < NestedMultimap #:nodoc:
|
10
|
+
def self.[](*args)
|
11
|
+
map = super
|
12
|
+
map.instance_variable_set('@fuzz', {})
|
13
|
+
map
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(default = [])
|
17
|
+
@fuzz = {}
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize_copy(original)
|
22
|
+
@fuzz = original.instance_variable_get('@fuzz').dup
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def store(*args)
|
27
|
+
keys = args.dup
|
28
|
+
value = keys.pop
|
29
|
+
key = keys.shift
|
30
|
+
|
31
|
+
raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
|
32
|
+
|
33
|
+
unless key.respond_to?(:=~)
|
34
|
+
raise ArgumentError, "unsupported key: #{args.first.inspect}"
|
35
|
+
end
|
36
|
+
|
37
|
+
if key.is_a?(Regexp)
|
38
|
+
@fuzz[value] = key
|
39
|
+
if keys.empty?
|
40
|
+
hash_each_pair { |k, l| l << value if k =~ key }
|
41
|
+
self.default << value
|
42
|
+
else
|
43
|
+
hash_each_pair { |k, _|
|
44
|
+
if k =~ key
|
45
|
+
args[0] = k
|
46
|
+
super(*args)
|
47
|
+
end
|
48
|
+
}
|
49
|
+
|
50
|
+
self.default = self.class.new(default) unless default.is_a?(self.class)
|
51
|
+
default[*keys.dup] = value
|
52
|
+
end
|
53
|
+
else
|
54
|
+
super(*args)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
alias_method :[]=, :store
|
58
|
+
|
59
|
+
def freeze
|
60
|
+
@fuzz.clear
|
61
|
+
@fuzz = nil
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
undef :index, :invert
|
66
|
+
|
67
|
+
def height
|
68
|
+
containers_with_default.max { |a, b| a.length <=> b.length }.length
|
69
|
+
end
|
70
|
+
|
71
|
+
def average_height
|
72
|
+
lengths = containers_with_default.map { |e| e.length }
|
73
|
+
lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
def update_container(key) #:nodoc:
|
78
|
+
super do |container|
|
79
|
+
if container.is_a?(self.class)
|
80
|
+
container.each_container_with_default do |c|
|
81
|
+
c.delete_if do |value|
|
82
|
+
(requirement = @fuzz[value]) && key !~ requirement
|
83
|
+
end
|
84
|
+
end
|
85
|
+
else
|
86
|
+
container.delete_if do |value|
|
87
|
+
(requirement = @fuzz[value]) && key !~ requirement
|
88
|
+
end
|
89
|
+
end
|
90
|
+
yield container
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rack/mount/utils'
|
2
|
+
|
3
|
+
module Rack::Mount
|
4
|
+
class Prefix #:nodoc:
|
5
|
+
KEY = 'rack.mount.prefix'.freeze
|
6
|
+
|
7
|
+
def initialize(app, prefix = nil)
|
8
|
+
@app, @prefix = app, prefix.freeze
|
9
|
+
freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
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
|
+
|
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
|
27
|
+
@app.call(env)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'rack/mount/meta_method'
|
2
|
+
|
3
|
+
module Rack::Mount
|
4
|
+
module Recognition
|
5
|
+
module CodeGeneration #:nodoc:
|
6
|
+
def _expired_call(env) #:nodoc:
|
7
|
+
raise 'route set not finalized'
|
8
|
+
end
|
9
|
+
|
10
|
+
def rehash
|
11
|
+
super
|
12
|
+
optimize_call!
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def expire!
|
17
|
+
class << self
|
18
|
+
alias_method :call, :_expired_call
|
19
|
+
end
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def optimize_container_iterator(container)
|
25
|
+
m = MetaMethod.new(:optimized_each, :req)
|
26
|
+
m << 'env = req.env'
|
27
|
+
|
28
|
+
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'
|
33
|
+
|
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"
|
38
|
+
end
|
39
|
+
|
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")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
m << 'nil'
|
60
|
+
# puts "\n#{m.inspect}"
|
61
|
+
container.instance_eval(m, __FILE__, __LINE__)
|
62
|
+
end
|
63
|
+
|
64
|
+
def optimize_call!
|
65
|
+
method = MetaMethod.new(:call, :env)
|
66
|
+
|
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'
|
73
|
+
|
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)
|
81
|
+
else
|
82
|
+
"req.#{key}"
|
83
|
+
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
|
+
|
94
|
+
# puts "\n#{method.inspect}"
|
95
|
+
instance_eval(method, __FILE__, __LINE__)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rack/mount/prefix'
|
2
|
+
|
3
|
+
module Rack::Mount
|
4
|
+
module Recognition
|
5
|
+
module Route #:nodoc:
|
6
|
+
attr_reader :named_captures
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
super
|
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
|
+
@named_captures = {}
|
18
|
+
@conditions.map { |method, condition|
|
19
|
+
@named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)|
|
20
|
+
named_captures[k.to_sym] = v.last - 1
|
21
|
+
named_captures
|
22
|
+
}.freeze
|
23
|
+
}
|
24
|
+
@named_captures.freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(req)
|
28
|
+
env = req.env
|
29
|
+
|
30
|
+
routing_args = @defaults.dup
|
31
|
+
if @conditions.all? { |method, condition|
|
32
|
+
value = req.send(method)
|
33
|
+
if m = value.match(condition)
|
34
|
+
matches = m.captures
|
35
|
+
@named_captures[method].each { |k, i|
|
36
|
+
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)
|
40
|
+
end
|
41
|
+
}
|
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
|
+
true
|
47
|
+
else
|
48
|
+
false
|
49
|
+
end
|
50
|
+
}
|
51
|
+
env[@set.parameters_key] = routing_args
|
52
|
+
@app.call(env)
|
53
|
+
else
|
54
|
+
Const::EXPECTATION_FAILED_RESPONSE
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'rack/mount/utils'
|
2
|
+
|
3
|
+
module Rack::Mount
|
4
|
+
module Recognition
|
5
|
+
module RouteSet
|
6
|
+
attr_reader :parameters_key
|
7
|
+
|
8
|
+
# Adds recognition related concerns to RouteSet.new.
|
9
|
+
def initialize(options = {})
|
10
|
+
@parameters_key = options.delete(:parameters_key) || Const::RACK_ROUTING_ARGS
|
11
|
+
@parameters_key.freeze
|
12
|
+
@recognition_key_analyzer = Analysis::Frequency.new_with_module(Analysis::Splitting)
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
# Adds recognition aspects to RouteSet#add_route.
|
18
|
+
def add_route(*args)
|
19
|
+
route = super
|
20
|
+
@recognition_key_analyzer << route.conditions
|
21
|
+
route
|
22
|
+
end
|
23
|
+
|
24
|
+
# Rack compatible recognition and dispatching method. Routes are
|
25
|
+
# tried until one returns a non-catch status code. If no routes
|
26
|
+
# match, the catch status code is returned.
|
27
|
+
#
|
28
|
+
# This method can only be invoked after the RouteSet has been
|
29
|
+
# finalized.
|
30
|
+
def call(env)
|
31
|
+
raise 'route set not finalized' unless @recognition_graph
|
32
|
+
|
33
|
+
set_expectation = env[Const::EXPECT] != Const::CONTINUE
|
34
|
+
env[Const::EXPECT] = Const::CONTINUE if set_expectation
|
35
|
+
|
36
|
+
env[Const::PATH_INFO] = Utils.normalize_path(env[Const::PATH_INFO])
|
37
|
+
|
38
|
+
cache = {}
|
39
|
+
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)
|
49
|
+
return result unless result[0].to_i == 417
|
50
|
+
end
|
51
|
+
set_expectation ? Const::NOT_FOUND_RESPONSE : Const::EXPECTATION_FAILED_RESPONSE
|
52
|
+
ensure
|
53
|
+
env.delete(Const::EXPECT) if set_expectation
|
54
|
+
end
|
55
|
+
|
56
|
+
def rehash #:nodoc:
|
57
|
+
@recognition_keys = build_recognition_keys
|
58
|
+
@recognition_graph = build_recognition_graph
|
59
|
+
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
def valid_conditions #:nodoc:
|
64
|
+
@valid_conditions ||= begin
|
65
|
+
conditions = @request_class.instance_methods(false)
|
66
|
+
conditions.map! { |m| m.to_sym }
|
67
|
+
conditions.freeze
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def expire!
|
73
|
+
@recognition_keys = @recognition_graph = nil
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_recognition_graph
|
78
|
+
build_nested_route_set(@recognition_keys) { |k, i|
|
79
|
+
@recognition_key_analyzer.possible_keys[i][k]
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_recognition_keys
|
84
|
+
@recognition_key_analyzer.report
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|