$:.unshift File.dirname(__FILE__) require 'usher/node' require 'usher/route' require 'usher/grapher' require 'usher/interface' require 'usher/exceptions' class Usher attr_reader :tree, :named_routes, :route_count, :routes SymbolArraySorter = proc {|a,b| a.hash <=> b.hash} # Returns whether the route set is empty # # set = Usher.new # set.empty? => true # set.add_route('/test') # set.empty? => false def empty? @route_count.zero? end # Resets the route set back to its initial state # # set = Usher.new # set.add_route('/test') # set.empty? => false # set.reset! # set.empty? => true def reset! @tree = Node.root(self) @named_routes = {} @routes = [] @route_count = 0 Grapher.instance.reset! end alias clear! reset! # Creates a route set def initialize reset! end # Adds a route referencable by +name+. Sett add_route for format +path+ and +options+. # # set = Usher.new # set.add_named_route(:test_route, '/test') def add_named_route(name, path, options = {}) add_route(path, options).name(name) end # Attaches a +route+ to a +name+ # # set = Usher.new # route = set.add_route('/test') # set.name(:test, route) def name(name, route) @named_routes[name] = route.primary_path end # Creates a route from +path+ and +options+ # # <tt>+path+</tt>:: # A path consists a mix of dynamic and static parts delimited by <tt>/</tt> # # *Dynamic* # # Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or # more parts. Example: # <b>/path/:variable/path</b> would match # # * /path/test/path # * /path/something_else/path # * /path/one_more/path # # In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key :variable. # However, /path/test/one_more/path would not be matched. # # Example: # <b>/path/*variable/path</b> would match # # * /path/one/two/three/path # * /path/four/five/path # # In the above examples, ['one', 'two', 'three'] and ['four', 'five'] respectively would be bound to the key :variable. # # <tt>+options+</tt>:: # -- # * :transformers - Transforms a variable before it gets to the conditions and requirements. Takes either a +proc+ or a +symbol+. If its a +symbol+, calls the method on the incoming parameter. If its a +proc+, its called with the variable. # * :requirements - After transformation, tests the condition using ===. If it returns false, it raises an +Usher::ValidationException+ # * :conditions - Accepts any of the following +:protocol+, +:domain+, +:port+, +:query_string+, +:remote_ip+, +:user_agent+, +:referer+ and +:method+. This can be either a +string+ or a +regexp+. # def add_route(path, options = {}) transformers = options.delete(:transformers) || {} conditions = options.delete(:conditions) || {} requirements = options.delete(:requirements) || {} options.delete_if do |k, v| if v.is_a?(Regexp) requirements[k] = v true end end route = Route.new(path, self, {:transformers => transformers, :conditions => conditions, :requirements => requirements}).to(options) @tree.add(route) @routes << route Grapher.instance.add_route(route) @route_count += 1 route end # Recognizes a +request+ and returns +nil+ or an Usher::Route::Path # # set = Usher.new # route = set.add_route('/test') # set.name(:test, route) def recognize(request) @tree.find(request) end def route_for_options(options) Grapher.instance.find_matching_path(options) end def generate_url(route, params) path = case route when Symbol @named_routes[route] when nil route_for_options(params) else route end params_hash = {} param_list = case params when Hash params_hash = params path.dynamic_parts.collect{|k| params_hash.delete(k.name)} when Array params else Array(params) end generated_path = '' path.parts.each do |p| case p when Route::Variable case p.type when :* generated_path << '/' << param_list.shift * '/' when :'.:' (dp = param_list.shift) && generated_path << '.' << dp.to_s else (dp = param_list.shift) && generated_path << '/' << dp.to_s end else generated_path << '/' << p.to_s end end unless params_hash.blank? has_query = generated_path[??] params_hash.each do |k,v| case v when Array v.each do |v_part| generated_path << (has_query ? '&' : has_query = true && '?') generated_path << CGI.escape("#{k.to_s}[]") generated_path << '=' generated_path << CGI.escape(v_part.to_s) end else generated_path << (has_query ? '&' : has_query = true && '?') generated_path << CGI.escape(k.to_s) generated_path << '=' generated_path << CGI.escape(v.to_s) end end end generated_path end end