Changeset 193
- Timestamp:
- 02/24/07 15:33:19 (2 years ago)
- Files:
-
- trunk/Rakefile (modified) (1 diff)
- trunk/lib/merb.rb (modified) (2 diffs)
- trunk/lib/merb/core_ext.rb (modified) (1 diff)
- trunk/lib/merb/merb_controller.rb (modified) (4 diffs)
- trunk/lib/merb/merb_dispatcher.rb (modified) (1 diff)
- trunk/lib/merb/merb_request.rb (modified) (1 diff)
- trunk/lib/merb/merb_server.rb (modified) (3 diffs)
- trunk/lib/merb/merb_upload_progress.rb (modified) (1 diff)
- trunk/lib/merb/merb_view_context.rb (modified) (3 diffs)
- trunk/lib/merb/mixins/basic_authentication_mixin.rb (modified) (1 diff)
- trunk/lib/merb/mixins/controller_mixin.rb (modified) (2 diffs)
- trunk/lib/merb/mixins/render_mixin.rb (modified) (4 diffs)
- trunk/lib/merb/mixins/responder_mixin.rb (modified) (1 diff)
- trunk/lib/merb/session/merb_ar_session.rb (modified) (2 diffs)
- trunk/lib/merb/session/merb_memory_session.rb (modified) (2 diffs)
- trunk/lib/merb/template (added)
- trunk/lib/merb/template.rb (added)
- trunk/lib/merb/template/erubis.rb (added)
- trunk/lib/merb/template/markaby.rb (added)
- trunk/lib/merb/template/xml_builder.rb (added)
- trunk/specs/merb/merb_controller_spec.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/Rakefile
r184 r193 15 15 16 16 NAME = "merb" 17 VERS = "0. 1.1"17 VERS = "0.2.0" 18 18 CLEAN.include ['**/.*.sw?', '*.gem', '.config'] 19 19 RDOC_OPTS = ['--quiet', '--title', "Merb Documentation", trunk/lib/merb.rb
r169 r193 13 13 14 14 module Merb 15 VERSION='0. 1.1' unless defined?VERSION15 VERSION='0.2.0' unless defined?VERSION 16 16 class Server 17 17 class << self … … 103 103 puts "Basic Authentication mixed in" 104 104 end 105 end 105 end trunk/lib/merb/core_ext.rb
r125 r193 10 10 merb_numeric 11 11 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 13 13 # puts that into params as well. 14 14 class Controller 15 16 shared_accessor :layout, :session_id_key, :cache_templates 15 16 trait :layout => :application 17 trait :session_id_key => :_session_id 17 18 18 19 include Merb::ControllerMixin … … 37 38 cookies = query_parse(env['HTTP_COOKIE'], ';,') 38 39 querystring = query_parse(env['QUERY_STRING']) 39 self.layout ||= :application40 self.session_id_key ||= :_session_id41 40 @in = req 42 41 if MULTIPART_REGEXP =~ env['CONTENT_TYPE'] … … 94 93 95 94 @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]) 97 96 98 97 allow = [:post, :put, :delete] … … 170 169 @response 171 170 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 172 193 173 194 trunk/lib/merb/merb_dispatcher.rb
r181 r193 61 61 end 62 62 63 end 63 end trunk/lib/merb/merb_request.rb
r186 r193 102 102 end 103 103 104 trunk/lib/merb/merb_server.rb
r188 r193 15 15 :allow_reloading => true, 16 16 :merb_root => Dir.pwd, 17 :template_ext => {:html => :herb, :js => :jerb, :xml => :xerb},18 17 :cache_templates => false 19 18 } … … 119 118 120 119 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; puts124 end125 120 require 'merb' 126 121 require @@merb_opts[:merb_root] / 'dist/conf/router.rb' … … 256 251 def mongrel_start(port) 257 252 @@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 258 257 initialize_merb 259 258 trunk/lib/merb/merb_upload_progress.rb
r106 r193 46 46 end 47 47 48 end 48 end trunk/lib/merb/merb_view_context.rb
r151 r193 15 15 # when evaluating the templates. 16 16 class ViewContext 17 include Merb::ErubisCaptureMixin18 17 include Merb::ViewContextMixin 19 18 include Merb::FormControls … … 32 31 end 33 32 33 # hack so markaby doesn't dup us and lose ivars. 34 def dup 35 self 36 end 37 34 38 # accessor for the view. refers to the current @controller object 35 39 def controller 36 40 @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 38 48 39 49 # catch any method calls that the controller responds to … … 49 59 end 50 60 51 end 61 end trunk/lib/merb/mixins/basic_authentication_mixin.rb
r178 r193 33 33 end 34 34 35 end 35 end trunk/lib/merb/mixins/controller_mixin.rb
r126 r193 2 2 module ControllerMixin 3 3 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 4 39 # redirect to another url It can be like /foo/bar 5 40 # for redirecting within your same app. Or it can … … 86 121 87 122 end 88 end 123 end trunk/lib/merb/mixins/render_mixin.rb
r174 r193 2 2 3 3 module RenderMixin 4 @@erbs = {}5 @@mtimes = {}6 4 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) 21 85 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 30 87 # this returns a ViewContext object populated with all 31 88 # the instance variables in your controller. This is used … … 39 96 # this when you want to render a template with 40 97 # .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]) 45 100 end 46 101 47 102 # renders nothing but sets the status, defaults 48 103 # to 200. does send one \n newline char, tyhis is for … … 58 113 # explicitely set the template name excluding the file 59 114 # 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}) 63 117 end 64 118 … … 73 127 # partials that live there like partial('shared/foo') 74 128 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 83 130 end 84 131 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}" 103 179 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 223 184 224 185 end 225 end 186 end trunk/lib/merb/mixins/responder_mixin.rb
r173 r193 45 45 end 46 46 end 47 end 47 end trunk/lib/merb/session/merb_ar_session.rb
r126 r193 7 7 def setup_session 8 8 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]]) 11 11 @_fingerprint_before = Marshal.dump(@session).hash 12 @_new_cookie = cookies[ session_id_key] != before12 @_new_cookie = cookies[ancestral_trait[:session_id_key]] != before 13 13 end 14 14 … … 18 18 @session.save 19 19 end 20 set_cookie( session_id_key, cookies[session_id_key], Time.now+Merb::Const::WEEK*2) if @_new_cookie20 set_cookie(ancestral_trait[:session_id_key], cookies[ancestral_trait[:session_id_key]], Time.now+Merb::Const::WEEK*2) if @_new_cookie 21 21 end 22 22 trunk/lib/merb/session/merb_memory_session.rb
r165 r193 5 5 def setup_session 6 6 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] != before7 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 10 10 end 11 11 12 12 def finalize_session 13 13 MERB_LOGGER.info("Finalize session") 14 set_cookie( session_id_key, cookies[session_id_key], Time.now+Merb::Const::WEEK*2) if @_new_cookie14 set_cookie(ancestral_trait[:session_id_key], cookies[ancestral_trait[:session_id_key]], Time.now+Merb::Const::WEEK*2) if @_new_cookie 15 15 end 16 16 … … 104 104 end # end DRbSession 105 105 106 end 106 end trunk/specs/merb/merb_controller_spec.rb
r173 r193 18 18 specify "Default layout should be application.rhtml" do 19 19 c = Merb::Controller.new @in.req, @in.env,{}, @res 20 c. layout.should == :application20 c.ancestral_trait[:layout].should == :application 21 21 end 22 22
