Browse files

Tilt!

* AbstractTemplate and StringTemplate
* Add ERBTemplate implementation
* Add HamlTemplate implementation
* Template file extension mappings
* Sass template support
* Be more adament about defaults in initialize
* Builder template support
* Add line file/number backtrace specs for ERB and Haml templates
  • Loading branch information...
1 parent 7fd462d commit 137cd5403dc9a12eea15dbc5f228d7dac226782b @rtomayko rtomayko committed Jan 25, 2009
View
13 .autotest
@@ -0,0 +1,13 @@
+require 'rubygems'
+require 'bacon'
+require 'autotest/bacon'
+
+class Autotest::Bacon < Autotest
+ undef make_test_cmd
+ def make_test_cmd(files_to_test)
+ args = files_to_test.keys.flatten.join(' ')
+ args = '-a' if args.empty?
+ # TODO : make regex to pass to -n using values
+ "#{ruby} -S bacon -I#{libs} -o TestUnit #{args}"
+ end
+end
View
18 COPYING
@@ -0,0 +1,18 @@
+Copyright (c) 2009 Ryan Tomayko <http://tomayko.com/about>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
62 README.md
@@ -0,0 +1,62 @@
+Tilt
+====
+
+Tilt wraps multiple template engines and makes them available through a simple
+generic interface.
+
+ * Custom scope, locals, and yield support
+ * Backtraces with correct filenames and line numbers
+ * Template compilation/caching
+ * Template reloading
+
+Usage
+-----
+
+All supported templates have an implementation class under the `Tilt` module.
+Each template implementation follows the exact same interface:
+
+ template = Tilt::HamlTemplate.new('templates/foo.haml')
+ output = template.render
+
+The `render` method takes an optional evaluation scope and locals hash
+arguments. In the following example, the template is evaluated within the
+context of the person object and can access the locals `x` and `y`:
+
+ template = Tilt::ERBTemplate.new('templates/foo.erb')
+ joe = Person.find('joe')
+ output = template.render(joe, :x => 35, :y => 42)
+
+The `render` method may be called multiple times without creating a new
+template object. Continuing the previous example, we can render in Jane's
+scope with a different set of locals:
+
+ jane = Person.find('jane')
+ output = template.render(jane, :x => 22, :y => nil)
+
+Blocks can be passed to the render method for templates that support running
+arbitrary ruby code and using `yield`. Assuming the following was in a file
+named `foo.erb`:
+
+ Hey <%= yield %>!
+
+The block passed to the `render` method is invoked on `yield`:
+
+ template = Tilt::ERBTemplate.new('foo.erb')
+ template.render { 'Joe' }
+ # => "Hey Joe!"
+
+
+Supported Template Engines
+--------------------------
+
+The following template engines are supported:
+
+ * ERB
+ * Interpolated Ruby String
+ * Haml (with the `haml` gem/library)
+ * Sass (with the `haml` gem/library)
+ * Builder (with the `builder` gem/library)
+ * Liquid (with the `liquid` gem/library)
+ * Markdown (with the `rdiscount` gem)
+ * Maruku (with the `maruku` gem)
+ * Textile (with the `redcloth` gem)
View
97 Rakefile
@@ -0,0 +1,97 @@
+task :default => :spec
+
+# SPECS =====================================================================
+
+desc 'Generate test coverage report'
+task :rcov do
+ sh "rcov -Ilib:test test/*_test.rb"
+end
+desc 'Run specs with unit test style output'
+task :test do |t|
+ sh 'bacon -qa'
+end
+
+desc 'Run specs with story style output'
+task :spec do |t|
+ sh 'bacon -a'
+end
+
+# PACKAGING =================================================================
+
+# load gemspec like github's gem builder to surface any SAFE issues.
+Thread.new do
+ require 'rubygems/specification'
+ $spec = eval("$SAFE=3\n#{File.read('tilt.gemspec')}")
+end.join
+
+def package(ext='')
+ "dist/tilt-#{$spec.version}" + ext
+end
+
+desc 'Build packages'
+task :package => %w[.gem .tar.gz].map {|e| package(e)}
+
+desc 'Build and install as local gem'
+task :install => package('.gem') do
+ sh "gem install #{package('.gem')}"
+end
+
+directory 'dist/'
+
+file package('.gem') => %w[dist/ tilt.gemspec] + $spec.files do |f|
+ sh "gem build tilt.gemspec"
+ mv File.basename(f.name), f.name
+end
+
+file package('.tar.gz') => %w[dist/] + $spec.files do |f|
+ sh "git archive --format=tar HEAD | gzip > #{f.name}"
+end
+
+desc 'Upload gem and tar.gz distributables to rubyforge'
+task :release => [package('.gem'), package('.tar.gz')] do |t|
+ sh <<-SH
+ rubyforge add_release wink tilt #{$spec.version} #{package('.gem')} &&
+ rubyforge add_file wink tilt #{$spec.version} #{package('.tar.gz')}
+ SH
+end
+
+# GEMSPEC ===================================================================
+
+file 'tilt.gemspec' => FileList['{lib,test}/**','Rakefile'] do |f|
+ # read spec file and split out manifest section
+ spec = File.read(f.name)
+ parts = spec.split(" # = MANIFEST =\n")
+ # determine file list from git ls-files
+ files = `git ls-files`.
+ split("\n").sort.reject{ |file| file =~ /^\./ }.
+ map{ |file| " #{file}" }.join("\n")
+ # piece file back together and write...
+ parts[1] = " s.files = %w[\n#{files}\n ]\n"
+ spec = parts.join(" # = MANIFEST =\n")
+ spec.sub!(/s.date = '.*'/, "s.date = '#{Time.now.strftime("%Y-%m-%d")}'")
+ File.open(f.name, 'w') { |io| io.write(spec) }
+ puts "updated #{f.name}"
+end
+
+# DOC =======================================================================
+
+# requires the hanna gem:
+# gem install mislav-hanna --source=http://gems.github.com
+desc 'Build API documentation (doc/api)'
+task 'rdoc' => 'rdoc/index.html'
+file 'rdoc/index.html' => FileList['lib/**/*.rb'] do |f|
+ rm_rf 'rdoc'
+ sh((<<-SH).gsub(/[\s\n]+/, ' ').strip)
+ hanna
+ --op doc/api
+ --promiscuous
+ --charset utf8
+ --fmt html
+ --inline-source
+ --line-numbers
+ --accessor option_accessor=RW
+ --main Tilt
+ --title 'Tilt API Documentation'
+ #{f.prerequisites.join(' ')}
+ SH
+end
View
197 lib/tilt.rb
@@ -0,0 +1,197 @@
+module Tilt
+ @template_mappings = {}
+
+ # Register a template implementation by file extension.
+ def self.register(ext, template_class)
+ ext = ext.sub(/^\./, '')
+ @template_mappings[ext.downcase] = template_class
+ end
+
+ # Create a new template for the given file using the file's extension
+ # to determine the the template mapping.
+ def self.new(file, line=nil, options={}, &block)
+ if template_class = self[File.basename(file)]
+ template_class.new(file, line, options, &block)
+ else
+ fail "No template engine registered for #{File.basename(file)}"
+ end
+ end
+
+ # Lookup a template class given for the given filename or file
+ # extension. Return nil when no implementation is found.
+ def self.[](filename)
+ ext = filename.downcase
+ until ext.empty?
+ return @template_mappings[ext] if @template_mappings.key?(ext)
+ ext = ext.sub(/^[^.]*\.?/, '')
+ end
+ nil
+ end
+
+ # Base class for template implementations. Subclasses must implement
+ # the #compile! method and one of the #evaluate or #template_source
+ # methods.
+
+ class AbstractTemplate
+ # Raw template data loaded from a file or given directly.
+ attr_reader :data
+
+ # The name of the file where the template data was loaded from.
+ attr_reader :file
+
+ # The line number in #file where template data was loaded from.
+ attr_reader :line
+
+ # A Hash of template engine specific options. This is passed directly
+ # to the underlying engine and is not used by the generic template
+ # interface.
+ attr_reader :options
+
+ # Create a new template with the file, line, and options specified. By
+ # default, template data is read from the file specified. When a block
+ # is given, it should read template data and return as a String. When
+ # file is nil, a block is required.
+ def initialize(file=nil, line=1, options={}, &block)
+ raise ArgumentError, "file or block required" if file.nil? && block.nil?
+ @file = file
+ @line = line || 1
+ @options = options || {}
+ @reader = block || lambda { |t| File.read(file) }
+ end
+
+ # Render the template in the given scope with the locals specified. If a
+ # block is given, it is typically available within the template via
+ # +yield+.
+ def render(scope=Object.new, locals={}, &block)
+ if @data.nil?
+ @data = @reader.call(self)
+ compile!
+ end
+ evaluate scope, locals || {}, &block
+ end
+
+ # The filename used in backtraces to describe the template.
+ def eval_file
+ @file || '(__TEMPLATE__)'
+ end
+
+ protected
+ # Do whatever preparation is necessary to "compile" the template. Subclasses
+ # must provide an implementation of this method.
+ def compile!
+ raise NotImplementedError
+ end
+
+ # Process the template and return the result. Subclasses should override
+ # this method unless they implement the #template_source.
+ def evaluate(scope, locals, &block)
+ source, offset = local_assignment_code(locals)
+ source = [source, template_source].join("\n")
+ scope.instance_eval source, eval_file, (line - offset)
+ end
+
+ # Return a string containing the (Ruby) source code for the template. The
+ # default Abstract#evaluate method requires this method be defined
+ def template_source
+ raise NotImplementedError
+ end
+
+ private
+ def local_assignment_code(locals)
+ return ['', 1] if locals.empty?
+ source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
+ [source.join("\n"), source.length]
+ end
+ end
+
+ # The template source is evaluated as a Ruby string. The #{} interpolation
+ # syntax can be used to generated dynamic output.
+ class StringTemplate < AbstractTemplate
+ def compile!
+ @code = "%Q{#{data}}"
+ end
+
+ def template_source
+ @code
+ end
+ end
+ register 'str', StringTemplate
+
+ # ERB template implementation. See:
+ # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
+ class ERBTemplate < AbstractTemplate
+ def compile!
+ require 'erb' unless defined?(::ERB)
+ @engine = ::ERB.new(data)
+ end
+
+ def template_source
+ @engine.src
+ end
+ end
+ %w[erb rhtml].each { |ext| register ext, ERBTemplate }
+
+ # Haml template implementation. See:
+ # http://haml.hamptoncatlin.com/
+ class HamlTemplate < AbstractTemplate
+ def compile!
+ require 'haml' unless defined?(::Haml)
+ @engine = ::Haml::Engine.new(data, haml_options)
+ end
+
+ def evaluate(scope, locals, &block)
+ @engine.render(scope, locals, &block)
+ end
+
+ private
+ def haml_options
+ options.merge(:filename => eval_file, :line => line)
+ end
+ end
+ register 'haml', HamlTemplate
+
+ # Sass template implementation. See:
+ # http://haml.hamptoncatlin.com/
+ #
+ # Sass templates do not support object scopes, locals, or yield.
+ class SassTemplate < AbstractTemplate
+ def compile!
+ require 'sass' unless defined?(::Sass)
+ @engine = ::Sass::Engine.new(data, sass_options)
+ end
+
+ def evaluate(scope, locals, &block)
+ @engine.render
+ end
+
+ private
+ def sass_options
+ options.merge(:filename => eval_file, :line => line)
+ end
+ end
+ register 'sass', SassTemplate
+
+ # Builder template implementation. See:
+ # http://builder.rubyforge.org/
+ class BuilderTemplate < AbstractTemplate
+ def compile!
+ require 'builder' unless defined?(::Builder)
+ end
+
+ def evaluate(scope, locals, &block)
+ xml = ::Builder::XmlMarkup.new(:indent => 2)
+ if data.respond_to?(:to_str)
+ locals[:xml] = xml
+ super(scope, locals, &block)
+ elsif data.kind_of?(Proc)
+ data.call(xml)
+ end
+ xml.target!
+ end
+
+ def template_source
+ data.to_str
+ end
+ end
+ register 'builder', BuilderTemplate
+end
View
0 README → test/.bacon
File renamed without changes.
View
44 test/spec_tilt.rb
@@ -0,0 +1,44 @@
+require 'bacon'
+require 'tilt'
+
+describe "Tilt" do
+ class MockTemplate
+ attr_reader :args, :block
+ def initialize(*args, &block)
+ @args = args
+ @block = block
+ end
+ end
+
+ it "registers template implementation classes by file extension" do
+ lambda { Tilt.register('mock', MockTemplate) }.should.not.raise
+ end
+
+ it "looks up template implementation classes by file extension" do
+ impl = Tilt['mock']
+ impl.should.equal MockTemplate
+
+ impl = Tilt['.mock']
+ impl.should.equal MockTemplate
+ end
+
+ it "looks up template implementation classes with multiple file extensions" do
+ impl = Tilt['index.html.mock']
+ impl.should.equal MockTemplate
+ end
+
+ it "looks up template implementation classes by file name" do
+ impl = Tilt['templates/test.mock']
+ impl.should.equal MockTemplate
+ end
+
+ it "gives nil when no template implementation classes exist for a filename" do
+ Tilt['none'].should.be.nil
+ end
+
+ it "creates a new template instance given a filename" do
+ template = Tilt.new('foo.mock', 1, :key => 'val') { 'Hello World!' }
+ template.args.should.equal ['foo.mock', 1, {:key => 'val'}]
+ template.block.call.should.equal 'Hello World!'
+ end
+end
View
100 test/spec_tilt_abstracttemplate.rb
@@ -0,0 +1,100 @@
+require 'bacon'
+require 'tilt'
+
+describe "Tilt::AbstractTemplate" do
+ it "raises ArgumentError when a file or block not given" do
+ lambda { Tilt::AbstractTemplate.new }.should.raise ArgumentError
+ end
+
+ it "can be constructed with a file" do
+ inst = Tilt::AbstractTemplate.new('foo.erb')
+ inst.file.should.equal 'foo.erb'
+ end
+
+ it "can be constructed with a file and line" do
+ inst = Tilt::AbstractTemplate.new('foo.erb', 55)
+ inst.file.should.equal 'foo.erb'
+ inst.line.should.equal 55
+ end
+
+ it "uses the filename provided for #eval_file" do
+ inst = Tilt::AbstractTemplate.new('foo.erb', 55)
+ inst.eval_file.should.equal 'foo.erb'
+ end
+
+ it "uses a default filename for #eval_file when no file provided" do
+ inst = Tilt::AbstractTemplate.new { 'Hi' }
+ inst.eval_file.should.not.be.nil
+ inst.eval_file.should.not.include "\n"
+ end
+
+ it "can be constructed with a data loading block" do
+ lambda {
+ Tilt::AbstractTemplate.new { |template| "Hello World!" }
+ }.should.not.raise
+ end
+
+ it "raises NotImplementedError when #compile! not defined" do
+ inst = Tilt::AbstractTemplate.new { |template| "Hello World!" }
+ lambda { inst.render }.should.raise NotImplementedError
+ end
+
+ class CompilingMockTemplate < Tilt::AbstractTemplate
+ def compile!
+ data.should.not.be.nil
+ @compiled = true
+ end
+ def compiled? ; @compiled ; end
+ end
+
+ it "raises NotImplementedError when #evaluate or #template_source not defined" do
+ inst = CompilingMockTemplate.new { |t| "Hello World!" }
+ lambda { inst.render }.should.raise NotImplementedError
+ inst.should.be.compiled
+ end
+
+ class SimpleMockTemplate < CompilingMockTemplate
+ def evaluate(scope, locals, &block)
+ should.be.compiled
+ scope.should.not.be.nil
+ locals.should.not.be.nil
+ "<em>#{@data}</em>"
+ end
+ end
+
+ it "compiles and evaluates the template on #render" do
+ inst = SimpleMockTemplate.new { |t| "Hello World!" }
+ inst.render.should.equal "<em>Hello World!</em>"
+ inst.should.be.compiled
+ end
+
+ class SourceGeneratingMockTemplate < CompilingMockTemplate
+ def template_source
+ "foo = [] ; foo << %Q{#{data}} ; foo.join"
+ end
+ end
+
+ it "evaluates template_source with locals support" do
+ inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{name}!' }
+ inst.render(Object.new, :name => 'Joe').should.equal "Hey Joe!"
+ inst.should.be.compiled
+ end
+
+ class Person
+ attr_accessor :name
+ def initialize(name)
+ @name = name
+ end
+ end
+
+ it "evaluates template_source in the object scope provided" do
+ inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{@name}!' }
+ scope = Person.new('Joe')
+ inst.render(scope).should.equal "Hey Joe!"
+ end
+
+ it "evaluates template_source with yield support" do
+ inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{yield}!' }
+ inst.render(Object.new){ 'Joe' }.should.equal "Hey Joe!"
+ end
+end
View
40 test/spec_tilt_buildertemplate.rb
@@ -0,0 +1,40 @@
+require 'bacon'
+require 'tilt'
+require 'erb'
+
+describe "Tilt::BuilderTemplate" do
+ it "is registered for '.builder' files" do
+ Tilt['test.builder'].should.equal Tilt::BuilderTemplate
+ Tilt['test.xml.builder'].should.equal Tilt::BuilderTemplate
+ end
+
+ it "compiles and evaluates the template on #render" do
+ template = Tilt::BuilderTemplate.new { |t| "xml.em 'Hello World!'" }
+ template.render.should.equal "<em>Hello World!</em>\n"
+ end
+
+ it "supports locals" do
+ template = Tilt::BuilderTemplate.new { "xml.em('Hey ' + name + '!')" }
+ template.render(Object.new, :name => 'Joe').should.equal "<em>Hey Joe!</em>\n"
+ end
+
+ it "is evaluated in the object scope provided" do
+ template = Tilt::BuilderTemplate.new { "xml.em('Hey ' + @name + '!')" }
+ scope = Object.new
+ scope.instance_variable_set :@name, 'Joe'
+ template.render(scope).should.equal "<em>Hey Joe!</em>\n"
+ end
+
+ it "evaluates template_source with yield support" do
+ template = Tilt::BuilderTemplate.new { "xml.em('Hey ' + yield + '!')" }
+ template.render { 'Joe' }.should.equal "<em>Hey Joe!</em>\n"
+ end
+
+ it "calls a block directly when" do
+ template =
+ Tilt::BuilderTemplate.new do |t|
+ lambda { |xml| xml.em('Hey Joe!') }
+ end
+ template.render.should.equal "<em>Hey Joe!</em>\n"
+ end
+end
View
78 test/spec_tilt_erbtemplate.rb
@@ -0,0 +1,78 @@
+require 'bacon'
+require 'tilt'
+require 'erb'
+
+describe "Tilt::ERBTemplate" do
+ it "is registered for '.erb' files" do
+ Tilt['test.erb'].should.equal Tilt::ERBTemplate
+ Tilt['test.html.erb'].should.equal Tilt::ERBTemplate
+ end
+
+ it "is registered for '.rhtml' files" do
+ Tilt['test.rhtml'].should.equal Tilt::ERBTemplate
+ end
+
+ it "compiles and evaluates the template on #render" do
+ template = Tilt::ERBTemplate.new { |t| "Hello World!" }
+ template.render.should.equal "Hello World!"
+ end
+
+ it "supports locals" do
+ template = Tilt::ERBTemplate.new { 'Hey <%= name %>!' }
+ template.render(Object.new, :name => 'Joe').should.equal "Hey Joe!"
+ end
+
+ it "is evaluated in the object scope provided" do
+ template = Tilt::ERBTemplate.new { 'Hey <%= @name %>!' }
+ scope = Object.new
+ scope.instance_variable_set :@name, 'Joe'
+ template.render(scope).should.equal "Hey Joe!"
+ end
+
+ it "evaluates template_source with yield support" do
+ template = Tilt::ERBTemplate.new { 'Hey <%= yield %>!' }
+ template.render { 'Joe' }.should.equal "Hey Joe!"
+ end
+
+ it "reports the file and line properly in backtraces without locals" do
+ data = File.read(__FILE__).split("\n__END__\n").last
+ fail unless data[0] == ?<
+ template = Tilt::ERBTemplate.new('test.erb', 11) { data }
+ begin
+ template.render
+ flunk 'should have raised an exception'
+ rescue => boom
+ boom.should.be.kind_of NameError
+ line = boom.backtrace.first
+ file, line, meth = line.split(":")
+ file.should.equal 'test.erb'
+ line.should.equal '13'
+ end
+ end
+
+ it "reports the file and line properly in backtraces with locals" do
+ data = File.read(__FILE__).split("\n__END__\n").last
+ fail unless data[0] == ?<
+ template = Tilt::ERBTemplate.new('test.erb', 1) { data }
+ begin
+ template.render(nil, :name => 'Joe', :foo => 'bar')
+ flunk 'should have raised an exception'
+ rescue => boom
+ boom.should.be.kind_of RuntimeError
+ line = boom.backtrace.first
+ file, line, meth = line.split(":")
+ file.should.equal 'test.erb'
+ line.should.equal '6'
+ end
+ end
+end
+
+__END__
+<html>
+<body>
+ <h1>Hey <%= name %>!</h1>
+
+
+ <p><% fail %></p>
+</body>
+</html>
View
79 test/spec_tilt_hamltemplate.rb
@@ -0,0 +1,79 @@
+require 'bacon'
+require 'tilt'
+
+begin
+ class ::MockError < NameError
+ end
+
+ require 'haml'
+ describe "Tilt::HamlTemplate" do
+ it "is registered for '.haml' files" do
+ Tilt['test.haml'].should.equal Tilt::HamlTemplate
+ end
+
+ it "compiles and evaluates the template on #render" do
+ template = Tilt::HamlTemplate.new { |t| "%p Hello World!" }
+ template.render.should.equal "<p>Hello World!</p>\n"
+ end
+
+ it "supports locals" do
+ template = Tilt::HamlTemplate.new { "%p= 'Hey ' + name + '!'" }
+ template.render(Object.new, :name => 'Joe').should.equal "<p>Hey Joe!</p>\n"
+ end
+
+ it "is evaluated in the object scope provided" do
+ template = Tilt::HamlTemplate.new { "%p= 'Hey ' + @name + '!'" }
+ scope = Object.new
+ scope.instance_variable_set :@name, 'Joe'
+ template.render(scope).should.equal "<p>Hey Joe!</p>\n"
+ end
+
+ it "evaluates template_source with yield support" do
+ template = Tilt::HamlTemplate.new { "%p= 'Hey ' + yield + '!'" }
+ template.render { 'Joe' }.should.equal "<p>Hey Joe!</p>\n"
+ end
+
+ it "reports the file and line properly in backtraces without locals" do
+ data = File.read(__FILE__).split("\n__END__\n").last
+ fail unless data[0] == ?%
+ template = Tilt::HamlTemplate.new('test.haml', 10) { data }
+ begin
+ template.render
+ fail 'should have raised an exception'
+ rescue => boom
+ boom.should.be.kind_of NameError
+ line = boom.backtrace.first
+ file, line, meth = line.split(":")
+ file.should.equal 'test.haml'
+ line.should.equal '12'
+ end
+ end
+
+ it "reports the file and line properly in backtraces with locals" do
+ data = File.read(__FILE__).split("\n__END__\n").last
+ fail unless data[0] == ?%
+ template = Tilt::HamlTemplate.new('test.haml') { data }
+ begin
+ res = template.render(Object.new, :name => 'Joe', :foo => 'bar')
+ rescue => boom
+ boom.should.be.kind_of ::MockError
+ line = boom.backtrace.first
+ file, line, meth = line.split(":")
+ file.should.equal 'test.haml'
+ line.should.equal '5'
+ end
+ end
+ end
+
+rescue LoadError => boom
+ warn "Tilt::HamlTemplate (disabled)\n"
+end
+
+__END__
+%html
+ %body
+ %h1= "Hey #{name}"
+
+ = raise MockError
+
+ %p we never get here
View
19 test/spec_tilt_sasstemplate.rb
@@ -0,0 +1,19 @@
+require 'bacon'
+require 'tilt'
+
+begin
+ require 'haml'
+ describe "Tilt::SassTemplate" do
+ it "is registered for '.sass' files" do
+ Tilt['test.sass'].should.equal Tilt::SassTemplate
+ end
+
+ it "compiles and evaluates the template on #render" do
+ template = Tilt::SassTemplate.new { |t| "#main\n :background-color #0000ff" }
+ template.render.should.equal "#main {\n background-color: #0000ff; }\n"
+ end
+ end
+
+rescue LoadError => boom
+ warn "Tilt::SassTemplate (disabled)\n"
+end
View
35 test/spec_tilt_stringtemplate.rb
@@ -0,0 +1,35 @@
+require 'bacon'
+require 'tilt'
+
+describe "Tilt::StringTemplate" do
+ it "is registered for '.str' files" do
+ Tilt['test.str'].should.equal Tilt::StringTemplate
+ end
+
+ it "compiles and evaluates the template on #render" do
+ template = Tilt::StringTemplate.new { |t| "Hello World!" }
+ template.render.should.equal "Hello World!"
+ end
+
+ it "supports locals" do
+ template = Tilt::StringTemplate.new { 'Hey #{name}!' }
+ template.render(Object.new, :name => 'Joe').should.equal "Hey Joe!"
+ end
+
+ it "is evaluated in the object scope provided" do
+ template = Tilt::StringTemplate.new { 'Hey #{@name}!' }
+ scope = Object.new
+ scope.instance_variable_set :@name, 'Joe'
+ template.render(scope).should.equal "Hey Joe!"
+ end
+
+ it "evaluates template_source with yield support" do
+ template = Tilt::StringTemplate.new { 'Hey #{yield}!' }
+ template.render { 'Joe' }.should.equal "Hey Joe!"
+ end
+
+ it "renders multiline templates" do
+ template = Tilt::StringTemplate.new { "Hello\nWorld!\n" }
+ template.render.should.equal "Hello\nWorld!\n"
+ end
+end
View
39 tilt.gemspec
@@ -0,0 +1,39 @@
+Gem::Specification.new do |s|
+ s.specification_version = 2 if s.respond_to? :specification_version=
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+
+ s.name = 'tilt'
+ s.version = '0.1'
+ s.date = '2009-01-25'
+
+ s.description = "A TemplateFactoryBuilderFactory"
+ s.summary = "A TemplateFactoryBuilderFactory"
+
+ s.authors = ["Ryan Tomayko"]
+ s.email = "r@tomayko.com"
+
+ # = MANIFEST =
+ s.files = %w[
+ README.md
+ Rakefile
+ lib/tilt.rb
+ test/.bacon
+ test/spec_tilt.rb
+ test/spec_tilt_abstracttemplate.rb
+ test/spec_tilt_erbtemplate.rb
+ test/spec_tilt_hamltemplate.rb
+ test/spec_tilt_stringtemplate.rb
+ ]
+ # = MANIFEST =
+
+ s.test_files = s.files.select {|path| path =~ /^test\/spec_.*.rb/}
+
+ s.extra_rdoc_files = %w[COPYING]
+
+ s.has_rdoc = true
+ s.homepage = "http://github.com/rtomayko/tilt/"
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Tilt", "--main", "Tilt"]
+ s.require_paths = %w[lib]
+ s.rubyforge_project = 'wink'
+ s.rubygems_version = '1.1.1'
+end

0 comments on commit 137cd54

Please sign in to comment.