Froze rails gems
[depot.git] / vendor / rails / actionpack / lib / action_controller / routing / segments.rb
1 module ActionController
2 module Routing
3 class Segment #:nodoc:
4 RESERVED_PCHAR = ':@&=+$,;'
5 SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
6 UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
7
8 # TODO: Convert :is_optional accessor to read only
9 attr_accessor :is_optional
10 alias_method :optional?, :is_optional
11
12 def initialize
13 @is_optional = false
14 end
15
16 def number_of_captures
17 Regexp.new(regexp_chunk).number_of_captures
18 end
19
20 def extraction_code
21 nil
22 end
23
24 # Continue generating string for the prior segments.
25 def continue_string_structure(prior_segments)
26 if prior_segments.empty?
27 interpolation_statement(prior_segments)
28 else
29 new_priors = prior_segments[0..-2]
30 prior_segments.last.string_structure(new_priors)
31 end
32 end
33
34 def interpolation_chunk
35 URI.escape(value, UNSAFE_PCHAR)
36 end
37
38 # Return a string interpolation statement for this segment and those before it.
39 def interpolation_statement(prior_segments)
40 chunks = prior_segments.collect { |s| s.interpolation_chunk }
41 chunks << interpolation_chunk
42 "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
43 end
44
45 def string_structure(prior_segments)
46 optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
47 end
48
49 # Return an if condition that is true if all the prior segments can be generated.
50 # If there are no optional segments before this one, then nil is returned.
51 def all_optionals_available_condition(prior_segments)
52 optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
53 optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
54 end
55
56 # Recognition
57
58 def match_extraction(next_capture)
59 nil
60 end
61
62 # Warning
63
64 # Returns true if this segment is optional? because of a default. If so, then
65 # no warning will be emitted regarding this segment.
66 def optionality_implied?
67 false
68 end
69 end
70
71 class StaticSegment < Segment #:nodoc:
72 attr_reader :value, :raw
73 alias_method :raw?, :raw
74
75 def initialize(value = nil, options = {})
76 super()
77 @value = value
78 @raw = options[:raw] if options.key?(:raw)
79 @is_optional = options[:optional] if options.key?(:optional)
80 end
81
82 def interpolation_chunk
83 raw? ? value : super
84 end
85
86 def regexp_chunk
87 chunk = Regexp.escape(value)
88 optional? ? Regexp.optionalize(chunk) : chunk
89 end
90
91 def number_of_captures
92 0
93 end
94
95 def build_pattern(pattern)
96 escaped = Regexp.escape(value)
97 if optional? && ! pattern.empty?
98 "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
99 elsif optional?
100 Regexp.optionalize escaped
101 else
102 escaped + pattern
103 end
104 end
105
106 def to_s
107 value
108 end
109 end
110
111 class DividerSegment < StaticSegment #:nodoc:
112 def initialize(value = nil, options = {})
113 super(value, {:raw => true, :optional => true}.merge(options))
114 end
115
116 def optionality_implied?
117 true
118 end
119 end
120
121 class DynamicSegment < Segment #:nodoc:
122 attr_reader :key
123
124 # TODO: Convert these accessors to read only
125 attr_accessor :default, :regexp
126
127 def initialize(key = nil, options = {})
128 super()
129 @key = key
130 @default = options[:default] if options.key?(:default)
131 @regexp = options[:regexp] if options.key?(:regexp)
132 @is_optional = true if options[:optional] || options.key?(:default)
133 end
134
135 def to_s
136 ":#{key}"
137 end
138
139 # The local variable name that the value of this segment will be extracted to.
140 def local_name
141 "#{key}_value"
142 end
143
144 def extract_value
145 "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
146 end
147
148 def value_check
149 if default # Then we know it won't be nil
150 "#{value_regexp.inspect} =~ #{local_name}" if regexp
151 elsif optional?
152 # If we have a regexp check that the value is not given, or that it matches.
153 # If we have no regexp, return nil since we do not require a condition.
154 "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
155 else # Then it must be present, and if we have a regexp, it must match too.
156 "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
157 end
158 end
159
160 def expiry_statement
161 "expired, hash = true, options if !expired && expire_on[:#{key}]"
162 end
163
164 def extraction_code
165 s = extract_value
166 vc = value_check
167 s << "\nreturn [nil,nil] unless #{vc}" if vc
168 s << "\n#{expiry_statement}"
169 end
170
171 def interpolation_chunk(value_code = local_name)
172 "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
173 end
174
175 def string_structure(prior_segments)
176 if optional? # We have a conditional to do...
177 # If we should not appear in the url, just write the code for the prior
178 # segments. This occurs if our value is the default value, or, if we are
179 # optional, if we have nil as our value.
180 "if #{local_name} == #{default.inspect}\n" +
181 continue_string_structure(prior_segments) +
182 "\nelse\n" + # Otherwise, write the code up to here
183 "#{interpolation_statement(prior_segments)}\nend"
184 else
185 interpolation_statement(prior_segments)
186 end
187 end
188
189 def value_regexp
190 Regexp.new "\\A#{regexp.to_s}\\Z" if regexp
191 end
192
193 def regexp_chunk
194 if regexp
195 if regexp_has_modifiers?
196 "(#{regexp.to_s})"
197 else
198 "(#{regexp.source})"
199 end
200 else
201 "([^#{Routing::SEPARATORS.join}]+)"
202 end
203 end
204
205 def number_of_captures
206 if regexp
207 regexp.number_of_captures + 1
208 else
209 1
210 end
211 end
212
213 def build_pattern(pattern)
214 pattern = "#{regexp_chunk}#{pattern}"
215 optional? ? Regexp.optionalize(pattern) : pattern
216 end
217
218 def match_extraction(next_capture)
219 # All non code-related keys (such as :id, :slug) are URI-unescaped as
220 # path parameters.
221 default_value = default ? default.inspect : nil
222 %[
223 value = if (m = match[#{next_capture}])
224 URI.unescape(m)
225 else
226 #{default_value}
227 end
228 params[:#{key}] = value if value
229 ]
230 end
231
232 def optionality_implied?
233 [:action, :id].include? key
234 end
235
236 def regexp_has_modifiers?
237 regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
238 end
239 end
240
241 class ControllerSegment < DynamicSegment #:nodoc:
242 def regexp_chunk
243 possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
244 "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
245 end
246
247 def number_of_captures
248 1
249 end
250
251 # Don't URI.escape the controller name since it may contain slashes.
252 def interpolation_chunk(value_code = local_name)
253 "\#{#{value_code}.to_s}"
254 end
255
256 # Make sure controller names like Admin/Content are correctly normalized to
257 # admin/content
258 def extract_value
259 "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
260 end
261
262 def match_extraction(next_capture)
263 if default
264 "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
265 else
266 "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
267 end
268 end
269 end
270
271 class PathSegment < DynamicSegment #:nodoc:
272 def interpolation_chunk(value_code = local_name)
273 "\#{#{value_code}}"
274 end
275
276 def extract_value
277 "#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
278 end
279
280 def default
281 ''
282 end
283
284 def default=(path)
285 raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
286 end
287
288 def match_extraction(next_capture)
289 "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
290 end
291
292 def regexp_chunk
293 regexp || "(.*)"
294 end
295
296 def number_of_captures
297 regexp ? regexp.number_of_captures : 1
298 end
299
300 def optionality_implied?
301 true
302 end
303
304 class Result < ::Array #:nodoc:
305 def to_s() join '/' end
306 def self.new_escaped(strings)
307 new strings.collect {|str| URI.unescape str}
308 end
309 end
310 end
311 end
312 end