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

Private Instance Methods

add_code(code) click to toggle source

Add ruby code to the template

    # File lib/erubi.rb
218 def add_code(code)
219   terminate_expression
220   @src << code
221   @src << ';' unless code[RANGE_LAST] == "\n"
222   @buffer_on_stack = false
223 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
227 def add_expression(indicator, code)
228   if ((indicator == '=') ^ @escape)
229     add_expression_result(code)
230   else
231     add_expression_result_escaped(code)
232   end
233 end
add_expression_result(code) click to toggle source

Add the result of Ruby expression to the template

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

Add the escaped result of Ruby expression to the template

    # File lib/erubi.rb
241 def add_expression_result_escaped(code)
242   with_buffer{@src << ' << ' << @escapefunc << '((' << code << '))'}
243 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
247 def add_postamble(postamble)
248   terminate_expression
249   @src << postamble
250 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
205 def add_text(text)
206   return if text.empty?
207 
208   if text.frozen?
209     text = text.gsub(/['\\]/, '\\\\\&')
210   else
211     text.gsub!(/['\\]/, '\\\\\&')
212   end
213 
214   with_buffer{@src << " << '" << text << @text_end}
215 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
253 def handle(indicator, code, tailch, rspace, lspace)
254   raise ArgumentError, "Invalid indicator: #{indicator}"
255 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
281 def terminate_expression
282   @src << '; ' if @chain_appends
283 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
263 def with_buffer
264   if @chain_appends
265     unless @buffer_on_stack
266       @src << '; ' << @bufvar
267     end
268     yield
269     @buffer_on_stack = true
270   else
271     @src << ' ' << @bufvar
272     yield
273     @src << ';'
274   end
275 end