lib/erubi.rb in erubi-1.9.0 vs lib/erubi.rb in erubi-1.10.0

- old
+ new

@@ -1,44 +1,49 @@ # frozen_string_literal: true module Erubi - VERSION = '1.9.0' + VERSION = '1.10.0' RANGE_ALL = 0..-1 + # :nocov: if RUBY_VERSION >= '1.9' RANGE_FIRST = 0 RANGE_LAST = -1 - TEXT_END = RUBY_VERSION >= '2.1' ? "'.freeze;" : "';" else - # :nocov: RANGE_FIRST = 0..0 RANGE_LAST = -1..-1 - TEXT_END = "';" end + TEXT_END = RUBY_VERSION >= '2.1' ? "'.freeze;" : "';" + MATCH_METHOD = RUBY_VERSION >= '2.4' ? :match? : :match + # :nocov: + begin require 'cgi/escape' + # :nocov: unless CGI.respond_to?(:escapeHTML) # work around for JRuby 9.1 CGI = Object.new CGI.extend(defined?(::CGI::Escape) ? ::CGI::Escape : ::CGI::Util) end + # :nocov: + # Escape characters with their HTML/XML equivalents. def self.h(value) CGI.escapeHTML(value.to_s) end rescue LoadError + # :nocov: ESCAPE_TABLE = {'&' => '&amp;'.freeze, '<' => '&lt;'.freeze, '>' => '&gt;'.freeze, '"' => '&quot;'.freeze, "'" => '&#39;'.freeze}.freeze if RUBY_VERSION >= '1.9' - # Escape the following characters with their HTML/XML - # equivalents. def self.h(value) value.to_s.gsub(/[&<>"']/, ESCAPE_TABLE) end else def self.h(value) value.to_s.gsub(/[&<>"']/){|s| ESCAPE_TABLE[s]} end end + # :nocov: end class Engine # The frozen ruby source code generated from the template, which can be evaled. attr_reader :src @@ -48,31 +53,35 @@ # The variable name used for the buffer variable. attr_reader :bufvar # Initialize a new Erubi::Engine. Options: - # :bufval :: The value to use for the buffer variable, as a string. - # :bufvar :: The variable name to use for the buffer variable, as a string (default '::String.new') - # :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 frozen string literals in the resulting source code. - # :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 up the buffer variable. - # :regexp :: The regexp to use for scanning. - # :src :: The initial value to use for the source code - # :trim :: Whether to trim leading and trailing whitespace, true by default. + # +:bufval+ :: The value to use for the buffer variable, as a string (default <tt>'::String.new'</tt>). + # +:bufvar+ :: The variable name to use for the buffer variable, as a string. + # +: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: <tt>'::Erubi.h'</tt>). + # +:escape+ :: Whether to make <tt><%=</tt> escape by default, and <tt><%==</tt> not escape by default. + # +:escape_html+ :: Same as +:escape+, with lower priority. + # +:filename+ :: The filename for the template. + # +:freeze+ :: Whether to enable frozen string literals in the resulting source code. + # +:literal_prefix+ :: The prefix to output when using escaped tag delimiters (default <tt>'<%'</tt>). + # +:literal_postfix+ :: The postfix to output when using escaped tag delimiters (default <tt>'%>'</tt>). + # +: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. 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] || /<%(={1,2}|-|\#|%)?(.*?)([-=])?%>([ \t]*\r?\n)?/m + literal_prefix = properties[:literal_prefix] || '<%' + literal_postfix = properties[:literal_postfix] || '%>' preamble = properties[:preamble] || "#{bufvar} = #{bufval};" postamble = properties[:postamble] || "#{bufvar}.to_s\n" @src = src = properties[:src] || String.new src << "# frozen_string_literal: true\n" if properties[:freeze] @@ -108,69 +117,76 @@ else rindex = text.rindex("\n") if rindex range = rindex+1..-1 s = text[range] - if s =~ /\A[ \t]*\z/ + if /\A[ \t]*\z/.send(MATCH_METHOD, s) lspace = s text[range] = '' end else - if is_bol && text =~ /\A[ \t]*\z/ - lspace = text.dup - text[RANGE_ALL] = '' + if is_bol && /\A[ \t]*\z/.send(MATCH_METHOD, text) + lspace = text + text = '' end end end end is_bol = rspace - add_text(text) if text && !text.empty? + add_text(text) case ch when '=' rspace = nil if tailch && !tailch.empty? - add_text(lspace) if lspace add_expression(indicator, code) add_text(rspace) if rspace - when '#' - n = code.count("\n") + (rspace ? 1 : 0) + when nil, '-' if trim && lspace && rspace - add_code("\n" * n) + add_code("#{lspace}#{code}#{rspace}") else add_text(lspace) if lspace - add_code("\n" * n) + add_code(code) add_text(rspace) if rspace end - when '%' - add_text("#{lspace}#{prefix||='<%'}#{code}#{tailch}#{postfix||='%>'}#{rspace}") - when nil, '-' + when '#' + n = code.count("\n") + (rspace ? 1 : 0) if trim && lspace && rspace - add_code("#{lspace}#{code}#{rspace}") + add_code("\n" * n) else add_text(lspace) if lspace - add_code(code) + 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 << "; ensure\n " << bufvar << " = __original_outvar\nend\n" if properties[:ensure] src.freeze freeze end private - # Add raw text to the template + # 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). def add_text(text) - @src << " #{@bufvar} << '" << text.gsub(/['\\]/, '\\\\\&') << TEXT_END unless text.empty? + return if text.empty? + + if text.frozen? + text = text.gsub(/['\\]/, '\\\\\&') + else + text.gsub!(/['\\]/, '\\\\\&') + end + @src << " " << @bufvar << " << '" << text << TEXT_END end # Add ruby code to the template def add_code(code) @src << code @@ -187,15 +203,15 @@ end end # Add the result of Ruby expression to the template def add_expression_result(code) - @src << " #{@bufvar} << (" << code << ').to_s;' + @src << ' ' << @bufvar << ' << (' << code << ').to_s;' end # Add the escaped result of Ruby expression to the template def add_expression_result_escaped(code) - @src << " #{@bufvar} << #{@escapefunc}((" << code << '));' + @src << ' ' << @bufvar << ' << ' << @escapefunc << '((' << code << '));' end # Add the given postamble to the src. Can be overridden in subclasses # to make additional changes to src that depend on the current state. def add_postamble(postamble)