aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2010-06-27 13:11:49 -0700
committerJeremy Kemper <jeremy@bitsweat.net>2010-06-27 16:28:04 -0700
commit654929190170c174c8b844d0adcd968c3049d515 (patch)
treedaf1571d1f1d82e1b4cee204bf8baa2fea4f2696 /actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb
parentd15256af6cb160d0cdd76695db22872f09d6e835 (diff)
downloadrails-654929190170c174c8b844d0adcd968c3049d515.tar.gz
rails-654929190170c174c8b844d0adcd968c3049d515.tar.bz2
rails-654929190170c174c8b844d0adcd968c3049d515.zip
Vendor unreleased rack-mount 0.6.6.pre dependency
Diffstat (limited to 'actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb')
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb409
1 files changed, 409 insertions, 0 deletions
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:
+ # - <tt>Generation::RouteSet.new</tt>
+ # - <tt>Recognition::RouteSet.new</tt>
+ 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
+ #
+ # <tt>app</tt>:: A valid Rack app to call if the conditions are met.
+ # <tt>conditions</tt>:: A hash of conditions to match against.
+ # Conditions may be expressed as strings or
+ # regexps to match against.
+ # <tt>defaults</tt>:: A hash of values that always gets merged in
+ # <tt>name</tt>:: 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 <tt>call</tt> and <tt>url</tt>.
+ # 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