pax_global_header00006660000000000000000000000064143511276040014515gustar00rootroot0000000000000052 comment=733bcbd6c8e032e66595edf21b255fd14b2c0062 erubi-1.12.0/000077500000000000000000000000001435112760400127045ustar00rootroot00000000000000erubi-1.12.0/.ci.gemfile000066400000000000000000000002641435112760400147110ustar00rootroot00000000000000source 'https://rubygems.org' gem 'rake', '<10.0.0' gem 'minitest-global_expectations' if RUBY_VERSION < '2.4.0' # Until mintest 5.12.0 is fixed gem 'minitest', '5.11.3' end erubi-1.12.0/.github/000077500000000000000000000000001435112760400142445ustar00rootroot00000000000000erubi-1.12.0/.github/workflows/000077500000000000000000000000001435112760400163015ustar00rootroot00000000000000erubi-1.12.0/.github/workflows/ci.yml000066400000000000000000000011441435112760400174170ustar00rootroot00000000000000name: CI on: push: branches: [ master ] pull_request: branches: [ master ] permissions: contents: read jobs: tests: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: [ "1.9.3", "2.0.0", 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, "3.0", 3.1, jruby-9.1, jruby-9.2, jruby-9.3, jruby-9.4, truffleruby-head ] name: ${{ matrix.ruby }} env: BUNDLE_GEMFILE: .ci.gemfile steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle exec rake erubi-1.12.0/.gitignore000066400000000000000000000000371435112760400146740ustar00rootroot00000000000000/erubi-*.gem /rdoc/ /coverage/ erubi-1.12.0/CHANGELOG000066400000000000000000000070021435112760400141150ustar00rootroot00000000000000=== 1.12.0 (2022-12-22) * Use erb/escape for faster html escaping if available (jeremyevans) * Default :freeze_template_literals option to false if running with --enable-frozen-string-literal (casperisfine) (#35) === 1.11.0 (2022-08-02) * Support :freeze_template_literals option for configuring whether to add .freeze to template literal strings (casperisfine) (#33) * Support :chain_appends option for chaining appends to the buffer variable (casperisfine, jeremyevans) (#32) * Avoid unnecessary defined? usage on Ruby 3+ when using the :ensure option (jeremyevans) === 1.10.0 (2020-11-13) * Improve template parsing, mostly by reducing allocations (jeremyevans) * Do not ship tests in the gem, reducing gem size about 20% (jeremyevans) * Support :literal_prefix and :literal_postfix options for how to output literal tags (e.g. <%% code %>) (jaredcwhite) (#26, #27) === 1.9.0 (2019-09-25) * Change default :bufvar from 'String.new' to '::String.new' to work with BasicObject (jeremyevans) === 1.8.0 (2018-12-18) * Support :yield_returns_buffer option in capture_end for always returning the (potentially modified) buffer in <%|= tags (evanleck) (#15) === 1.7.1 (2018-03-05) * Make whitespace handling for <%# %> tags more compatible with Erubis (jeremyevans) (#14) === 1.7.0 (2017-10-09) * Fix escaping in erubi/capture_end, the setting was previously inverted (jeremyevans) (#10) === 1.6.1 (2017-06-27) * Fix usage on newer versions of JRuby 9.1 (jeremyevans) === 1.6.0 (2017-02-27) * Use cgi/escape if available for 6x faster HTML escaping (k0kubun, jeremyevans) (#4) === 1.5.0 (2017-01-26) * Drop tilt/erubi file, as tilt now ships with Erubi support (jeremyevans) * Drop erubi/capture file, Erubi::CaptureEngine support (jeremyevans) === 1.4.0 (2017-01-20) * Allow postambles to depend on internal state of engine (jeremyevans) * Allow overriding of behavior for <%= and <%== tags to depend on which indicator was used (jeremyevans) * Make whitespace handling for <% %> tags more compatible with Erubis for subclasses overriding add_text (jeremyevans) === 1.3.0 (2016-12-29) * Support :capture=>:explicit option in tilt support to use Erubi::CaptureEndEngine (jeremyevans) * Add erubi/capture_end containing Erubi::CaptureEndEngine, allowing <%|= and <%|== for opening capture tags, and <%| for closing capture tags (jeremyevans) === 1.2.1 (2016-11-21) * Don't automatically freeze template text strings on ruby 1.9 or 2.0 (jeremyevans) === 1.2.0 (2016-11-21) * Engine#src now returns a frozen string (jeremyevans) * Automatically freeze template text strings on ruby 2.1+, reducing garbage generated (jeremyevans) * Allow overriding of behavior for <%= and <%== tags (ujifgc) (#1) === 1.1.0 (2016-11-14) * Add :ensure option to supporting restoring bufvar to original value (jeremyevans) * Don't have tilt support require erb (jeremyevans) * Support :engine_class option in tilt support to override engine class used (jeremyevans) * Support :capture option in tilt support to use Erubi::CaptureEngine (jeremyevans) * Add erubi/capture file containing Erubi::CaptureEngine, allowing <%|= and <%|== for capture (and escaping) blocks in templates (jeremyevans) * Raise ArgumentError if template source code contains indicators matched by regexp but not handled (jeremyevans) * Add :bufval option to support arbitrary buffer values (jeremyevans) * Add :regexp option to specify regexp used for scanning (jeremyevans) * Add :src option to specify initial template source (jeremyevans) === 1.0.0 (2016-11-10) * Initial Public Release erubi-1.12.0/MIT-LICENSE000066400000000000000000000021371435112760400143430ustar00rootroot00000000000000copyright(c) 2006-2011 kuwata-lab.com all rights reserved. copyright(c) 2016-2021 Jeremy Evans Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. erubi-1.12.0/README.rdoc000066400000000000000000000065421435112760400145210ustar00rootroot00000000000000= Erubi Erubi is a ERB template engine for ruby. It is a simplified fork of Erubis, using the same basic algorithm, with the following differences: * Handles postfix conditionals when using escaping (e.g. <%= foo if bar %>) * Supports frozen_string_literal: true in templates via :freeze option * Works with ruby's --enable-frozen-string-literal option * Automatically freezes strings for template text when ruby optimizes it (on ruby 2.1+) * Escapes ' (apostrophe) when escaping for better XSS protection * Has 6x faster escaping on ruby 2.3+ by using cgi/escape * Has 81% smaller memory footprint (calculated using +ObjectSpace.memsize_of_all+) * Does no monkey patching (Erubis adds a method to Kernel) * Uses an immutable design (all options passed to the constructor, which returns a frozen object) * Has simpler internals (1 file, <150 lines of code) * Is not dead (Erubis hasn't been updated since 2011) It is not designed with Erubis API compatibility in mind, though most Erubis ERB syntax works, with the following exceptions: * No support for <%=== for debug output = Installation gem install erubi = Source Code Source code is available on GitHub at https://github.com/jeremyevans/erubi = Usage Erubi only has built in support for retrieving the generated source for a file: require 'erubi' eval(Erubi::Engine.new(File.read('filename.erb')).src) Most users will probably use Erubi via Rails or Tilt. Erubi is the default erb template handler in Tilt 2.0.6+ and Rails 5.1+. == Capturing Erubi does not support capturing block output into the template by default. However, it comes with an +erubi/capture_end+ file that supports capturing via <%|= and <%|== tags which are closed with a <%| tag: <%|= form do %> <%| end %> This offers similar functionality to that offered by Rails' <%= tags, but without the corner cases with that approach (which are due to attempting to parse ruby code via a regexp). Similar to the <%= and <%== tags, <%|= captures by default and <%|== captures and escapes by default, but this can be reversed via the +:escape_capture+ or +:escape+ options. To use the capture_end support with tilt: require 'tilt' require 'erubi/capture_end' Tilt.new("filename.erb", :engine_class=>Erubi::CaptureEndEngine).render When using the capture_end support, any methods (such as +form+ in the example above) should return the (potentially modified) buffer. Since the buffer variable is a local variable and not an instance variable by default, you'll probably want to set the +:bufvar+ variable when using the capture_end support to an instance variable, and have any methods used access that instance variable. Example: def form @_buf << "
" yield @_buf << "
" @_buf end puts eval(Erubi::CaptureEndEngine.new(<<-END, :bufvar=>:@_buf).src) before <%|= form do %> inside <%| end %> after END # Output: # before #
# inside #
# after Alternatively, passing the option :yield_returns_buffer => true will return the buffer captured by the block instead of the last expression in the block. = Reporting Bugs The bug tracker is located at https://github.com/jeremyevans/erubi/issues = License MIT = Authors Jeremy Evans kuwata-lab.com erubi-1.12.0/Rakefile000066400000000000000000000026731435112760400143610ustar00rootroot00000000000000require "rake" require "rake/clean" NAME = 'erubi' CLEAN.include ["#{NAME}-*.gem", "rdoc", "coverage"] # Gem Packaging and Release desc "Packages #{NAME}" task :package=>[:clean] do |p| sh %{gem build #{NAME}.gemspec} end ### RDoc RDOC_DEFAULT_OPTS = ["--line-numbers", "--inline-source", '--title', 'Erubi: Small ERB Implementation'] begin gem 'hanna-nouveau' RDOC_DEFAULT_OPTS.concat(['-f', 'hanna']) rescue Gem::LoadError end rdoc_task_class = begin require "rdoc/task" RDoc::Task rescue LoadError require "rake/rdoctask" Rake::RDocTask end RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc'] RDOC_FILES = %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb" rdoc_task_class.new do |rdoc| rdoc.rdoc_dir = "rdoc" rdoc.options += RDOC_OPTS rdoc.rdoc_files.add RDOC_FILES end ### Specs spec = proc do |env| env.each{|k,v| ENV[k] = v} sh "#{FileUtils::RUBY} #{'-w' if RUBY_VERSION >= '3'} test/test.rb" env.each{|k,v| ENV.delete(k)} end desc "Run specs" task "spec" do spec.call({}) end task :default=>:spec desc "Run specs with coverage" task "spec_cov" do spec.call('COVERAGE'=>'1') end ### Other desc "Start an IRB shell using the extension" task :irb do require 'rbconfig' ruby = ENV['RUBY'] || File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) irb = ENV['IRB'] || File.join(RbConfig::CONFIG['bindir'], File.basename(ruby).sub('ruby', 'irb')) sh %{#{irb} -I lib -r #{NAME}} end erubi-1.12.0/erubi.gemspec000066400000000000000000000023161435112760400153610ustar00rootroot00000000000000# frozen_string_literal: true require File.expand_path("../lib/erubi", __FILE__) Gem::Specification.new do |s| s.name = 'erubi' s.version = Erubi::VERSION s.platform = Gem::Platform::RUBY s.extra_rdoc_files = ["README.rdoc", "CHANGELOG", "MIT-LICENSE"] s.rdoc_options += ["--quiet", "--line-numbers", "--inline-source", '--title', 'Erubi: Small ERB Implementation', '--main', 'README.rdoc'] s.license = "MIT" s.summary = "Small ERB Implementation" s.author = ["Jeremy Evans", 'kuwata-lab.com'] s.email = "code@jeremyevans.net" s.homepage = "https://github.com/jeremyevans/erubi" s.files = %w(MIT-LICENSE CHANGELOG README.rdoc Rakefile lib/erubi.rb lib/erubi/capture_end.rb) s.description = "Erubi is a ERB template engine for ruby. It is a simplified fork of Erubis" s.add_development_dependency "minitest" s.add_development_dependency "minitest-global_expectations" s.metadata = { 'bug_tracker_uri' => 'https://github.com/jeremyevans/erubi/issues', 'mailing_list_uri' => 'https://github.com/jeremyevans/erubi/discussions', 'changelog_uri' => 'https://github.com/jeremyevans/erubi/blob/master/CHANGELOG', 'source_code_uri' => 'https://github.com/jeremyevans/erubi', } end erubi-1.12.0/lib/000077500000000000000000000000001435112760400134525ustar00rootroot00000000000000erubi-1.12.0/lib/erubi.rb000066400000000000000000000244311435112760400151110ustar00rootroot00000000000000# frozen_string_literal: true module Erubi VERSION = '1.12.0' # :nocov: if RUBY_VERSION >= '1.9' RANGE_FIRST = 0 RANGE_LAST = -1 else RANGE_FIRST = 0..0 RANGE_LAST = -1..-1 end MATCH_METHOD = RUBY_VERSION >= '2.4' ? :match? : :match SKIP_DEFINED_FOR_INSTANCE_VARIABLE = RUBY_VERSION > '3' FREEZE_TEMPLATE_LITERALS = !eval("''").frozen? && RUBY_VERSION >= '2.1' # :nocov: begin require 'erb/escape' # :nocov: define_singleton_method(:h, ERB::Escape.instance_method(:html_escape)) # :nocov: rescue LoadError 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 = {'&' => '&'.freeze, '<' => '<'.freeze, '>' => '>'.freeze, '"' => '"'.freeze, "'" => '''.freeze}.freeze if RUBY_VERSION >= '1.9' 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 end class Engine # The default regular expression used for scanning. DEFAULT_REGEXP = /<%(={1,2}|-|\#|%)?(.*?)([-=])?%>([ \t]*\r?\n)?/m # The frozen ruby source code generated from the template, which can be evaled. attr_reader :src # The filename of the template, if one was given. attr_reader :filename # 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 (default '::String.new'). # +:bufvar+ :: The variable name to use for the buffer variable, as a string. # +:chain_appends+ :: Whether to chain << 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. 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] || DEFAULT_REGEXP literal_prefix = properties[:literal_prefix] || '<%' literal_postfix = properties[:literal_postfix] || '%>' preamble = properties[:preamble] || "#{bufvar} = #{bufval};" postamble = properties[:postamble] || "#{bufvar}.to_s\n" @chain_appends = properties[:chain_appends] @text_end = if properties.fetch(:freeze_template_literals, FREEZE_TEMPLATE_LITERALS) "'.freeze" else "'" end @buffer_on_stack = false @src = src = properties[:src] || String.new src << "# frozen_string_literal: true\n" if properties[:freeze] if properties[:ensure] src << "begin; __original_outvar = #{bufvar}" if SKIP_DEFINED_FOR_INSTANCE_VARIABLE && /\A@[^@]/ =~ bufvar src << "; " else src << " if defined?(#{bufvar}); " end end unless @escapefunc = properties[:escapefunc] if escape @escapefunc = '__erubi.h' src << "__erubi = ::Erubi; " else @escapefunc = '::Erubi.h' end end src << preamble pos = 0 is_bol = true input.scan(regexp) do |indicator, code, tailch, rspace| match = Regexp.last_match len = match.begin(0) - pos text = input[pos, len] pos = match.end(0) ch = indicator ? indicator[RANGE_FIRST] : nil lspace = nil unless ch == '=' if text.empty? lspace = "" if is_bol elsif text[RANGE_LAST] == "\n" lspace = "" else rindex = text.rindex("\n") if rindex range = rindex+1..-1 s = text[range] if /\A[ \t]*\z/.send(MATCH_METHOD, s) lspace = s text[range] = '' end else if is_bol && /\A[ \t]*\z/.send(MATCH_METHOD, text) lspace = text text = '' end end end end is_bol = rspace add_text(text) case ch when '=' rspace = nil if tailch && !tailch.empty? add_expression(indicator, code) add_text(rspace) if rspace when nil, '-' if trim && lspace && rspace add_code("#{lspace}#{code}#{rspace}") else add_text(lspace) if lspace add_code(code) add_text(rspace) if rspace end when '#' n = code.count("\n") + (rspace ? 1 : 0) if trim && lspace && rspace add_code("\n" * n) else add_text(lspace) if lspace 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.freeze freeze end private # 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) return if text.empty? if text.frozen? text = text.gsub(/['\\]/, '\\\\\&') else text.gsub!(/['\\]/, '\\\\\&') end with_buffer{@src << " << '" << text << @text_end} end # Add ruby code to the template def add_code(code) terminate_expression @src << code @src << ';' unless code[RANGE_LAST] == "\n" @buffer_on_stack = false end # Add the given ruby expression result to the template, # escaping it based on the indicator given and escape flag. def add_expression(indicator, code) if ((indicator == '=') ^ @escape) add_expression_result(code) else add_expression_result_escaped(code) end end # Add the result of Ruby expression to the template def add_expression_result(code) with_buffer{@src << ' << (' << code << ').to_s'} end # Add the escaped result of Ruby expression to the template def add_expression_result_escaped(code) with_buffer{@src << ' << ' << @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) terminate_expression @src << postamble end # Raise an exception, as the base engine class does not support handling other indicators. def handle(indicator, code, tailch, rspace, lspace) raise ArgumentError, "Invalid indicator: #{indicator}" end # 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. def with_buffer if @chain_appends unless @buffer_on_stack @src << '; ' << @bufvar end yield @buffer_on_stack = true else @src << ' ' << @bufvar yield @src << ';' end end # 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. def terminate_expression @src << '; ' if @chain_appends end end end erubi-1.12.0/lib/erubi/000077500000000000000000000000001435112760400145605ustar00rootroot00000000000000erubi-1.12.0/lib/erubi/capture_end.rb000066400000000000000000000050531435112760400174010ustar00rootroot00000000000000# frozen_string_literal: true require 'erubi' module Erubi # An engine class that supports capturing blocks via the <%|= and <%|== tags, # explicitly ending the captures using <%| end %> blocks. class CaptureEndEngine < Engine # Initializes the engine. Accepts the same arguments as ::Erubi::Engine, and these # additional options: # :escape_capture :: Whether to make <%|= escape by default, and <%|== not escape by default, # defaults to the same value as :escape. # :yield_returns_buffer :: Whether to have <%| tags insert the buffer as an expression, so that # <%| end %> tags will have the buffer be the last expression inside # the block, and therefore have the buffer be returned by the yield # expression. Normally the buffer will be returned anyway, but there # are cases where the last expression will not be the buffer, # and therefore a different object will be returned. def initialize(input, properties={}) properties = Hash[properties] escape = properties.fetch(:escape){properties.fetch(:escape_html, false)} @escape_capture = properties.fetch(:escape_capture, escape) @yield_returns_buffer = properties.fetch(:yield_returns_buffer, false) @bufval = properties[:bufval] ||= '::String.new' @bufstack = '__erubi_stack' properties[:regexp] ||= /<%(\|?={1,2}|-|\#|%|\|)?(.*?)([-=])?%>([ \t]*\r?\n)?/m super end private # Handle the <%|= and <%|== tags def handle(indicator, code, tailch, rspace, lspace) case indicator when '|=', '|==' rspace = nil if tailch && !tailch.empty? add_text(lspace) if lspace escape_capture = !((indicator == '|=') ^ @escape_capture) terminate_expression @src << "begin; (#{@bufstack} ||= []) << #{@bufvar}; #{@bufvar} = #{@bufval}; #{@bufstack}.last << #{@escapefunc if escape_capture}((" << code @buffer_on_stack = false add_text(rspace) if rspace when '|' rspace = nil if tailch && !tailch.empty? add_text(lspace) if lspace if @yield_returns_buffer terminate_expression @src << " #{@bufvar}; " end @src << code << ")).to_s; ensure; #{@bufvar} = #{@bufstack}.pop; end;" @buffer_on_stack = false add_text(rspace) if rspace else super end end end end erubi-1.12.0/test/000077500000000000000000000000001435112760400136635ustar00rootroot00000000000000erubi-1.12.0/test/test.rb000066400000000000000000000537001435112760400151740ustar00rootroot00000000000000require 'rubygems' unless defined?(TESTDIR) TESTDIR = File.dirname(__FILE__) LIBDIR = TESTDIR == '.' ? '../lib' : File.dirname(TESTDIR) + '/lib' $: << TESTDIR $: << LIBDIR end if ENV['COVERAGE'] require 'coverage' require 'simplecov' ENV.delete('COVERAGE') SimpleCov.instance_eval do enable_coverage :branch start do add_filter "/test/" add_group('Missing'){|src| src.covered_percent < 100} add_group('Covered'){|src| src.covered_percent == 100} end end end require 'erubi' require 'erubi/capture_end' ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins gem 'minitest' require 'minitest/global_expectations/autorun' describe Erubi::Engine do before do @options = {} end def check_output(input, src, result, &block) t = (@options[:engine] || Erubi::Engine).new(input, @options) tsrc = t.src eval(tsrc, block.binding).must_equal result strip_freeze = defined?(@strip_freeze) ? @strip_freeze : RUBY_VERSION >= '2.1' tsrc = tsrc.gsub(/\.freeze/, '') if strip_freeze tsrc.must_equal src end def setup_foo @foo = Object.new @foo.instance_variable_set(:@t, self) def self.a; @a; end def @foo.bar @t.a << "a" yield @t.a << 'b' @t.a.buffer.upcase! end end def setup_bar def self.bar @a << "a" yield @a << 'b' @a.upcase end def self.baz @a << "c" yield @a << 'd' @a * 2 end def self.quux @a << "a" 3.times do |i| @a << "c#{i}" yield i @a << "d#{i}" end @a << "b" @a.upcase end end it "should handle no tags with frozen source" do check_output(<"2'] check_output(< <% i = 0 list.each_with_index do |item, i| %> <%= i+1 %> <%== item %> <% end %> <%== i+1 %> END1 _buf = ::String.new; _buf << ' '; i = 0 list.each_with_index do |item, i| _buf << ' '; end _buf << '
'; _buf << ( i+1 ).to_s; _buf << ' '; _buf << ::Erubi.h(( item )); _buf << '
'; _buf << ::Erubi.h(( i+1 )); _buf << ' '; _buf.to_s END2
1 &'<>"2
1 END3 end it "should escape all backslashes and apostrophes in text" do list = list = ['&\'<>"2'] check_output(< ' ' \\ \\ <% i = 0 list.each_with_index do |item, i| %> <%= i+1 -%> <%== item %> <% end %> <%== i+1 %> <% %> END1 _buf = ::String.new; _buf << '\\' \\' \\\\ \\\\ '; i = 0 list.each_with_index do |item, i| _buf << ' '; end _buf << '
'; _buf << ( i+1 ).to_s; _buf << ' '; _buf << ::Erubi.h(( item )); _buf << '
'; _buf << ::Erubi.h(( i+1 )); _buf << ' '; _buf.to_s END2 ' ' \\ \\
1 &'<>"2
1 END3 end it "should strip only whitespace for <%, <%- and <%# tags" do check_output(< a <%- a = 2 %> b <%# a = 3 %> c /<% a = 1 %> a / <%- a = 2 %> b //<%# a = 3 %> c <% a = 1 %> / a <%- a = 2 %>/ b <%# a = 3 %>// c END1 _buf = ::String.new; a = 1 _buf << 'a '; a = 2 _buf << 'b '; _buf << 'c /'; a = 1 ; _buf << ' '; _buf << 'a / '; a = 2 ; _buf << ' '; _buf << 'b //'; _buf << ' '; _buf << 'c '; _buf << ' '; a = 1 ; _buf << ' / a '; _buf << ' '; a = 2 ; _buf << '/ b '; _buf << ' ';; _buf << '// c '; _buf.to_s END2 a b c / a / b // c / a / b // c END3 end it "should handle ensure option" do list = list = ['&\'<>"2'] @options[:ensure] = true @options[:bufvar] = '@a' @a = 'bar' check_output(< <% i = 0 list.each_with_index do |item, i| %> <%= i+1 %> <%== item %> <% end %> <%== i+1 %> END1 begin; __original_outvar = @a#{' if defined?(@a)' if RUBY_VERSION < '3'}; @a = ::String.new; @a << ' '; i = 0 list.each_with_index do |item, i| @a << ' '; end @a << '
'; @a << ( i+1 ).to_s; @a << ' '; @a << ::Erubi.h(( item )); @a << '
'; @a << ::Erubi.h(( i+1 )); @a << ' '; @a.to_s ; ensure @a = __original_outvar end END2
1 &'<>"2
1 END3 @a.must_equal 'bar' end it "should handle chain_appends option" do @options[:chain_appends] = true list = list = ['&\'<>"2'] check_output(< <% i = 0 list.each_with_index do |item, i| %> <%= i+1 %> <%== item %> <% end %> <%== i+1 %> END1 _buf = ::String.new;; _buf << ' '; i = 0 list.each_with_index do |item, i| ; _buf << ' '; end ; _buf << '
' << ( i+1 ).to_s << ' ' << ::Erubi.h(( item )) << '
' << ::Erubi.h(( i+1 )) << ' ' ; _buf.to_s END2
1 &'<>"2
1 END3 end it "should handle :freeze_template_literals => true option" do @options[:freeze_template_literals] = true list = list = ['&\'<>"2'] @strip_freeze = false check_output(< <% i = 0 list.each_with_index do |item, i| %> <%= i+1 %> <%== item %> <% end %> <%== i+1 %> END1 _buf = ::String.new; _buf << ' '.freeze; i = 0 list.each_with_index do |item, i| _buf << ' '.freeze; end _buf << '
'.freeze; _buf << ( i+1 ).to_s; _buf << ' '.freeze; _buf << ::Erubi.h(( item )); _buf << '
'.freeze; _buf << ::Erubi.h(( i+1 )); _buf << ' '.freeze; _buf.to_s END2
1 &'<>"2
1 END3 end it "should handle :freeze_template_literals => false option" do @options[:freeze_template_literals] = false list = list = ['&\'<>"2'] @strip_freeze = false check_output(< <% i = 0 list.each_with_index do |item, i| %> <%= i+1 %> <%== item %> <% end %> <%== i+1 %> END1 _buf = ::String.new; _buf << ' '; i = 0 list.each_with_index do |item, i| _buf << ' '; end _buf << '
'; _buf << ( i+1 ).to_s; _buf << ' '; _buf << ::Erubi.h(( item )); _buf << '
'; _buf << ::Erubi.h(( i+1 )); _buf << ' '; _buf.to_s END2
1 &'<>"2
1 END3 end it "should handle ensure option with no bufvar" do list = list = ['&\'<>"2'] @options[:ensure] = true check_output(< <% i = 0 list.each_with_index do |item, i| %> <%= i+1 %> <%== item %> <% end %> <%== i+1 %> END1 begin; __original_outvar = _buf if defined?(_buf); _buf = ::String.new; _buf << ' '; i = 0 list.each_with_index do |item, i| _buf << ' '; end _buf << '
'; _buf << ( i+1 ).to_s; _buf << ' '; _buf << ::Erubi.h(( item )); _buf << '
'; _buf << ::Erubi.h(( i+1 )); _buf << ' '; _buf.to_s ; ensure _buf = __original_outvar end END2
1 &'<>"2
1 END3 end it "should handle trailing rspace with - modifier in <%|= and <%|" do eval(::Erubi::CaptureEndEngine.new("<%|= '&' -%>\n<%| -%>\n").src).must_equal '&' end it "should handle lspace in <%|=" do eval(::Erubi::CaptureEndEngine.new("<%|= %><%| %><%|= %><%| %>").src).must_equal '' end it "should have <%|= with CaptureEndEngine not escape by default" do eval(::Erubi::CaptureEndEngine.new('<%|= "&" %><%| %>').src).must_equal '&' eval(::Erubi::CaptureEndEngine.new('<%|= "&" %><%| %>', :escape=>false).src).must_equal '&' eval(::Erubi::CaptureEndEngine.new('<%|= "&" %><%| %>', :escape_capture=>false).src).must_equal '&' eval(::Erubi::CaptureEndEngine.new('<%|= "&" %><%| %>', :escape=>true).src).must_equal '&' eval(::Erubi::CaptureEndEngine.new('<%|= "&" %><%| %>', :escape_capture=>true).src).must_equal '&' end it "should have <%|== with CaptureEndEngine escape by default" do eval(::Erubi::CaptureEndEngine.new('<%|== "&" %><%| %>').src).must_equal '&' eval(::Erubi::CaptureEndEngine.new('<%|== "&" %><%| %>', :escape=>true).src).must_equal '&' eval(::Erubi::CaptureEndEngine.new('<%|== "&" %><%| %>', :escape_capture=>true).src).must_equal '&' eval(::Erubi::CaptureEndEngine.new('<%|== "&" %><%| %>', :escape=>false).src).must_equal '&' eval(::Erubi::CaptureEndEngine.new('<%|== "&" %><%| %>', :escape_capture=>false).src).must_equal '&' end [['', false], ['=', true]].each do |ind, escape| it "should allow <%|=#{ind} and <%| for capturing with CaptureEndEngine with :escape_capture => #{escape} and :escape => #{!escape}" do @options[:bufvar] = '@a' @options[:capture] = true @options[:escape_capture] = escape @options[:escape] = !escape @options[:engine] = ::Erubi::CaptureEndEngine setup_bar check_output(< <%|=#{ind} bar do %> <%=#{ind} '&' %> <%| end %> END1 #{'__erubi = ::Erubi; ' unless escape}@a = ::String.new; @a << ' '; @a << ' ';begin; (__erubi_stack ||= []) << @a; @a = ::String.new; __erubi_stack.last << (( bar do @a << ' '; @a << ' '; @a << #{!escape ? '__erubi' : '::Erubi'}.h(( '&' )); @a << ' '; @a << ' '; end )).to_s; ensure; @a = __erubi_stack.pop; end; @a << ' '; @a << '
'; @a.to_s END2 A & B
END3 end end [['', true], ['=', false]].each do |ind, escape| it "should allow <%|=#{ind} and <%| for capturing with CaptureEndEngine when with :escape => #{escape}" do @options[:bufvar] = '@a' @options[:escape] = escape @options[:engine] = ::Erubi::CaptureEndEngine setup_bar check_output(< <%|=#{ind} bar do %> <%=#{ind} '&' %> <%| end %> END1 #{'__erubi = ::Erubi; ' if escape}@a = ::String.new; @a << ' '; @a << ' ';begin; (__erubi_stack ||= []) << @a; @a = ::String.new; __erubi_stack.last << #{escape ? '__erubi' : '::Erubi'}.h(( bar do @a << ' '; @a << ' '; @a << #{escape ? '__erubi' : '::Erubi'}.h(( '&' )); @a << ' '; @a << ' '; end )).to_s; ensure; @a = __erubi_stack.pop; end; @a << ' '; @a << '
'; @a.to_s END2 A <B>&AMP;</B> B
END3 end it "should handle loops in <%|=#{ind} and <%| for capturing with CaptureEndEngine when with :escape => #{escape}" do @options[:bufvar] = '@a' @options[:escape] = escape @options[:engine] = ::Erubi::CaptureEndEngine setup_bar check_output(< <%|=#{ind} quux do |i| %> <%=#{ind} "\#{i}&" %> <%| end %> END1 #{'__erubi = ::Erubi; ' if escape}@a = ::String.new; @a << ' '; @a << ' ';begin; (__erubi_stack ||= []) << @a; @a = ::String.new; __erubi_stack.last << #{escape ? '__erubi' : '::Erubi'}.h(( quux do |i| @a << ' '; @a << ' '; @a << #{escape ? '__erubi' : '::Erubi'}.h(( "\#{i}&" )); @a << ' '; @a << ' '; end )).to_s; ensure; @a = __erubi_stack.pop; end; @a << ' '; @a << '
'; @a.to_s END2 AC0 <B>0&AMP;</B> D0C1 <B>1&AMP;</B> D1C2 <B>2&AMP;</B> D2B
END3 end it "should allow <%|=#{ind} and <%| for nested capturing with CaptureEndEngine when with :escape => #{escape}" do @options[:bufvar] = '@a' @options[:escape] = escape @options[:engine] = ::Erubi::CaptureEndEngine setup_bar check_output(< <%|=#{ind} bar do %> <%=#{ind} '&' %> <%|=#{ind} baz do %>e<%| end %> <%| end %> END1 #{'__erubi = ::Erubi; ' if escape}@a = ::String.new; @a << ' '; @a << ' ';begin; (__erubi_stack ||= []) << @a; @a = ::String.new; __erubi_stack.last << #{escape ? '__erubi' : '::Erubi'}.h(( bar do @a << ' '; @a << ' '; @a << #{escape ? '__erubi' : '::Erubi'}.h(( '&' )); @a << ' '; @a << ' ';begin; (__erubi_stack ||= []) << @a; @a = ::String.new; __erubi_stack.last << #{escape ? '__erubi' : '::Erubi'}.h(( baz do @a << 'e'; end )).to_s; ensure; @a = __erubi_stack.pop; end; @a << ' '; @a << ' '; end )).to_s; ensure; @a = __erubi_stack.pop; end; @a << ' '; @a << '
'; @a.to_s END2 A <B>&AMP;</B> CEDCED B
END3 end end [:outvar, :bufvar].each do |var| it "should handle :#{var} and :freeze options" do @options[var] = "@_out_buf" @options[:freeze] = true @items = [2] i = i = 0 check_output(< <% for item in @items %> <%= i+1 %> <%== item %> <% end %> END1 # frozen_string_literal: true @_out_buf = ::String.new; @_out_buf << ' '; for item in @items @_out_buf << ' '; end @_out_buf << '
'; @_out_buf << ( i+1 ).to_s; @_out_buf << ' '; @_out_buf << ::Erubi.h(( item )); @_out_buf << '
'; @_out_buf.to_s END2
1 2
END3 end end it "should handle <%% and <%# syntax" do @items = [2] i = i = 0 check_output(< <%% for item in @items %> <%# i+1 %> <%# item %> <%% end %> END1 _buf = ::String.new; _buf << ' '; _buf << '<% for item in @items %> '; _buf << ' '; _buf << ' <% end %> '; _buf << '
';; _buf << ' ';; _buf << '
'; _buf.to_s END2 <% for item in @items %> <% end %>
END3 end it "should handle <%% with a different literal prefix/postfix" do @options[:literal_prefix] = "{%" @options[:literal_postfix] = "%}" @items = [2] i = i = 0 check_output(< <%% for item in @items %> <%% end %> <%%= "literal" %> END1 _buf = ::String.new; _buf << ' '; _buf << ' {% for item in @items %} '; _buf << ' '; _buf << ' {% end %} '; _buf << ' {%= "literal" %} '; _buf << '
'; _buf.to_s END2 {% for item in @items %} {% end %} {%= "literal" %}
END3 end it "should handle :trim => false option" do @options[:trim] = false @items = [2] i = i = 0 check_output(< <% for item in @items %> <%# i+1 %> <%== item %> <% end %><%#%> <% i = 1 %>a <% i = 1 %> END1 _buf = ::String.new; _buf << ' '; _buf << ' '; for item in @items ; _buf << ' '; _buf << ' '; _buf << ' '; end ; _buf << ' '; _buf << ' '; i = 1 ; _buf << 'a '; _buf << ' '; i = 1 ; _buf << ' '; _buf << '
'; _buf << ' '; _buf << ::Erubi.h(( item )); _buf << '
'; _buf.to_s END2 a
2
END3 end [:escape, :escape_html].each do |opt| it "should handle :#{opt} and :escapefunc options" do @options[opt] = true @options[:escapefunc] = 'h.call' h = h = proc{|s| s.to_s*2} list = list = ['2'] check_output(< <% i = 0 list.each_with_index do |item, i| %> <%= i+1 %> <%== item %> <% end %> <%== i+1 %> END1 _buf = ::String.new; _buf << ' '; i = 0 list.each_with_index do |item, i| _buf << ' '; end _buf << '
'; _buf << h.call(( i+1 )); _buf << ' '; _buf << ( item ).to_s; _buf << '
'; _buf << ( i+1 ).to_s; _buf << ' '; _buf.to_s END2
11 2
1 END3 end end it "should handle :escape option without :escapefunc option" do @options[:escape] = true list = list = ['&\'<>"2'] check_output(< <% i = 0 list.each_with_index do |item, i| %> <%== i+1 %> <%= item %> <% end %> END1 __erubi = ::Erubi; _buf = ::String.new; _buf << ' '; i = 0 list.each_with_index do |item, i| _buf << ' '; end _buf << '
'; _buf << ( i+1 ).to_s; _buf << ' '; _buf << __erubi.h(( item )); _buf << '
'; _buf.to_s END2
1 &'<>"2
END3 end it "should handle :preamble and :postamble options" do @options[:preamble] = '_buf = String.new("1");' @options[:postamble] = "_buf[0...18]\n" list = list = ['2'] check_output(< <% i = 0 list.each_with_index do |item, i| %> <%= i+1 %> <%== item %> <% end %> <%== i+1 %> END1 _buf = String.new("1"); _buf << ' '; i = 0 list.each_with_index do |item, i| _buf << ' '; end _buf << '
'; _buf << ( i+1 ).to_s; _buf << ' '; _buf << ::Erubi.h(( item )); _buf << '
'; _buf << ::Erubi.h(( i+1 )); _buf << ' '; _buf[0...18] END2 1 END3 end it "should have working filename accessor" do Erubi::Engine.new('', :filename=>'foo.rb').filename.must_equal 'foo.rb' end it "should have working bufvar accessor" do Erubi::Engine.new('', :bufvar=>'foo').bufvar.must_equal 'foo' Erubi::Engine.new('', :outvar=>'foo').bufvar.must_equal 'foo' end it "should work with BasicObject methods" do c = Class.new(BasicObject) c.class_eval("def a; #{Erubi::Engine.new('2').src} end") c.new.a.must_equal '2' end if defined?(BasicObject) it "should return frozen object" do Erubi::Engine.new('').frozen?.must_equal true end it "should have frozen src" do Erubi::Engine.new('').src.frozen?.must_equal true end it "should raise an error if a tag is not handled when a custom regexp is used" do proc{Erubi::Engine.new('<%] %>', :regexp =>/<%(={1,2}|\]|-|\#|%)?(.*?)([-=])?%>([ \t]*\r?\n)?/m)}.must_raise ArgumentError proc{Erubi::CaptureEndEngine.new('<%] %>', :regexp =>/<%(={1,2}|\]|-|\#|%)?(.*?)([-=])?%>([ \t]*\r?\n)?/m)}.must_raise ArgumentError end it "should respect the :yield_returns_buffer option for making templates return the (potentially modified) buffer" do @options[:engine] = ::Erubi::CaptureEndEngine @options[:bufvar] = '@a' def self.bar a = String.new a << "a" yield 'burgers' case b = (yield 'salads') when String a << b a << 'b' a.upcase end end check_output(< Let's eat <%= item %>! <% nil %><%| end %> END1 @a = ::String.new;begin; (__erubi_stack ||= []) << @a; @a = ::String.new; __erubi_stack.last << (( bar do |item| @a << ' '; @a << 'Let\\'s eat '; @a << ( item ).to_s; @a << '! '; nil ; end )).to_s; ensure; @a = __erubi_stack.pop; end; @a << ' '; @a.to_s END2 END3 @options[:yield_returns_buffer] = true check_output(< Let's eat <%= item %>! <% i = i = nil %><%| end %> END1 @a = ::String.new;begin; (__erubi_stack ||= []) << @a; @a = ::String.new; __erubi_stack.last << (( bar do |item| @a << ' '; @a << 'Let\\'s eat '; @a << ( item ).to_s; @a << '! '; i = i = nil ; @a; end )).to_s; ensure; @a = __erubi_stack.pop; end; @a << ' '; @a.to_s END2 A LET'S EAT BURGERS! LET'S EAT SALADS! B END3 end it "should respect the :yield_returns_buffer option for making templates return the (potentially modified) buffer as the result of the block" do @options[:engine] = ::Erubi::CaptureEndEngine @options[:yield_returns_buffer] = true def self.bar(foo = nil) if foo.nil? yield else foo end end check_output(< Let's eat the tacos! <%| end %> Delicious! END1 _buf = ::String.new;begin; (__erubi_stack ||= []) << _buf; _buf = ::String.new; __erubi_stack.last << (( bar do _buf << ' '; _buf << 'Let\\'s eat the tacos! '; _buf; end )).to_s; ensure; _buf = __erubi_stack.pop; end; _buf << ' '; _buf << ' Delicious! '; _buf.to_s END2 Let's eat the tacos! Delicious! END3 check_output(< Let's eat burgers! <%| end %> Delicious! END1 _buf = ::String.new;begin; (__erubi_stack ||= []) << _buf; _buf = ::String.new; __erubi_stack.last << (( bar(\"Don't eat the burgers!\") do _buf << ' '; _buf << 'Let\\'s eat burgers! '; _buf; end )).to_s; ensure; _buf = __erubi_stack.pop; end; _buf << ' '; _buf << ' Delicious! '; _buf.to_s END2 Don't eat the burgers! Delicious! END3 end end