vendor/rails/actionpack/lib/action_controller/routing/segments.rb in radiant-0.7.2 vs vendor/rails/actionpack/lib/action_controller/routing/segments.rb in radiant-0.8.0

- old
+ new

@@ -1,18 +1,28 @@ module ActionController module Routing class Segment #:nodoc: RESERVED_PCHAR = ':@&=+$,;' - UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze + SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}" + if RUBY_VERSION >= '1.9' + UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze + else + UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze + end + # TODO: Convert :is_optional accessor to read only attr_accessor :is_optional alias_method :optional?, :is_optional def initialize - self.is_optional = false + @is_optional = false end + def number_of_captures + Regexp.new(regexp_chunk).number_of_captures + end + def extraction_code nil end # Continue generating string for the prior segments. @@ -61,16 +71,18 @@ false end end class StaticSegment < Segment #:nodoc: - attr_accessor :value, :raw + attr_reader :value, :raw alias_method :raw?, :raw - def initialize(value = nil) + def initialize(value = nil, options = {}) super() - self.value = value + @value = value + @raw = options[:raw] if options.key?(:raw) + @is_optional = options[:optional] if options.key?(:optional) end def interpolation_chunk raw? ? value : super end @@ -78,10 +90,14 @@ def regexp_chunk chunk = Regexp.escape(value) optional? ? Regexp.optionalize(chunk) : chunk end + def number_of_captures + 0 + end + def build_pattern(pattern) escaped = Regexp.escape(value) if optional? && ! pattern.empty? "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})" elsif optional? @@ -95,29 +111,31 @@ value end end class DividerSegment < StaticSegment #:nodoc: - def initialize(value = nil) - super(value) - self.raw = true - self.is_optional = true + def initialize(value = nil, options = {}) + super(value, {:raw => true, :optional => true}.merge(options)) end def optionality_implied? true end end class DynamicSegment < Segment #:nodoc: - attr_accessor :key, :default, :regexp + attr_reader :key + # TODO: Convert these accessors to read only + attr_accessor :default, :regexp + def initialize(key = nil, options = {}) super() - self.key = key - self.default = options[:default] if options.key? :default - self.is_optional = true if options[:optional] || options.key?(:default) + @key = key + @default = options[:default] if options.key?(:default) + @regexp = options[:regexp] if options.key?(:regexp) + @is_optional = true if options[:optional] || options.key?(:default) end def to_s ":#{key}" end @@ -128,10 +146,11 @@ end def extract_value "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}" end + def value_check if default # Then we know it won't be nil "#{value_regexp.inspect} =~ #{local_name}" if regexp elsif optional? # If we have a regexp check that the value is not given, or that it matches. @@ -139,10 +158,11 @@ "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp else # Then it must be present, and if we have a regexp, it must match too. "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}" end end + def expiry_statement "expired, hash = true, options if !expired && expire_on[:#{key}]" end def extraction_code @@ -150,11 +170,11 @@ vc = value_check s << "\nreturn [nil,nil] unless #{vc}" if vc s << "\n#{expiry_statement}" end - def interpolation_chunk(value_code = "#{local_name}") + def interpolation_chunk(value_code = local_name) "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}" end def string_structure(prior_segments) if optional? # We have a conditional to do... @@ -173,25 +193,27 @@ def value_regexp Regexp.new "\\A#{regexp.to_s}\\Z" if regexp end def regexp_chunk - if regexp - if regexp_has_modifiers? - "(#{regexp.to_s})" - else - "(#{regexp.source})" - end - else - "([^#{Routing::SEPARATORS.join}]+)" - end + regexp ? regexp_string : default_regexp_chunk end + def regexp_string + regexp_has_modifiers? ? "(#{regexp.to_s})" : "(#{regexp.source})" + end + + def default_regexp_chunk + "([^#{Routing::SEPARATORS.join}]+)" + end + + def number_of_captures + regexp ? regexp.number_of_captures + 1 : 1 + end + def build_pattern(pattern) - chunk = regexp_chunk - chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0 - pattern = "#{chunk}#{pattern}" + pattern = "#{regexp_chunk}#{pattern}" optional? ? Regexp.optionalize(pattern) : pattern end def match_extraction(next_capture) # All non code-related keys (such as :id, :slug) are URI-unescaped as @@ -212,21 +234,20 @@ end def regexp_has_modifiers? regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0 end - end class ControllerSegment < DynamicSegment #:nodoc: def regexp_chunk possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name } "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" end # Don't URI.escape the controller name since it may contain slashes. - def interpolation_chunk(value_code = "#{local_name}") + def interpolation_chunk(value_code = local_name) "\#{#{value_code}.to_s}" end # Make sure controller names like Admin/Content are correctly normalized to # admin/content @@ -242,11 +263,11 @@ end end end class PathSegment < DynamicSegment #:nodoc: - def interpolation_chunk(value_code = "#{local_name}") + def interpolation_chunk(value_code = local_name) "\#{#{value_code}}" end def extract_value "#{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}" @@ -262,14 +283,18 @@ def match_extraction(next_capture) "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}" end - def regexp_chunk - regexp || "(.*)" + def default_regexp_chunk + "(.*)" end + def number_of_captures + regexp ? regexp.number_of_captures : 1 + end + def optionality_implied? true end class Result < ::Array #:nodoc: @@ -277,7 +302,42 @@ def self.new_escaped(strings) new strings.collect {|str| URI.unescape str} end end end + + # The OptionalFormatSegment allows for any resource route to have an optional + # :format, which decreases the amount of routes created by 50%. + class OptionalFormatSegment < DynamicSegment + + def initialize(key = nil, options = {}) + super(:format, {:optional => true}.merge(options)) + end + + def interpolation_chunk + "." + super + end + + def regexp_chunk + '/|(\.[^/?\.]+)?' + end + + def to_s + '(.:format)?' + end + + def extract_value + "#{local_name} = options[:#{key}] && options[:#{key}].to_s.downcase" + end + + #the value should not include the period (.) + def match_extraction(next_capture) + %[ + if (m = match[#{next_capture}]) + params[:#{key}] = URI.unescape(m.from(1)) + end + ] + end + end + end end