class Erubi::Engine

:nocov:

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
 91 def initialize(input, properties={})
 92   @escape = escape = properties.fetch(:escape){properties.fetch(:escape_html, false)}
 93   trim       = properties[:trim] != false
 94   @filename  = properties[:filename]
 95   @bufvar = bufvar = properties[:bufvar] || properties[:outvar] || "_buf"
 96   bufval = properties[:bufval] || '::String.new'
 97   regexp = properties[:regexp] || DEFAULT_REGEXP
 98   literal_prefix = properties[:literal_prefix] || '<%'
 99   literal_postfix = properties[:literal_postfix] || '%>'
100   preamble   = properties[:preamble] || "#{bufvar} = #{bufval};"
101   postamble  = properties[:postamble] || "#{bufvar}.to_s\n"
102   @chain_appends = properties[:chain_appends]
103   @text_end = if properties.fetch(:freeze_template_literals, FREEZE_TEMPLATE_LITERALS)
104     "'.freeze"
105   else
106     "'"
107   end
108 
109   @buffer_on_stack = false
110   @src = src = properties[:src] || String.new
111   src << "# frozen_string_literal: true\n" if properties[:freeze]
112   if properties[:ensure]
113     src << "begin; __original_outvar = #{bufvar}"
114     if SKIP_DEFINED_FOR_INSTANCE_VARIABLE && /\A@[^@]/ =~ bufvar
115       src << "; "
116     else
117       src << " if defined?(#{bufvar}); "
118     end
119   end
120 
121   unless @escapefunc = properties[:escapefunc]
122     if escape
123       @escapefunc = '__erubi.h'
124       src << "__erubi = ::Erubi; "
125     else
126       @escapefunc = '::Erubi.h'
127     end
128   end
129 
130   src << preamble
131 
132   pos = 0
133   is_bol = true
134   input.scan(regexp) do |indicator, code, tailch, rspace|
135     match = Regexp.last_match
136     len  = match.begin(0) - pos
137     text = input[pos, len]
138     pos  = match.end(0)
139     ch   = indicator ? indicator[RANGE_FIRST] : nil
140 
141     lspace = nil
142 
143     unless ch == '='
144       if text.empty?
145         lspace = "" if is_bol
146       elsif text[RANGE_LAST] == "\n"
147         lspace = ""
148       else
149         rindex = text.rindex("\n")
150         if rindex
151           range = rindex+1..-1
152           s = text[range]
153           if /\A[ \t]*\z/.send(MATCH_METHOD, s)
154             lspace = s
155             text[range] = ''
156           end
157         else
158           if is_bol && /\A[ \t]*\z/.send(MATCH_METHOD, text)
159             lspace = text
160             text = ''
161           end
162         end
163       end
164     end
165 
166     is_bol = rspace
167     add_text(text)
168     case ch
169     when '='
170       rspace = nil if tailch && !tailch.empty?
171       add_expression(indicator, code)
172       add_text(rspace) if rspace
173     when nil, '-'
174       if trim && lspace && rspace
175         add_code("#{lspace}#{code}#{rspace}")
176       else
177         add_text(lspace) if lspace
178         add_code(code)
179         add_text(rspace) if rspace
180       end
181     when '#'
182       n = code.count("\n") + (rspace ? 1 : 0)
183       if trim && lspace && rspace
184         add_code("\n" * n)
185       else
186         add_text(lspace) if lspace
187         add_code("\n" * n)
188         add_text(rspace) if rspace
189       end
190     when '%'
191       add_text("#{lspace}#{literal_prefix}#{code}#{tailch}#{literal_postfix}#{rspace}")
192     else
193       handle(indicator, code, tailch, rspace, lspace)
194     end
195   end
196   rest = pos == 0 ? input : input[pos..-1]
197   add_text(rest)
198 
199   src << "\n" unless src[RANGE_LAST] == "\n"
200   add_postamble(postamble)
201   src << "; ensure\n  " << bufvar << " = __original_outvar\nend\n" if properties[:ensure]
202   src.freeze
203   freeze
204 end

Private Instance Methods

_dup_string_if_frozen(string) click to toggle source
    # File lib/erubi.rb
209 def _dup_string_if_frozen(string)
210   +string
211 end
add_code(code) click to toggle source

Add ruby code to the template

    # File lib/erubi.rb
232 def add_code(code)
233   terminate_expression
234   @src << code
235   @src << ';' unless code[RANGE_LAST] == "\n"
236   @buffer_on_stack = false
237 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
241 def add_expression(indicator, code)
242   if ((indicator == '=') ^ @escape)
243     add_expression_result(code)
244   else
245     add_expression_result_escaped(code)
246   end
247 end
add_expression_result(code) click to toggle source

Add the result of Ruby expression to the template

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

Add the escaped result of Ruby expression to the template

    # File lib/erubi.rb
255 def add_expression_result_escaped(code)
256   with_buffer{@src << ' << ' << @escapefunc << '((' << code << '))'}
257 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
261 def add_postamble(postamble)
262   terminate_expression
263   @src << postamble
264 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
222 def add_text(text)
223   return if text.empty?
224 
225   text = _dup_string_if_frozen(text)
226   text.gsub!(/['\\]/, '\\\\\&')
227 
228   with_buffer{@src << " << '" << text << @text_end}
229 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
267 def handle(indicator, code, tailch, rspace, lspace)
268   raise ArgumentError, "Invalid indicator: #{indicator}"
269 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
295 def terminate_expression
296   @src << '; ' if @chain_appends
297 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
277 def with_buffer
278   if @chain_appends
279     unless @buffer_on_stack
280       @src << '; ' << @bufvar
281     end
282     yield
283     @buffer_on_stack = true
284   else
285     @src << ' ' << @bufvar
286     yield
287     @src << ';'
288   end
289 end