Changeset 193

Show
Ignore:
Timestamp:
02/24/07 15:33:19 (2 years ago)
Author:
e.@brainspl.at
Message:

Merged from template_ext branch. New rendering system with support for Erubis, Markaby and XML Builder templates based on extension. Much more flexible system . even use .herb for layouts and .mab markaby for content , mix and match at will. XML Bulder templates are exempt from layout. See docs in render_mixin for new way to render everything. Some backwards caompat stuff left in like render_js and stuff. May go away in the future may not. Please play with this and see if any of your apps break.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/Rakefile

    r184 r193  
    1515 
    1616NAME = "merb" 
    17 VERS = "0.1.1
     17VERS = "0.2.0
    1818CLEAN.include ['**/.*.sw?', '*.gem', '.config'] 
    1919RDOC_OPTS = ['--quiet', '--title', "Merb Documentation", 
  • trunk/lib/merb.rb

    r169 r193  
    1313 
    1414module Merb 
    15   VERSION='0.1.1' unless defined?VERSION 
     15  VERSION='0.2.0' unless defined?VERSION 
    1616  class Server 
    1717    class << self 
     
    103103    puts "Basic Authentication mixed in" 
    104104  end 
    105 end   
     105end 
  • trunk/lib/merb/core_ext.rb

    r125 r193  
    1010    merb_numeric 
    1111    merb_symbol 
    12   ].each {|fn| require File.join(corelib, fn)}  
     12  ].each {|fn| require File.join(corelib, fn)} 
  • trunk/lib/merb/merb_controller.rb

    r185 r193  
    1313  # puts that into params as well. 
    1414  class Controller 
    15      
    16     shared_accessor :layout, :session_id_key, :cache_templates 
     15         
     16    trait :layout => :application 
     17    trait :session_id_key => :_session_id 
    1718         
    1819    include Merb::ControllerMixin 
     
    3738      cookies = query_parse(env['HTTP_COOKIE'], ';,') 
    3839      querystring = query_parse(env['QUERY_STRING']) 
    39       self.layout ||= :application 
    40       self.session_id_key ||= :_session_id 
    4140      @in = req 
    4241      if MULTIPART_REGEXP =~ env['CONTENT_TYPE'] 
     
    9493       
    9594      @cookies, @params = cookies.dup, querystring.dup.merge(args) 
    96       @cookies[session_id_key] = @params[session_id_key] if @params.key?(session_id_key
     95      @cookies[ancestral_trait[:session_id_key]] = @params[ancestral_trait[:session_id_key]] if @params.key?(ancestral_trait[:session_id_key]
    9796       
    9897      allow = [:post, :put, :delete] 
     
    170169      @response 
    171170    end 
     171     
     172    trait :template_extensions => { } 
     173    trait :template_root => File.expand_path(MERB_ROOT / "dist/app/views") 
     174    trait :layout_root   => File.expand_path(MERB_ROOT / "dist/app/views/layout") 
     175    # lookup the trait[:template_extensions] for the extname of the filename 
     176    # you pass. Answers with the engine that matches the extension, Template::Erubis 
     177    # is used if none matches. 
     178    def engine_for(file) 
     179      extension = File.extname(file)[1..-1] 
     180      ancestral_trait[:template_extensions][extension] 
     181    rescue 
     182      ::Merb::Template::Erubis 
     183    end 
     184    
     185    # This method is called by templating-engines to register themselves with 
     186    # a list of extensions that will be looked up on #render of an action. 
     187    def self.register_engine(engine, *extensions) 
     188      [extensions].flatten.uniq.each do |ext| 
     189        trait[:template_extensions][ext] = engine 
     190      end   
     191    end 
     192 
    172193     
    173194     
  • trunk/lib/merb/merb_dispatcher.rb

    r181 r193  
    6161  end 
    6262   
    63 end   
     63end 
  • trunk/lib/merb/merb_request.rb

    r186 r193  
    102102end     
    103103     
    104      
  • trunk/lib/merb/merb_server.rb

    r188 r193  
    1515        :allow_reloading => true, 
    1616        :merb_root => Dir.pwd, 
    17         :template_ext => {:html => :herb, :js => :jerb, :xml => :xerb}, 
    1817        :cache_templates => false 
    1918      } 
     
    119118       
    120119      def initialize_merb 
    121         unless  @@merb_opts[:generate] ||  @@merb_opts[:console] ||  @@merb_opts[:only_drb] ||  @@merb_opts[:kill] 
    122           puts %{Merb started with these options:} 
    123           puts @@merb_opts.to_yaml; puts 
    124         end 
    125120        require 'merb' 
    126121        require @@merb_opts[:merb_root] / 'dist/conf/router.rb' 
     
    256251      def mongrel_start(port) 
    257252        @@merb_opts[:port] = port 
     253        unless  @@merb_opts[:generate] ||  @@merb_opts[:console] ||  @@merb_opts[:only_drb] ||  @@merb_opts[:kill] 
     254          puts %{Merb started with these options:} 
     255          puts @@merb_opts.to_yaml; puts 
     256        end 
    258257        initialize_merb 
    259258       
  • trunk/lib/merb/merb_upload_progress.rb

    r106 r193  
    4646  end 
    4747   
    48 end   
     48end 
  • trunk/lib/merb/merb_view_context.rb

    r151 r193  
    1515  # when evaluating the templates. 
    1616  class ViewContext 
    17     include Merb::ErubisCaptureMixin 
    1817    include Merb::ViewContextMixin 
    1918    include Merb::FormControls 
     
    3231    end 
    3332     
     33    # hack so markaby doesn't dup us and lose ivars. 
     34    def dup 
     35      self 
     36    end 
     37       
    3438    # accessor for the view. refers to the current @controller object 
    3539    def controller 
    3640      @controller 
    37     end   
     41    end 
     42       
     43    alias_method :old_respond_to?, :respond_to? 
     44     
     45    def respond_to?(sym, include_private=false) 
     46      old_respond_to?(sym, include_private) || @controller.respond_to?(sym, include_private)   
     47    end 
    3848     
    3949    # catch any method calls that the controller responds to 
     
    4959  end 
    5060   
    51 end   
     61end 
  • trunk/lib/merb/mixins/basic_authentication_mixin.rb

    r178 r193  
    3333  end 
    3434   
    35 end   
     35end 
  • trunk/lib/merb/mixins/controller_mixin.rb

    r126 r193  
    22  module ControllerMixin 
    33   
     4    # render using chunked encoding 
     5    # def stream 
     6    #   prefix = '<p>' 
     7    #   suffix = "</p>\r\n" 
     8    #   render_chunked do 
     9    #     IO.popen("cat /tmp/test.log") do |io| 
     10    #       done = false 
     11    #       until done == true do 
     12    #         sleep 0.3 
     13    #         line = io.gets.chomp 
     14    #         if line == 'EOF' 
     15    #           done = true 
     16    #         else 
     17    #           send_chunk(prefix + line + suffix) 
     18    #         end 
     19    #       end 
     20    #     end 
     21    #   end 
     22    # end 
     23    def render_chunked(&blk) 
     24      headers['Transfer-Encoding'] = 'chunked' 
     25      Proc.new { 
     26        response.send_status_no_connection_close(0) 
     27        response.send_header 
     28        blk.call 
     29        response.write("0\r\n\r\n") 
     30      } 
     31    end 
     32     
     33    # for use within a render_chunked response 
     34    def send_chunk(data) 
     35      response.write('%x' % data.size + "\r\n") 
     36      response.write(data + "\r\n") 
     37    end 
     38     
    439    # redirect to another url It can be like /foo/bar 
    540    # for redirecting within your same app. Or it can 
     
    86121   
    87122  end 
    88 end   
     123end 
  • trunk/lib/merb/mixins/render_mixin.rb

    r174 r193  
    22 
    33  module RenderMixin 
    4     @@erbs = {} 
    5     @@mtimes = {} 
    64     
    7     def self.erbs 
    8       @@erbs 
    9     end   
    10      
    11     # shortcut to a template path based on name. 
    12     def template_dir(loc) 
    13       File.expand_path(MERB_ROOT / "/dist/app/views/#{loc}") 
    14     end   
    15      
    16     # returns the current method name. Used for 
    17     # auto discovery of which template to render 
    18     # based on the action name. 
    19     def current_method_name(depth=0) 
    20       caller[depth] =~ /`(.*)'$/; $1 
     5    # universal render method. Template handlers are registered 
     6    # by template extension. So you can use the same render method 
     7    # for any kind of template that implements an adapter module. 
     8    # out of the box Merb support Erubis, Markaby and Builder templates 
     9    # 
     10    # Erubis template ext:  .herb .jerb .erb 
     11    # Markaby template ext: .mab 
     12    # Builder template ext: .rxml .builder .xerb  
     13    # 
     14    # Examples: 
     15    # 
     16    # render 
     17    # looks for views/controllername/actionname.* and renders 
     18    # the template with the proper engine based on its file extension. 
     19    # 
     20    # render :layout => :none 
     21    # renders the current template with no layout. XMl Builder templates 
     22    # are exempt from layout by default. 
     23    #  
     24    # render :action => 'foo' 
     25    # renders views/controllername/foo.* 
     26    # 
     27    # render :nothing => 200 
     28    # renders nothing with a status of 200 
     29    # 
     30    # render :js => "$('some-div').toggle();" 
     31    # if the right hand side of :js => is a string then the proper 
     32    # javascript headers will be set and the string will be returned  
     33    # verbatim as js. 
     34    # 
     35    # render :js => :spinner 
     36    # when the rhs of :js => is a Symbol, it will be used as the  
     37    # action/template name so: views/controllername/spinner.jerb 
     38    # will be rendered as javascript 
     39    # 
     40    # render :js => true 
     41    # this will just look for the current controller/action tenmplate 
     42    # with the .jerb extension and render it as javascript 
     43    # 
     44    # render :xml => @posts.to_xml 
     45    # render :xml => "<foo><bar>Hi!</bar></foo>" 
     46    # this will set the appropriate xml headers and render the rhs 
     47    # of :xml => as a string. SO you can pass any xml string to this 
     48    # to be rendered.  
     49    # 
     50    def render(opts={}, &blk) 
     51      action = opts[:action] || params[:action] 
     52       
     53      case 
     54      when status = opts[:nothing] 
     55        return render_nothing(status) 
     56      when partial = opts[:partial] 
     57        template = find_partial(partial, opts) 
     58        opts[:layout] = :none 
     59      when js = opts[:js] 
     60        headers['Content-Type'] = "text/javascript" 
     61        opts[:layout] = :none 
     62        if String === js 
     63          return js 
     64        elsif Symbol === js 
     65          template = find_template(:action => js, :ext => 'jerb') 
     66        else   
     67          template = find_template(:action => action, :ext => 'jerb') 
     68        end 
     69      when xml = opts[:xml] 
     70        headers['Content-Type'] = 'application/xml' 
     71        headers['Encoding']     = 'UTF-8' 
     72        return xml 
     73      else   
     74        template = find_template(:action => action) 
     75      end 
     76         
     77      engine = engine_for(template) 
     78      options = { 
     79        :file     => template, 
     80        :view_context  => _view_context, 
     81        :opts => opts 
     82      } 
     83      content = engine.transform(options) 
     84      return engine.exempt_from_layout? ? content : wrap_layout(content, opts) 
    2185    end 
    22      
    23     # given html, js and xml this method returns the template  
    24     # extension from the :template_ext map froom your app's 
    25     # configuration. defaults to .herb, .jerb & .xerb 
    26     def template_extension_for(ext) 
    27       (@tmpl_ext_cache ||= Merb::Server.template_ext)[ext] 
    28     end 
    29      
     86 
    3087    # this returns a ViewContext object populated with all 
    3188    # the instance variables in your controller. This is used 
     
    3996    # this when you want to render a template with  
    4097    # .jerb extension. 
    41     def render_js(template=current_method_name(1)) 
    42       headers['Content-Type'] = "text/javascript" 
    43       template = new_eruby_obj(template_dir(self.class.name.snake_case) / "/#{template}.#{template_extension_for(:js)}")  
    44       template.evaluate(_view_context) 
     98    def render_js(template=nil) 
     99      render :js => true, :action => (template || params[:action]) 
    45100    end 
    46      
     101 
    47102    # renders nothing but sets the status, defaults 
    48103    # to 200. does send one \n newline char, tyhis is for 
     
    58113    # explicitely set the template name excluding the file 
    59114    # extension 
    60     def render_no_layout(template=current_method_name(1)) 
    61       template = new_eruby_obj(template_dir(self.class.name.snake_case) / "/#{template}.#{template_extension_for(:html)}") 
    62       template.evaluate(_view_context) 
     115    def render_no_layout(opts={}) 
     116      render opts.merge({:layout => :none}) 
    63117    end  
    64118     
     
    73127    # partials that live there like partial('shared/foo') 
    74128    def partial(template) 
    75       if template =~ /\// 
    76         t = template.split('/') 
    77         template = t.pop 
    78         tmpl = new_eruby_obj(template_dir(t.join('/')) / "/_#{template}.#{template_extension_for(:html)}")  
    79       else   
    80         tmpl = new_eruby_obj(template_dir(self.class.name.snake_case) / "/_#{template}.#{template_extension_for(:html)}") 
    81       end 
    82       tmpl.evaluate(_view_context) 
     129      render :partial => template 
    83130    end  
    84131     
    85     # This creates and returns a new Erubis object populated 
    86     # with the template from path. If there is no matching 
    87     # template then we rescue the Errno::ENOENT exception 
    88     # and raise a no template found message 
    89     def new_eruby_obj(path) 
    90       if @@erbs[path] && !cache_template?(path) 
    91         return @@erbs[path] 
    92       else   
    93         begin 
    94           returning Erubis::MEruby.new(IO.read(path)) do |eruby| 
    95             eruby.init_evaluator :filename => path 
    96             if cache_template?(path) 
    97               @@erbs[path] = eruby 
    98               @@mtimes[path] = Time.now 
    99             end   
    100           end 
    101         rescue Errno::ENOENT 
    102           raise "No template found at path: #{path}" 
     132    private 
     133     
     134      def wrap_layout(content, opts={}) 
     135        return content if ((opts[:layout] == :none) || (ancestral_trait[:layout] == :none)) 
     136               
     137        if ancestral_trait[:layout] != :application 
     138          layout_choice = find_template(:layout => ancestral_trait[:layout]) 
     139        else 
     140          if name = find_template(:layout => self.class.name.snake_case) 
     141            layout_choice = name 
     142          else 
     143            layout_choice = find_template(:layout => :application) 
     144          end   
     145        end       
     146         
     147        _view_context.instance_variable_set('@_layout_content', content) 
     148        engine = engine_for(layout_choice) 
     149        options = { 
     150          :file     => layout_choice, 
     151          :view_context  => _view_context, 
     152          :opts => opts 
     153        } 
     154        engine.transform(options) 
     155      end 
     156       
     157       
     158      def find_template(opts={}) 
     159        if action = opts[:action] 
     160          path = 
     161            File.expand_path(MERB_ROOT / "dist/app/views" / self.class.name.snake_case / action) 
     162        elsif layout = opts[:layout] 
     163          path = ancestral_trait[:layout_root] / layout 
     164        else 
     165          raise "called find_template without an :action or :layout"   
     166        end   
     167        extensions = [ancestral_trait[:template_extensions].keys].flatten.uniq 
     168        glob = "#{path}.{#{opts[:ext] || extensions.join(',')}}" 
     169        Dir[glob].first 
     170      end 
     171       
     172      def find_partial(template, opts={}) 
     173        if template =~ /\// 
     174          t = template.split('/') 
     175          template = t.pop 
     176          path = ancestral_trait[:template_root] / t.join('/') / "_#{template}" 
     177        else   
     178          path = ancestral_trait[:template_root]  / self.class.name.snake_case / "_#{template}" 
    103179        end 
    104       end   
    105     end 
    106      
    107     def cache_template?(path) 
    108       return false unless Merb::Server.config[:cache_templates] 
    109       return true unless @@erbs[path] 
    110       @@mtimes[path] < File.mtime(path) || 
    111         (File.symlink?(path) && (@@mtimes[path] < File.lstat(path).mtime)) 
    112     end 
    113      
    114     # this is the xml builder render method. This method 
    115     # builds the Builder::XmlMarkup object for you and adds 
    116     # the xml headers and encoding. Then it evals your template 
    117     # in the context of the xml object. So your .xerb templates 
    118     # will look like this: 
    119     # xml.foo {|xml| 
    120     #   xml.bar "baz" 
    121     # } 
    122     def render_xml(template=current_method_name(1)) 
    123       _xml_body = IO.read( template_dir(self.class.name.snake_case) + "/#{template}.#{template_extension_for(:xml)}" )  
    124       headers['Content-Type'] = 'application/xml' 
    125       headers['Encoding']     = 'UTF-8' 
    126       _view_context.instance_eval %{ 
    127         xml = Builder::XmlMarkup.new :indent => 2 
    128         xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8" 
    129         #{_xml_body} 
    130         return xml.target! 
    131       }  
    132     end 
    133      
    134     # render using chunked encoding 
    135     # def stream 
    136     #   prefix = '<p>' 
    137     #   suffix = "</p>\r\n" 
    138     #   render_chunked do 
    139     #     IO.popen("cat /tmp/test.log") do |io| 
    140     #       done = false 
    141     #       until done == true do 
    142     #         sleep 0.3 
    143     #         line = io.gets.chomp 
    144     #         if line == 'EOF' 
    145     #           done = true 
    146     #         else 
    147     #           send_chunk(prefix + line + suffix) 
    148     #         end 
    149     #       end 
    150     #     end 
    151     #   end 
    152     # end 
    153     def render_chunked(&blk) 
    154       headers['Transfer-Encoding'] = 'chunked' 
    155       Proc.new { 
    156         response.send_status_no_connection_close(0) 
    157         response.send_header 
    158         blk.call 
    159         response.write("0\r\n\r\n") 
    160       } 
    161     end 
    162      
    163     def send_chunk(data) 
    164       response.write('%x' % data.size + "\r\n") 
    165       response.write(data + "\r\n") 
    166     end 
    167      
    168     # This is the main render method that handles layouts. 
    169     # render will use layout/application.rhtml unless 
    170     # there is a layout named after the current controller 
    171     # or if self.layout= has been set to another value in 
    172     # the current controller. You can over-ride this setting 
    173     # by passing an options hash with a :layout => 'layoutname'. 
    174     # if you with to not render a layout then pass :layout => :none 
    175     # the first argument to render is the template name. if you do 
    176     # not pass a template name, it will set the template to  
    177     # views/controller/action automatically. 
    178     # examples: 
    179     # class Test < Merb::Controller 
    180     #   # renders views/test/foo.herb  
    181     #   # in layout application.herb 
    182     #   def foo 
    183     #     # code 
    184     #     render 
    185     #   end 
    186     # 
    187     #   # renders views/test/foo.herb 
    188     #   # in layout application.herb     
    189     #   def bar 
    190     #     # code 
    191     #     render :foo 
    192     #   end 
    193     # 
    194     #   # renders views/test/baz.herb 
    195     #   # with no layout     
    196     #   def baz 
    197     #     # code 
    198     #     render :layout => :none 
    199     #   end     
    200     def render(opts={}) 
    201       template = opts.is_a?(Symbol) ? opts : (opts[:action] || params[:action]) 
    202       tmpl_ext = template_extension_for(:html) 
    203       MERB_LOGGER.info("Rendering template: #{template}.#{tmpl_ext}") 
    204       name = self.class.name.snake_case 
    205       template = new_eruby_obj(template_dir(name) / "/#{template}.#{tmpl_ext}") 
    206       layout_content = template.evaluate(_view_context) 
    207       self.layout = opts[:layout].intern if opts.has_key?(:layout) 
    208       return layout_content if (layout == :none) 
    209       if layout != :application 
    210         layout_choice = layout 
    211       else 
    212         if File.exist?(template_dir("layout/#{name}.#{tmpl_ext}")) 
    213           layout_choice = name 
    214         else 
    215           layout_choice = layout 
    216         end   
    217       end       
    218       MERB_LOGGER.info("With Layout: #{layout_choice}.#{tmpl_ext}") 
    219       _view_context.instance_variable_set('@_layout_content', layout_content) 
    220       layout_tmpl = new_eruby_obj("#{template_dir('layout')}/#{layout_choice}.#{tmpl_ext}")      
    221       layout_tmpl.evaluate(_view_context) 
    222     end 
     180        extensions = [ancestral_trait[:template_extensions].keys].flatten.uniq 
     181        glob = "#{path}.{#{opts[:ext] || extensions.join(',')}}" 
     182        Dir[glob].first 
     183      end 
    223184     
    224185  end   
    225 end   
     186end 
  • trunk/lib/merb/mixins/responder_mixin.rb

    r173 r193  
    4545    end 
    4646  end 
    47 end   
     47end 
  • trunk/lib/merb/session/merb_ar_session.rb

    r126 r193  
    77    def setup_session 
    88      MERB_LOGGER.info("Setting up session") 
    9       before = cookies[session_id_key
    10       @session, cookies[session_id_key] = Merb::Session.persist(cookies[session_id_key]) 
     9      before = cookies[ancestral_trait[:session_id_key]
     10      @session, cookies[ancestral_trait[:session_id_key]] = Merb::Session.persist(cookies[ancestral_trait[:session_id_key]]) 
    1111      @_fingerprint_before = Marshal.dump(@session).hash 
    12       @_new_cookie = cookies[session_id_key] != before 
     12      @_new_cookie = cookies[ancestral_trait[:session_id_key]] != before 
    1313    end 
    1414 
     
    1818        @session.save 
    1919      end 
    20       set_cookie(session_id_key, cookies[session_id_key], Time.now+Merb::Const::WEEK*2) if @_new_cookie 
     20      set_cookie(ancestral_trait[:session_id_key], cookies[ancestral_trait[:session_id_key]], Time.now+Merb::Const::WEEK*2) if @_new_cookie 
    2121    end  
    2222 
  • trunk/lib/merb/session/merb_memory_session.rb

    r165 r193  
    55    def setup_session 
    66      MERB_LOGGER.info("Setting up session") 
    7       before = cookies[session_id_key
    8       @session , cookies[session_id_key] = Merb::MemorySession.persist(cookies[session_id_key]) 
    9       @_new_cookie = cookies[session_id_key] != before 
     7      before = cookies[ancestral_trait[:session_id_key]
     8      @session , cookies[ancestral_trait[:session_id_key]] = Merb::MemorySession.persist(cookies[ancestral_trait[:session_id_key]]) 
     9      @_new_cookie = cookies[ancestral_trait[:session_id_key]] != before 
    1010    end 
    1111 
    1212    def finalize_session 
    1313      MERB_LOGGER.info("Finalize session") 
    14       set_cookie(session_id_key, cookies[session_id_key], Time.now+Merb::Const::WEEK*2) if @_new_cookie 
     14      set_cookie(ancestral_trait[:session_id_key], cookies[ancestral_trait[:session_id_key]], Time.now+Merb::Const::WEEK*2) if @_new_cookie 
    1515    end  
    1616 
     
    104104  end # end DRbSession 
    105105 
    106 end   
     106end 
  • trunk/specs/merb/merb_controller_spec.rb

    r173 r193  
    1818  specify "Default layout should be application.rhtml" do 
    1919    c = Merb::Controller.new @in.req, @in.env,{}, @res 
    20     c.layout.should == :application 
     20    c.ancestral_trait[:layout].should == :application 
    2121  end   
    2222