module ActionController
module Routing #:nodoc:
class << self
def expiry_hash(options, recall)
k = v = nil
expire_on = {}
options.each {|k, v| expire_on[k] = ((rcv = recall[k]) && (rcv != v))}
expire_on
end
def extract_parameter_value(parameter) #:nodoc:
CGI.escape((parameter.respond_to?(:to_param) ? parameter.to_param : parameter).to_s)
end
def controller_relative_to(controller, previous)
if controller.nil? then previous
elsif controller[0] == ?/ then controller[1..-1]
elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
else controller
end
end
def treat_hash(hash, keys_to_delete = [])
k = v = nil
hash.each do |k, v|
if v then hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s
else
hash.delete k
keys_to_delete << k
end
end
hash
end
def test_condition(expression, condition)
case condition
when String then "(#{expression} == #{condition.inspect})"
when Regexp then
condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source
"(#{condition.inspect} =~ #{expression})"
when Array then
conds = condition.collect do |condition|
cond = test_condition(expression, condition)
(cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})"
end
"(#{conds.join(' || ')})"
when true then expression
when nil then "! #{expression}"
else
raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil"
end
end
end
class Component #:nodoc:
def dynamic?() false end
def optional?() false end
def key() nil end
def self.new(string, *args)
return super(string, *args) unless self == Component
case string
when ':controller' then ControllerComponent.new(:controller, *args)
when /^:(\w+)$/ then DynamicComponent.new($1, *args)
when /^\*(\w+)$/ then PathComponent.new($1, *args)
else StaticComponent.new(string, *args)
end
end
end
class StaticComponent < Component #:nodoc:
attr_reader :value
def initialize(value)
@value = value
end
def write_recognition(g)
g.if_next_matches(value) do |gp|
gp.move_forward {|gpp| gpp.continue}
end
end
def write_generation(g)
g.add_segment(value) {|gp| gp.continue }
end
end
class DynamicComponent < Component #:nodoc:
attr_reader :key, :default
attr_accessor :condition
def dynamic?() true end
def optional?() @optional end
def default=(default)
@optional = true
@default = default
end
def initialize(key, options = {})
@key = key.to_sym
@optional = false
default, @condition = options[:default], options[:condition]
self.default = default if options.key?(:default)
end
def default_check(g)
presence = "#{g.hash_value(key, !! default)}"
if default
"!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})"
else
"! #{presence}"
end
end
def write_generation(g)
wrote_dropout = write_dropout_generation(g)
write_continue_generation(g, wrote_dropout)
end
def write_dropout_generation(g)
return false unless optional? && g.after.all? {|c| c.optional?}
check = [default_check(g)]
gp = g.dup # Use another generator to write the conditions after the first &&
# We do this to ensure that the generator will not assume x_value is set. It will
# not be set if it follows a false condition -- for example, false && (x = 2)
check += gp.after.map {|c| c.default_check gp}
gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here
true
end
def write_continue_generation(g, use_else)
test = Routing.test_condition(g.hash_value(key, true, default), condition || true)
check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test]
g.send(*check) do |gp|
gp.expire_for_keys(key) unless gp.after.empty?
add_segments_to(gp) {|gpp| gpp.continue}
end
end
def add_segments_to(g)
g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp}
end
def recognition_check(g)
test_type = [true, nil].include?(condition) ? :presence : :constraint
prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : ''
check = prefix + Routing.test_condition(g.next_segment(true), condition || true)
g.if(check) {|gp| yield gp, test_type}
end
def write_recognition(g)
test_type = nil
recognition_check(g) do |gp, test_type|
assign_result(gp) {|gpp| gpp.continue}
end
if optional? && g.after.all? {|c| c.optional?}
call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"]
g.send(*call) do |gp|
assign_default(gp)
gp.after.each {|c| c.assign_default(gp)}
gp.finish(false)
end
end
end
def assign_result(g, with_default = false)
g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})"
g.move_forward {|gp| yield gp}
end
def assign_default(g)
g.constant_result key, default unless default.nil?
end
end
class ControllerComponent < DynamicComponent #:nodoc:
def key() :controller end
def add_segments_to(g)
g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp}
end
def recognition_check(g)
g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})"
g.if('controller_result') do |gp|
gp << 'controller_value, segments_to_controller = controller_result'
if condition
gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')"
gp.if(Routing.test_condition("controller_path", condition)) do |gpp|
gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint}
end
else
gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint}
end
end
end
def assign_result(g)
g.result key, 'controller_value'
yield g
end
def assign_default(g)
ControllerComponent.assign_controller(g, default)
end
class << self
def assign_controller(g, controller)
expr = "::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller"
g.result :controller, expr, true
end
def traverse_to_controller(segments, start_at = 0)
mod = ::Object
length = segments.length
index = start_at
mod_name = controller_name = segment = nil
while index < length
return nil unless /\A[A-Za-z][A-Za-z\d_]*\Z/ =~ (segment = segments[index])
index += 1
mod_name = segment.camelize
controller_name = "#{mod_name}Controller"
path_suffix = File.join(segments[start_at..(index - 1)])
next_mod = nil
# If the controller is already present, or if we load it, return it.
if mod.const_defined?(controller_name) || attempt_load(mod, controller_name, path_suffix + "_controller") == :defined
controller = mod.const_get(controller_name)
return nil unless controller.is_a?(Class) && controller.ancestors.include?(ActionController::Base) # it's not really a controller?
return [controller, (index - start_at)]
end
# No controller? Look for the module
if mod.const_defined? mod_name
next_mod = mod.send(:const_get, mod_name)
next_mod = nil unless next_mod.is_a?(Module)
else
# Try to load a file that defines the module we want.
case attempt_load(mod, mod_name, path_suffix)
when :defined then next_mod = mod.const_get mod_name
when :dir then # We didn't find a file, but there's a dir.
next_mod = Module.new # So create a module for the directory
mod.send :const_set, mod_name, next_mod
else
return nil
end
end
mod = next_mod
return nil unless mod && mod.is_a?(Module)
end
nil
end
protected
def safe_load_paths #:nodoc:
if defined?(RAILS_ROOT)
$LOAD_PATH.select do |base|
base = File.expand_path(base)
extended_root = File.expand_path(RAILS_ROOT)
# Exclude all paths that are not nested within app, lib, or components.
base.match(/\A#{Regexp.escape(extended_root)}\/*(app|lib|components)\/[a-z]/) || base =~ %r{rails-[\d.]+/builtin}
end
else
$LOAD_PATH
end
end
def attempt_load(mod, const_name, path)
has_dir = false
safe_load_paths.each do |load_path|
full_path = File.join(load_path, path)
file_path = full_path + '.rb'
if File.file?(file_path) # Found a .rb file? Load it up
require_dependency(file_path)
return :defined if mod.const_defined? const_name
else
has_dir ||= File.directory?(full_path)
end
end
return (has_dir ? :dir : nil)
end
end
end
class PathComponent < DynamicComponent #:nodoc:
def optional?() true end
def default() [] end
def condition() nil end
def default=(value)
raise RoutingError, "All path components have an implicit default of []" unless value == []
end
def write_generation(g)
raise RoutingError, 'Path components must occur last' unless g.after.empty?
g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do
g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)"
g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish }
end
g.else { g.finish }
end
def write_recognition(g)
raise RoutingError, "Path components must occur last" unless g.after.empty?
start = g.index_name
start = "(#{start})" unless /^\w+$/ =~ start
value_expr = "#{g.path_name}[#{start}..-1] || []"
g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})"
g.finish(false)
end
class Result < ::Array #:nodoc:
def to_s() join '/' end
def self.new_escaped(strings)
new strings.collect {|str| CGI.unescape str}
end
end
end
class Route #:nodoc:
attr_accessor :components, :known
attr_reader :path, :options, :keys, :defaults
def initialize(path, options = {})
@path, @options = path, options
initialize_components path
defaults, conditions = initialize_hashes options.dup
@defaults = defaults.dup
configure_components(defaults, conditions)
add_default_requirements
initialize_keys
end
def inspect
"<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>"
end
def write_generation(generator = CodeGeneration::GenerationGenerator.new)
generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || [])
if known.empty? then generator.go
else
# Alter the conditions to allow :action => 'index' to also catch :action => nil
altered_known = known.collect do |k, v|
if k == :action && v== 'index' then [k, [nil, 'index']]
else [k, v]
end
end
generator.if(generator.check_conditions(altered_known)) {|gp| gp.go }
end
generator
end
def write_recognition(generator = CodeGeneration::<