class Tilt::Template

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

def _dup_string_if_frozen(string)

def _dup_string_if_frozen(string)
  +string
end

def _dup_string_if_frozen(string)

def _dup_string_if_frozen(string)
  string.frozen? ? string.dup : string
end

def basename(suffix='')

The basename of the template file.
def basename(suffix='')
  File.basename(@file, suffix) if @file
end

def binary(string)

def binary(string)
  original_encoding = string.encoding
  string.force_encoding(Encoding::BINARY)
  yield
ensure
  string.force_encoding(original_encoding)
end

def bind_compiled_method(method_source, offset, scope_class)

def bind_compiled_method(method_source, offset, scope_class)
  path = compiled_path
  if path && scope_class.name
    path = path.dup
    if defined?(@compiled_path_counter)
      path << '-' << @compiled_path_counter.succ!
    else
      @compiled_path_counter = "0".dup
    end
    path << ".rb"
    # Wrap method source in a class block for the scope, so constant lookup works
    if freeze_string_literals?
      method_source_prefix = "# frozen-string-literal: true\n"
      method_source = method_source.sub(/\A# frozen-string-literal: true\n/, '')
    end
    method_source = "#{method_source_prefix}class #{scope_class.name}\n#{method_source}\nend"
    load_compiled_method(path, method_source)
  else
    if path
      warn "compiled_path (#{compiled_path.inspect}) ignored on template with anonymous scope_class (#{scope_class.inspect})"
    end
    eval_compiled_method(method_source, offset, scope_class)
  end
end

def compile_template_method(local_keys, scope_class=nil)

def compile_template_method(local_keys, scope_class=nil)
  source, offset = precompiled(local_keys)
  if @fixed_locals
    method_args = @fixed_locals
  else
    method_args = "(locals)"
    local_code = local_extraction(local_keys)
  end
  method_name = "__tilt_#{Thread.current.object_id.abs}"
  method_source = String.new
  method_source.force_encoding(source.encoding)
  if freeze_string_literals?
    method_source << "# frozen-string-literal: true\n"
  end
  # Don't indent method source, to avoid indentation warnings when using compiled paths
  method_source << "::Tilt::TOPOBJECT.class_eval do\ndef #{method_name}#{method_args}\n#{local_code}\n"
  offset += method_source.count("\n")
  method_source << source
  method_source << "\nend;end;"
  bind_compiled_method(method_source, offset, scope_class)
  unbind_compiled_method(method_name)
end

def compiled_method(locals_keys, scope_class=nil)

Tilt's normal rendering.
directly on the scope class, which are much faster to call than
Returns an UnboundMethod, which can be used to define methods
The compiled method for the locals keys and scope_class provided.
def compiled_method(locals_keys, scope_class=nil)
  if @fixed_locals
    if @scope_class
      return @compiled_method
    else
      key = scope_class
    end
  elsif @scope_class
    key = locals_keys.dup.freeze
  else
    key = [scope_class, locals_keys].freeze
  end
  LOCK.synchronize do
    if meth = @compiled_method[key]
      return meth
    end
  end
  meth = compile_template_method(locals_keys, scope_class)
  LOCK.synchronize do
    @compiled_method[key] = meth
  end
  meth
end

def compiled_path=(path)

instead of this method in new code.
is recommended to use the :compiled_path template option
template is compiled during initialization in that case. It
this after the template is created has no effect, since the
:scope_class template option, and using fixed_locals, calling
has affect for future template compilations. When using the
:compiled_path template option. Note that this only
Set the prefix to use for compiled paths, similar to using the
def compiled_path=(path)
  if path
    # Use expanded paths when loading, since that is helpful
    # for coverage.  Remove any .rb suffix, since that will
    # be added back later.
    path = File.expand_path(path.sub(/\.rb\z/i, ''))
  end
  @compiled_path = path
end

def default_mime_type

Use `.metadata[:mime_type]` instead.
def default_mime_type
  metadata[:mime_type]
end

def default_mime_type=(value)

Use `.metadata[:mime_type] = val` instead.
def default_mime_type=(value)
  metadata[:mime_type] = value
end

def eval_compiled_method(method_source, offset, scope_class)

def eval_compiled_method(method_source, offset, scope_class)
  (scope_class || Object).class_eval(method_source, eval_file, line - offset)
end

def eval_file

The filename used in backtraces to describe the template.
def eval_file
  @file || '(__TEMPLATE__)'
end

def evaluate(scope, locals, &block)

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

locals specified and with support for yielding to the block.
evaluation is guaranteed to be performed in the scope object with the
Execute the compiled template and return the result string. Template
def evaluate(scope, locals, &block)
  if @fixed_locals
    locals_keys = EMPTY_ARRAY
  else
    locals_keys = locals.keys
    locals_keys.sort!{|x, y| x.to_s <=> y.to_s}
  end
  unless scope_class = @scope_class
    scope_class = case scope
    when Object
      Module === scope ? scope : scope.class
    else
      # :nocov:
      USE_BIND_CALL ? CLASS_METHOD.bind_call(scope) : CLASS_METHOD.bind(scope).call
      # :nocov:
    end
  end
  evaluate_method(compiled_method(locals_keys, scope_class), scope, locals, &block)
end

def evaluate_method(method, scope, locals, &block)

def evaluate_method(method, scope, locals, &block)
  if @fixed_locals
    method.bind_call(scope, **locals, &block)
  else
    method.bind_call(scope, locals, &block)
  end
end

def evaluate_method(method, scope, locals, &block)

def evaluate_method(method, scope, locals, &block)
  if @fixed_locals
    if locals.empty?
      # Empty keyword splat on Ruby 2.0-2.6 passes empty hash
      method.bind(scope).call(&block)
    else
      method.bind(scope).call(**locals, &block)
    end
  else
    method.bind(scope).call(locals, &block)
  end
end

def extract_encoding(script, &block)

def extract_encoding(script, &block)
  extract_magic_comment(script, &block) || script.encoding
end

def extract_fixed_locals

argument string will be used when defining the template method if given.
surrounded by parentheses if there are fixed locals. The method
if there are no fixed locals specified, or a method argument string
Extract fixed locals from the template code string. Should return nil
def extract_fixed_locals
  if @data.is_a?(String) && (match = /\#\s*locals:\s*(\(.*\))/.match(@data))
    match[1]
  end
end

def extract_magic_comment(script)

def extract_magic_comment(script)
  was_frozen = script.frozen?
  script = _dup_string_if_frozen(script)
  if was_frozen
    yield script
  end
  binary(script) do
    script[/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/n, 1]
  end
end

def fixed_locals?

Whether the template uses fixed locals.
def fixed_locals?
  @fixed_locals ? true : false
end

def freeze_string_literals?

def freeze_string_literals?
  false
end

def initialize(file=nil, line=nil, options=nil)

uses the class of the scope provided to render.
:scope_class :: Force the scope class used for the method. By default,
and no embedded locals are found (or scanned for).
only used if :fixed_locals is not provided
:default_fixed_locals :: Similar to fixed_locals, but lowest priority,
and extracted from the template code.
:extract_fixed_locals :: Whether embedded fixed locals should be scanned for
disable the scan for embedded fixed locals.
surrounded by parentheses. Can be set to false to
containing the parameters for the compiled method,
extracting locals from that. Should be a string
the locals hash as a positional argument, and
the method with a splat of locals, instead of passing
:fixed_locals :: Force a specific method parameter signature, and call
an encoding magic comment.
:skip_compiled_encoding_detection :: Do not scan template code for
encoding.
:default_encoding :: Force the encoding of the template to the given

libraries:
are used by Tilt::Template itself and not the underlying template
All arguments are optional. The following options are respected and

a block is required.
it should read template data and return as a String. When file is nil,
default, template data is read from the file. When a block is given,
Create a new template with the file, line, and options specified. By
def initialize(file=nil, line=nil, options=nil)
  @file, @line, @options = nil, 1, nil
  process_arg(options)
  process_arg(line)
  process_arg(file)
  raise ArgumentError, "file or block required" unless @file || block_given?
  @options ||= {}
  # Force a specific scope class, instead of using the class of the provided
  # scope as the scope class.
  @scope_class = @options.delete :scope_class
  # Force the encoding of the input data
  @default_encoding = @options.delete :default_encoding
  # Skip encoding detection from magic comments and forcing that encoding
  # for compiled templates
  @skip_compiled_encoding_detection = @options.delete :skip_compiled_encoding_detection
  # Compiled path to use.  This must be specified as an option if
  # providing the :scope_class option and using fixed locals,
  # since template compilation occurs during initialization in that case.
  if compiled_path = @options.delete(:compiled_path)
    self.compiled_path = compiled_path
  end
  # load template data and prepare (uses binread to avoid encoding issues)
  @data = block_given? ? yield(self) : read_template_file
  if @data.respond_to?(:force_encoding)
    if default_encoding
      @data = _dup_string_if_frozen(@data)
      @data.force_encoding(default_encoding)
    end
    if !@data.valid_encoding?
      raise Encoding::InvalidByteSequenceError, "#{eval_file} is not valid #{@data.encoding}"
    end
  end
  set_fixed_locals
  prepare
  set_compiled_method_cache
end

def load_compiled_method(path, method_source)

def load_compiled_method(path, method_source)
  # Write to a temporary path specific to the current process, and
  # rename after writing. This prevents issues during parallel
  # coverage testing.
  tmp_path = "#{path}-#{$$}"
  File.binwrite(tmp_path, method_source)
  File.rename(tmp_path, path)
  # Use load and not require, so unbind_compiled_method does not
  # break if the same path is used more than once.
  load path
end

def local_extraction(local_keys)

def local_extraction(local_keys)
  assignments = 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
  s = "locals = locals[:locals]"
  if assignments.delete(s)
    # If there is a locals key itself named `locals`, delete it from the ordered keys so we can
    # assign it last. This is important because the assignment of all other locals depends on the
    # `locals` local variable still matching the `locals` method argument given to the method
    # created in `#compile_template_method`.
    assignments << s
  end
  assignments.join("\n")
end

def metadata

metadata.
An empty Hash that the template engine can populate with various
def metadata
  @metadata ||= {}
end

def metadata

metadata.
An empty Hash that the template engine can populate with various
def metadata
  if respond_to?(:allows_script?)
    self.class.metadata.merge(:allows_script => allows_script?)
  else
    self.class.metadata
  end
end

def name

The template file's basename with all extensions chomped off.
def name
  if bname = basename
    bname.split('.', 2).first
  end
end

def precompiled(local_keys)

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

offset is the integer line offset where line reporting should begin.
source is the string containing (Ruby) source code for the template and
postamble and returns a two-tuple of the form: [source, offset], where
Generates all template source by combining the preamble, template, and
def precompiled(local_keys)
  preamble = precompiled_preamble(local_keys)
  template = precompiled_template(local_keys)
  postamble = precompiled_postamble(local_keys)
  source = String.new
  unless skip_compiled_encoding_detection?
    # Ensure that our generated source code has the same encoding as the
    # the source code generated by the template engine.
    template_encoding = extract_encoding(template){|t| template = t}
    if template.encoding != template_encoding
      # template should never be frozen here. If it was frozen originally,
      # then extract_encoding should yield a dup.
      template.force_encoding(template_encoding)
    end
  end
  source.force_encoding(template.encoding)
  source << preamble << "\n" << template << "\n" << postamble
  [source, preamble.count("\n")+1]
end

def precompiled_postamble(local_keys)

def precompiled_postamble(local_keys)
  ''
end

def precompiled_preamble(local_keys)

def precompiled_preamble(local_keys)
  ''
end

def precompiled_template(local_keys)

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

def prepare

Empty by default as some subclasses do not need separate preparation.

variables set in this method are available when #evaluate is called.
engine. Called immediately after template data is loaded. Instance
Do whatever preparation is necessary to setup the underlying template
def prepare
end

def process_arg(arg)

def process_arg(arg)
  if 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
end

def read_template_file

def read_template_file
  data = File.binread(file)
  # Set it to the default external (without verifying)
  # :nocov:
  data.force_encoding(Encoding.default_external) if Encoding.default_external
  # :nocov:
  data
end

def render(scope=nil, locals=nil, &block)

+yield+.
block is given, it is typically available within the template via
Render the template in the given scope with the locals specified. If a
def render(scope=nil, locals=nil, &block)
  evaluate(scope || Object.new, locals || EMPTY_HASH, &block)
end

def set_compiled_method_cache

def set_compiled_method_cache
  @compiled_method = if @fixed_locals && @scope_class
    # No hash needed, only a single compiled method per template.
    compile_template_method(EMPTY_ARRAY, @scope_class)
  else
    {}
  end
end

def set_fixed_locals

be determined.
Set the fixed locals for the template, which may be nil if no fixed locals can
def set_fixed_locals
  fixed_locals = @options.delete(:fixed_locals)
  extract_fixed_locals = @options.delete(:extract_fixed_locals)
  default_fixed_locals = @options.delete(:default_fixed_locals)
  if fixed_locals.nil?
    if extract_fixed_locals.nil?
      extract_fixed_locals = Tilt.extract_fixed_locals
    end
    if extract_fixed_locals
      fixed_locals = extract_fixed_locals()
    end
    if fixed_locals.nil?
      fixed_locals = default_fixed_locals
    end
  end
  @fixed_locals = fixed_locals
end

def skip_compiled_encoding_detection?

def skip_compiled_encoding_detection?
  @skip_compiled_encoding_detection
end

def unbind_compiled_method(method_name)

def unbind_compiled_method(method_name)
  method = TOPOBJECT.instance_method(method_name)
  TOPOBJECT.class_eval { remove_method(method_name) }
  method
end