From 654929190170c174c8b844d0adcd968c3049d515 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 27 Jun 2010 13:11:49 -0700 Subject: Vendor unreleased rack-mount 0.6.6.pre dependency --- .../rack-mount-0.6.6.pre/rack/mount/route_set.rb | 409 +++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb (limited to 'actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb') diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb new file mode 100644 index 0000000000..0e5a65a640 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb @@ -0,0 +1,409 @@ +require 'rack/mount/multimap' +require 'rack/mount/route' +require 'rack/mount/utils' + +module Rack::Mount + class RoutingError < StandardError; end + + class RouteSet + # Initialize a new RouteSet without optimizations + def self.new_without_optimizations(options = {}, &block) + new(options.merge(:_optimize => false), &block) + end + + # Basic RouteSet initializer. + # + # If a block is given, the set is yielded and finalized. + # + # See other aspects for other valid options: + # - Generation::RouteSet.new + # - Recognition::RouteSet.new + def initialize(options = {}, &block) + @parameters_key = options.delete(:parameters_key) || 'rack.routing_args' + @parameters_key.freeze + + @named_routes = {} + + @recognition_key_analyzer = Analysis::Splitting.new + @generation_key_analyzer = Analysis::Frequency.new + + @request_class = options.delete(:request_class) || Rack::Request + @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym } + + extend CodeGeneration unless options[:_optimize] == false + @optimized_recognize_defined = false + + @routes = [] + expire! + + if block_given? + yield self + rehash + end + end + + # Builder method to add a route to the set + # + # app:: A valid Rack app to call if the conditions are met. + # conditions:: A hash of conditions to match against. + # Conditions may be expressed as strings or + # regexps to match against. + # defaults:: A hash of values that always gets merged in + # name:: Symbol identifier for the route used with named + # route generations + def add_route(app, conditions = {}, defaults = {}, name = nil) + unless conditions.is_a?(Hash) + raise ArgumentError, 'conditions must be a Hash' + end + + unless conditions.all? { |method, pattern| + @valid_conditions.include?(method) + } + raise ArgumentError, 'conditions may only include ' + + @valid_conditions.inspect + end + + route = Route.new(app, conditions, defaults, name) + @routes << route + + @recognition_key_analyzer << route.conditions + + @named_routes[route.name] = route if route.name + @generation_key_analyzer << route.generation_keys + + expire! + route + end + + def recognize(obj) + raise 'route set not finalized' unless @recognition_graph + + cache = {} + keys = @recognition_keys.map { |key| + if key.respond_to?(:call) + key.call(cache, obj) + else + obj.send(key) + end + } + + @recognition_graph[*keys].each do |route| + matches = {} + params = route.defaults.dup + + if route.conditions.all? { |method, condition| + value = obj.send(method) + if condition.is_a?(Regexp) && (m = value.match(condition)) + matches[method] = m + captures = m.captures + route.named_captures[method].each do |k, i| + if v = captures[i] + params[k] = v + end + end + true + elsif value == condition + true + else + false + end + } + if block_given? + yield route, matches, params + else + return route, matches, params + end + end + end + + nil + end + + X_CASCADE = 'X-Cascade'.freeze + PASS = 'pass'.freeze + PATH_INFO = 'PATH_INFO'.freeze + + # Rack compatible recognition and dispatching method. Routes are + # tried until one returns a non-catch status code. If no routes + # match, the catch status code is returned. + # + # This method can only be invoked after the RouteSet has been + # finalized. + def call(env) + raise 'route set not finalized' unless @recognition_graph + + env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO]) + + request = nil + req = @request_class.new(env) + recognize(req) do |route, matches, params| + # TODO: We only want to unescape params from uri related methods + params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) } + + if route.prefix? + env[Prefix::KEY] = matches[:path_info].to_s + end + + env[@parameters_key] = params + result = route.app.call(env) + return result unless result[1][X_CASCADE] == PASS + end + + request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']] + end + + # Generates a url from Rack env and identifiers or significant keys. + # + # To generate a url by named route, pass the name in as a +Symbol+. + # url(env, :dashboard) # => "/dashboard" + # + # Additional parameters can be passed in as a hash + # url(env, :people, :id => "1") # => "/people/1" + # + # If no name route is given, it will fall back to a slower + # generation search. + # url(env, :controller => "people", :action => "show", :id => "1") + # # => "/people/1" + def url(env, *args) + named_route, params = nil, {} + + case args.length + when 2 + named_route, params = args[0], args[1].dup + when 1 + if args[0].is_a?(Hash) + params = args[0].dup + else + named_route = args[0] + end + else + raise ArgumentError + end + + only_path = params.delete(:only_path) + recall = env[@parameters_key] || {} + + unless result = generate(:all, named_route, params, recall, + :parameterize => lambda { |name, param| Utils.escape_uri(param) }) + return + end + + parts, params = result + return unless parts + + params.each do |k, v| + if v + params[k] = v + else + params.delete(k) + end + end + + req = stubbed_request_class.new(env) + req._stubbed_values = parts.merge(:query_string => Utils.build_nested_query(params)) + only_path ? req.fullpath : req.url + end + + def generate(method, *args) #:nodoc: + raise 'route set not finalized' unless @generation_graph + + method = nil if method == :all + named_route, params, recall, options = extract_params!(*args) + merged = recall.merge(params) + route = nil + + if named_route + if route = @named_routes[named_route.to_sym] + recall = route.defaults.merge(recall) + url = route.generate(method, params, recall, options) + [url, params] + else + raise RoutingError, "#{named_route} failed to generate from #{params.inspect}" + end + else + keys = @generation_keys.map { |key| + if k = merged[key] + k.to_s + else + nil + end + } + @generation_graph[*keys].each do |r| + next unless r.significant_params? + if url = r.generate(method, params, recall, options) + return [url, params] + end + end + + raise RoutingError, "No route matches #{params.inspect}" + end + end + + # Number of routes in the set + def length + @routes.length + end + + def rehash #:nodoc: + @recognition_keys = build_recognition_keys + @recognition_graph = build_recognition_graph + @generation_keys = build_generation_keys + @generation_graph = build_generation_graph + end + + # Finalizes the set and builds optimized data structures. You *must* + # freeze the set before you can use call and url. + # So remember to call freeze after you are done adding routes. + def freeze + unless frozen? + rehash + + @recognition_key_analyzer = nil + @generation_key_analyzer = nil + @valid_conditions = nil + + @routes.each { |route| route.freeze } + @routes.freeze + end + + super + end + + def marshal_dump #:nodoc: + hash = {} + + instance_variables_to_serialize.each do |ivar| + hash[ivar] = instance_variable_get(ivar) + end + + if graph = hash[:@recognition_graph] + hash[:@recognition_graph] = graph.dup + end + + hash + end + + def marshal_load(hash) #:nodoc: + hash.each do |ivar, value| + instance_variable_set(ivar, value) + end + end + + protected + def recognition_stats + { :keys => @recognition_keys, + :keys_size => @recognition_keys.size, + :graph_size => @recognition_graph.size, + :graph_height => @recognition_graph.height, + :graph_average_height => @recognition_graph.average_height } + end + + private + def expire! #:nodoc: + @recognition_keys = @recognition_graph = nil + @recognition_key_analyzer.expire! + + @generation_keys = @generation_graph = nil + @generation_key_analyzer.expire! + end + + def instance_variables_to_serialize + instance_variables.map { |ivar| ivar.to_sym } - [:@stubbed_request_class, :@optimized_recognize_defined] + end + + # An internal helper method for constructing a nested set from + # the linear route set. + # + # build_nested_route_set([:request_method, :path_info]) { |route, method| + # route.send(method) + # } + def build_nested_route_set(keys, &block) + graph = Multimap.new + @routes.each_with_index do |route, index| + catch(:skip) do + k = keys.map { |key| block.call(key, index) } + Utils.pop_trailing_nils!(k) + k.map! { |key| key || /.+/ } + graph[*k] = route + end + end + graph + end + + def build_recognition_graph + build_nested_route_set(@recognition_keys) { |k, i| + @recognition_key_analyzer.possible_keys[i][k] + } + end + + def build_recognition_keys + @recognition_key_analyzer.report + end + + def build_generation_graph + build_nested_route_set(@generation_keys) { |k, i| + throw :skip unless @routes[i].significant_params? + + if k = @generation_key_analyzer.possible_keys[i][k] + k.to_s + else + nil + end + } + end + + def build_generation_keys + @generation_key_analyzer.report + end + + def extract_params!(*args) + case args.length + when 4 + named_route, params, recall, options = args + when 3 + if args[0].is_a?(Hash) + params, recall, options = args + else + named_route, params, recall = args + end + when 2 + if args[0].is_a?(Hash) + params, recall = args + else + named_route, params = args + end + when 1 + if args[0].is_a?(Hash) + params = args[0] + else + named_route = args[0] + end + else + raise ArgumentError + end + + named_route ||= nil + params ||= {} + recall ||= {} + options ||= {} + + [named_route, params.dup, recall.dup, options.dup] + end + + def stubbed_request_class + @stubbed_request_class ||= begin + klass = Class.new(@request_class) + klass.public_instance_methods.each do |method| + next if method =~ /^__|object_id/ + klass.class_eval <<-RUBY + def #{method}(*args, &block) + @_stubbed_values[:#{method}] || super + end + RUBY + end + klass.class_eval { attr_accessor :_stubbed_values } + klass + end + end + end +end -- cgit v1.2.3