class Erubi::Engine

Constants

DEFAULT_REGEXP

The default regular expression used for scanning.

Attributes

bufvar[R]

The variable name used for the buffer variable.

filename[R]

The filename of the template, if one was given.

src[R]

The frozen ruby source code generated from the template, which can be evaled.

Public Class Methods

new(input, properties={}) click to toggle source

Initialize a new Erubi::Engine. Options:

:bufval

The value to use for the buffer variable, as a string (default '::String.new').

:bufvar

The variable name to use for the buffer variable, as a string.

:chain_appends

Whether to chain <tt><<</t> calls to the buffer variable. Offers better performance, but can cause issues when the buffer variable is reassigned during template rendering (default false).

:ensure

Wrap the template in a begin/ensure block restoring the previous value of bufvar.

:escapefunc

The function to use for escaping, as a string (default: '::Erubi.h').

:escape

Whether to make <%= escape by default, and <%== not escape by default.

:escape_html

Same as :escape, with lower priority.

:filename

The filename for the template.

:freeze

Whether to enable add a frozen_string_literal: true magic comment at the top of the resulting source code. Note this may cause problems if you are wrapping the resulting source code in other code, because the magic comment only has an effect at the beginning of the file, and having the magic comment later in the file can trigger warnings.

:freeze_template_literals

Whether to suffix all literal strings for template code with .freeze (default: true on Ruby 2.1+, false on Ruby 2.0 and older). Can be set to false on Ruby 2.3+ when frozen string literals are enabled in order to improve performance.

:literal_prefix

The prefix to output when using escaped tag delimiters (default '<%').

:literal_postfix

The postfix to output when using escaped tag delimiters (default '%>').

:outvar

Same as :bufvar, with lower priority.

:postamble

The postamble for the template, by default returns the resulting source code.

:preamble

The preamble for the template, by default initializes the buffer variable.

:regexp

The regexp to use for scanning.

:src

The initial value to use for the source code, an empty string by default.

:trim

Whether to trim leading and trailing whitespace, true by default.

# File lib/erubi.rb, line 93
def initialize(input, properties={})
  @escape = escape = properties.fetch(:escape){properties.fetch(:escape_html, false)}
  trim       = properties[:trim] != false
  @filename  = properties[:filename]
  @bufvar = bufvar = properties[:bufvar] || properties[:outvar] || "_buf"
  bufval = properties[:bufval] || '::String.new'
  regexp = properties[:regexp] || DEFAULT_REGEXP
  literal_prefix = properties[:literal_prefix] || '<%'
  literal_postfix = properties[:literal_postfix] || '%>'
  preamble   = properties[:preamble] || "#{bufvar} = #{bufval};"
  postamble  = properties[:postamble] || "#{bufvar}.to_s\n"
  @chain_appends = properties[:chain_appends]
  @text_end = if properties.fetch(:freeze_template_literals, FREEZE_TEMPLATE_LITERALS)
    "'.freeze"
  else
    "'"
  end

  @buffer_on_stack = false
  @src = src = properties[:src] || String.new
  src << "# frozen_string_literal: true\n" if properties[:freeze]
  if properties[:ensure]
    src << "begin; __original_outvar = #{bufvar}"
    if SKIP_DEFINED_FOR_INSTANCE_VARIABLE && /\A@[^@]/ =~ bufvar
      src << "; "
    else
      src << " if defined?(#{bufvar}); "
    end
  end

  unless @escapefunc = properties[:escapefunc]
    if escape
      @escapefunc = '__erubi.h'
      src << "__erubi = ::Erubi; "
    else
      @escapefunc = '::Erubi.h'
    end
  end

  src << preamble

  pos = 0
  is_bol = true
  input.scan(regexp) do |indicator, code, tailch, rspace|
    match = Regexp.last_match
    len  = match.begin(0) - pos
    text = input[pos, len]
    pos  = match.end(0)
    ch   = indicator ? indicator[RANGE_FIRST] : nil

    lspace = nil

    unless ch == '='
      if text.empty?
        lspace = "" if is_bol
      elsif text[RANGE_LAST] == "\n"
        lspace = ""
      else
        rindex = text.rindex("\n")
        if rindex
          range = rindex+1..-1
          s = text[range]
          if /\A[ \t]*\z/.send(MATCH_METHOD, s)
            lspace = s
            text[range] = ''
          end
        else
          if is_bol && /\A[ \t]*\z/.send(MATCH_METHOD, text)
            lspace = text
            text = ''
          end
        end
      end
    end

    is_bol = rspace
    add_text(text)
    case ch
    when '='
      rspace = nil if tailch && !tailch.empty?
      add_expression(indicator, code)
      add_text(rspace) if rspace
    when nil, '-'
      if trim && lspace && rspace
        add_code("#{lspace}#{code}#{rspace}")
      else
        add_text(lspace) if lspace
        add_code(code)
        add_text(rspace) if rspace
      end
    when '#'
      n = code.count("\n") + (rspace ? 1 : 0)
      if trim && lspace && rspace
        add_code("\n" * n)
      else
        add_text(lspace) if lspace
        add_code("\n" * n)
        add_text(rspace) if rspace
      end
    when '%'
      add_text("#{lspace}#{literal_prefix}#{code}#{tailch}#{literal_postfix}#{rspace}")
    else
      handle(indicator, code, tailch, rspace, lspace)
    end
  end
  rest = pos == 0 ? input : input[pos..-1]
  add_text(rest)

  src << "\n" unless src[RANGE_LAST] == "\n"
  add_postamble(postamble)
  src << "; ensure\n  " << bufvar << " = __original_outvar\nend\n" if properties[:ensure]
  src.freeze
  freeze
end

Private Instance Methods

add_code(code) click to toggle source

Add ruby code to the template

# File lib/erubi.rb, line 225
def add_code(code)
  terminate_expression
  @src << code
  @src << ';' unless code[RANGE_LAST] == "\n"
  @buffer_on_stack = false
end
add_expression(indicator, code) click to toggle source

Add the given ruby expression result to the template, escaping it based on the indicator given and escape flag.

# File lib/erubi.rb, line 234
def add_expression(indicator, code)
  if ((indicator == '=') ^ @escape)
    add_expression_result(code)
  else
    add_expression_result_escaped(code)
  end
end
add_expression_result(code) click to toggle source

Add the result of Ruby expression to the template

# File lib/erubi.rb, line 243
def add_expression_result(code)
  with_buffer{@src << ' << (' << code << ').to_s'}
end
add_expression_result_escaped(code) click to toggle source

Add the escaped result of Ruby expression to the template

# File lib/erubi.rb, line 248
def add_expression_result_escaped(code)
  with_buffer{@src << ' << ' << @escapefunc << '((' << code << '))'}
end
add_postamble(postamble) click to toggle source

Add the given postamble to the src. Can be overridden in subclasses to make additional changes to src that depend on the current state.

# File lib/erubi.rb, line 254
def add_postamble(postamble)
  terminate_expression
  @src << postamble
end
add_text(text) click to toggle source

Add raw text to the template. Modifies argument if argument is mutable as a memory optimization. Must be called with a string, cannot be called with nil (Rails's subclass depends on it).

# File lib/erubi.rb, line 212
def add_text(text)
  return if text.empty?

  if text.frozen?
    text = text.gsub(/['\\]/, '\\\\\&')
  else
    text.gsub!(/['\\]/, '\\\\\&')
  end

  with_buffer{@src << " << '" << text << @text_end}
end
handle(indicator, code, tailch, rspace, lspace) click to toggle source

Raise an exception, as the base engine class does not support handling other indicators.

# File lib/erubi.rb, line 260
def handle(indicator, code, tailch, rspace, lspace)
  raise ArgumentError, "Invalid indicator: #{indicator}"
end
terminate_expression() click to toggle source

Make sure that any current expression has been terminated. The default is to terminate all expressions, but when the chain_appends option is used, expressions may not be terminated.

# File lib/erubi.rb, line 288
def terminate_expression
  @src << '; ' if @chain_appends
end
with_buffer() { || ... } click to toggle source

Make sure the buffer variable is the target of the next append before yielding to the block. Mark that the buffer is the target of the next append after the block executes.

This method should only be called if the block will result in code where << will append to the bufvar.

# File lib/erubi.rb, line 270
def with_buffer
  if @chain_appends
    unless @buffer_on_stack
      @src << '; ' << @bufvar
    end
    yield
    @buffer_on_stack = true
  else
    @src << ' ' << @bufvar
    yield
    @src << ';'
  end
end