class Erubi::Engine
def add_code(code)
def add_code(code) terminate_expression @src << code @src << ';' unless code[RANGE_LAST] == "\n" @buffer_on_stack = false end
def add_expression(indicator, code)
Add the given ruby expression result to the template,
def add_expression(indicator, code) if ((indicator == '=') ^ @escape) add_expression_result(code) else add_expression_result_escaped(code) end end
def add_expression_result(code)
def add_expression_result(code) with_buffer{@src << ' << (' << code << ').to_s'} end
def add_expression_result_escaped(code)
def add_expression_result_escaped(code) with_buffer{@src << ' << ' << @escapefunc << '((' << code << '))'} end
def add_postamble(postamble)
Add the given postamble to the src. Can be overridden in subclasses
def add_postamble(postamble) terminate_expression @src << postamble end
def add_text(text)
Add raw text to the template. Modifies argument if argument is mutable as a memory optimization.
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
def handle(indicator, code, tailch, rspace, lspace)
def handle(indicator, code, tailch, rspace, lspace) raise ArgumentError, "Invalid indicator: #{indicator}" end
def initialize(input, properties={})
+:src+ :: The initial value to use for the source code, an empty string by default.
+:regexp+ :: The regexp to use for scanning.
+:preamble+ :: The preamble for the template, by default initializes the buffer variable.
+:postamble+ :: The postamble for the template, by default returns the resulting source code.
+:outvar+ :: Same as +:bufvar+, with lower priority.
+:literal_postfix+ :: The postfix to output when using escaped tag delimiters (default '%>').
+:literal_prefix+ :: The prefix to output when using escaped tag delimiters (default '<%').
in order to improve performance.
Can be set to +false+ on Ruby 2.3+ when frozen string literals are enabled
(default: +true+ on Ruby 2.1+, +false+ on Ruby 2.0 and older).
+:freeze_template_literals+ :: Whether to suffix all literal strings for template code with .freeze
the file, and having the magic comment later in the file can trigger warnings.
source code in other code, because the magic comment only has an effect at the beginning of
the resulting source code. Note this may cause problems if you are wrapping the resulting
+:freeze+ :: Whether to enable add a frozen_string_literal: true magic comment at the top of
+:filename+ :: The filename for the template.
+:escape_html+ :: Same as +:escape+, with lower priority.
+:escape+ :: Whether to make <%= escape by default, and <%== not escape by default.
+:escapefunc+ :: The function to use for escaping, as a string (default: '::Erubi.h').
+:ensure+ :: Wrap the template in a begin/ensure block restoring the previous value of bufvar.
template rendering (default +false+).
performance, but can cause issues when the buffer variable is reassigned during
+:chain_appends+ :: Whether to chain << calls to the buffer variable. Offers better
+:bufvar+ :: The variable name to use for the buffer variable, as a string.
+:bufval+ :: The value to use for the buffer variable, as a string (default '::String.new').
Initialize a new Erubi::Engine. Options:
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
def terminate_expression
the chain_appends option is used, expressions may not be
The default is to terminate all expressions, but when
Make sure that any current expression has been terminated.
def terminate_expression @src << '; ' if @chain_appends end
def with_buffer
This method should only be called if the block will result in
of the next append after the block executes.
before yielding to the block. Mark that the buffer is the target
Make sure the buffer variable is the target of the next append
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