class Tilt::Template

Base class for template implementations. Subclasses must implement the prepare method and one of the evaluate or precompiled_template methods.

Attributes

data[R]

Template source; loaded from a file or given directly.

file[R]

The name of the file where the template data was loaded from.

line[R]

The line number in file where template data was loaded from.

options[R]

A Hash of template engine specific options. This is passed directly to the underlying engine and is not used by the generic template interface.

Public Class Methods

default_mime_type() click to toggle source

@deprecated Use `.metadata` instead.

# File lib/tilt/template.rb, line 37
def default_mime_type
  metadata[:mime_type]
end
default_mime_type=(value) click to toggle source

@deprecated Use `.metadata = val` instead.

# File lib/tilt/template.rb, line 42
def default_mime_type=(value)
  metadata[:mime_type] = value
end
metadata() click to toggle source

An empty Hash that the template engine can populate with various metadata.

# File lib/tilt/template.rb, line 32
def metadata
  @metadata ||= {}
end
new(file=nil, line=1, options={}, &block) click to toggle source

Create a new template with the file, line, and options specified. By default, template data is read from the file. When a block is given, it should read template data and return as a String. When file is nil, a block is required.

All arguments are optional.

# File lib/tilt/template.rb, line 53
def initialize(file=nil, line=1, options={}, &block)
  @file, @line, @options = nil, 1, {}

  [options, line, file].compact.each do |arg|
    case
    when arg.respond_to?(:to_str)  ; @file = arg.to_str
    when arg.respond_to?(:to_int)  ; @line = arg.to_int
    when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
    when arg.respond_to?(:path)    ; @file = arg.path
    when arg.respond_to?(:to_path) ; @file = arg.to_path
    else raise TypeError, "Can't load the template file. Pass a string with a path " +
      "or an object that responds to 'to_str', 'path' or 'to_path'"
    end
  end

  raise ArgumentError, "file or block required" if (@file || block).nil?

  # used to hold compiled template methods
  @compiled_method = {}

  # used on 1.9 to set the encoding if it is not set elsewhere (like a magic comment)
  # currently only used if template compiles to ruby
  @default_encoding = @options.delete :default_encoding

  # load template data and prepare (uses binread to avoid encoding issues)
  @reader = block || lambda { |t| read_template_file }
  @data = @reader.call(self)

  if @data.respond_to?(:force_encoding)
    @data.force_encoding(default_encoding) if default_encoding

    if !@data.valid_encoding?
      raise Encoding::InvalidByteSequenceError, "#{eval_file} is not valid #{@data.encoding}"
    end
  end

  prepare
end

Public Instance Methods

basename(suffix='') click to toggle source

The basename of the template file.

# File lib/tilt/template.rb, line 105
def basename(suffix='')
  File.basename(file, suffix) if file
end
eval_file() click to toggle source

The filename used in backtraces to describe the template.

# File lib/tilt/template.rb, line 115
def eval_file
  file || '(__TEMPLATE__)'
end
metadata() click to toggle source

An empty Hash that the template engine can populate with various metadata.

# File lib/tilt/template.rb, line 121
def metadata
  if respond_to?(:allows_script?)
    self.class.metadata.merge(:allows_script => allows_script?)
  else
    self.class.metadata
  end
end
name() click to toggle source

The template file's basename with all extensions chomped off.

# File lib/tilt/template.rb, line 110
def name
  basename.split('.', 2).first if basename
end
render(scope=nil, locals={}, &block) click to toggle source

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.

# File lib/tilt/template.rb, line 95
def render(scope=nil, locals={}, &block)
  scope ||= Object.new
  current_template = Thread.current[:tilt_current_template]
  Thread.current[:tilt_current_template] = self
  evaluate(scope, locals || {}, &block)
ensure
  Thread.current[:tilt_current_template] = current_template
end

Protected Instance Methods

default_encoding() click to toggle source

The encoding of the source data. Defaults to the default_encoding-option if present. You may override this method in your template class if you have a better hint of the data's encoding.

# File lib/tilt/template.rb, line 137
def default_encoding
  @default_encoding
end
evaluate(scope, locals, &block) click to toggle source

Execute the compiled template and return the result string. Template evaluation is guaranteed to be performed in the scope object with the locals specified and with support for yielding to the block.

This method is only used by source generating templates. Subclasses that override render() may not support all features.

# File lib/tilt/template.rb, line 156
def evaluate(scope, locals, &block)
  locals_keys = locals.keys
  if SYMBOL_ARRAY_SORTABLE
    locals_keys.sort!
  else
    locals_keys.sort!{|x, y| x.to_s <=> y.to_s}
  end
  method = compiled_method(locals_keys)
  method.bind(scope).call(locals, &block)
end
precompiled(local_keys) click to toggle source

Generates all template source by combining the preamble, template, and postamble and returns a two-tuple of the form: [source, offset], where source is the string containing (Ruby) source code for the template and offset is the integer line offset where line reporting should begin.

Template subclasses may override this method when they need complete control over source generation or want to adjust the default line offset. In most cases, overriding the precompiled_template method is easier and more appropriate.

# File lib/tilt/template.rb, line 176
def precompiled(local_keys)
  preamble = precompiled_preamble(local_keys)
  template = precompiled_template(local_keys)
  postamble = precompiled_postamble(local_keys)
  source = ''

  # Ensure that our generated source code has the same encoding as the
  # the source code generated by the template engine.
  if source.respond_to?(:force_encoding)
    template_encoding = extract_encoding(template)

    source.force_encoding(template_encoding)
    template.force_encoding(template_encoding)
  end

  source << preamble << "\n" << template << "\n" << postamble

  [source, preamble.count("\n")+1]
end
precompiled_postamble(local_keys) click to toggle source
# File lib/tilt/template.rb, line 210
def precompiled_postamble(local_keys)
  ''
end
precompiled_preamble(local_keys) click to toggle source
# File lib/tilt/template.rb, line 206
def precompiled_preamble(local_keys)
  ''
end
precompiled_template(local_keys) click to toggle source

A string containing the (Ruby) source code for the template. The default #evaluate implementation requires either this method or the precompiled method be overridden. When defined, the base Template guarantees correct file/line handling, locals support, custom scopes, proper encoding, and support for template compilation.

# File lib/tilt/template.rb, line 202
def precompiled_template(local_keys)
  raise NotImplementedError
end
prepare() click to toggle source

Do whatever preparation is necessary to setup the underlying template engine. 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.

# File lib/tilt/template.rb, line 146
def prepare
  raise NotImplementedError
end

Private Instance Methods

binary(string) { || ... } click to toggle source
# File lib/tilt/template.rb, line 287
def binary(string)
  original_encoding = string.encoding
  string.force_encoding(Encoding::BINARY)
  yield
ensure
  string.force_encoding(original_encoding)
end
compile_template_method(local_keys) click to toggle source
# File lib/tilt/template.rb, line 244
def compile_template_method(local_keys)
  source, offset = precompiled(local_keys)
  local_code = local_extraction(local_keys)

  method_name = "__tilt_#{Thread.current.object_id.abs}"
  method_source = ""

  if method_source.respond_to?(:force_encoding)
    method_source.force_encoding(source.encoding) 
  end

  method_source << <<-RUBY
    TOPOBJECT.class_eval do
      def #{method_name}(locals)
        Thread.current[:tilt_vars] = [self, locals]
        class << self
          this, locals = Thread.current[:tilt_vars]
          this.instance_eval do
            #{local_code}
  RUBY
  offset += method_source.count("\n")
  method_source << source
  method_source << "\nend;end;end;end"
  Object.class_eval(method_source, eval_file, line - offset)
  unbind_compiled_method(method_name)
end
compiled_method(locals_keys) click to toggle source

The compiled method for the locals keys provided.

# File lib/tilt/template.rb, line 228
def compiled_method(locals_keys)
  LOCK.synchronize do
    @compiled_method[locals_keys] ||= compile_template_method(locals_keys)
  end
end
extract_encoding(script) click to toggle source
# File lib/tilt/template.rb, line 277
def extract_encoding(script)
  extract_magic_comment(script) || script.encoding
end
extract_magic_comment(script) click to toggle source
# File lib/tilt/template.rb, line 281
def extract_magic_comment(script)
  binary(script) do
    script[/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/n, 1]
  end
end
local_extraction(local_keys) click to toggle source
# File lib/tilt/template.rb, line 234
def local_extraction(local_keys)
  local_keys.map do |k|
    if k.to_s =~ /\A[a-z_][a-zA-Z_0-9]*\z/
      "#{k} = locals[#{k.inspect}]"
    else
      raise "invalid locals key: #{k.inspect} (keys must be variable names)"
    end
  end.join("\n")
end
read_template_file() click to toggle source

!@endgroup

# File lib/tilt/template.rb, line 218
def read_template_file
  data = File.open(file, 'rb') { |io| io.read }
  if data.respond_to?(:force_encoding)
    # Set it to the default external (without verifying)
    data.force_encoding(Encoding.default_external) if Encoding.default_external
  end
  data
end
unbind_compiled_method(method_name) click to toggle source
# File lib/tilt/template.rb, line 271
def unbind_compiled_method(method_name)
  method = TOPOBJECT.instance_method(method_name)
  TOPOBJECT.class_eval { remove_method(method_name) }
  method
end