require 'rack/mount/generatable_regexp' require 'rack/mount/regexp_with_named_groups' require 'rack/mount/utils' module Rack::Mount # Route is an internal class used to wrap a single route attributes. # # Plugins should not depend on any method on this class or instantiate # new Route objects. Instead use the factory method, RouteSet#add_route # to create new routes and add them to the set. class Route # Valid rack application to call if conditions are met attr_reader :app # A hash of conditions to match against. Conditions may be expressed # as strings or regexps to match against. attr_reader :conditions # A hash of values that always gets merged into the parameters hash attr_reader :defaults # Symbol identifier for the route used with named route generations attr_reader :name attr_reader :named_captures def initialize(app, conditions, defaults, name) unless app.respond_to?(:call) raise ArgumentError, 'app must be a valid rack application' \ ' and respond to call' end @app = app @name = name ? name.to_sym : nil @defaults = (defaults || {}).freeze @conditions = {} conditions.each do |method, pattern| next unless method && pattern pattern = Regexp.compile("\\A#{Regexp.escape(pattern)}\\Z") if pattern.is_a?(String) if pattern.is_a?(Regexp) pattern = Utils.normalize_extended_expression(pattern) pattern = RegexpWithNamedGroups.new(pattern) pattern.extend(GeneratableRegexp::InstanceMethods) pattern.defaults = @defaults end @conditions[method] = pattern.freeze end @named_captures = {} @conditions.map { |method, condition| next unless condition.respond_to?(:named_captures) @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)| named_captures[k.to_sym] = v.last - 1 named_captures }.freeze } @named_captures.freeze @has_significant_params = @conditions.any? { |method, condition| (condition.respond_to?(:required_params) && condition.required_params.any?) || (condition.respond_to?(:required_defaults) && condition.required_defaults.any?) } if @conditions.has_key?(:path_info) && !Utils.regexp_anchored?(@conditions[:path_info]) @prefix = true @app = Prefix.new(@app) else @prefix = false end @conditions.freeze end def prefix? @prefix end def generation_keys @conditions.inject({}) { |keys, (method, condition)| if condition.respond_to?(:required_defaults) keys.merge!(condition.required_defaults) else keys end } end def significant_params? @has_significant_params end def generate(method, params = {}, recall = {}, options = {}) if method.nil? result = @conditions.inject({}) { |h, (m, condition)| if condition.respond_to?(:generate) h[m] = condition.generate(params, recall, options) end h } return nil if result.values.compact.empty? else if condition = @conditions[method] if condition.respond_to?(:generate) result = condition.generate(params, recall, options) end end end if result @defaults.each do |key, value| params.delete(key) if params[key] == value end end result end def inspect #:nodoc: "#<#{self.class.name} @app=#{@app.inspect} @conditions=#{@conditions.inspect} @defaults=#{@defaults.inspect} @name=#{@name.inspect}>" end end end