forked from rtomayko/tilt
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
Showing
14 changed files
with
821 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
File renamed without changes.
Oops, something went wrong.