pax_global_header 0000666 0000000 0000000 00000000064 14351127604 0014515 g ustar 00root root 0000000 0000000 52 comment=733bcbd6c8e032e66595edf21b255fd14b2c0062
erubi-1.12.0/ 0000775 0000000 0000000 00000000000 14351127604 0012704 5 ustar 00root root 0000000 0000000 erubi-1.12.0/.ci.gemfile 0000664 0000000 0000000 00000000264 14351127604 0014711 0 ustar 00root root 0000000 0000000 source '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/ 0000775 0000000 0000000 00000000000 14351127604 0014244 5 ustar 00root root 0000000 0000000 erubi-1.12.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14351127604 0016301 5 ustar 00root root 0000000 0000000 erubi-1.12.0/.github/workflows/ci.yml 0000664 0000000 0000000 00000001144 14351127604 0017417 0 ustar 00root root 0000000 0000000 name: 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/.gitignore 0000664 0000000 0000000 00000000037 14351127604 0014674 0 ustar 00root root 0000000 0000000 /erubi-*.gem
/rdoc/
/coverage/
erubi-1.12.0/CHANGELOG 0000664 0000000 0000000 00000007002 14351127604 0014115 0 ustar 00root root 0000000 0000000 === 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-LICENSE 0000664 0000000 0000000 00000002137 14351127604 0014343 0 ustar 00root root 0000000 0000000 copyright(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.rdoc 0000664 0000000 0000000 00000006542 14351127604 0014521 0 ustar 00root root 0000000 0000000 = 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 << "
"
@_buf
end
puts eval(Erubi::CaptureEndEngine.new(<<-END, :bufvar=>:@_buf).src)
before
<%|= form do %>
inside
<%| end %>
after
END
# Output:
# before
#
# 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/Rakefile 0000664 0000000 0000000 00000002673 14351127604 0014361 0 ustar 00root root 0000000 0000000 require "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.gemspec 0000664 0000000 0000000 00000002316 14351127604 0015361 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14351127604 0013452 5 ustar 00root root 0000000 0000000 erubi-1.12.0/lib/erubi.rb 0000664 0000000 0000000 00000024431 14351127604 0015111 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14351127604 0014560 5 ustar 00root root 0000000 0000000 erubi-1.12.0/lib/erubi/capture_end.rb 0000664 0000000 0000000 00000005053 14351127604 0017401 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14351127604 0013663 5 ustar 00root root 0000000 0000000 erubi-1.12.0/test/test.rb 0000664 0000000 0000000 00000053700 14351127604 0015174 0 ustar 00root root 0000000 0000000 require '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| %>
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| %>
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 << '
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 = 0
list.each_with_index do |item, i|
; _buf << '
' << ( i+1 ).to_s << '
' << ::Erubi.h(( item )) << '
'; end
; _buf << '
' << ::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| %>
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| %>
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 << '
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 << '
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 << '
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 << '
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 %>
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 << '
';; _buf << '
'; _buf << ' <% end %>
'; _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 %>
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 << '
'; _buf << ::Erubi.h(( item )); _buf << '
'; _buf << ' '; end ;
_buf << '
'; _buf << ' '; i = 1 ; _buf << 'a
'; _buf << ' '; i = 1 ; _buf << '
'; _buf << '
';
_buf.to_s
END2
2
a
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| %>
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 = 0
list.each_with_index do |item, i|
_buf << '
'; _buf << ( i+1 ).to_s; _buf << '
'; _buf << __erubi.h(( item )); _buf << '
'; end
_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| %>
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