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