From e5e00471fe0920967c3289bde73bc9b1e1f172eb Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Sun, 19 Sep 2010 14:56:12 +0200 Subject: [PATCH 1/2] Skip implicit layouts for nested templates. That way the following will produce valid HTML: @@ layout !!! = yield @@ content %html %head= haml :head %body= haml :body That way using render methods for partials is a lot easier. Tests included. --- lib/sinatra/base.rb | 16 ++++++++++------ test/templates_test.rb | 21 +++++++++++++++++++++ test/views/explicitly_nested.str | 1 + test/views/hello.str | 1 + test/views/layout2.str | 2 ++ test/views/nested.str | 1 + 6 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 test/views/explicitly_nested.str create mode 100644 test/views/hello.str create mode 100644 test/views/layout2.str create mode 100644 test/views/nested.str diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index 24422b4c..4d004998 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -371,14 +371,18 @@ module Sinatra options[:outvar] ||= '@_out_buf' # extract generic options - locals = options.delete(:locals) || locals || {} - views = options.delete(:views) || settings.views || "./views" - layout = options.delete(:layout) - layout = :layout if layout.nil? || layout == true + locals = options.delete(:locals) || locals || {} + views = options.delete(:views) || settings.views || "./views" + @default_layout = :layout if @default_layout.nil? + layout = options.delete(:layout) + layout = @default_layout if layout.nil? or layout == true # compile and render template - template = compile_template(engine, data, options, views) - output = template.render(self, locals, &block) + layout_was = @default_layout + @default_layout = false if layout + template = compile_template(engine, data, options, views) + output = template.render(self, locals, &block) + @default_layout = layout_was # render layout if layout diff --git a/test/templates_test.rb b/test/templates_test.rb index 3eb7fd34..48e8cef7 100644 --- a/test/templates_test.rb +++ b/test/templates_test.rb @@ -78,6 +78,27 @@ class TemplatesTest < Test::Unit::TestCase assert_equal "Layout 3!\nHello World!\n", body end + it 'avoids wrapping layouts around nested templates' do + render_app { render :str, :nested, :layout => :layout2 } + assert ok? + assert_equal "

String Layout!

\n

Hello From String

", body + end + + it 'allows explicitly wrapping layouts around nested templates' do + render_app { render :str, :explicitly_nested, :layout => :layout2 } + assert ok? + assert_equal "

String Layout!

\n

String Layout!

\n

Hello From String

", body + end + + it 'two independent render calls do not disable layouts' do + render_app do + render :str, :explicitly_nested, :layout => :layout2 + render :str, :nested, :layout => :layout2 + end + assert ok? + assert_equal "

String Layout!

\n

Hello From String

", body + end + it 'loads templates from source file' do mock_app { enable :inline_templates } assert_equal "this is foo\n\n", @app.templates[:foo][0] diff --git a/test/views/explicitly_nested.str b/test/views/explicitly_nested.str new file mode 100644 index 00000000..a3b0875b --- /dev/null +++ b/test/views/explicitly_nested.str @@ -0,0 +1 @@ +#{render :str, :hello, :layout => :layout2} \ No newline at end of file diff --git a/test/views/hello.str b/test/views/hello.str new file mode 100644 index 00000000..6c121b37 --- /dev/null +++ b/test/views/hello.str @@ -0,0 +1 @@ +

Hello From String

\ No newline at end of file diff --git a/test/views/layout2.str b/test/views/layout2.str new file mode 100644 index 00000000..c6391811 --- /dev/null +++ b/test/views/layout2.str @@ -0,0 +1,2 @@ +

String Layout!

+#{yield} \ No newline at end of file diff --git a/test/views/nested.str b/test/views/nested.str new file mode 100644 index 00000000..603038a4 --- /dev/null +++ b/test/views/nested.str @@ -0,0 +1 @@ +#{render :str, :hello} \ No newline at end of file From 1d676f41f855cdab9aacda0037a4217ec3c9c45e Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Sun, 19 Sep 2010 17:57:48 +0200 Subject: [PATCH 2/2] Sets default content type according to template engine used instead of just text/html. It does so by including a Mixin into the the returned string offering a content_type method. Therefore all of the following examples produce the expected results: # text/html get('/') do haml :index end # text/css get('/') do sass :index end # text/css get('/') do haml :index sass :index end # text/html get('/') do haml '= sass :index' end It also allows setting the default content type for a template engine: set :builder, :content_type => :html Tests and README adjustments (all languages) included. --- README.de.rdoc | 7 ------- README.jp.rdoc | 3 --- README.rdoc | 7 ------- lib/sinatra/base.rb | 30 ++++++++++++++++++++++++------ test/builder_test.rb | 26 +++++++++++++++++++++++++- test/coffee_test.rb | 26 +++++++++++++++++++++++++- test/helper.rb | 1 + test/helpers_test.rb | 14 +++++++------- test/less_test.rb | 28 ++++++++++++++++++++++++++-- test/routing_test.rb | 2 +- test/sass_test.rb | 26 +++++++++++++++++++++++++- test/scss_test.rb | 26 +++++++++++++++++++++++++- test/templates_test.rb | 22 +++++++++++++++++++++- 13 files changed, 180 insertions(+), 38 deletions(-) diff --git a/README.de.rdoc b/README.de.rdoc index 79fe9f99..34c14868 100644 --- a/README.de.rdoc +++ b/README.de.rdoc @@ -229,7 +229,6 @@ Das buidler gem wird benötigt um Builder-Templates rendern zu können: require 'builder' get '/' do - content_type 'application/xml', :charset => 'utf-8' builder :index end @@ -243,7 +242,6 @@ Das haml gem wird benötigt um SASS-Templates rendern zu können: require 'sass' get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' sass :stylesheet end @@ -257,7 +255,6 @@ und individuell überschrieben werden. set :sass, :style => :compact # Standard Sass-Style ist :nested get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' sass :stylesheet, :style => :expanded # überschrieben end @@ -269,7 +266,6 @@ Das haml gem wird benötigt um SCSS-Templates rendern zu können: require 'sass' get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' scss :stylesheet end @@ -283,7 +279,6 @@ und individuell überschrieben werden. set :scss, :style => :compact # Standard Scss-Style ist :nested get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' scss :stylesheet, :style => :expanded # überschrieben end @@ -295,7 +290,6 @@ Das less gem wird benötigt um Less-Templates rendern zu können: require 'less' get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' less :stylesheet end @@ -434,7 +428,6 @@ Das coffee-script gem und das `coffee`-Programm werden benötigt um CoffeScript- require 'coffee-script' get '/application.js' do - content_type 'text/javascript', :charset => 'utf-8' coffee :application end diff --git a/README.jp.rdoc b/README.jp.rdoc index ad8250a1..7cf6bbe3 100644 --- a/README.jp.rdoc +++ b/README.jp.rdoc @@ -154,7 +154,6 @@ builderを使うにはbuilderライブラリが必要です: require 'builder' get '/' do - content_type 'application/xml', :charset => 'utf-8' builder :index end @@ -168,7 +167,6 @@ Sassテンプレートを使うにはsassライブラリが必要です: require 'sass' get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' sass :stylesheet end @@ -182,7 +180,6 @@ see {Options and Configurations}[http://www.sinatrarb.com/configuration.html], set :sass, {:style => :compact } # デフォルトのSass styleは :nested get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' sass :stylesheet, :sass_options => {:style => :expanded } # 上書き end diff --git a/README.rdoc b/README.rdoc index b6d00b11..74603d28 100644 --- a/README.rdoc +++ b/README.rdoc @@ -225,7 +225,6 @@ The builder gem/library is required to render builder templates: require 'builder' get '/' do - content_type 'application/xml', :charset => 'utf-8' builder :index end @@ -239,7 +238,6 @@ The sass gem/library is required to render Sass templates: require 'sass' get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' sass :stylesheet end @@ -253,7 +251,6 @@ and overridden on an individual basis. set :sass, :style => :compact # default Sass style is :nested get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' sass :stylesheet, :style => :expanded # overridden end @@ -265,7 +262,6 @@ The sass gem/library is required to render Scss templates: require 'sass' get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' scss :stylesheet end @@ -279,7 +275,6 @@ and overridden on an individual basis. set :scss, :style => :compact # default Scss style is :nested get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' scss :stylesheet, :style => :expanded # overridden end @@ -291,7 +286,6 @@ The less gem/library is required to render Less templates: require 'less' get '/stylesheet.css' do - content_type 'text/css', :charset => 'utf-8' less :stylesheet end @@ -422,7 +416,6 @@ CoffeeScript templates: require 'coffee-script' get '/application.js' do - content_type 'text/javascript', :charset => 'utf-8' coffee :application end diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index 4d004998..97796f62 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -77,10 +77,12 @@ module Sinatra # evaluation is deferred until the body is read with #each. def body(value=nil, &block) if block_given? - def block.each ; yield call ; end + def block.each; yield(call) end response.body = block - else + elsif value response.body = value + else + response.body end end @@ -137,6 +139,7 @@ module Sinatra def content_type(type, params={}) mime_type = mime_type(type) fail "Unknown media type: %p" % type if mime_type.nil? + params[:charset] ||= defined?(Encoding) ? Encoding.default_external.to_s.downcase : 'utf-8' if params.any? params = params.collect { |kv| "%s=%s" % kv }.join(', ') response['Content-Type'] = [mime_type, params].join(";") @@ -300,6 +303,10 @@ module Sinatra # :locals A hash with local variables that should be available # in the template module Templates + module ContentTyped + attr_accessor :content_type + end + include Tilt::CompileSite def erb(template, options={}, locals={}) @@ -315,21 +322,22 @@ module Sinatra end def sass(template, options={}, locals={}) - options[:layout] = false + options.merge! :layout => false, :default_content_type => :css render :sass, template, options, locals end def scss(template, options={}, locals={}) - options[:layout] = false + options.merge! :layout => false, :default_content_type => :css render :scss, template, options, locals end def less(template, options={}, locals={}) - options[:layout] = false + options.merge! :layout => false, :default_content_type => :css render :less, template, options, locals end def builder(template=nil, options={}, locals={}, &block) + options[:default_content_type] = :xml options, template = template, nil if template.is_a?(Hash) template = Proc.new { block } if template.nil? render :builder, template, options, locals @@ -360,7 +368,7 @@ module Sinatra end def coffee(template, options={}, locals={}) - options[:layout] = false + options.merge! :layout => false, :default_content_type => :js render :coffee, template, options, locals end @@ -376,6 +384,7 @@ module Sinatra @default_layout = :layout if @default_layout.nil? layout = options.delete(:layout) layout = @default_layout if layout.nil? or layout == true + content_type = options.delete(:content_type) || options.delete(:default_content_type) # compile and render template layout_was = @default_layout @@ -393,6 +402,7 @@ module Sinatra end end + output.extend(ContentTyped).content_type = content_type if content_type output end @@ -457,8 +467,16 @@ module Sinatra template_cache.clear if settings.reload_templates force_encoding(@params) + @response['Content-Type'] = nil invoke { dispatch! } invoke { error_block!(response.status) } + unless @response['Content-Type'] + if body.respond_to?(:to_ary) and body.first.respond_to? :content_type + content_type body.first.content_type + else + content_type :html + end + end status, header, body = @response.finish diff --git a/test/builder_test.rb b/test/builder_test.rb index 04ab3a58..92a0ffe3 100644 --- a/test/builder_test.rb +++ b/test/builder_test.rb @@ -2,9 +2,10 @@ require File.dirname(__FILE__) + '/helper' require 'builder' class BuilderTest < Test::Unit::TestCase - def builder_app(&block) + def builder_app(options = {}, &block) mock_app { set :views, File.dirname(__FILE__) + '/views' + set options get '/', &block } get '/' @@ -16,6 +17,29 @@ class BuilderTest < Test::Unit::TestCase assert_equal %{\n}, body end + it 'defaults content type to xml' do + builder_app { builder 'xml.instruct!' } + assert ok? + assert_equal "application/xml;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + builder_app do + content_type :html + builder 'xml.instruct!' + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + builder_app(:builder => { :content_type => 'html' }) do + builder 'xml.instruct!' + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + it 'renders inline blocks' do builder_app { @name = "Frank & Mary" diff --git a/test/coffee_test.rb b/test/coffee_test.rb index c71f9d25..f9677ae7 100644 --- a/test/coffee_test.rb +++ b/test/coffee_test.rb @@ -4,9 +4,10 @@ begin require 'coffee-script' class CoffeeTest < Test::Unit::TestCase - def coffee_app(&block) + def coffee_app(options = {}, &block) mock_app { set :views, File.dirname(__FILE__) + '/views' + set(options) get '/', &block } get '/' @@ -18,6 +19,29 @@ class CoffeeTest < Test::Unit::TestCase assert_equal "(function() {\n alert('Aye!');\n})();\n", body end + it 'defaults content type to javascript' do + coffee_app { coffee "alert 'Aye!'\n" } + assert ok? + assert_equal "application/javascript;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + coffee_app do + content_type :html + coffee "alert 'Aye!'\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + coffee_app(:coffee => { :content_type => 'html' }) do + coffee "alert 'Aye!'\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + it 'renders .coffee files in views path' do coffee_app { coffee :hello } assert ok? diff --git a/test/helper.rb b/test/helper.rb index 70699295..2220ebca 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,4 +1,5 @@ ENV['RACK_ENV'] = 'test' +Encoding.default_external = "UTF-8" if defined? Encoding begin require 'rack' diff --git a/test/helpers_test.rb b/test/helpers_test.rb index 57165461..ce58714c 100644 --- a/test/helpers_test.rb +++ b/test/helpers_test.rb @@ -291,21 +291,21 @@ class HelpersTest < Test::Unit::TestCase } get '/' - assert_equal 'text/plain', response['Content-Type'] + assert_equal 'text/plain;charset=utf-8', response['Content-Type'] assert_equal 'Hello World', body end it 'takes media type parameters (like charset=)' do mock_app { get '/' do - content_type 'text/html', :charset => 'utf-8' + content_type 'text/html', :charset => 'latin1' "

Hello, World

" end } get '/' assert ok? - assert_equal 'text/html;charset=utf-8', response['Content-Type'] + assert_equal 'text/html;charset=latin1', response['Content-Type'] assert_equal "

Hello, World

", body end @@ -320,7 +320,7 @@ class HelpersTest < Test::Unit::TestCase get '/foo.xml' assert ok? - assert_equal 'application/foo', response['Content-Type'] + assert_equal 'application/foo;charset=utf-8', response['Content-Type'] assert_equal 'I AM FOO', body end @@ -366,19 +366,19 @@ class HelpersTest < Test::Unit::TestCase it 'sets the Content-Type response header if a mime-type can be located' do send_file_app get '/file.txt' - assert_equal 'text/plain', response['Content-Type'] + assert_equal 'text/plain;charset=utf-8', response['Content-Type'] end it 'sets the Content-Type response header if type option is set to a file extesion' do send_file_app :type => 'html' get '/file.txt' - assert_equal 'text/html', response['Content-Type'] + assert_equal 'text/html;charset=utf-8', response['Content-Type'] end it 'sets the Content-Type response header if type option is set to a mime type' do send_file_app :type => 'application/octet-stream' get '/file.txt' - assert_equal 'application/octet-stream', response['Content-Type'] + assert_equal 'application/octet-stream;charset=utf-8', response['Content-Type'] end it 'sets the Content-Length response header' do diff --git a/test/less_test.rb b/test/less_test.rb index f6cd72fd..ef9ac158 100644 --- a/test/less_test.rb +++ b/test/less_test.rb @@ -2,20 +2,44 @@ require File.dirname(__FILE__) + '/helper' require 'less' class LessTest < Test::Unit::TestCase - def less_app(&block) + def less_app(options = {}, &block) mock_app { set :views, File.dirname(__FILE__) + '/views' + set options get '/', &block } get '/' end it 'renders inline Less strings' do - less_app { less "@white_color: #fff; #main { background-color: @white_color }"} + less_app { less "@white_color: #fff; #main { background-color: @white_color }" } assert ok? assert_equal "#main { background-color: #ffffff; }\n", body end + it 'defaults content type to css' do + less_app { less "@white_color: #fff; #main { background-color: @white_color }" } + assert ok? + assert_equal "text/css;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + less_app do + content_type :html + less "@white_color: #fff; #main { background-color: @white_color }" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + less_app(:less => { :content_type => 'html' }) do + less "@white_color: #fff; #main { background-color: @white_color }" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + it 'renders .less files in views path' do less_app { less :hello } assert ok? diff --git a/test/routing_test.rb b/test/routing_test.rb index 956f5b64..85961a55 100644 --- a/test/routing_test.rb +++ b/test/routing_test.rb @@ -80,7 +80,7 @@ class RoutingTest < Test::Unit::TestCase get '/foo' assert_equal 404, status - assert_equal 'text/html', response["Content-Type"] + assert_equal 'text/html;charset=utf-8', response["Content-Type"] assert_equal "

Not Found

", response.body end diff --git a/test/sass_test.rb b/test/sass_test.rb index 596e3c45..e1a24329 100644 --- a/test/sass_test.rb +++ b/test/sass_test.rb @@ -4,9 +4,10 @@ begin require 'sass' class SassTest < Test::Unit::TestCase - def sass_app(&block) + def sass_app(options = {}, &block) mock_app { set :views, File.dirname(__FILE__) + '/views' + set options get '/', &block } get '/' @@ -18,6 +19,29 @@ class SassTest < Test::Unit::TestCase assert_equal "#sass {\n background-color: white; }\n", body end + it 'defaults content type to css' do + sass_app { sass "#sass\n :background-color white\n" } + assert ok? + assert_equal "text/css;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + sass_app do + content_type :html + sass "#sass\n :background-color white\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + sass_app(:sass => { :content_type => 'html' }) do + sass "#sass\n :background-color white\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + it 'renders .sass files in views path' do sass_app { sass :hello } assert ok? diff --git a/test/scss_test.rb b/test/scss_test.rb index 7fccb8f2..d1df4919 100644 --- a/test/scss_test.rb +++ b/test/scss_test.rb @@ -4,9 +4,10 @@ begin require 'sass' class ScssTest < Test::Unit::TestCase - def scss_app(&block) + def scss_app(options = {}, &block) mock_app { set :views, File.dirname(__FILE__) + '/views' + set options get '/', &block } get '/' @@ -18,6 +19,29 @@ class ScssTest < Test::Unit::TestCase assert_equal "#scss {\n background-color: white; }\n", body end + it 'defaults content type to css' do + scss_app { scss "#scss {\n background-color: white; }\n" } + assert ok? + assert_equal "text/css;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + scss_app do + content_type :html + scss "#scss {\n background-color: white; }\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + scss_app(:scss => { :content_type => 'html' }) do + scss "#scss {\n background-color: white; }\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + it 'renders .scss files in views path' do scss_app { scss :hello } assert ok? diff --git a/test/templates_test.rb b/test/templates_test.rb index 48e8cef7..48f0ec65 100644 --- a/test/templates_test.rb +++ b/test/templates_test.rb @@ -15,9 +15,11 @@ class TestTemplate < Tilt::Template end class TemplatesTest < Test::Unit::TestCase - def render_app(base=Sinatra::Base, &block) + def render_app(base=Sinatra::Base, options = {}, &block) + base, options = Sinatra::Base, base if base.is_a? Hash mock_app(base) { set :views, File.dirname(__FILE__) + '/views' + set options get '/', &block template(:layout3) { "Layout 3!\n" } } @@ -156,6 +158,24 @@ class TemplatesTest < Test::Unit::TestCase assert_equal 'bar', body end + it 'allows setting default content type per template engine' do + render_app(:str => { :content_type => :txt }) { render :str, 'foo' } + assert_equal 'text/plain;charset=utf-8', response['Content-Type'] + end + + it 'setting default content type does not affect other template engines' do + render_app(:str => { :content_type => :txt }) { render :test, 'foo' } + assert_equal 'text/html;charset=utf-8', response['Content-Type'] + end + + it 'setting default content type per template engine does not override content_type' do + render_app :str => { :content_type => :txt } do + content_type :html + render :str, 'foo' + end + assert_equal 'text/html;charset=utf-8', response['Content-Type'] + end + it 'uses templates in superclasses before subclasses' do base = Class.new(Sinatra::Base) base.template(:foo) { 'template in superclass' }