From ca5f8f35cb5636ae2d7686a3c265ecfa8a7426bc Mon Sep 17 00:00:00 2001 From: Blake Mizerany Date: Mon, 1 Oct 2007 01:10:46 -0700 Subject: [PATCH] Rendering refactoring with layouts --- lib/sinatra/context.rb | 72 ++++++++++++++++++++++++++++ lib/sinatra/context/renderer.rb | 38 +++++++++++++++ lib/sinatra/core_ext/metaid.rb | 15 ++++++ lib/sinatra/event.rb | 85 +-------------------------------- test/sinatra/renderer_test.rb | 47 ++++++++++++++++++ vendor/erb/init.rb | 2 +- vendor/erb/lib/erb.rb | 9 ++-- vendor/haml/init.rb | 2 +- vendor/haml/lib/haml.rb | 11 +++-- 9 files changed, 189 insertions(+), 92 deletions(-) create mode 100644 lib/sinatra/context.rb create mode 100644 lib/sinatra/context/renderer.rb create mode 100644 lib/sinatra/core_ext/metaid.rb create mode 100644 test/sinatra/renderer_test.rb diff --git a/lib/sinatra/context.rb b/lib/sinatra/context.rb new file mode 100644 index 0000000000..63b88387b0 --- /dev/null +++ b/lib/sinatra/context.rb @@ -0,0 +1,72 @@ +require File.dirname(__FILE__) + '/context/renderer' + +module Sinatra + + class EventContext + + cattr_accessor :logger + attr_reader :request + + include Sinatra::Renderer + + def initialize(request) + @request = request + @headers = {} + end + + def status(value = nil) + @status = value if value + @status || 200 + end + + def body(value = nil, &block) + @body = value if value + @body = block.call if block + @body + end + + def error(value = nil) + if value + status 500 + @error = value + erb :error, :views_directory => SINATRA_ROOT + '/files/' + end + @error + end + + # This allows for: + # header 'Content-Type' => 'text/html' + # header 'Foo' => 'Bar' + # or + # headers 'Content-Type' => 'text/html', + # 'Foo' => 'Bar' + # + # Whatever blows your hair back + def headers(value = nil) + @headers.merge!(value) if value + @headers + end + alias :header :headers + + def session + request.env['rack.session'] + end + + def params + @params ||= @request.params.symbolize_keys + end + + def redirect(path) + logger.info "Redirecting to: #{path}" + status 302 + header 'Location' => path + end + + def log_event + logger.info "#{request.request_method} #{request.path_info} | Status: #{status} | Params: #{params.inspect}" + logger.exception(error) if error + end + + end + +end diff --git a/lib/sinatra/context/renderer.rb b/lib/sinatra/context/renderer.rb new file mode 100644 index 0000000000..a703c7ab8b --- /dev/null +++ b/lib/sinatra/context/renderer.rb @@ -0,0 +1,38 @@ +Layouts = Hash.new + +module Sinatra + + module Renderer + + DEFAULT_OPTIONS = { + :views_directory => 'views', + :layout => :layout + } + + def render(template, renderer, options = {}) + options = DEFAULT_OPTIONS.merge(options) + + layout = block_given? ? yield : Layouts[options[:layout]] + + result_method = 'render_%s' % renderer + + if layout + send(result_method, layout) { send(result_method, determine_template(template, renderer, options)) } + else + send(result_method, determine_template(template, renderer, options)) + end + end + + protected + + def determine_template(template, ext, options) + if template.is_a?(Symbol) + File.read("%s/%s.%s" % [options[:views_directory], template, ext]) + else + template + end + end + + end + +end diff --git a/lib/sinatra/core_ext/metaid.rb b/lib/sinatra/core_ext/metaid.rb new file mode 100644 index 0000000000..f701081b60 --- /dev/null +++ b/lib/sinatra/core_ext/metaid.rb @@ -0,0 +1,15 @@ +class Object + # The hidden singleton lurks behind everyone + def metaclass; class << self; self; end; end + def meta_eval &blk; metaclass.instance_eval &blk; end + + # Adds methods to a metaclass + def meta_def name, &blk + meta_eval { define_method name, &blk } + end + + # Defines an instance method within a class + def class_def name, &blk + class_eval { define_method name, &blk } + end +end diff --git a/lib/sinatra/event.rb b/lib/sinatra/event.rb index 8cd31b2855..4aa5954d06 100644 --- a/lib/sinatra/event.rb +++ b/lib/sinatra/event.rb @@ -28,98 +28,17 @@ def present_error def not_found Event.new(:get, 'not_found', false) do status 404 - views_dir SINATRA_ROOT + '/files' if request.path_info == '/' && request.request_method == 'GET' - erb :default_index + erb :default_index, :views_directory => SINATRA_ROOT + '/files' else - erb :not_found + erb :not_found, :views_directory => SINATRA_ROOT + '/files' end end end end - - class EventContext - - cattr_accessor :logger - - attr_reader :request - - def initialize(request) - @request = request - @headers = {} - end - - def status(value = nil) - @status = value if value - @status || 200 - end - def body(value = nil, &block) - @body = value if value - @body = block.call if block - @body - end - - def error(value = nil) - if value - status 500 - @error = value - views_dir SINATRA_ROOT + '/files/' - erb :error - end - @error - end - - # This allows for: - # header 'Content-Type' => 'text/html' - # header 'Foo' => 'Bar' - # or - # headers 'Content-Type' => 'text/html', - # 'Foo' => 'Bar' - # - # Whatever blows your hair back - def headers(value = nil) - @headers.merge!(value) if value - @headers - end - alias :header :headers - - def session - request.env['rack.session'] - end - - def params - @params ||= @request.params.symbolize_keys - end - - def views_dir(value = nil) - @views_dir = value if value - @views_dir || File.dirname($0) + '/views' - end - - def redirect(path) - logger.info "Redirecting to: #{path}" - status 302 - header 'Location' => path - end - - def determine_template(content, ext) - if content.is_a?(Symbol) - File.read("%s/%s.%s" % [views_dir, content, ext]) - else - content - end - end - - def log_event - logger.info "#{request.request_method} #{request.path_info} | Status: #{status} | Params: #{params.inspect}" - logger.exception(error) if error - end - - end - class Event cattr_accessor :logger diff --git a/test/sinatra/renderer_test.rb b/test/sinatra/renderer_test.rb new file mode 100644 index 0000000000..a538b14cb5 --- /dev/null +++ b/test/sinatra/renderer_test.rb @@ -0,0 +1,47 @@ +require File.dirname(__FILE__) + '/../helper' + +class Sinatra::EventContext + + def render_foo(template) + require 'erb' + ERB.new(template).result(binding) + end + +end + +describe "Renderer" do + + before(:each) do + Layouts.clear + @context = Sinatra::EventContext.new(stub()) + end + + it "should render render a tempalate" do + @context.render('foo', :foo).should.equal 'foo' + end + + it "should render with a layout if given" do + result = @context.render('content', :foo) do + 'X <%= yield %> X' + end + + result.should.equal 'X content X' + end + + it "should render default layout if it exists and layout if no layout name given" do + Layouts[:layout] = 'X <%= yield %> Y' + @context.render('foo', :foo).should.equal 'X foo Y' + + Layouts[:foo] = 'Foo <%= yield %> Layout' + @context.render('bar', :foo, :layout => :foo).should.equal 'Foo bar Layout' + end + + it "should read template from a file if exists" do + File.expects(:read).with('views/bar.foo').returns('foo content') + @context.render(:bar, :foo).should.equal 'foo content' + + File.expects(:read).with('views2/bar.foo').returns('foo content') + @context.render(:bar, :foo, :views_directory => 'views2').should.equal 'foo content' + end + +end diff --git a/vendor/erb/init.rb b/vendor/erb/init.rb index 0ea9d0f9e7..4d594284d1 100644 --- a/vendor/erb/init.rb +++ b/vendor/erb/init.rb @@ -1,3 +1,3 @@ require File.dirname(__FILE__) + '/lib/erb' -Sinatra::EventContext.send(:include, Sinatra::Erb::InstanceMethods) +Sinatra::EventContext.send(:include, Sinatra::Erb::EventContext) diff --git a/vendor/erb/lib/erb.rb b/vendor/erb/lib/erb.rb index b294e87041..f56c187112 100644 --- a/vendor/erb/lib/erb.rb +++ b/vendor/erb/lib/erb.rb @@ -2,13 +2,16 @@ module Sinatra module Erb - module InstanceMethods + module EventContext - def erb(content) + def render_erb(content) require 'erb' - body ERB.new(determine_template(content, :erb)).result(binding) + body ERB.new(content).result(binding) end + def erb(template, options = {}, &layout) + render(template, :erb, options, &layout) + end end end diff --git a/vendor/haml/init.rb b/vendor/haml/init.rb index 6c393d224f..dd88d6cd31 100644 --- a/vendor/haml/init.rb +++ b/vendor/haml/init.rb @@ -1,3 +1,3 @@ require File.dirname(__FILE__) + '/lib/haml' -Sinatra::EventContext.send(:include, Sinatra::Haml::InstanceMethods) +Sinatra::EventContext.send(:include, Sinatra::Haml::EventContext) diff --git a/vendor/haml/lib/haml.rb b/vendor/haml/lib/haml.rb index 5514605edd..5376a71575 100644 --- a/vendor/haml/lib/haml.rb +++ b/vendor/haml/lib/haml.rb @@ -2,13 +2,16 @@ module Sinatra module Haml - module InstanceMethods + module EventContext - def haml(content) - require 'haml' - body ::Haml::Engine.new(determine_template(content, :haml)).render(self) + def render_haml(content) + require 'haml' + body ::Haml::Engine.new(content).render(self) end + def haml(template, options = {}, &layout) + render(template, :haml, options, &layout) + end end end