From caf4857024572ebab33fd28f61b4f6c8b44e77c7 Mon Sep 17 00:00:00 2001 From: Ryan Tomayko Date: Sat, 8 Mar 2008 20:40:01 -0500 Subject: [PATCH] Builder Rendering Helper (.builder templates) w/ doco and tests. --- lib/sinatra.rb | 94 ++++++++++++++++++++++- test/builder_test.rb | 101 +++++++++++++++++++++++++ test/views/foo.builder | 1 + test/views/layout_test/foo.builder | 1 + test/views/layout_test/layout.builder | 3 + test/views/no_layout/no_layout.builder | 1 + 6 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 test/builder_test.rb create mode 100644 test/views/foo.builder create mode 100644 test/views/layout_test/foo.builder create mode 100644 test/views/layout_test/layout.builder create mode 100644 test/views/no_layout/no_layout.builder diff --git a/lib/sinatra.rb b/lib/sinatra.rb index 730cb2ac..ce46b288 100644 --- a/lib/sinatra.rb +++ b/lib/sinatra.rb @@ -405,7 +405,98 @@ module Sinatra end end - + + # Generating conservative XML content using Builder templates. + # + # Builder templates can be inline by passing a block to the builder method, or in + # external files with +.builder+ extension by passing the name of the template + # to the +builder+ method as a Symbol. + # + # === Inline Rendering + # + # If the builder method is given a block, the block is called directly with an + # +XmlMarkup+ instance and the result is returned as String: + # get '/who.xml' do + # builder do |xml| + # xml.instruct! + # xml.person do + # xml.name "Francis Albert Sinatra", + # :aka => "Frank Sinatra" + # xml.email 'frank@capitolrecords.com' + # end + # end + # end + # + # Yields the following XML: + # + # + # Francis Albert Sinatra + # Frank Sinatra + # + # + # === Builder Template Files + # + # Builder templates can be stored in separate files with a +.builder+ + # extension under the view path. An +XmlMarkup+ object named +xml+ is automatically + # made available to template. + # + # Example: + # get '/bio.xml' do + # builder :bio + # end + # + # The "views/bio.builder" file might contain the following: + # xml.instruct! :xml, :version => '1.1' + # xml.person do + # xml.name "Francis Albert Sinatra" + # xml.aka "Frank Sinatra" + # xml.aka "Ol' Blue Eyes" + # xml.aka "The Chairman of the Board" + # xml.born 'date' => '1915-12-12' do + # xml.text! "Hoboken, New Jersey, U.S.A." + # end + # xml.died 'age' => 82 + # end + # + # And yields the following output: + # + # + # Francis Albert Sinatra + # Frank Sinatra + # Ol' Blue Eyes + # The Chairman of the Board + # Hoboken, New Jersey, U.S.A. + # + # + # + # NOTE: Builder must be installed or a LoadError will be raised the first time an + # attempt is made to render a builder template. + # + # See http://builder.rubyforge.org/ for comprehensive documentation on Builder. + module Builder + + def builder(content=nil, options={}, &block) + options, content = content, nil if content.is_a?(Hash) + content = Proc.new { block } if content.nil? + render(:builder, content, options) + end + + private + + def render_builder(content, &b) + require 'builder' + xml = ::Builder::XmlMarkup.new(:indent => 2) + case content + when String + eval(content, binding, '', 1) + when Proc + content.call(xml) + end + xml.target! + end + + end + class EventContext include ResponseHelpers @@ -413,6 +504,7 @@ module Sinatra include RenderingHelpers include Erb include Haml + include Builder attr_accessor :request, :response diff --git a/test/builder_test.rb b/test/builder_test.rb new file mode 100644 index 00000000..5abc83bd --- /dev/null +++ b/test/builder_test.rb @@ -0,0 +1,101 @@ +require File.dirname(__FILE__) + '/helper' + +context "Builder" do + + setup do + Sinatra.application = nil + end + + context "without layouts" do + + setup do + Sinatra.application = nil + end + + specify "should render" do + + get '/no_layout' do + builder 'xml.instruct!' + end + + get_it '/no_layout' + should.be.ok + body.should == %(\n) + + end + + specify "should render inline block" do + + get '/no_layout_and_inlined' do + @name = "Frank & Mary" + builder do |xml| + xml.couple @name + end + end + + get_it '/no_layout_and_inlined' + should.be.ok + body.should == %(Frank & Mary\n) + + end + + end + + + + context "Templates (in general)" do + + setup do + Sinatra.application = nil + end + + specify "are read from files if Symbols" do + + get '/from_file' do + @name = 'Blue' + builder :foo, :views_directory => File.dirname(__FILE__) + "/views" + end + + get_it '/from_file' + should.be.ok + body.should.equal %(You rock Blue!\n) + + end + + specify "use layout.ext by default if available" do + + get '/' do + builder :foo, :views_directory => File.dirname(__FILE__) + "/views/layout_test" + end + + get_it '/' + should.be.ok + body.should.equal "\nis foo!\n\n" + + end + + specify "renders without layout" do + + get '/' do + builder :no_layout, :views_directory => File.dirname(__FILE__) + "/views/no_layout" + end + + get_it '/' + should.be.ok + body.should.equal "No Layout!\n" + + end + + specify "raises error if template not found" do + + get '/' do + builder :not_found + end + + lambda { get_it '/' }.should.raise(Errno::ENOENT) + + end + + end + +end diff --git a/test/views/foo.builder b/test/views/foo.builder new file mode 100644 index 00000000..dfa91a64 --- /dev/null +++ b/test/views/foo.builder @@ -0,0 +1 @@ +xml.exclaim "You rock #{@name}!" diff --git a/test/views/layout_test/foo.builder b/test/views/layout_test/foo.builder new file mode 100644 index 00000000..910eb1de --- /dev/null +++ b/test/views/layout_test/foo.builder @@ -0,0 +1 @@ +xml.this "is foo!" diff --git a/test/views/layout_test/layout.builder b/test/views/layout_test/layout.builder new file mode 100644 index 00000000..9491f574 --- /dev/null +++ b/test/views/layout_test/layout.builder @@ -0,0 +1,3 @@ +xml.layout do + xml << yield +end diff --git a/test/views/no_layout/no_layout.builder b/test/views/no_layout/no_layout.builder new file mode 100644 index 00000000..cbf4be6d --- /dev/null +++ b/test/views/no_layout/no_layout.builder @@ -0,0 +1 @@ +xml.foo "No Layout!"