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

Private Instance Methods

add_code(code) click to toggle source

Add ruby code to the template

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

Add the result of Ruby expression to the template

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

Add the escaped result of Ruby expression to the template

    # File lib/erubi.rb
249 def add_expression_result_escaped(code)
250   with_buffer{@src << ' << ' << @escapefunc << '((' << code << '))'}
251 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
255 def add_postamble(postamble)
256   terminate_expression
257   @src << postamble
258 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
213 def add_text(text)
214   return if text.empty?
215 
216   if text.frozen?
217     text = text.gsub(/['\\]/, '\\\\\&')
218   else
219     text.gsub!(/['\\]/, '\\\\\&')
220   end
221 
222   with_buffer{@src << " << '" << text << @text_end}
223 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
261 def handle(indicator, code, tailch, rspace, lspace)
262   raise ArgumentError, "Invalid indicator: #{indicator}"
263 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
289 def terminate_expression
290   @src << '; ' if @chain_appends
291 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
271 def with_buffer
272   if @chain_appends
273     unless @buffer_on_stack
274       @src << '; ' << @bufvar
275     end
276     yield
277     @buffer_on_stack = true
278   else
279     @src << ' ' << @bufvar
280     yield
281     @src << ';'
282   end
283 end