1 module ActionController
4 RESERVED_PCHAR
= ':@&=+$,;'
5 SAFE_PCHAR
= "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
6 if RUBY_VERSION >= '1.9'
7 UNSAFE_PCHAR
= Regexp
.new("[^#{SAFE_PCHAR}]", false).freeze
9 UNSAFE_PCHAR
= Regexp
.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
12 # TODO: Convert :is_optional accessor to read only
13 attr_accessor
:is_optional
14 alias_method
:optional?, :is_optional
20 def number_of_captures
21 Regexp
.new(regexp_chunk
).number_of_captures
28 # Continue generating string for the prior segments.
29 def continue_string_structure(prior_segments
)
30 if prior_segments
.empty
?
31 interpolation_statement(prior_segments
)
33 new_priors
= prior_segments
[0..-2]
34 prior_segments
.last
.string_structure(new_priors
)
38 def interpolation_chunk
39 URI
.escape(value
, UNSAFE_PCHAR
)
42 # Return a string interpolation statement for this segment and those before it.
43 def interpolation_statement(prior_segments
)
44 chunks
= prior_segments
.collect
{ |s
| s
.interpolation_chunk
}
45 chunks
<< interpolation_chunk
46 "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
49 def string_structure(prior_segments
)
50 optional
? ? continue_string_structure(prior_segments
) : interpolation_statement(prior_segments
)
53 # Return an if condition that is true if all the prior segments can be generated.
54 # If there are no optional segments before this one, then nil is returned.
55 def all_optionals_available_condition(prior_segments
)
56 optional_locals
= prior_segments
.collect
{ |s
| s
.local_name
if s
.optional
? && s
.respond_to
?(:local_name) }.compact
57 optional_locals
.empty
? ? nil : " if #{optional_locals * ' && '}"
62 def match_extraction(next_capture
)
68 # Returns true if this segment is optional? because of a default. If so, then
69 # no warning will be emitted regarding this segment.
70 def optionality_implied
?
75 class StaticSegment
< Segment
#:nodoc:
76 attr_reader
:value, :raw
77 alias_method
:raw?, :raw
79 def initialize(value
= nil, options
= {})
82 @raw = options
[:raw] if options
.key
?(:raw)
83 @is_optional = options
[:optional] if options
.key
?(:optional)
86 def interpolation_chunk
91 chunk
= Regexp
.escape(value
)
92 optional
? ? Regexp
.optionalize(chunk
) : chunk
95 def number_of_captures
99 def build_pattern(pattern
)
100 escaped
= Regexp
.escape(value
)
101 if optional
? && ! pattern
.empty
?
102 "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
104 Regexp
.optionalize escaped
115 class DividerSegment
< StaticSegment
#:nodoc:
116 def initialize(value
= nil, options
= {})
117 super(value
, {:raw => true, :optional => true}.merge(options
))
120 def optionality_implied
?
125 class DynamicSegment
< Segment
#:nodoc:
128 # TODO: Convert these accessors to read only
129 attr_accessor
:default, :regexp
131 def initialize(key
= nil, options
= {})
134 @default = options
[:default] if options
.key
?(:default)
135 @regexp = options
[:regexp] if options
.key
?(:regexp)
136 @is_optional = true if options
[:optional] || options
.key
?(:default)
143 # The local variable name that the value of this segment will be extracted to.
149 "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
153 if default
# Then we know it won't be nil
154 "#{value_regexp.inspect} =~ #{local_name}" if regexp
156 # If we have a regexp check that the value is not given, or that it matches.
157 # If we have no regexp, return nil since we do not require a condition.
158 "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
159 else # Then it must be present, and if we have a regexp, it must match too.
160 "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
165 "expired, hash = true, options if !expired && expire_on[:#{key}]"
171 s
<< "\nreturn [nil,nil] unless #{vc}" if vc
172 s
<< "\n#{expiry_statement}"
175 def interpolation_chunk(value_code
= local_name
)
176 "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
179 def string_structure(prior_segments
)
180 if optional
? # We have a conditional to do...
181 # If we should not appear in the url, just write the code for the prior
182 # segments. This occurs if our value is the default value, or, if we are
183 # optional, if we have nil as our value.
184 "if #{local_name} == #{default.inspect}\n" +
185 continue_string_structure(prior_segments
) +
186 "\nelse\n" + # Otherwise, write the code up to here
187 "#{interpolation_statement(prior_segments)}\nend"
189 interpolation_statement(prior_segments
)
194 Regexp
.new
"\\A#{regexp.to_s}\\Z" if regexp
198 regexp
? regexp_string
: default_regexp_chunk
202 regexp_has_modifiers
? ? "(#{regexp.to_s})" : "(#{regexp.source})"
205 def default_regexp_chunk
206 "([^#{Routing::SEPARATORS.join}]+)"
209 def number_of_captures
210 regexp
? regexp
.number_of_captures
+ 1 : 1
213 def build_pattern(pattern
)
214 pattern
= "#{regexp_chunk}#{pattern}"
215 optional
? ? Regexp
.optionalize(pattern
) : pattern
218 def match_extraction(next_capture
)
219 # All non code-related keys (such as :id, :slug) are URI-unescaped as
221 default_value
= default
? default
.inspect
: nil
223 value
= if (m
= match
[#{next_capture}])
228 params
[:#{key}] = value if value
232 def optionality_implied
?
233 [:action, :id].include? key
236 def regexp_has_modifiers
?
237 regexp
.options
& (Regexp
::IGNORECASE | Regexp
::EXTENDED) != 0
241 class ControllerSegment
< DynamicSegment
#:nodoc:
243 possible_names
= Routing
.possible_controllers
.collect
{ |name
| Regexp
.escape name
}
244 "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
247 # Don't URI.escape the controller name since it may contain slashes.
248 def interpolation_chunk(value_code
= local_name
)
249 "\#{#{value_code}.to_s}"
252 # Make sure controller names like Admin/Content are correctly normalized to
255 "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
258 def match_extraction(next_capture
)
260 "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
262 "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
267 class PathSegment
< DynamicSegment
#:nodoc:
268 def interpolation_chunk(value_code
= local_name
)
273 "#{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}"
281 raise RoutingError
, "paths cannot have non-empty default values" unless path
.blank
?
284 def match_extraction(next_capture
)
285 "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match
[" + next_capture + "]" if !default}"
288 def default_regexp_chunk
292 def number_of_captures
293 regexp
? regexp
.number_of_captures
: 1
296 def optionality_implied
?
300 class Result
< ::Array #:nodoc:
301 def to_s() join
'/' end
302 def self.new_escaped(strings
)
303 new strings
.collect
{|str
| URI
.unescape str
}
308 # The OptionalFormatSegment allows for any resource route to have an optional
309 # :format, which decreases the amount of routes created by 50%.
310 class OptionalFormatSegment
< DynamicSegment
312 def initialize(key
= nil, options
= {})
313 super(:format, {:optional => true}.merge(options
))
316 def interpolation_chunk
329 "#{local_name} = options[:#{key}] && options[:#{key}].to_s.downcase"
332 #the value should not include the period (.)
333 def match_extraction(next_capture
)
335 if (m
= match
[#{next_capture}])
336 params
[:#{key}] = URI.unescape(m.from(1))