module Merb # Merb::RouteMatcher is the request routing mapper for the merb framework. # You can define placeholder parts of the url with the :symbol notation. # so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo' # will match against a request to /foo/123/baz/456. It will then # use the class Bar as your merb controller and call the foo method on it. # the foo method will recieve a hash with {:bar => '123', :id => '456'} # as the content. So the :placeholders sections of your routes become # a hash of arguments to your controller methods. This route maps # the default /controller/action and /controller/action/id urls: # r.add '/:class/:method/:id' class RouteMatcher attr_accessor :sections @@section_regexp = /(?::([a-z*_]+))/.freeze # setup the router and yield it out to # add routes to it. Then compile all routes def self.prepare @@routes = Array.new @@compiled_statement = String.new yield self compile_router end # init @sections for route segment recognition def initialize @sections = Hash.new end # all defined routes in their raw form. def routes @@routes end # the final compiled lambda that gets used # as the body of the route_request method. def compiled_statement @@compiled_statement end # add a route to be compiled def self.add(*route) @@routes << [route[0], (route[1] || {})] end # build up a string that defines a lambda # that does a case statement on the PATH_INFO # against each of the compiled routes in turn. # first route that matches wins. def self.compile_router router_lambda = @@routes.inject("lambda{|path| \n case path\n") { |m,r| m << compile(r) } <<" else\n return {:controller=>'Noroutefound', :action=>'noroute'}\n end\n}" @@compiled_statement = router_lambda define_method(:route_request, &eval(router_lambda)) end # compile each individual route into a when /.../ # component of the case statement. Takes /:sections # if the route def that start with : and turns them # into placeholders for whatever urls match against # the route in question. Special case for the default # /:controller/:action/:id route. def self.compile(route) raise ArgumentError unless String === route[0] if route[0] == '/:controller/:action/:id' return ' when /\A\/([^\/;.,?]+)(?:\/?\Z|\/([^\/;.,?]+)\/?)(?:\/?\Z|\/([^\/;.,?]+)\/?)\Z/ @sections[:controller] = $1 @sections[:action] = $2 || \'index\' @sections[:id] = $3 || nil return @sections'<<"\n" end code, count = '', 0 while route[0] =~ @@section_regexp route[0] = route[0].dup name = $1 (name =~ /(\*+)(\w+)/) ? (flag = true; name = $2) : (flag = false) count += 1 if flag route[0].sub!(@@section_regexp, "([^;.,?]+)") else route[0].sub!(@@section_regexp, "([^\/;.,?]+)") end code << " @sections[:#{name}] = $#{count}\n" end condition = " when Regexp.new('#{route[0]}')" statement = "#{condition}\n#{code}" statement << " return #{route[1].inspect}.merge(@sections)\n" statement end end end