aboutsummaryrefslogblamecommitdiffstats
path: root/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb
blob: 47bbab3784e89e4a6219e57b0be8f1d6651bdf58 (plain) (tree)

















































































































































































































                                                                                             
require 'rack/mount/utils'

module Rack::Mount
  class GeneratableRegexp < Regexp #:nodoc:
    class DynamicSegment #:nodoc:
      attr_reader :name, :requirement

      def initialize(name, requirement)
        @name, @requirement = name.to_sym, requirement
        freeze
      end

      def ==(obj)
        @name == obj.name && @requirement == obj.requirement
      end

      def =~(str)
        @requirement =~ str
      end

      def to_hash
        { @name => @requirement }
      end

      def inspect
        "/(?<#{@name}>#{@requirement.source})/"
      end
    end

    module InstanceMethods
      def self.extended(obj)
        obj.segments
      end

      def defaults=(defaults)
        @required_captures = nil
        @required_params = nil
        @required_defaults = nil
        @defaults = defaults
      end

      def defaults
        @defaults ||= {}
      end

      def generatable?
        segments.any?
      end

      def generate(params = {}, recall = {}, options = {})
        return nil unless generatable?

        merged = recall.merge(params)
        return nil unless required_params.all? { |p| merged.include?(p) }
        return nil unless required_defaults.all? { |k, v| merged[k] == v }

        generate_from_segments(segments, params, merged, options)
      end

      def segments
        @segments ||= begin
          defaults
          segments = []
          catch(:halt) do
            expression = Utils.parse_regexp(self)
            segments = parse_segments(expression)
          end
          segments
        end
      end

      def captures
        segments.flatten.find_all { |s| s.is_a?(DynamicSegment) }
      end

      def required_captures
        @required_captures ||= segments.find_all { |s|
          s.is_a?(DynamicSegment) && !@defaults.include?(s.name)
        }.freeze
      end

      def required_params
        @required_params ||= required_captures.map { |s| s.name }.freeze
      end

      def required_defaults
        @required_defaults ||= begin
          required_defaults = @defaults.dup
          captures.inject({}) { |h, s| h.merge!(s.to_hash) }.keys.each { |name|
            required_defaults.delete(name)
          }
          required_defaults
        end
      end

      def freeze
        segments
        captures
        required_captures
        required_params
        required_defaults
        super
      end

      private
        def parse_segments(segments)
          s = []
          segments.each_with_index do |part, index|
            case part
            when Regin::Anchor
              # ignore
            when Regin::Character
              throw :halt unless part.literal?

              if s.last.is_a?(String)
                s.last << part.value.dup
              else
                s << part.value.dup
              end
            when Regin::Group
              if part.name
                s << DynamicSegment.new(part.name, part.expression.to_regexp(true))
              else
                s << parse_segments(part.expression)
              end
            when Regin::Expression
              return parse_segments(part)
            else
              throw :halt
            end
          end

          s
        end

        EMPTY_STRING = ''.freeze

        def generate_from_segments(segments, params, merged, options, optional = false)
          if optional
            return EMPTY_STRING if segments.all? { |s| s.is_a?(String) }
            return EMPTY_STRING unless segments.flatten.any? { |s|
              params.has_key?(s.name) if s.is_a?(DynamicSegment)
            }
            return EMPTY_STRING if segments.any? { |segment|
              if segment.is_a?(DynamicSegment)
                value = merged[segment.name] || @defaults[segment.name]
                value = parameterize(segment.name, value, options)

                merged_value  = parameterize(segment.name, merged[segment.name], options)
                default_value = parameterize(segment.name, @defaults[segment.name], options)

                if value.nil? || segment !~ value
                  true
                elsif merged_value == default_value
                  # Nasty control flow
                  return :clear_remaining_segments
                else
                  false
                end
              end
            }
          end

          generated = segments.map do |segment|
            case segment
            when String
              segment
            when DynamicSegment
              value = params[segment.name] || merged[segment.name] || @defaults[segment.name]
              value = parameterize(segment.name, value, options)
              if value && segment =~ value.to_s
                value
              else
                return
              end
            when Array
              value = generate_from_segments(segment, params, merged, options, true)
              if value == :clear_remaining_segments
                segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
                EMPTY_STRING
              elsif value.nil?
                EMPTY_STRING
              else
                value
              end
            end
          end

          # Delete any used items from the params
          segments.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }

          generated.join
        end

        def parameterize(name, value, options)
          if block = options[:parameterize]
            block.call(name, value)
          else
            value
          end
        end
    end
    include InstanceMethods

    def initialize(regexp)
      super
      segments
    end
  end
end