mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
Bundle tilt
This commit is contained in:
parent
d4f2c51a02
commit
aabf1c27b1
1 changed files with 297 additions and 0 deletions
297
lib/tilt.rb
Normal file
297
lib/tilt.rb
Normal file
|
@ -0,0 +1,297 @@
|
|||
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.to_s.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 Template
|
||||
# Template source; 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.
|
||||
# Called immediately after template #data is loaded. Instance variables
|
||||
# set in this method are available when #evaluate is called.
|
||||
#
|
||||
# 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 Template#evaluate implementation 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
|
||||
|
||||
def require_template_library(name)
|
||||
warn "WARN: loading '#{name}' library in a non thread-safe way; " +
|
||||
"explicit require '#{name}' suggested."
|
||||
require name
|
||||
end
|
||||
end
|
||||
|
||||
# Extremely simple template cache implementation.
|
||||
class Cache
|
||||
def initialize
|
||||
@cache = {}
|
||||
end
|
||||
|
||||
def fetch(*key)
|
||||
key = key.map { |part| part.to_s }.join(":")
|
||||
@cache[key] ||= yield
|
||||
end
|
||||
|
||||
def clear
|
||||
@cache = {}
|
||||
end
|
||||
end
|
||||
|
||||
# Template Implementations ================================================
|
||||
|
||||
# The template source is evaluated as a Ruby string. The #{} interpolation
|
||||
# syntax can be used to generated dynamic output.
|
||||
class StringTemplate < Template
|
||||
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
|
||||
#
|
||||
# It's suggested that your program require 'erb' at load
|
||||
# time when using this template engine.
|
||||
class ERBTemplate < Template
|
||||
def compile!
|
||||
require_template_library 'erb' unless defined?(::ERB)
|
||||
@engine = ::ERB.new(data, nil, nil, '@_out_buf')
|
||||
end
|
||||
|
||||
def template_source
|
||||
@engine.src
|
||||
end
|
||||
|
||||
def evaluate(scope, locals, &block)
|
||||
source, offset = local_assignment_code(locals)
|
||||
source = [source, template_source].join("\n")
|
||||
|
||||
original_out_buf =
|
||||
scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
|
||||
scope.instance_variable_get(:@_out_buf)
|
||||
|
||||
scope.instance_eval source, eval_file, line - offset
|
||||
|
||||
output = scope.instance_variable_get(:@_out_buf)
|
||||
scope.instance_variable_set(:@_out_buf, original_out_buf)
|
||||
|
||||
output
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# ERB generates a line to specify the character coding of the generated
|
||||
# source in 1.9. Account for this in the line offset.
|
||||
if RUBY_VERSION >= '1.9.0'
|
||||
def local_assignment_code(locals)
|
||||
source, offset = super
|
||||
[source, offset + 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
%w[erb rhtml].each { |ext| register ext, ERBTemplate }
|
||||
|
||||
# Haml template implementation. See:
|
||||
# http://haml.hamptoncatlin.com/
|
||||
#
|
||||
# It's suggested that your program require 'haml' at load
|
||||
# time when using this template engine.
|
||||
class HamlTemplate < Template
|
||||
def compile!
|
||||
require_template_library 'haml' unless defined?(::Haml::Engine)
|
||||
@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.
|
||||
#
|
||||
# It's suggested that your program require 'sass' at load
|
||||
# time when using this template engine.
|
||||
class SassTemplate < Template
|
||||
def compile!
|
||||
require_template_library 'sass' unless defined?(::Sass::Engine)
|
||||
@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/
|
||||
#
|
||||
# It's suggested that your program require 'builder' at load
|
||||
# time when using this template engine.
|
||||
class BuilderTemplate < Template
|
||||
def compile!
|
||||
require_template_library '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
|
||||
|
||||
# Liquid template implementation. See:
|
||||
# http://liquid.rubyforge.org/
|
||||
#
|
||||
# LiquidTemplate does not support scopes or yield blocks.
|
||||
#
|
||||
# It's suggested that your program require 'liquid' at load
|
||||
# time when using this template engine.
|
||||
class LiquidTemplate < Template
|
||||
def compile!
|
||||
require_template_library 'liquid' unless defined?(::Liquid::Template)
|
||||
@engine = ::Liquid::Template.parse(data)
|
||||
end
|
||||
|
||||
def evaluate(scope, locals, &block)
|
||||
locals = locals.inject({}) { |hash,(k,v)| hash[k.to_s] = v ; hash }
|
||||
@engine.render(locals)
|
||||
end
|
||||
end
|
||||
register 'liquid', LiquidTemplate
|
||||
|
||||
# Discount Markdown implementation.
|
||||
class RDiscountTemplate < Template
|
||||
def compile!
|
||||
require_template_library 'rdiscount' unless defined?(::RDiscount)
|
||||
@engine = RDiscount.new(data)
|
||||
end
|
||||
|
||||
def evaluate(scope, locals, &block)
|
||||
@engine.to_html
|
||||
end
|
||||
end
|
||||
register 'markdown', RDiscountTemplate
|
||||
|
||||
end
|
Loading…
Reference in a new issue