1 module ActionController
4 RESERVED_PCHAR
= ':@&=+$,;'
5 SAFE_PCHAR
= "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
6 UNSAFE_PCHAR
= Regexp
.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
8 # TODO: Convert :is_optional accessor to read only
9 attr_accessor
:is_optional
10 alias_method
:optional?, :is_optional
16 def number_of_captures
17 Regexp
.new(regexp_chunk
).number_of_captures
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
)
29 new_priors
= prior_segments
[0..-2]
30 prior_segments
.last
.string_structure(new_priors
)
34 def interpolation_chunk
35 URI
.escape(value
, UNSAFE_PCHAR
)
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)}"
45 def string_structure(prior_segments
)
46 optional
? ? continue_string_structure(prior_segments
) : interpolation_statement(prior_segments
)
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 * ' && '}"
58 def match_extraction(next_capture
)
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
?
71 class StaticSegment
< Segment
#:nodoc:
72 attr_reader
:value, :raw
73 alias_method
:raw?, :raw
75 def initialize(value
= nil, options
= {})
78 @raw = options
[:raw] if options
.key
?(:raw)
79 @is_optional = options
[:optional] if options
.key
?(:optional)
82 def interpolation_chunk
87 chunk
= Regexp
.escape(value
)
88 optional
? ? Regexp
.optionalize(chunk
) : chunk
91 def number_of_captures
95 def build_pattern(pattern
)
96 escaped
= Regexp
.escape(value
)
97 if optional
? && ! pattern
.empty
?
98 "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
100 Regexp
.optionalize escaped
111 class DividerSegment
< StaticSegment
#:nodoc:
112 def initialize(value
= nil, options
= {})
113 super(value
, {:raw => true, :optional => true}.merge(options
))
116 def optionality_implied
?
121 class DynamicSegment
< Segment
#:nodoc:
124 # TODO: Convert these accessors to read only
125 attr_accessor
:default, :regexp
127 def initialize(key
= nil, options
= {})
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)
139 # The local variable name that the value of this segment will be extracted to.
145 "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
149 if default
# Then we know it won't be nil
150 "#{value_regexp.inspect} =~ #{local_name}" if regexp
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}"
161 "expired, hash = true, options if !expired && expire_on[:#{key}]"
167 s
<< "\nreturn [nil,nil] unless #{vc}" if vc
168 s
<< "\n#{expiry_statement}"
171 def interpolation_chunk(value_code
= local_name
)
172 "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
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"
185 interpolation_statement(prior_segments
)
190 Regexp
.new
"\\A#{regexp.to_s}\\Z" if regexp
195 if regexp_has_modifiers
?
201 "([^#{Routing::SEPARATORS.join}]+)"
205 def number_of_captures
207 regexp
.number_of_captures
+ 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 def number_of_captures
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}"
256 # Make sure controller names like Admin/Content are correctly normalized to
259 "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
262 def match_extraction(next_capture
)
264 "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
266 "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
271 class PathSegment
< DynamicSegment
#:nodoc:
272 def interpolation_chunk(value_code
= local_name
)
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}"
285 raise RoutingError
, "paths cannot have non-empty default values" unless path
.blank
?
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}"
296 def number_of_captures
297 regexp
? regexp
.number_of_captures
: 1
300 def optionality_implied
?
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
}