rack-mount 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rack/mount.rb +3 -13
- data/lib/rack/mount/analysis/frequency.rb +0 -2
- data/lib/rack/mount/analysis/splitting.rb +2 -2
- data/lib/rack/mount/code_generation.rb +113 -0
- data/lib/rack/mount/generatable_regexp.rb +1 -1
- data/lib/rack/mount/route.rb +69 -5
- data/lib/rack/mount/route_set.rb +289 -46
- data/lib/rack/mount/strexp.rb +53 -50
- data/lib/rack/mount/utils.rb +7 -7
- data/lib/rack/mount/vendor/reginald/reginald.rb +2 -1
- data/lib/rack/mount/vendor/reginald/reginald/alternation.rb +11 -1
- data/lib/rack/mount/vendor/reginald/reginald/atom.rb +17 -5
- data/lib/rack/mount/vendor/reginald/reginald/character.rb +11 -2
- data/lib/rack/mount/vendor/reginald/reginald/character_class.rb +8 -20
- data/lib/rack/mount/vendor/reginald/reginald/collection.rb +10 -6
- data/lib/rack/mount/vendor/reginald/reginald/expression.rb +30 -20
- data/lib/rack/mount/vendor/reginald/reginald/group.rb +22 -8
- data/lib/rack/mount/vendor/reginald/reginald/options.rb +47 -0
- data/lib/rack/mount/vendor/reginald/reginald/parser.rb +137 -273
- data/lib/rack/mount/vendor/reginald/reginald/tokenizer.rb +1 -1
- data/lib/rack/mount/vendor/reginald/reginald/version.rb +3 -0
- data/lib/rack/mount/version.rb +1 -1
- metadata +6 -10
- data/lib/rack/mount/exceptions.rb +0 -3
- data/lib/rack/mount/generation/route.rb +0 -59
- data/lib/rack/mount/generation/route_set.rb +0 -200
- data/lib/rack/mount/mixover.rb +0 -60
- data/lib/rack/mount/recognition/code_generation.rb +0 -124
- data/lib/rack/mount/recognition/route.rb +0 -62
- data/lib/rack/mount/recognition/route_set.rb +0 -121
data/lib/rack/mount.rb
CHANGED
@@ -11,32 +11,22 @@ module Rack #:nodoc:
|
|
11
11
|
# other request attributes, session information, or even data dynamically
|
12
12
|
# pulled from a database.
|
13
13
|
module Mount
|
14
|
+
autoload :CodeGeneration, 'rack/mount/code_generation'
|
14
15
|
autoload :GeneratableRegexp, 'rack/mount/generatable_regexp'
|
15
|
-
autoload :Mixover, 'rack/mount/mixover'
|
16
16
|
autoload :Multimap, 'rack/mount/multimap'
|
17
17
|
autoload :Prefix, 'rack/mount/prefix'
|
18
18
|
autoload :RegexpWithNamedGroups, 'rack/mount/regexp_with_named_groups'
|
19
19
|
autoload :Route, 'rack/mount/route'
|
20
20
|
autoload :RouteSet, 'rack/mount/route_set'
|
21
|
-
autoload :RoutingError, 'rack/mount/
|
21
|
+
autoload :RoutingError, 'rack/mount/route_set'
|
22
22
|
autoload :Strexp, 'rack/mount/strexp'
|
23
23
|
autoload :Utils, 'rack/mount/utils'
|
24
|
+
autoload :Version, 'rack/mount/version'
|
24
25
|
|
25
26
|
module Analysis #:nodoc:
|
26
27
|
autoload :Frequency, 'rack/mount/analysis/frequency'
|
27
28
|
autoload :Histogram, 'rack/mount/analysis/histogram'
|
28
29
|
autoload :Splitting, 'rack/mount/analysis/splitting'
|
29
30
|
end
|
30
|
-
|
31
|
-
module Generation #:nodoc:
|
32
|
-
autoload :Route, 'rack/mount/generation/route'
|
33
|
-
autoload :RouteSet, 'rack/mount/generation/route_set'
|
34
|
-
end
|
35
|
-
|
36
|
-
module Recognition #:nodoc:
|
37
|
-
autoload :CodeGeneration, 'rack/mount/recognition/code_generation'
|
38
|
-
autoload :Route, 'rack/mount/recognition/route'
|
39
|
-
autoload :RouteSet, 'rack/mount/recognition/route_set'
|
40
|
-
end
|
41
31
|
end
|
42
32
|
end
|
@@ -2,7 +2,7 @@ require 'rack/mount/utils'
|
|
2
2
|
|
3
3
|
module Rack::Mount
|
4
4
|
module Analysis
|
5
|
-
|
5
|
+
class Splitting < Frequency
|
6
6
|
NULL = "\0".freeze
|
7
7
|
|
8
8
|
class Key < Struct.new(:method, :index, :separators)
|
@@ -67,7 +67,7 @@ module Rack::Mount
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
if inside = part[0]
|
70
|
+
if inside = part.expression[0]
|
71
71
|
if inside.is_a?(Reginald::Character) && inside.literal?
|
72
72
|
boundaries << inside.to_s
|
73
73
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Rack::Mount
|
2
|
+
module CodeGeneration #:nodoc:
|
3
|
+
def _expired_recognize(env) #:nodoc:
|
4
|
+
raise 'route set not finalized'
|
5
|
+
end
|
6
|
+
|
7
|
+
def rehash
|
8
|
+
super
|
9
|
+
optimize_recognize!
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def expire!
|
14
|
+
if @optimized_recognize_defined
|
15
|
+
remove_metaclass_method :recognize
|
16
|
+
|
17
|
+
class << self
|
18
|
+
alias_method :recognize, :_expired_recognize
|
19
|
+
end
|
20
|
+
|
21
|
+
@optimized_recognize_defined = false
|
22
|
+
end
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def optimize_container_iterator(container)
|
28
|
+
body = []
|
29
|
+
|
30
|
+
container.each_with_index { |route, i|
|
31
|
+
body << "route = self[#{i}]"
|
32
|
+
body << 'matches = {}'
|
33
|
+
body << 'params = route.defaults.dup'
|
34
|
+
|
35
|
+
conditions = []
|
36
|
+
route.conditions.each do |method, condition|
|
37
|
+
b = []
|
38
|
+
if condition.is_a?(Regexp)
|
39
|
+
b << "if m = obj.#{method}.match(#{condition.inspect})"
|
40
|
+
b << "matches[:#{method}] = m"
|
41
|
+
if (named_captures = route.named_captures[method]) && named_captures.any?
|
42
|
+
b << 'captures = m.captures'
|
43
|
+
b << 'p = nil'
|
44
|
+
b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = captures[#{j}]" }.join('; ')
|
45
|
+
end
|
46
|
+
else
|
47
|
+
b << "if m = obj.#{method} == route.conditions[:#{method}]"
|
48
|
+
end
|
49
|
+
b << 'true'
|
50
|
+
b << 'end'
|
51
|
+
conditions << "(#{b.join('; ')})"
|
52
|
+
end
|
53
|
+
|
54
|
+
body << <<-RUBY
|
55
|
+
if #{conditions.join(' && ')}
|
56
|
+
yield route, matches, params
|
57
|
+
end
|
58
|
+
RUBY
|
59
|
+
}
|
60
|
+
|
61
|
+
container.instance_eval(<<-RUBY, __FILE__, __LINE__)
|
62
|
+
def optimized_each(obj)
|
63
|
+
#{body.join("\n")}
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
RUBY
|
67
|
+
end
|
68
|
+
|
69
|
+
def optimize_recognize!
|
70
|
+
keys = @recognition_keys.map { |key|
|
71
|
+
if key.respond_to?(:call_source)
|
72
|
+
key.call_source(:cache, :obj)
|
73
|
+
else
|
74
|
+
"obj.#{key}"
|
75
|
+
end
|
76
|
+
}.join(', ')
|
77
|
+
|
78
|
+
@optimized_recognize_defined = true
|
79
|
+
|
80
|
+
remove_metaclass_method :recognize
|
81
|
+
|
82
|
+
instance_eval(<<-RUBY, __FILE__, __LINE__)
|
83
|
+
def recognize(obj)
|
84
|
+
cache = {}
|
85
|
+
container = @recognition_graph[#{keys}]
|
86
|
+
optimize_container_iterator(container) unless container.respond_to?(:optimized_each)
|
87
|
+
|
88
|
+
if block_given?
|
89
|
+
container.optimized_each(obj) do |route, matches, params|
|
90
|
+
yield route, matches, params
|
91
|
+
end
|
92
|
+
else
|
93
|
+
container.optimized_each(obj) do |route, matches, params|
|
94
|
+
return route, matches, params
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
RUBY
|
101
|
+
end
|
102
|
+
|
103
|
+
# method_defined? can't distinguish between instance
|
104
|
+
# and meta methods. So we have to rescue if the method
|
105
|
+
# has not been defined in the metaclass yet.
|
106
|
+
def remove_metaclass_method(symbol)
|
107
|
+
metaclass = class << self; self; end
|
108
|
+
metaclass.send(:remove_method, symbol)
|
109
|
+
rescue NameError => e
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/rack/mount/route.rb
CHANGED
@@ -9,11 +9,6 @@ module Rack::Mount
|
|
9
9
|
# new Route objects. Instead use the factory method, RouteSet#add_route
|
10
10
|
# to create new routes and add them to the set.
|
11
11
|
class Route
|
12
|
-
extend Mixover
|
13
|
-
|
14
|
-
# Include generation and recognition concerns
|
15
|
-
include Generation::Route, Recognition::Route
|
16
|
-
|
17
12
|
# Valid rack application to call if conditions are met
|
18
13
|
attr_reader :app
|
19
14
|
|
@@ -27,6 +22,8 @@ module Rack::Mount
|
|
27
22
|
# Symbol identifier for the route used with named route generations
|
28
23
|
attr_reader :name
|
29
24
|
|
25
|
+
attr_reader :named_captures
|
26
|
+
|
30
27
|
def initialize(app, conditions, defaults, name)
|
31
28
|
unless app.respond_to?(:call)
|
32
29
|
raise ArgumentError, 'app must be a valid rack application' \
|
@@ -54,9 +51,76 @@ module Rack::Mount
|
|
54
51
|
@conditions[method] = pattern.freeze
|
55
52
|
end
|
56
53
|
|
54
|
+
@named_captures = {}
|
55
|
+
@conditions.map { |method, condition|
|
56
|
+
next unless condition.respond_to?(:named_captures)
|
57
|
+
@named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)|
|
58
|
+
named_captures[k.to_sym] = v.last - 1
|
59
|
+
named_captures
|
60
|
+
}.freeze
|
61
|
+
}
|
62
|
+
@named_captures.freeze
|
63
|
+
|
64
|
+
if @conditions.has_key?(:path_info) &&
|
65
|
+
!Utils.regexp_anchored?(@conditions[:path_info])
|
66
|
+
@prefix = true
|
67
|
+
@app = Prefix.new(@app)
|
68
|
+
else
|
69
|
+
@prefix = false
|
70
|
+
end
|
71
|
+
|
57
72
|
@conditions.freeze
|
58
73
|
end
|
59
74
|
|
75
|
+
def prefix?
|
76
|
+
@prefix
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def generation_keys
|
81
|
+
@conditions.inject({}) { |keys, (method, condition)|
|
82
|
+
if condition.respond_to?(:required_defaults)
|
83
|
+
keys.merge!(condition.required_defaults)
|
84
|
+
else
|
85
|
+
keys
|
86
|
+
end
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def significant_params?
|
91
|
+
@has_significant_params ||= @conditions.any? { |method, condition|
|
92
|
+
(condition.respond_to?(:required_params) && condition.required_params.any?) ||
|
93
|
+
(condition.respond_to?(:required_defaults) && condition.required_defaults.any?)
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def generate(method, params = {}, recall = {}, options = {})
|
98
|
+
if method.nil?
|
99
|
+
result = @conditions.inject({}) { |h, (m, condition)|
|
100
|
+
if condition.respond_to?(:generate)
|
101
|
+
h[m] = condition.generate(params, recall, options)
|
102
|
+
end
|
103
|
+
h
|
104
|
+
}
|
105
|
+
return nil if result.values.compact.empty?
|
106
|
+
else
|
107
|
+
if condition = @conditions[method]
|
108
|
+
if condition.respond_to?(:generate)
|
109
|
+
result = condition.generate(params, recall, options)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
if result
|
115
|
+
@defaults.each do |key, value|
|
116
|
+
params.delete(key) if params[key] == value
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
|
60
124
|
def inspect #:nodoc:
|
61
125
|
"#<#{self.class.name} @app=#{@app.inspect} @conditions=#{@conditions.inspect} @defaults=#{@defaults.inspect} @name=#{@name.inspect}>"
|
62
126
|
end
|
data/lib/rack/mount/route_set.rb
CHANGED
@@ -3,16 +3,12 @@ require 'rack/mount/route'
|
|
3
3
|
require 'rack/mount/utils'
|
4
4
|
|
5
5
|
module Rack::Mount
|
6
|
-
class
|
7
|
-
extend Mixover
|
8
|
-
|
9
|
-
# Include generation and recognition concerns
|
10
|
-
include Generation::RouteSet, Recognition::RouteSet
|
11
|
-
include Recognition::CodeGeneration
|
6
|
+
class RoutingError < StandardError; end
|
12
7
|
|
8
|
+
class RouteSet
|
13
9
|
# Initialize a new RouteSet without optimizations
|
14
|
-
def self.new_without_optimizations(
|
15
|
-
|
10
|
+
def self.new_without_optimizations(options = {}, &block)
|
11
|
+
new(options.merge(:_optimize => false), &block)
|
16
12
|
end
|
17
13
|
|
18
14
|
# Basic RouteSet initializer.
|
@@ -23,12 +19,19 @@ module Rack::Mount
|
|
23
19
|
# - <tt>Generation::RouteSet.new</tt>
|
24
20
|
# - <tt>Recognition::RouteSet.new</tt>
|
25
21
|
def initialize(options = {}, &block)
|
22
|
+
@parameters_key = options.delete(:parameters_key) || 'rack.routing_args'
|
23
|
+
@parameters_key.freeze
|
24
|
+
|
25
|
+
@named_routes = {}
|
26
|
+
|
27
|
+
@recognition_key_analyzer = Analysis::Splitting.new
|
28
|
+
@generation_key_analyzer = Analysis::Frequency.new
|
29
|
+
|
26
30
|
@request_class = options.delete(:request_class) || Rack::Request
|
27
|
-
@valid_conditions =
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
31
|
+
@valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym }
|
32
|
+
|
33
|
+
extend CodeGeneration unless options[:_optimize] == false
|
34
|
+
@optimized_recognize_defined = false
|
32
35
|
|
33
36
|
@routes = []
|
34
37
|
expire!
|
@@ -49,26 +52,191 @@ module Rack::Mount
|
|
49
52
|
# <tt>name</tt>:: Symbol identifier for the route used with named
|
50
53
|
# route generations
|
51
54
|
def add_route(app, conditions = {}, defaults = {}, name = nil)
|
52
|
-
|
55
|
+
unless conditions.is_a?(Hash)
|
56
|
+
raise ArgumentError, 'conditions must be a Hash'
|
57
|
+
end
|
58
|
+
|
59
|
+
unless conditions.all? { |method, pattern|
|
60
|
+
@valid_conditions.include?(method)
|
61
|
+
}
|
62
|
+
raise ArgumentError, 'conditions may only include ' +
|
63
|
+
@valid_conditions.inspect
|
64
|
+
end
|
65
|
+
|
53
66
|
route = Route.new(app, conditions, defaults, name)
|
54
67
|
@routes << route
|
68
|
+
|
69
|
+
@recognition_key_analyzer << route.conditions
|
70
|
+
|
71
|
+
@named_routes[route.name] = route if route.name
|
72
|
+
@generation_key_analyzer << route.generation_keys
|
73
|
+
|
55
74
|
expire!
|
56
75
|
route
|
57
76
|
end
|
58
77
|
|
59
|
-
|
78
|
+
def recognize(obj)
|
79
|
+
raise 'route set not finalized' unless @recognition_graph
|
80
|
+
|
81
|
+
cache = {}
|
82
|
+
keys = @recognition_keys.map { |key|
|
83
|
+
if key.respond_to?(:call)
|
84
|
+
key.call(cache, obj)
|
85
|
+
else
|
86
|
+
obj.send(key)
|
87
|
+
end
|
88
|
+
}
|
89
|
+
|
90
|
+
@recognition_graph[*keys].each do |route|
|
91
|
+
matches = {}
|
92
|
+
params = route.defaults.dup
|
93
|
+
|
94
|
+
if route.conditions.all? { |method, condition|
|
95
|
+
value = obj.send(method)
|
96
|
+
if condition.is_a?(Regexp) && (m = value.match(condition))
|
97
|
+
matches[method] = m
|
98
|
+
captures = m.captures
|
99
|
+
route.named_captures[method].each do |k, i|
|
100
|
+
if v = captures[i]
|
101
|
+
params[k] = v
|
102
|
+
end
|
103
|
+
end
|
104
|
+
true
|
105
|
+
elsif value == condition
|
106
|
+
true
|
107
|
+
else
|
108
|
+
false
|
109
|
+
end
|
110
|
+
}
|
111
|
+
if block_given?
|
112
|
+
yield route, matches, params
|
113
|
+
else
|
114
|
+
return route, matches, params
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
X_CASCADE = 'X-Cascade'.freeze
|
123
|
+
PASS = 'pass'.freeze
|
124
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
125
|
+
|
126
|
+
# Rack compatible recognition and dispatching method. Routes are
|
127
|
+
# tried until one returns a non-catch status code. If no routes
|
128
|
+
# match, the catch status code is returned.
|
129
|
+
#
|
130
|
+
# This method can only be invoked after the RouteSet has been
|
131
|
+
# finalized.
|
60
132
|
def call(env)
|
61
|
-
raise
|
133
|
+
raise 'route set not finalized' unless @recognition_graph
|
134
|
+
|
135
|
+
env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO])
|
136
|
+
|
137
|
+
request = nil
|
138
|
+
req = @request_class.new(env)
|
139
|
+
recognize(req) do |route, matches, params|
|
140
|
+
# TODO: We only want to unescape params from uri related methods
|
141
|
+
params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) }
|
142
|
+
|
143
|
+
if route.prefix?
|
144
|
+
env[Prefix::KEY] = matches[:path_info].to_s
|
145
|
+
end
|
146
|
+
|
147
|
+
env[@parameters_key] = params
|
148
|
+
result = route.app.call(env)
|
149
|
+
return result unless result[1][X_CASCADE] == PASS
|
150
|
+
end
|
151
|
+
|
152
|
+
request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']]
|
62
153
|
end
|
63
154
|
|
64
|
-
#
|
65
|
-
|
66
|
-
|
155
|
+
# Generates a url from Rack env and identifiers or significant keys.
|
156
|
+
#
|
157
|
+
# To generate a url by named route, pass the name in as a +Symbol+.
|
158
|
+
# url(env, :dashboard) # => "/dashboard"
|
159
|
+
#
|
160
|
+
# Additional parameters can be passed in as a hash
|
161
|
+
# url(env, :people, :id => "1") # => "/people/1"
|
162
|
+
#
|
163
|
+
# If no name route is given, it will fall back to a slower
|
164
|
+
# generation search.
|
165
|
+
# url(env, :controller => "people", :action => "show", :id => "1")
|
166
|
+
# # => "/people/1"
|
167
|
+
def url(env, *args)
|
168
|
+
named_route, params = nil, {}
|
169
|
+
|
170
|
+
case args.length
|
171
|
+
when 2
|
172
|
+
named_route, params = args[0], args[1].dup
|
173
|
+
when 1
|
174
|
+
if args[0].is_a?(Hash)
|
175
|
+
params = args[0].dup
|
176
|
+
else
|
177
|
+
named_route = args[0]
|
178
|
+
end
|
179
|
+
else
|
180
|
+
raise ArgumentError
|
181
|
+
end
|
182
|
+
|
183
|
+
only_path = params.delete(:only_path)
|
184
|
+
recall = env[@parameters_key] || {}
|
185
|
+
|
186
|
+
unless result = generate(:all, named_route, params, recall,
|
187
|
+
:parameterize => lambda { |name, param| Utils.escape_uri(param) })
|
188
|
+
return
|
189
|
+
end
|
190
|
+
|
191
|
+
parts, params = result
|
192
|
+
return unless parts
|
193
|
+
|
194
|
+
params.each do |k, v|
|
195
|
+
if v
|
196
|
+
params[k] = v
|
197
|
+
else
|
198
|
+
params.delete(k)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
req = stubbed_request_class.new(env)
|
203
|
+
req._stubbed_values = parts.merge(:query_string => Utils.build_nested_query(params))
|
204
|
+
only_path ? req.fullpath : req.url
|
67
205
|
end
|
68
206
|
|
69
|
-
|
70
|
-
|
71
|
-
|
207
|
+
def generate(method, *args) #:nodoc:
|
208
|
+
raise 'route set not finalized' unless @generation_graph
|
209
|
+
|
210
|
+
method = nil if method == :all
|
211
|
+
named_route, params, recall, options = extract_params!(*args)
|
212
|
+
merged = recall.merge(params)
|
213
|
+
route = nil
|
214
|
+
|
215
|
+
if named_route
|
216
|
+
if route = @named_routes[named_route.to_sym]
|
217
|
+
recall = route.defaults.merge(recall)
|
218
|
+
url = route.generate(method, params, recall, options)
|
219
|
+
[url, params]
|
220
|
+
else
|
221
|
+
raise RoutingError, "#{named_route} failed to generate from #{params.inspect}"
|
222
|
+
end
|
223
|
+
else
|
224
|
+
keys = @generation_keys.map { |key|
|
225
|
+
if k = merged[key]
|
226
|
+
k.to_s
|
227
|
+
else
|
228
|
+
nil
|
229
|
+
end
|
230
|
+
}
|
231
|
+
@generation_graph[*keys].each do |r|
|
232
|
+
next unless r.significant_params?
|
233
|
+
if url = r.generate(method, params, recall, options)
|
234
|
+
return [url, params]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
raise RoutingError, "No route matches #{params.inspect}"
|
239
|
+
end
|
72
240
|
end
|
73
241
|
|
74
242
|
# Number of routes in the set
|
@@ -77,6 +245,10 @@ module Rack::Mount
|
|
77
245
|
end
|
78
246
|
|
79
247
|
def rehash #:nodoc:
|
248
|
+
@recognition_keys = build_recognition_keys
|
249
|
+
@recognition_graph = build_recognition_graph
|
250
|
+
@generation_keys = build_generation_keys
|
251
|
+
@generation_graph = build_generation_graph
|
80
252
|
end
|
81
253
|
|
82
254
|
# Finalizes the set and builds optimized data structures. You *must*
|
@@ -85,7 +257,11 @@ module Rack::Mount
|
|
85
257
|
def freeze
|
86
258
|
unless frozen?
|
87
259
|
rehash
|
88
|
-
|
260
|
+
|
261
|
+
@recognition_key_analyzer = nil
|
262
|
+
@generation_key_analyzer = nil
|
263
|
+
@valid_conditions = nil
|
264
|
+
|
89
265
|
@routes.each { |route| route.freeze }
|
90
266
|
@routes.freeze
|
91
267
|
end
|
@@ -104,44 +280,35 @@ module Rack::Mount
|
|
104
280
|
hash[:@recognition_graph] = graph.dup
|
105
281
|
end
|
106
282
|
|
107
|
-
included_modules = (class << self; included_modules; end)
|
108
|
-
included_modules.reject! { |mod| mod == Kernel }
|
109
|
-
hash[:included_modules] = included_modules
|
110
|
-
|
111
283
|
hash
|
112
284
|
end
|
113
285
|
|
114
286
|
def marshal_load(hash) #:nodoc:
|
115
|
-
hash.delete(:included_modules).reverse.each { |mod| extend(mod) }
|
116
|
-
|
117
287
|
hash.each do |ivar, value|
|
118
288
|
instance_variable_set(ivar, value)
|
119
289
|
end
|
120
290
|
end
|
121
291
|
|
292
|
+
protected
|
293
|
+
def recognition_stats
|
294
|
+
{ :keys => @recognition_keys,
|
295
|
+
:keys_size => @recognition_keys.size,
|
296
|
+
:graph_size => @recognition_graph.size,
|
297
|
+
:graph_height => @recognition_graph.height,
|
298
|
+
:graph_average_height => @recognition_graph.average_height }
|
299
|
+
end
|
300
|
+
|
122
301
|
private
|
123
302
|
def expire! #:nodoc:
|
124
|
-
|
303
|
+
@recognition_keys = @recognition_graph = nil
|
304
|
+
@recognition_key_analyzer.expire!
|
125
305
|
|
126
|
-
|
127
|
-
@
|
306
|
+
@generation_keys = @generation_graph = nil
|
307
|
+
@generation_key_analyzer.expire!
|
128
308
|
end
|
129
309
|
|
130
310
|
def instance_variables_to_serialize
|
131
|
-
instance_variables.map { |ivar| ivar.to_sym }
|
132
|
-
end
|
133
|
-
|
134
|
-
def validate_conditions!(conditions)
|
135
|
-
unless conditions.is_a?(Hash)
|
136
|
-
raise ArgumentError, 'conditions must be a Hash'
|
137
|
-
end
|
138
|
-
|
139
|
-
unless conditions.all? { |method, pattern|
|
140
|
-
@valid_conditions.include?(method)
|
141
|
-
}
|
142
|
-
raise ArgumentError, 'conditions may only include ' +
|
143
|
-
@valid_conditions.inspect
|
144
|
-
end
|
311
|
+
instance_variables.map { |ivar| ivar.to_sym } - [:@stubbed_request_class, :@optimized_recognize_defined]
|
145
312
|
end
|
146
313
|
|
147
314
|
# An internal helper method for constructing a nested set from
|
@@ -162,5 +329,81 @@ module Rack::Mount
|
|
162
329
|
end
|
163
330
|
graph
|
164
331
|
end
|
332
|
+
|
333
|
+
def build_recognition_graph
|
334
|
+
build_nested_route_set(@recognition_keys) { |k, i|
|
335
|
+
@recognition_key_analyzer.possible_keys[i][k]
|
336
|
+
}
|
337
|
+
end
|
338
|
+
|
339
|
+
def build_recognition_keys
|
340
|
+
@recognition_key_analyzer.report
|
341
|
+
end
|
342
|
+
|
343
|
+
def build_generation_graph
|
344
|
+
build_nested_route_set(@generation_keys) { |k, i|
|
345
|
+
throw :skip unless @routes[i].significant_params?
|
346
|
+
|
347
|
+
if k = @generation_key_analyzer.possible_keys[i][k]
|
348
|
+
k.to_s
|
349
|
+
else
|
350
|
+
nil
|
351
|
+
end
|
352
|
+
}
|
353
|
+
end
|
354
|
+
|
355
|
+
def build_generation_keys
|
356
|
+
@generation_key_analyzer.report
|
357
|
+
end
|
358
|
+
|
359
|
+
def extract_params!(*args)
|
360
|
+
case args.length
|
361
|
+
when 4
|
362
|
+
named_route, params, recall, options = args
|
363
|
+
when 3
|
364
|
+
if args[0].is_a?(Hash)
|
365
|
+
params, recall, options = args
|
366
|
+
else
|
367
|
+
named_route, params, recall = args
|
368
|
+
end
|
369
|
+
when 2
|
370
|
+
if args[0].is_a?(Hash)
|
371
|
+
params, recall = args
|
372
|
+
else
|
373
|
+
named_route, params = args
|
374
|
+
end
|
375
|
+
when 1
|
376
|
+
if args[0].is_a?(Hash)
|
377
|
+
params = args[0]
|
378
|
+
else
|
379
|
+
named_route = args[0]
|
380
|
+
end
|
381
|
+
else
|
382
|
+
raise ArgumentError
|
383
|
+
end
|
384
|
+
|
385
|
+
named_route ||= nil
|
386
|
+
params ||= {}
|
387
|
+
recall ||= {}
|
388
|
+
options ||= {}
|
389
|
+
|
390
|
+
[named_route, params.dup, recall.dup, options.dup]
|
391
|
+
end
|
392
|
+
|
393
|
+
def stubbed_request_class
|
394
|
+
@stubbed_request_class ||= begin
|
395
|
+
klass = Class.new(@request_class)
|
396
|
+
klass.public_instance_methods.each do |method|
|
397
|
+
next if method =~ /^__|object_id/
|
398
|
+
klass.class_eval <<-RUBY
|
399
|
+
def #{method}(*args, &block)
|
400
|
+
@_stubbed_values[:#{method}] || super
|
401
|
+
end
|
402
|
+
RUBY
|
403
|
+
end
|
404
|
+
klass.class_eval { attr_accessor :_stubbed_values }
|
405
|
+
klass
|
406
|
+
end
|
407
|
+
end
|
165
408
|
end
|
166
409
|
end
|