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

add_code(code) click to toggle source

Add ruby code to the template

    # File lib/erubi.rb
223 def add_code(code)
224   terminate_expression
225   @src << code
226   @src << ';' unless code[RANGE_LAST] == "\n"
227   @buffer_on_stack = false
228 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
232 def add_expression(indicator, code)
233   if ((indicator == '=') ^ @escape)
234     add_expression_result(code)
235   else
236     add_expression_result_escaped(code)
237   end
238 end
add_expression_result(code) click to toggle source

Add the result of Ruby expression to the template

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

Add the escaped result of Ruby expression to the template

    # File lib/erubi.rb
246 def add_expression_result_escaped(code)
247   with_buffer{@src << ' << ' << @escapefunc << '((' << code << '))'}
248 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
252 def add_postamble(postamble)
253   terminate_expression
254   @src << postamble
255 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
210 def add_text(text)
211   return if text.empty?
212 
213   if text.frozen?
214     text = text.gsub(/['\\]/, '\\\\\&')
215   else
216     text.gsub!(/['\\]/, '\\\\\&')
217   end
218 
219   with_buffer{@src << " << '" << text << @text_end}
220 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
258 def handle(indicator, code, tailch, rspace, lspace)
259   raise ArgumentError, "Invalid indicator: #{indicator}"
260 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
286 def terminate_expression
287   @src << '; ' if @chain_appends
288 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
268 def with_buffer
269   if @chain_appends
270     unless @buffer_on_stack
271       @src << '; ' << @bufvar
272     end
273     yield
274     @buffer_on_stack = true
275   else
276     @src << ' ' << @bufvar
277     yield
278     @src << ';'
279   end
280 end