Changeset 647

Show
Ignore:
Timestamp:
09/18/07 03:39:35 (1 year ago)
Author:
r.@tinyclouds.org
Message:

initial dispatcher interface

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/ry_routes/lib/merb/controller.rb

    r640 r647  
    3636        cont.set_dispatch_variables(request, response, status, headers) 
    3737        cont 
     38      end 
     39       
     40      # returns the action for a request 
     41      def route(request) 
     42         
    3843      end 
    3944    end 
  • branches/ry_routes/lib/merb/dispatcher.rb

    r640 r647  
    2727        if request.route_params.empty? 
    2828          raise ControllerExceptions::NotFound, "No routes match the request" 
    29         elsif request.controller_name.nil? 
     29        elsif request.controller_class.nil? 
    3030          raise ControllerExceptions::NotFound, "Route matched, but route did not specify a controller"  
    3131        end 
    3232        MERB_LOGGER.debug("Routed to: #{request.route_params.inspect}") 
    33         # set controller class and the action to call 
    3433        klass = request.controller_class 
    35         dispatch_action(klass, request.action, request, response) 
     34        action = klass.route(request) 
     35        dispatch_action(klass, action, request, response) 
    3636      rescue => exception 
    3737        exception = controller_exception(exception) 
  • branches/ry_routes/lib/merb/request.rb

    r640 r647  
    103103    end 
    104104     
    105     def route 
    106       @route ||= Merb::Router.routes[route_index] 
    107     end 
    108      
    109     # returns two objects, route_index and route_params 
    110105    def route_match 
    111       @route_match ||= Merb::Router.match(self, body_and_query_params
     106      @route_match ||= Router.match(path_info
    112107    end 
    113108    private :route_match 
    114109     
    115     def route_index 
     110    def controller_class 
    116111      route_match.first 
    117112    end 
    118113     
    119114    def route_params 
    120       route_match.last 
    121     end 
    122      
    123     def controller_name 
    124       route_params[:controller] 
    125     end 
    126      
    127     def controller_class 
    128       path = "#{MERB_ROOT}/app/controllers/#{controller_name}.rb" 
    129       cnt = controller_name.to_const_string 
    130        
    131       if !File.exist?(path) 
    132         raise ControllerExceptions::NotFound, "Bad controller! #{cnt}" 
    133       end unless $TESTING 
    134        
    135       begin 
    136         if cnt == "Application" 
    137           raise ControllerExceptions::NotFound, "The 'Application' controller has no public actions"  
    138         end 
    139         return Object.full_const_get(cnt) 
    140       rescue NameError 
    141         raise ControllerExceptions::NotFound 
    142       end 
    143     end 
    144      
    145     def action 
    146       route_params[:action] 
     115      route_match.second 
    147116    end 
    148117     
  • branches/ry_routes/lib/merb/router.rb

    r494 r647  
    22 
    33  class Router 
    4     SEGMENT_REGEXP = /(:([a-z_][a-z0-9_]*|:))/ 
    5     SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/ 
    6     JUST_BRACKETS = /\[(\d+)\]/ 
    7     PARENTHETICAL_SEGMENT_STRING = "([^\/.,;?]+)".freeze 
    8  
    9     @@named_routes = {} 
    10     @@routes = [] 
    11     cattr_accessor :routes, :named_routes 
    12  
     4     
     5    SECTION_REGEXP = /(?::([a-z*_]+))/.freeze 
     6     
    137    class << self 
     8       
    149      def prepare 
    15         @@routes = [] 
    16         yield Behavior.new({}, {:action => 'index'}) # defaults 
    17         compile 
     10        @@matcher = RouteMatcher.new 
     11        @@generator = RouteGenerator.new 
     12       
     13        yield self 
     14       
     15        @@matcher.compile_router 
     16      end 
     17       
     18      def add(*route) 
     19        @@matcher.add(*route) 
     20      end 
     21       
     22      def matcher 
     23        @@matcher 
     24      end 
     25       
     26      def generator 
     27        @@generator 
     28      end 
     29       
     30      def match(path) 
     31        @@matcher.route_request(path) 
     32      end 
     33       
     34      def generate(method, *args) 
     35        @@generator.generate(method, *args) 
     36      end 
     37       
     38      def resources(res, opts={}) 
     39        opts[:prefix] ||= "" 
     40        if block_given? 
     41          procs = [] 
     42          yield Resource.new(res, procs, opts) 
     43          procs.reverse.each &:call 
     44        else 
     45          generate_resources_routes(res,opts) 
     46        end 
     47      end 
     48       
     49      def resource(res, opts={}) 
     50        opts[:prefix] ||= "" 
     51        if block_given? 
     52          procs = [] 
     53          yield Resource.new(res, procs, opts) 
     54          procs.reverse.each &:call 
     55        else 
     56          generate_singleton_routes(res,opts) 
     57        end 
     58      end 
     59       
     60      def default_routes(*a) 
     61        @@matcher.default_routes(*a) 
    1862      end 
    1963       
    2064      def compiled_statement 
    21         @@compiled_statement = "lambda { |request, params|\n" 
    22         @@compiled_statement << "  cached_path = request.path\n  cached_method = request.method.to_s\n  " 
    23         # @@compiled_statement << "  puts cached_path.inspect; puts cached_method.inspect\n" 
    24         @@routes.each_with_index { |route, i| @@compiled_statement << route.compile(i == 0) } 
    25         @@compiled_statement << "  else\n    [nil, {}]\n" 
    26         @@compiled_statement << "  end\n" 
    27         @@compiled_statement << "}" 
    28       end 
    29        
    30       def compile 
    31         meta_def(:match, &eval(compiled_statement)) 
    32       end 
    33        
    34       def generate(name, params = {}, fallback = {}) 
    35         raise "Named route not found: #{name}" unless @@named_routes.has_key? name 
    36         @@named_routes[name].generate(params, fallback) 
    37       end 
    38     end # self 
     65        @@matcher.compiled_statement 
     66      end 
     67       
     68      def compiled_regexen 
     69        @@matcher.compiled_regexen 
     70      end 
     71         
     72      def generate_resources_routes(res, opts) 
     73        [@@matcher,@@generator].each { |r| r.generate_resources_routes(res, opts) } 
     74      end 
     75       
     76      def generate_singleton_routes(res, opts) 
     77        [@@matcher,@@generator].each { |r| r.generate_singleton_routes(res, opts) } 
     78      end 
     79 
     80    end # class << self 
    3981     
    40     # Cache procs for future reference in eval statement 
    41     class CachedProc 
    42       @@index = 0 
    43       @@list = [] 
    44  
    45       attr_accessor :cache, :index 
    46  
    47       def initialize(cache) 
    48         @cache, @index = cache, CachedProc.register(self) 
    49       end 
    50  
    51       # Make each CachedProc object embeddable within a string 
    52       def to_s() "CachedProc[#{@index}].cache" end 
    53  
    54       class << self 
    55         def register(cached_code) 
    56           CachedProc[@@index] = cached_code 
    57           @@index += 1 
    58           @@index - 1 
    59         end 
    60         def []=(index, code) @@list[index] = code end 
    61         def [](index) @@list[index] end 
    62       end 
    63     end # CachedProc 
    64      
    65     class Route 
    66       attr_reader :conditions, :conditional_block 
    67       attr_reader :params, :behavior, :segments, :index, :symbol 
    68  
    69       def initialize(conditions, params, behavior = nil, &conditional_block) 
    70         @conditions, @params, @behavior = conditions, params, behavior 
    71         @conditional_block = conditional_block 
    72         if @behavior && (path = @behavior.merged_original_conditions[:path]) 
    73           @segments = segments_from_path(path) 
    74         end 
    75       end 
    76        
    77       # Registers itself in the Router.routes array 
    78       def register 
    79         @index = Router.routes.size 
    80         Router.routes << self 
    81         self 
    82       end 
    83        
    84       # Get the symbols out of the segments array 
    85       def symbol_segments 
    86         segments.select{ |s| s.is_a?(Symbol) } 
    87       end 
    88        
    89       # Turn a path into string and symbol segments so it can be reconstructed, as in the 
    90       # case of a named route. 
    91       def segments_from_path(path) 
    92         # Remove leading ^ and trailing $ from each segment (left-overs from regexp joining) 
    93         strip = proc { |str| str.gsub(/^\^/, '').gsub(/\$$/, '') } 
    94         segments = [] 
    95         while match = (path.match(SEGMENT_REGEXP)) 
    96           segments << strip[match.pre_match] unless match.pre_match.empty? 
    97           segments << match[2].intern 
    98           path = strip[match.post_match] 
    99         end 
    100         segments << strip[path] unless path.empty? 
    101         segments 
    102       end 
    103        
    104       # Name this route 
    105       def name(symbol = nil) 
    106         @symbol = symbol 
    107         Router.named_routes[@symbol] = self 
    108       end 
    109        
    110       def regexp? 
    111         behavior.regexp? || behavior.send(:ancestors).any?{ |a| a.regexp? } 
    112       end 
    113        
    114       # Given a hash of +params+, returns a string using the stored route segments 
    115       # for reconstruction of the URL. 
    116       def generate(params = {}, fallback = {}) 
    117         url = @segments.map do |segment| 
    118           value = 
    119             if segment.is_a? Symbol 
    120               if params.is_a? Hash 
    121                 params[segment] || fallback[segment] 
    122               else 
    123                 if params.respond_to?(segment) 
    124                   params.send(segment) 
    125                 else 
    126                   fallback[segment] 
    127                 end 
    128               end 
    129             elsif segment.respond_to? :to_s 
    130               segment 
    131             else 
    132               raise "Segment type '#{segment.class}' can't be converted to a string" 
    133             end 
    134           (value.respond_to?(:to_param) ? value.to_param : value).to_s 
    135         end.join 
    136       end 
    137        
    138       def if_conditions(params_as_string) 
    139         cond = [] 
    140         condition_string = proc do |key, value, regexp_string| 
    141           max = Behavior.count_parens_up_to(value.source, value.source.size) 
    142           captures = if max == 0 then "" else 
    143             " && (" + 
    144               (1..max).to_a.map{ |n| "#{key}#{n}" }.join(", ") + " = " + 
    145               (1..max).to_a.map{ |n| "$#{n}"}.join(", ") + 
    146             ")" 
    147           end 
    148           "    (#{value.inspect} =~ #{regexp_string}" + captures + ")" 
    149         end 
    150         @conditions.each_pair do |key, value| 
    151            
    152           # Note: =~ is slightly faster than .match 
    153           cond << case key 
    154           when :path then condition_string[key, value, "cached_path"] 
    155           when :method then condition_string[key, value, "cached_method"] 
    156           else condition_string[key, value, "request.#{key}.to_s"] 
    157           end 
    158         end 
    159         if @conditional_block 
    160           str = "  # #{@conditional_block.inspect.scan(/@([^>]+)/).flatten.first}\n" 
    161           str << "    (block_result = #{CachedProc.new(@conditional_block)}.call(request, params.merge({#{params_as_string}})))" if @conditional_block 
    162           cond << str 
    163         end 
    164         cond 
    165       end 
    166  
    167       def compile(first = false) 
    168         code = "" 
    169         default_params = {:action => "index"} 
    170         get_value = proc do |key| 
    171           if default_params.has_key?(key) && params[key][0] != ?" 
    172             "#{params[key]} || \"#{default_params[key]}\"" 
    173           else 
    174             "#{params[key]}" 
    175           end 
    176         end 
    177         params_as_string = params.keys.map{|k| "#{k.inspect} => #{get_value[k]}"}.join(", ") 
    178         code << "  els" unless first 
    179         code << "if  # #{@behavior.merged_original_conditions.inspect}\n  " 
    180         code << if_conditions(params_as_string).join(" &&\n  ") << "\n" 
    181         code << "    # then\n" 
    182         if @conditional_block 
    183           code << "    [#{@index.inspect}, block_result]\n" 
    184         else 
    185           code << "    [#{@index.inspect}, {#{params_as_string}}]\n" 
    186         end 
    187       end 
    188        
    189       def behavior_trace 
    190         if @behavior 
    191           puts @behavior.send(:ancestors).reverse.map{|a| a.inspect}.join("\n"); puts @behavior.inspect; puts 
    192         else 
    193           puts "No behavior to trace #{self}" 
    194         end 
    195       end 
    196     end # Route 
    197      
    198     # The Behavior class is an interim route-building class that ties pattern-matching +conditions+ to 
    199     # output parameters, +params+. 
    200     class Behavior 
    201       attr_reader :placeholders, :conditions, :params 
    202       attr_accessor :parent 
    203  
    204       def initialize(conditions = {}, params = {}, parent = nil) 
    205         @conditions, @params, @parent = conditions, {}, parent 
    206         @placeholders = {} 
    207         stringify_conditions 
    208         copy_original_conditions 
    209         deduce_placeholders 
    210         # Must wait until after deducing placeholders to set @params 
    211         @params.merge!(params) 
    212       end 
    213  
    214       def add(path, params = {}) 
    215         match(path).to(params) 
    216       end 
    217        
    218       # Matches a +path+ and any number of optional request methods as conditions of a route. 
    219       # Alternatively, +path+ can be a hash of conditions, in which case +conditions+ is ignored. 
    220       # Yields 'self' so that sub-matching may occur. 
    221       def match(path = '', conditions = {}, &block) 
    222         if path.is_a? Hash 
    223           conditions = path 
    224         else 
    225           conditions[:path] = path 
    226         end 
    227         match_without_path(conditions, &block) 
    228       end 
    229        
    230       def match_without_path(conditions = {}) 
    231         new_behavior = self.class.new(conditions, {}, self) 
    232         conditions.delete :path if ['', '^$'].include?(conditions[:path]) 
    233         yield new_behavior if block_given? 
    234         new_behavior 
    235       end 
    236  
    237       def to_route(params = {}, &conditional_block) 
    238         @params.merge!(params) 
    239         Route.new(compiled_conditions, compiled_params, self, &conditional_block) 
    240       end 
    241        
    242       # Creates a Route from one or more Behavior objects, unless a +block+ is passed in. 
    243       # If a block is passed in, a Behavior object is yielded and further .to operations 
    244       # may be called in the block.  For example: 
    245       #   r.match('/:controller/:id).to(:action => 'show') 
    246       # vs. 
    247       #   r.to(:controller => "simple") do |simple| 
    248       #     simple.match('/test').to(:action => 'index') 
    249       #     simple.match('/other').to(:action => 'other') 
    250       #   end 
    251       def to(params = {}, &block) 
     82    class RouteMatcher 
     83      attr_reader :routes, :compiled_statement, :compiled_regexen 
     84 
     85      def initialize 
     86        @routes = Array.new 
     87        # The final compiled lambda that gets used as the body of the route_request method. 
     88        @compiled_statement = String.new 
     89        @compiled_regexen   = Array.new 
     90      end 
     91 
     92      # Add a route to be compiled. 
     93      def add(*route) 
     94        opt = Hash === route.last ? route.pop : {} 
     95        if n = opt[:namespace] 
     96          path = "/#{n}#{route[0]}"  
     97        else 
     98          path = route[0] 
     99        end     
     100        @routes << [path, opt] 
     101      end 
     102       
     103      def raw_add(*route) 
     104        @routes << [route[0], (route[1]||{})] 
     105      end 
     106       
     107      # Build up a string that defines a lambda that does a case statement on 
     108      # the PATH_INFO against each of the compiled routes in turn. First route 
     109      # that matches wins. 
     110      def compile_router 
     111        router_lambda = @routes.inject("lambda{|path| \n  sections={}\n  case path\n") { |m,r| 
     112          m << compile(r) 
     113        } << "  else\n    return {:controller=>'Noroutefound', :action=>'noroute'}\n  end\n}" 
     114        @compiled_statement = router_lambda 
     115        meta_def(:route_request, &eval(router_lambda)) 
     116      end   
     117       
     118      # Compile each individual route into a when /.../ component of the case 
     119      # statement. Takes /:sections of the route def that start with : and 
     120      # turns them into placeholders for whatever urls match against the route 
     121      # in question. 
     122      def compile(route) 
     123        raise ArgumentError unless String === route[0] 
     124        code, count = '', 0 
     125        while route[0] =~ Router::SECTION_REGEXP 
     126          route[0] = route[0].dup 
     127          name = $1 
     128          (name =~ /(\*+)(\w+)/) ? (flag = true; name = $2) : (flag = false) 
     129          count += 1 
     130          if flag 
     131            route[0].sub!(Router::SECTION_REGEXP, "([^,?]+)") 
     132          else   
     133            route[0].sub!(Router::SECTION_REGEXP, "([^\/,?]+)") 
     134          end 
     135          code << "    sections[:#{name}] = $#{count}\n" 
     136        end 
     137        @compiled_regexen << Regexp.new(route[0]) 
     138        index = @compiled_regexen.size - 1 
     139        condition = "  when @compiled_regexen[#{index}] " 
     140        statement = "#{condition}\n#{code}" 
     141        statement << "    return #{route[1].inspect}.update(sections)\n" 
     142        statement 
     143      end 
     144       
     145      def generate_resources_routes(res,opt) 
     146        with_options opt.merge(:controller => res.to_s, :rest => true) do |r| 
     147          r.raw_add "#{opt[:prefix]}/#{res}/:id[;/]edit", :allowed => {:get => 'edit'} 
     148          r.raw_add "#{opt[:prefix]}/#{res}/new[;/]:action", :allowed => {:get => 'new', :post => 'new', :put => 'new', :delete => 'new'} 
     149          r.raw_add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'} 
     150          if mem = opt[:member] 
     151            mem.keys.sort_by{|x| "#{x}"}.each {|action| 
     152              allowed = mem[action].injecting({}) {|h, verb| h[verb] = "#{action}"} 
     153              r.raw_add "#{opt[:prefix]}/#{res}/:id[;/]+#{action}", :allowed => allowed 
     154            } 
     155          end 
     156          if coll = opt[:collection] 
     157            coll.keys.sort_by{|x| "#{x}"}.each {|action| 
     158              allowed = coll[action].injecting({}) {|h, verb| h[verb] = "#{action}"} 
     159              r.raw_add "#{opt[:prefix]}/#{res}[;/]#{action}", :allowed => allowed 
     160            } 
     161          end   
     162          r.raw_add "#{opt[:prefix]}/#{res}/:id\\.:format", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'} 
     163          r.raw_add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'index', :post => 'create'} 
     164          r.raw_add "#{opt[:prefix]}/#{res}/:id", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'} 
     165          r.raw_add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'index', :post => 'create'} 
     166        end 
     167      end 
     168       
     169      def generate_singleton_routes(res,opt) 
     170        with_options opt.merge(:controller => res.to_s, :rest => true ) do |r| 
     171          r.raw_add "#{opt[:prefix]}/#{res}[;/]edit", :allowed => {:get => 'edit'} 
     172          r.raw_add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'show'} 
     173          r.raw_add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'} 
     174          r.raw_add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'show', :post => 'create', :put => 'update', :delete => 'destroy'} 
     175        end 
     176      end 
     177       
     178      def default_routes(opt={}) 
     179        namespace = opt[:namespace] ? "/#{opt[:namespace]}" : "" 
     180        with_options opt do |r| 
     181          r.raw_add namespace + "/:controller/:action/:id\\.:format" 
     182          r.raw_add namespace + "/:controller/:action/:id" 
     183          r.raw_add namespace + "/:controller/:action\\.:format" 
     184          r.raw_add namespace + "/:controller/:action" 
     185          r.raw_add namespace + "/:controller\\.:format", :action => 'index' 
     186          r.raw_add namespace + "/:controller", :action => 'index' 
     187        end   
     188      end 
     189    end 
     190 
     191    class RouteGenerator 
     192 
     193      attr_accessor :paths 
     194 
     195      def initialize 
     196        @paths = {} 
     197      end 
     198 
     199      def add(name, path) 
     200        @paths[name.to_sym] = path 
     201      end 
     202       
     203      def generate(name, *args) 
     204        options = Hash === args.last ? args.pop : {} 
     205        obj = args[0] 
     206        options.each do |key, value| 
     207          next unless value.respond_to?(:to_param) 
     208          unless key.to_s =~ /_?id$/ 
     209            old_key = key 
     210            options[old_key] = value.to_param 
     211            key = "#{key}_id".intern 
     212          end 
     213          options[key] = value.to_param 
     214        end   
     215         
     216        path = @paths[name].dup 
     217        while path =~ Router::SECTION_REGEXP 
     218          if obj.respond_to?($1) && ! obj.nil? 
     219            path.sub!(Router::SECTION_REGEXP, obj.send($1).to_s) 
     220          else   
     221            path.sub!(Router::SECTION_REGEXP, options[$1.intern].to_s) 
     222          end 
     223        end 
     224        if f = options[:format] 
     225          "#{path}.#{f}" 
     226        else 
     227          path 
     228        end    
     229      end 
     230       
     231      def generate_singleton_routes(res,opt) 
     232        res = res.to_s 
     233        if opt[:namespace] 
     234          namespace = "#{opt[:namespace]}_" 
     235          name = "/#{opt[:namespace]}" 
     236        else 
     237          namespace = '' 
     238          name = '' 
     239        end 
     240        add namespace + "edit_#{res}", name + "#{opt[:prefix]}/#{res}/edit" 
     241        add namespace + "new_#{res}", name + "#{opt[:prefix]}/#{res}/new" 
     242        add namespace + res, name + "#{opt[:prefix]}/#{res}" 
     243      end 
     244       
     245      def generate_resources_routes(res,opt) 
     246        res = res.to_s 
     247        res_singular = res.singularize 
     248        if opt[:namespace] 
     249          namespace = "#{opt[:namespace]}_" 
     250          name = "/#{opt[:namespace]}" 
     251        else 
     252          namespace = '' 
     253          name = '' 
     254        end   
     255        add namespace + res,                    name + "#{opt[:prefix]}/#{res}" 
     256        add namespace + res_singular,           name + "#{opt[:prefix]}/#{res}/:id" 
     257        add namespace + "new_#{res_singular}",  name + "#{opt[:prefix]}/#{res}/new" 
     258        add namespace + "custom_new_#{res_singular}",  name + "#{opt[:prefix]}/#{res}/new/:action" 
     259        add namespace + "edit_#{res_singular}", name + "#{opt[:prefix]}/#{res}/:id/edit" 
     260        if mem = opt[:member] 
     261          mem.keys.sort_by{|x| "#{x}"}.each {|action| 
     262            add namespace + "#{action}_#{res_singular}", name + "#{opt[:prefix]}/#{res}/:id/#{action}" 
     263          } 
     264        end 
     265        if coll = opt[:collection] 
     266          coll.keys.sort_by{|x| "#{x}"}.each {|action| 
     267            add namespace + "#{action}_#{res_singular}", name + "#{opt[:prefix]}/#{res}/#{action}" 
     268          } 
     269        end 
     270      end 
     271    end 
     272 
     273    class Resource 
     274       
     275      def initialize(resource, procs=[], opts={}) 
     276        @resource, @procs, @opts = resource, procs, opts 
     277        @procs << proc do 
     278           [::Merb::Router.matcher, ::Merb::Router.generator].each {|r| r.generate_resources_routes(@resource, @opts) }  
     279        end 
     280      end 
     281 
     282      def resources(res, opts={}) 
     283        (opts[:prefix]||='') << "/#{@resource}/:#{@resource.to_s.singularize}_id" 
     284 
     285        opts[:prefix] = @opts[:prefix] + opts[:prefix] 
    252286        if block_given? 
    253           new_behavior = self.class.new({}, params, self) 
    254           yield new_behavior if block_given? 
    255           new_behavior 
    256         else 
    257           to_route(params).register 
    258         end 
    259       end 
    260        
    261       # Takes a block and stores it for defered conditional routes.  The block takes the 
    262       # +request+ object and the +params+ hash as parameters and should return a hash of params. 
    263       # For example: 
    264       #   r.defer_to do |request, params} 
    265       #     params.merge(:controller => 'here', :action => 'there') if External.says_so? 
    266       #   end 
    267       def defer_to(params = {}, &conditional_block) 
    268         Router.routes << (route = to_route(params, &conditional_block)) 
    269         route 
    270       end 
    271  
    272       def default_routes(params = {}, &block) 
    273         match(%r[/:controller(/:action(/:id)?)?(\.:format)?]).to(params, &block) 
    274       end 
    275        
    276       def to_resources(params = {}, &block) 
    277         many_behaviors_to(resources_behaviors, params, &block) 
    278       end 
    279  
    280       def resources(name, options = {}) 
    281         # singular = name.singularize 
    282         next_level = match("/#{name}") 
    283         behaviors = [] 
    284          
    285         # Add optional member actions 
    286         options[:member].each_pair do |action, methods| 
    287           conditions = {:path => %r[^/:id[/;]#{action}$], :method => /^(#{[methods].flatten.join("|")})$/} 
    288           behaviors << Behavior.new(conditions, {:action => action.to_s}, next_level) 
    289         end if options[:member] 
    290          
    291         # Add optional collection actions 
    292         options[:collection].each_pair do |action, methods| 
    293           conditions = {:path => %r[^[/;]#{action}$], :method => /^(#{[methods].flatten.join("|")})$/} 
    294           behaviors << Behavior.new(conditions, {:action => action.to_s}, next_level) 
    295         end if options[:collection] 
    296          
    297         controller = options[:controller] || merged_params[:controller] || name.to_s 
    298         routes = many_behaviors_to(behaviors + next_level.send(:resources_behaviors), :controller => controller) 
    299          
    300         singular = name.to_s.singularize 
    301          
    302         # Add names to some routes 
    303         next_level.match("").to_route.name(:"#{name}") 
    304         next_level.match("/:id").to_route.name(:"#{singular}") 
    305         next_level.match("/new").to_route.name(:"new_#{singular}") 
    306         next_level.match("/:id/edit").to_route.name(:"edit_#{singular}") 
    307         next_level.match("/:action/:id").to_route.name(:"custom_#{singular}") 
    308          
    309         yield next_level.match("/:#{singular}_id") if block_given? 
    310         routes 
    311       end 
    312  
    313       def to_resource(params = {}, &block) 
    314         many_behaviors_to(resource_behaviors, params, &block) 
    315       end 
    316  
    317       def resource(name, options = {}) 
    318         next_level = match("/#{name}") 
    319  
    320         controller = options[:controller] || merged_params[:controller] || name.to_s 
    321         routes = next_level.to_resource(:controller => controller) 
    322  
    323         next_level.match("").to_route.name(:"#{name}") 
    324         next_level.match("/new").to_route.name(:"new_#{name}") 
    325         next_level.match("/edit").to_route.name(:"edit_#{name}") 
    326  
    327         yield next_level if block_given? 
    328         routes 
    329       end 
    330  
    331       def merged_original_conditions 
    332         if parent.nil? 
    333           @original_conditions 
    334         else 
    335           merged_so_far = parent.merged_original_conditions 
    336           path = Behavior.concat_without_endcaps(merged_so_far[:path], @original_conditions[:path]) 
    337           path ? 
    338             merged_so_far.merge(@original_conditions).merge(:path => path) : 
    339             merged_so_far.merge(@original_conditions) 
    340         end 
    341       end 
    342        
    343       def merged_conditions 
    344         if parent.nil? 
    345           @conditions 
    346         else 
    347           merged_so_far = parent.merged_conditions 
    348           path = Behavior.concat_without_endcaps(merged_so_far[:path], @conditions[:path]) 
    349           path ? 
    350             merged_so_far.merge(@conditions).merge(:path => path) : 
    351             merged_so_far.merge(@conditions) 
    352         end 
    353       end 
    354  
    355       def merged_params 
    356         if parent.nil? 
    357           @params 
    358         else 
    359           parent.merged_params.merge(@params) 
    360         end 
    361       end 
    362  
    363       def merged_placeholders 
    364         placeholders = {} 
    365         (ancestors.reverse + [self]).each do |a| 
    366           a.placeholders.each_pair do |key, pair| 
    367             param, place = pair 
    368             placeholders[key] = [param, place + (param == :path ? a.total_previous_captures : 0)] 
    369           end 
    370         end 
    371         placeholders 
    372       end 
    373        
    374       def inspect 
    375         "[captures: #{path_captures}, conditions: #{@original_conditions.inspect}, params: #{@params.inspect}, placeholders: #{@placeholders.inspect}]" 
    376       end 
    377        
    378       def regexp? 
    379         @conditions_have_regexp 
    380       end 
    381  
    382     protected 
    383       def resources_behaviors(parent = self) 
    384         [ 
    385           Behavior.new({:path => %r[^/?(\.:format)?$],     :method => :get},    {:action => "index"},   parent), 
    386           Behavior.new({:path => %r[^/index(\.:format)?$], :method => :get},    {:action => "index"},   parent), 
    387           Behavior.new({:path => %r[^/new$],               :method => :get},    {:action => "new"},     parent), 
    388           Behavior.new({:path => %r[^/?(\.:format)?$],     :method => :post},   {:action => "create"},  parent), 
    389           Behavior.new({:path => %r[^/:id(\.:format)?$],   :method => :get},    {:action => "show"},    parent), 
    390           Behavior.new({:path => %r[^/:id[;/]edit$],       :method => :get},    {:action => "edit"},    parent), 
    391           Behavior.new({:path => %r[^/:id(\.:format)?$],   :method => :put},    {:action => "update"},  parent), 
    392           Behavior.new({:path => %r[^/:id(\.:format)?$],   :method => :delete}, {:action => "destroy"}, parent) 
    393         ] 
    394       end 
    395        
    396       def resource_behaviors(parent = self) 
    397         [ 
    398           Behavior.new({:path => %r{^[;/]new$},        :method => :get},    {:action => "new"},       parent), 
    399           Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :post},   {:action => "create"},    parent), 
    400           Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :get},    {:action => "show"},      parent), 
    401           Behavior.new({:path => %r{^[;/]edit$},       :method => :get},    {:action => "edit"},      parent), 
    402           Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :put},    {:action => "update"},    parent), 
    403           Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :delete}, {:action => "destroy"},   parent) 
    404         ] 
    405       end 
    406  
    407       # Creates a series of routes from an array of Behavior objects. 
    408       # You can pass in optional +params+, and an optional block that will be 
    409       # passed along to the #to method. 
    410       def many_behaviors_to(behaviors, params = {}, &conditional_block) 
    411         routes = [] 
    412         behaviors.each { |b| routes << b.to(params, &conditional_block) } 
    413         routes 
    414       end 
    415  
    416       # Convert conditions to regular expression string sources for consistency 
    417       def stringify_conditions 
    418         @conditions_have_regexp = false 
    419         @conditions.each_pair do |key, value| 
    420           # TODO: Other Regexp special chars 
    421           @conditions[key] = case value 
    422             when String then "^" + value.escape_regexp + "$" 
    423             when Symbol then "^" + value.to_s + "$" 
    424             when Regexp: 
    425               @conditions_have_regexp = true 
    426               value.source 
    427             end 
    428         end 
    429       end 
    430  
    431       def copy_original_conditions 
    432         @original_conditions = {} 
    433         @conditions.each_pair do |key, value| 
    434           @original_conditions[key] = value.dup 
    435         end 
    436         @original_conditions 
    437       end 
    438        
    439       def deduce_placeholders 
    440         @conditions.each_pair do |match_key, source| 
    441           while match = SEGMENT_REGEXP.match(source) 
    442             source.sub!(SEGMENT_REGEXP, PARENTHETICAL_SEGMENT_STRING) 
    443             unless match[2] == ":" # No need to store anonymous place holders 
    444               placeholder_key = match[2].intern 
    445               @params[placeholder_key] = "#{match[1]}" 
    446               @placeholders[placeholder_key] = [match_key, Behavior.count_parens_up_to(source, match.offset(1)[0])] 
    447             end 
    448           end 
    449         end 
    450       end 
    451  
    452       def ancestors(list = []) 
    453         if parent.nil? 
    454           list 
    455         else 
    456           list.push parent 
    457           parent.ancestors(list) 
    458           list 
    459         end 
    460       end 
    461        
    462       # Count the number of regexp captures in the :path condition 
    463       def path_captures 
    464         return 0 unless conditions[:path] 
    465         Behavior.count_parens_up_to(conditions[:path], conditions[:path].size) 
    466       end 
    467        
    468       def total_previous_captures 
    469         ancestors.map{|a| a.path_captures}.inject(0){|sum, n| sum + n} 
    470       end 
    471  
    472       # def merge_with_ancestors 
    473       #   self.class.new(merged_conditions, merged_params) 
    474       # end 
    475  
    476       def compiled_conditions(conditions = merged_conditions) 
    477         compiled = {} 
    478         conditions.each { |key, value| compiled[key] = Regexp.new(value) } 
    479         compiled 
    480       end 
    481  
    482       # Compiles the params hash into 'eval'-able form. 
    483       # For example: 
    484       #   @params = {:controller => "admin/:controller"} 
    485       # Could become: 
    486       #   {:controller => "'admin/' + matches[:path][1]"} 
    487       # 
    488       def compiled_params(params = merged_params, placeholders = merged_placeholders) 
    489         compiled = {} 
    490         params.each_pair do |key, value| 
    491           raise ArgumentError, "param value must be string (#{value.inspect})" unless value.is_a? String 
    492           result = [] 
    493           value = value.dup 
    494           match = true 
    495           while match 
    496             if match = SEGMENT_REGEXP_WITH_BRACKETS.match(value) 
    497               result << match.pre_match unless match.pre_match.empty? 
    498               ph_key = match[1][1..-1].intern 
    499               if match[2] # has brackets, e.g. :path[2] 
    500                 result << :"#{ph_key}#{match[3]}" 
    501               else # no brackets, e.g. a named placeholder such as :controller 
    502                 if place = placeholders[ph_key] 
    503                   result << :"#{place[0]}#{place[1]}" 
    504                 else 
    505                   raise "Placeholder not found while compiling routes: :#{ph_key}" 
    506                 end 
    507               end 
    508               value = match.post_match 
    509             elsif match = JUST_BRACKETS.match(value) 
    510               # This is a reference to :path 
    511               result << match.pre_match unless match.pre_match.empty? 
    512               result << :"path#{match[1]}" 
    513               value = match.post_match 
    514             else 
    515               result << value unless value.empty? 
    516             end 
    517           end 
    518           compiled[key] = Behavior.array_to_code(result).gsub("\\_", "_") 
    519         end 
    520         compiled 
    521       end 
    522  
    523     public 
    524       # Count the number of open parentheses in +string+, up to and including +pos+ 
    525       def self.count_parens_up_to(string, pos) 
    526         string[0..pos].gsub(/[^\(]/, "").size 
    527       end 
    528  
    529       # Concatenate strings and remove regexp end caps 
    530       def self.concat_without_endcaps(string1, string2) 
    531         return nil if !string1 and !string2 
    532         return string1 if string2.nil? 
    533         return string2 if string1.nil? 
    534         s1 = string1[-1] == ?$ ? string1[0..-2] : string1 
    535         s2 = string2[0] == ?^ ? string2[1..-1] : string2 
    536         s1 + s2 
    537       end 
    538  
    539       # Join an array's elements into a string using " + " as a joiner, and 
    540       # surround string elements in quotes. 
    541       def self.array_to_code(arr) 
    542         code = "" 
    543         arr.each_with_index do |part, i| 
    544           code << " + " if i > 0 
    545           case part 
    546           when Symbol 
    547             code << "#{part}" 
    548           when String 
    549             code << "\"" + part + "\"" 
    550           else 
    551             raise "Don't know how to compile array part: #{part.class} [#{i}]" 
    552           end 
    553         end 
    554         code 
    555       end 
    556     end # Behavior 
    557      
     287          yield self.class.new(res, @procs, opts) 
     288        else 
     289          @procs << proc do 
     290             [::Merb::Router.matcher, ::Merb::Router.generator].each {|r| r.generate_resources_routes(res, opts) } 
     291          end 
     292        end 
     293      end 
     294       
     295      def resource(res, opts={}) 
     296        (opts[:prefix]||='') << "/#{@resource}/:#{@resource.to_s.singularize}_id" 
     297 
     298        opts[:prefix] = @opts[:prefix] + opts[:prefix] 
     299        if block_given? 
     300          yield self.class.new(res, @procs, opts) 
     301        else 
     302          @procs << proc do 
     303             [::Merb::Router.matcher, ::Merb::Router.generator].each { |r| r.generate_singleton_routes(res, opts) } 
     304          end    
     305        end 
     306      end 
     307    end 
    558308  end 
    559309end