lib/tilt.rb in tilt-0.2 vs lib/tilt.rb in tilt-0.3
- old
+ new
@@ -1,33 +1,49 @@
module Tilt
+ VERSION = '0.3'
+
@template_mappings = {}
+ # Hash of template path pattern => template implementation
+ # class mappings.
+ def self.mappings
+ @template_mappings
+ end
+
# Register a template implementation by file extension.
def self.register(ext, template_class)
- ext = ext.sub(/^\./, '')
- @template_mappings[ext.downcase] = template_class
+ ext = ext.to_s.sub(/^\./, '')
+ 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)]
+ if template_class = self[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(/^[^.]*\.?/, '')
+ def self.[](file)
+ if @template_mappings.key?(pattern = file.to_s.downcase)
+ @template_mappings[pattern]
+ elsif @template_mappings.key?(pattern = File.basename(pattern))
+ @template_mappings[pattern]
+ else
+ while !pattern.empty?
+ if @template_mappings.key?(pattern)
+ return @template_mappings[pattern]
+ else
+ pattern = pattern.sub(/^[^.]*\.?/, '')
+ end
+ end
+ nil
end
- nil
end
# Base class for template implementations. Subclasses must implement
# the #compile! method and one of the #evaluate or #template_source
# methods.
@@ -50,30 +66,48 @@
# 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?
+ options, line = line, 1 if line.is_a?(Hash)
@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)
+ # Load template source and compile the template. The template is
+ # loaded and compiled the first time this method is called; subsequent
+ # calls are no-ops.
+ def compile
if @data.nil?
@data = @reader.call(self)
compile!
end
+ 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)
+ compile
evaluate scope, locals || {}, &block
end
+ # The basename of the template file.
+ def basename(suffix='')
+ File.basename(file, suffix) if file
+ end
+
+ # The template file's basename with all extensions chomped off.
+ def name
+ basename.split('.', 2).first if basename
+ end
+
# The filename used in backtraces to describe the template.
def eval_file
- @file || '(__TEMPLATE__)'
+ file || '(__TEMPLATE__)'
end
protected
# Do whatever preparation is necessary to "compile" the template.
# Called immediately after template #data is loaded. Instance variables
@@ -105,12 +139,14 @@
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."
+ if Thread.list.size > 1
+ warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
+ "explicit require '#{name}' suggested."
+ end
require name
end
end
# Extremely simple template cache implementation.
@@ -150,11 +186,11 @@
# 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')
+ @engine = ::ERB.new(data, options[:safe], options[:trim], '@_out_buf')
end
def template_source
@engine.src
end
@@ -186,10 +222,24 @@
end
end
end
%w[erb rhtml].each { |ext| register ext, ERBTemplate }
+ # Erubis template implementation. See:
+ # http://www.kuwata-lab.com/erubis/
+ #
+ # It's suggested that your program require 'erubis' at load
+ # time when using this template engine.
+ class ErubisTemplate < ERBTemplate
+ def compile!
+ require_template_library 'erubis' unless defined?(::Erubis)
+ Erubis::Eruby.class_eval(%Q{def add_preamble(src) src << "@_out_buf = _buf = '';" end})
+ @engine = ::Erubis::Eruby.new(data)
+ end
+ end
+ register 'erubis', ErubisTemplate
+
# Haml template implementation. See:
# http://haml.hamptoncatlin.com/
#
# It's suggested that your program require 'haml' at load
# time when using this template engine.
@@ -291,7 +341,109 @@
def evaluate(scope, locals, &block)
@engine.to_html
end
end
register 'markdown', RDiscountTemplate
+ register 'md', RDiscountTemplate
+ # Mustache is written and maintained by Chris Wanstrath. See:
+ # http://github.com/defunkt/mustache
+ #
+ # It's suggested that your program require 'mustache' at load
+ # time when using this template engine.
+ #
+ # Mustache templates support the following options:
+ #
+ # * :view - The Mustache subclass that should be used a the view. When
+ # this option is specified, the template file will be determined from
+ # the view class, and the :namespace and :mustaches options are
+ # irrelevant.
+ #
+ # * :namespace - The class or module where View classes are located.
+ # If you have Hurl::App::Views, namespace should be Hurl:App. This
+ # defaults to Object, causing ::Views to be searched for classes.
+ #
+ # * :mustaches - Where mustache views (.rb files) are located, or nil
+ # disable auto-requiring of views based on template names. By default,
+ # the view file is assumed to be in the same directory as the template
+ # file.
+ #
+ # All other options are assumed to be attribute writer's on the Mustache
+ # class and are set when a template is compiled. They are:
+ #
+ # * :path - The base path where mustache templates (.html files) are
+ # located. This defaults to the current working directory.
+ #
+ # * :template_extension - The file extension used on mustache templates.
+ # The default is 'html'.
+ #
+ class MustacheTemplate < Template
+ attr_reader :engine
+
+ # Locates and compiles the Mustache object used to create new views. The
+ def compile!
+ require_template_library 'mustache' unless defined?(::Mustache)
+
+ @view_name = Mustache.classify(name.to_s)
+ @namespace = options[:namespace] || Object
+
+ # Figure out which Mustache class to use.
+ @engine =
+ if options[:view]
+ @view_name = options[:view].name
+ options[:view]
+ elsif @namespace.const_defined?(:Views) &&
+ @namespace::Views.const_defined?(@view_name)
+ @namespace::Views.const_get(@view_name)
+ elsif load_mustache_view
+ engine = @namespace::Views.const_get(@view_name)
+ engine.template = data
+ engine
+ else
+ Mustache
+ end
+
+ # set options on the view class
+ options.each do |key, value|
+ next if %w[view namespace mustaches].include?(key.to_s)
+ @engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
+ end
+ end
+
+ def evaluate(scope=nil, locals={}, &block)
+ # Create a new instance for playing with
+ instance = @engine.new
+
+ # Copy instance variables from scope to the view
+ scope.instance_variables.each do |name|
+ instance.instance_variable_set(name, scope.instance_variable_get(name))
+ end
+
+ # Locals get added to the view's context
+ locals.each do |local, value|
+ instance[local] = value
+ end
+
+ # If we're passed a block it's a subview. Sticking it in yield
+ # lets us use {{yield}} in layout.html to render the actual page.
+ instance[:yield] = block.call if block
+
+ instance.template = data unless instance.compiled?
+
+ instance.to_html
+ end
+
+ # Require the mustache view lib if it exists.
+ def load_mustache_view
+ return if name.nil?
+ path = "#{options[:mustaches]}/#{name}.rb"
+ if options[:mustaches] && File.exist?(path)
+ require path.chomp('.rb')
+ path
+ elsif File.exist?(path = file.sub(/\.[^\/]+$/, '.rb'))
+ require path.chomp('.rb')
+ path
+ end
+ end
+ end
+ register 'mustache', MustacheTemplate
end