mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 00:05:42 +03:00
Compare commits
4 Commits
symbol-fil
...
StrainerTe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27e51b0455 | ||
|
|
05c8214f7d | ||
|
|
13936a24f1 | ||
|
|
c0ffee4133 |
@@ -13,8 +13,6 @@ matrix:
|
||||
- rvm: *latest_ruby
|
||||
script: bundle exec rake memory_profile:run
|
||||
name: Profiling Memory Usage
|
||||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
|
||||
branches:
|
||||
only:
|
||||
|
||||
2
Gemfile
2
Gemfile
@@ -22,6 +22,6 @@ group :test do
|
||||
gem 'rubocop-performance', require: false
|
||||
|
||||
platform :mri, :truffleruby do
|
||||
gem 'liquid-c', github: 'Shopify/liquid-c', branch: 'symbol-filter-names'
|
||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'master'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
* Split Strainer class as a factory and a template (#1208) [Thierry Joyal]
|
||||
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
|
||||
* StaticRegisters#fetch to raise on missing key (#1250) [Thierry Joyal]
|
||||
|
||||
## 4.0.3 / 2019-03-12
|
||||
|
||||
|
||||
@@ -57,14 +57,14 @@ require 'liquid/forloop_drop'
|
||||
require 'liquid/extensions'
|
||||
require 'liquid/errors'
|
||||
require 'liquid/interrupts'
|
||||
require 'liquid/filter'
|
||||
require 'liquid/filter_template'
|
||||
require 'liquid/strainer_factory'
|
||||
require 'liquid/strainer_template'
|
||||
require 'liquid/expression'
|
||||
require 'liquid/context'
|
||||
require 'liquid/parser_switching'
|
||||
require 'liquid/tag'
|
||||
require 'liquid/tag/disabler'
|
||||
require 'liquid/tag/disableable'
|
||||
require 'liquid/block'
|
||||
require 'liquid/block_body'
|
||||
require 'liquid/document'
|
||||
@@ -88,3 +88,4 @@ require 'liquid/template_factory'
|
||||
# Load all the tags of the standard library
|
||||
#
|
||||
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
|
||||
Dir["#{__dir__}/liquid/registers/*.rb"].each { |f| require f }
|
||||
|
||||
@@ -171,7 +171,13 @@ module Liquid
|
||||
private
|
||||
|
||||
def render_node(context, output, node)
|
||||
node.render_to_output_buffer(context, output)
|
||||
if node.disabled?(context)
|
||||
output << node.disabled_error_message
|
||||
return
|
||||
end
|
||||
disable_tags(context, node.disabled_tags) do
|
||||
node.render_to_output_buffer(context, output)
|
||||
end
|
||||
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
||||
context.handle_error(e, node.line_number)
|
||||
rescue ::StandardError => e
|
||||
@@ -179,6 +185,11 @@ module Liquid
|
||||
output << context.handle_error(e, line_number)
|
||||
end
|
||||
|
||||
def disable_tags(context, tags, &block)
|
||||
return yield if tags.empty?
|
||||
context.registers[:disabled_tags].disable(tags, &block)
|
||||
end
|
||||
|
||||
def raise_if_resource_limits_reached(context, length)
|
||||
context.resource_limits.render_length += length
|
||||
return unless context.resource_limits.reached?
|
||||
|
||||
@@ -44,7 +44,6 @@ module Liquid
|
||||
@interrupts = []
|
||||
@filters = []
|
||||
@global_filter = nil
|
||||
@disabled_tags = {}
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
@@ -145,7 +144,6 @@ module Liquid
|
||||
subcontext.strainer = nil
|
||||
subcontext.errors = errors
|
||||
subcontext.warnings = warnings
|
||||
subcontext.disabled_tags = @disabled_tags
|
||||
end
|
||||
end
|
||||
|
||||
@@ -210,24 +208,9 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def with_disabled_tags(tag_names)
|
||||
tag_names.each do |name|
|
||||
@disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
tag_names.each do |name|
|
||||
@disabled_tags[name] -= 1
|
||||
end
|
||||
end
|
||||
|
||||
def tag_disabled?(tag_name)
|
||||
@disabled_tags.fetch(tag_name, 0) > 0
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
|
||||
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters
|
||||
|
||||
private
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ module Liquid
|
||||
StandardError = Class.new(Error)
|
||||
SyntaxError = Class.new(Error)
|
||||
StackLevelError = Class.new(Error)
|
||||
TaintedError = Class.new(Error)
|
||||
MemoryError = Class.new(Error)
|
||||
ZeroDivisionError = Class.new(Error)
|
||||
FloatDomainError = Class.new(Error)
|
||||
@@ -53,6 +54,5 @@ module Liquid
|
||||
UndefinedDropMethod = Class.new(Error)
|
||||
UndefinedFilter = Class.new(Error)
|
||||
MethodOverrideError = Class.new(Error)
|
||||
DisabledError = Class.new(Error)
|
||||
InternalError = Class.new(Error)
|
||||
end
|
||||
|
||||
43
lib/liquid/filter.rb
Normal file
43
lib/liquid/filter.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'set'
|
||||
|
||||
module Liquid
|
||||
# A filter in liquid is a class which contain invokable logic from liquid templates.
|
||||
#
|
||||
# Public methods in filter classes are callable.
|
||||
#
|
||||
# The use for liquid filters is to make logic functions available to the web designers.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class StringFilter < Liquid::Filter
|
||||
# def upcase(input)
|
||||
# input.upcase
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# tmpl = Liquid::Template.parse('Result: {{ "test" | upcase }}')
|
||||
# tmpl.render({}, filters: [StringFilter])
|
||||
# => "Result: TEST"
|
||||
class Filter
|
||||
class << self
|
||||
def invokable_methods
|
||||
@invokable_methods ||= begin
|
||||
blacklist = Liquid::Filter.public_instance_methods
|
||||
whitelist = public_instance_methods - blacklist
|
||||
|
||||
Set.new(whitelist.map(&:to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(context)
|
||||
@context = context
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :context
|
||||
end
|
||||
end
|
||||
40
lib/liquid/filter_template.rb
Normal file
40
lib/liquid/filter_template.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'set'
|
||||
|
||||
module Liquid
|
||||
# FilterTemplate is the computed class for the filters system.
|
||||
#
|
||||
# Historically Liquid used to include filters as Module to the context strainer.
|
||||
# This lead to the absence of sandbox between filters (one filter could override private methods of another filter).
|
||||
#
|
||||
# With the implementation of Liquid::Filter, it is now possible for the modules from legacy code to be automatically
|
||||
# wrapped into a Liquid::Filter generated class.
|
||||
#
|
||||
# This should not be considered as the base behaviour, it is preferred to create filters going forward directly as
|
||||
# classes that are child of Liquid::Filter.
|
||||
class FilterTemplate < Filter
|
||||
class << self
|
||||
def include(mod)
|
||||
super
|
||||
|
||||
@init_module = mod
|
||||
end
|
||||
|
||||
# Override of the `invokable_methods`.
|
||||
# We can't rely on the parent logic as some modules might have been defining methods that shadow Class methods.
|
||||
#
|
||||
# Eg.:
|
||||
# mod = Liquid::StandardFilters
|
||||
# filter = Class.new(FilterTemplate)
|
||||
# filter.include(mod)
|
||||
# mod.public_instance_methods - (filter.public_instance_methods - Class.public_instance_methods)
|
||||
# => [:prepend]
|
||||
def invokable_methods
|
||||
whitelist = @init_module.public_instance_methods
|
||||
|
||||
@invokable_methods ||= Set.new(whitelist.map(&:to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
module BlockBodyProfilingHook
|
||||
def render_node(context, output, node)
|
||||
class BlockBody
|
||||
def render_node_with_profiling(context, output, node)
|
||||
Profiler.profile_node_render(node) do
|
||||
super
|
||||
render_node_without_profiling(context, output, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
BlockBody.prepend(BlockBodyProfilingHook)
|
||||
|
||||
module IncludeProfilingHook
|
||||
def render_to_output_buffer(context, output)
|
||||
alias_method :render_node_without_profiling, :render_node
|
||||
alias_method :render_node, :render_node_with_profiling
|
||||
end
|
||||
|
||||
class Include < Tag
|
||||
def render_to_output_buffer_with_profiling(context, output)
|
||||
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
|
||||
super
|
||||
render_to_output_buffer_without_profiling(context, output)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :render_to_output_buffer_without_profiling, :render_to_output_buffer
|
||||
alias_method :render_to_output_buffer, :render_to_output_buffer_with_profiling
|
||||
end
|
||||
Include.prepend(IncludeProfilingHook)
|
||||
end
|
||||
|
||||
32
lib/liquid/registers/disabled_tags.rb
Normal file
32
lib/liquid/registers/disabled_tags.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
module Liquid
|
||||
class DisabledTags < Register
|
||||
def initialize
|
||||
@disabled_tags = {}
|
||||
end
|
||||
|
||||
def disabled?(tag)
|
||||
@disabled_tags.key?(tag) && @disabled_tags[tag] > 0
|
||||
end
|
||||
|
||||
def disable(tags)
|
||||
tags.each(&method(:increment))
|
||||
yield
|
||||
ensure
|
||||
tags.each(&method(:decrement))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def increment(tag)
|
||||
@disabled_tags[tag] ||= 0
|
||||
@disabled_tags[tag] += 1
|
||||
end
|
||||
|
||||
def decrement(tag)
|
||||
@disabled_tags[tag] -= 1
|
||||
end
|
||||
end
|
||||
|
||||
Template.add_register(:disabled_tags, DisabledTags.new)
|
||||
end
|
||||
@@ -41,7 +41,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def escape(input)
|
||||
CGI.escapeHTML(input.to_s) unless input.nil?
|
||||
CGI.escapeHTML(input.to_s).untaint unless input.nil?
|
||||
end
|
||||
alias_method :h, :escape
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module Liquid
|
||||
class StaticRegisters
|
||||
attr_reader :static
|
||||
attr_reader :static, :registers
|
||||
|
||||
def initialize(registers = {})
|
||||
@static = registers.is_a?(StaticRegisters) ? registers.static : registers
|
||||
@@ -25,16 +25,8 @@ module Liquid
|
||||
@registers.delete(key)
|
||||
end
|
||||
|
||||
UNDEFINED = Object.new
|
||||
|
||||
def fetch(key, default = UNDEFINED, &block)
|
||||
if @registers.key?(key)
|
||||
@registers.fetch(key)
|
||||
elsif default != UNDEFINED
|
||||
@static.fetch(key, default, &block)
|
||||
else
|
||||
@static.fetch(key, &block)
|
||||
end
|
||||
def fetch(key, default = nil)
|
||||
key?(key) ? self[key] : default
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
|
||||
@@ -14,40 +14,69 @@ module Liquid
|
||||
end
|
||||
|
||||
class << self
|
||||
def add_filter(filter)
|
||||
return if include?(filter)
|
||||
|
||||
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
||||
if invokable_non_public_methods.any?
|
||||
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
||||
def add_filter(mod)
|
||||
filter = if mod.is_a?(Class) && mod.ancestors.include?(Liquid::Filter)
|
||||
mod
|
||||
elsif mod.instance_of?(Module)
|
||||
convert_mod_to_filter(mod)
|
||||
else
|
||||
raise(ArgumentError, "wrong argument type Proc (expected Liquid::Filter)")
|
||||
end
|
||||
|
||||
include(filter)
|
||||
filter.invokable_methods.each do |method|
|
||||
filter_method_map[method] = filter
|
||||
end
|
||||
end
|
||||
|
||||
filter.public_instance_methods.each { |name| filter_methods[name] = true }
|
||||
def filter_for(method)
|
||||
filter_method_map[method]
|
||||
end
|
||||
|
||||
def invokable?(method)
|
||||
filter_methods.key?(method.to_sym)
|
||||
filter_method_map.key?(method)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_methods
|
||||
@filter_methods ||= {}
|
||||
def filter_method_map
|
||||
@filter_method_map ||= {}
|
||||
end
|
||||
|
||||
def convert_mod_to_filter(mod)
|
||||
@filter_classes ||= {}
|
||||
@filter_classes[mod] ||= begin
|
||||
klass = Class.new(FilterTemplate)
|
||||
klass.include(mod)
|
||||
klass
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def invoke(method, *args)
|
||||
if self.class.invokable?(method)
|
||||
send(method, *args)
|
||||
elsif @context.strict_filters
|
||||
raise Liquid::UndefinedFilter, "undefined filter #{method}"
|
||||
begin
|
||||
instance = filter_instance_for(method)
|
||||
instance.public_send(method, *args)
|
||||
rescue ::ArgumentError => e
|
||||
raise Liquid::ArgumentError, e.message, e.backtrace
|
||||
end
|
||||
elsif context.strict_filters
|
||||
raise(Liquid::UndefinedFilter, "undefined filter #{method}")
|
||||
else
|
||||
args.first
|
||||
end
|
||||
rescue ::ArgumentError => e
|
||||
raise Liquid::ArgumentError, e.message, e.backtrace
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_instance_for(method)
|
||||
@filter_instances ||= {}
|
||||
@filter_instances.fetch(method) do
|
||||
klass = self.class.filter_for(method)
|
||||
klass.new(context)
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :context
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,13 +13,15 @@ module Liquid
|
||||
tag
|
||||
end
|
||||
|
||||
def disable_tags(*tag_names)
|
||||
@disabled_tags ||= []
|
||||
@disabled_tags.concat(tag_names)
|
||||
prepend(Disabler)
|
||||
def disable_tags(*tags)
|
||||
disabled_tags.push(*tags)
|
||||
end
|
||||
|
||||
private :new
|
||||
|
||||
def disabled_tags
|
||||
@disabled_tags ||= []
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
@@ -44,6 +46,14 @@ module Liquid
|
||||
''
|
||||
end
|
||||
|
||||
def disabled?(context)
|
||||
context.registers[:disabled_tags].disabled?(tag_name)
|
||||
end
|
||||
|
||||
def disabled_error_message
|
||||
"#{tag_name} #{options[:locale].t('errors.disabled.tag')}"
|
||||
end
|
||||
|
||||
# For backwards compatibility with custom tags. In a future release, the semantics
|
||||
# of the `render_to_output_buffer` method will become the default and the `render`
|
||||
# method will be removed.
|
||||
@@ -55,5 +65,9 @@ module Liquid
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
|
||||
def disabled_tags
|
||||
self.class.disabled_tags
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class Tag
|
||||
module Disableable
|
||||
def render_to_output_buffer(context, output)
|
||||
if context.tag_disabled?(tag_name)
|
||||
output << disabled_error(context)
|
||||
return
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def disabled_error(context)
|
||||
# raise then rescue the exception so that the Context#exception_renderer can re-raise it
|
||||
raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
|
||||
rescue DisabledError => exc
|
||||
context.handle_error(exc, line_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class Tag
|
||||
module Disabler
|
||||
module ClassMethods
|
||||
attr_reader :disabled_tags
|
||||
end
|
||||
|
||||
def self.prepended(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
context.with_disabled_tags(self.class.disabled_tags) do
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -12,6 +12,10 @@ module Liquid
|
||||
class Assign < Tag
|
||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
||||
|
||||
def self.syntax_error_translation_key
|
||||
"errors.syntax.assign"
|
||||
end
|
||||
|
||||
attr_reader :to, :from
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
@@ -20,7 +24,7 @@ module Liquid
|
||||
@to = Regexp.last_match(1)
|
||||
@from = Variable.new(Regexp.last_match(2), options)
|
||||
else
|
||||
raise SyntaxError, options[:locale].t('errors.syntax.assign')
|
||||
raise SyntaxError, options[:locale].t(self.class.syntax_error_translation_key)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ module Liquid
|
||||
# {% include 'product' for products %}
|
||||
#
|
||||
class Include < Tag
|
||||
prepend Tag::Disableable
|
||||
|
||||
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
||||
Syntax = SYNTAX
|
||||
|
||||
|
||||
@@ -64,6 +64,23 @@ module Liquid
|
||||
attr_accessor :error_mode
|
||||
Template.error_mode = :lax
|
||||
|
||||
attr_reader :taint_mode
|
||||
|
||||
# Sets how strict the taint checker should be.
|
||||
# :lax is the default, and ignores the taint flag completely
|
||||
# :warn adds a warning, but does not interrupt the rendering
|
||||
# :error raises an error when tainted output is used
|
||||
# @deprecated Since it is being deprecated in ruby itself.
|
||||
def taint_mode=(mode)
|
||||
taint_supported = Object.new.taint.tainted?
|
||||
if mode != :lax && !taint_supported
|
||||
raise NotImplementedError, "#{RUBY_ENGINE} #{RUBY_VERSION} doesn't support taint checking"
|
||||
end
|
||||
@taint_mode = mode
|
||||
end
|
||||
|
||||
Template.taint_mode = :lax
|
||||
|
||||
attr_accessor :default_exception_renderer
|
||||
Template.default_exception_renderer = lambda do |exception|
|
||||
exception
|
||||
@@ -80,6 +97,14 @@ module Liquid
|
||||
tags[name.to_s] = klass
|
||||
end
|
||||
|
||||
attr_accessor :registers
|
||||
Template.registers = {}
|
||||
private :registers=
|
||||
|
||||
def add_register(name, klass)
|
||||
registers[name.to_sym] = klass
|
||||
end
|
||||
|
||||
# Pass a module with filter methods which should be available
|
||||
# to all liquid views. Good for registering the standard library
|
||||
def register_filter(mod)
|
||||
@@ -94,13 +119,14 @@ module Liquid
|
||||
# To enable profiling, pass in <tt>profile: true</tt> as an option.
|
||||
# See Liquid::Profiler for more information
|
||||
def parse(source, options = {})
|
||||
new.parse(source, options)
|
||||
template = Template.new
|
||||
template.parse(source, options)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize
|
||||
@rethrow_errors = false
|
||||
@resource_limits = ResourceLimits.new(Template.default_resource_limits)
|
||||
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
|
||||
end
|
||||
|
||||
# Parse source code.
|
||||
@@ -186,6 +212,10 @@ module Liquid
|
||||
context.add_filters(args.pop)
|
||||
end
|
||||
|
||||
Template.registers.each do |key, register|
|
||||
context_register[key] = register
|
||||
end
|
||||
|
||||
# Retrying a render resets resource usage
|
||||
context.resource_limits.reset
|
||||
|
||||
|
||||
@@ -86,7 +86,9 @@ module Liquid
|
||||
context.invoke(filter_name, output, *filter_args)
|
||||
end
|
||||
|
||||
context.apply_global_filter(obj)
|
||||
obj = context.apply_global_filter(obj)
|
||||
taint_check(context, obj)
|
||||
obj
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
@@ -123,7 +125,7 @@ module Liquid
|
||||
filter_args << Expression.parse(a)
|
||||
end
|
||||
end
|
||||
result = [filter_name.to_sym, filter_args]
|
||||
result = [filter_name, filter_args]
|
||||
result << keyword_args if keyword_args
|
||||
result
|
||||
end
|
||||
@@ -140,6 +142,25 @@ module Liquid
|
||||
parsed_args
|
||||
end
|
||||
|
||||
def taint_check(context, obj)
|
||||
return if Template.taint_mode == :lax
|
||||
return unless obj.tainted?
|
||||
|
||||
@markup =~ QuotedFragment
|
||||
name = Regexp.last_match(0)
|
||||
|
||||
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
|
||||
error.line_number = line_number
|
||||
error.template_name = context.template_name
|
||||
|
||||
case Template.taint_mode
|
||||
when :warn
|
||||
context.warnings << error
|
||||
when :error
|
||||
raise error
|
||||
end
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[@node.name] + @node.filters.flatten
|
||||
|
||||
@@ -27,6 +27,6 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.require_path = "lib"
|
||||
|
||||
s.add_development_dependency('rake', '~> 13.0')
|
||||
s.add_development_dependency('rake', '~> 11.3')
|
||||
s.add_development_dependency('minitest')
|
||||
end
|
||||
|
||||
@@ -49,6 +49,10 @@ class ProductDrop < Liquid::Drop
|
||||
ContextDrop.new
|
||||
end
|
||||
|
||||
def user_input
|
||||
(+"foo").taint
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def callmenot
|
||||
@@ -110,6 +114,34 @@ class DropsTest < Minitest::Test
|
||||
assert_equal(' ', tpl.render!('product' => ProductDrop.new))
|
||||
end
|
||||
|
||||
if taint_supported?
|
||||
def test_rendering_raises_on_tainted_attr
|
||||
with_taint_mode(:error) do
|
||||
tpl = Liquid::Template.parse('{{ product.user_input }}')
|
||||
assert_raises TaintedError do
|
||||
tpl.render!('product' => ProductDrop.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_rendering_warns_on_tainted_attr
|
||||
with_taint_mode(:warn) do
|
||||
tpl = Liquid::Template.parse('{{ product.user_input }}')
|
||||
context = Context.new('product' => ProductDrop.new)
|
||||
tpl.render!(context)
|
||||
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
|
||||
assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
|
||||
end
|
||||
end
|
||||
|
||||
def test_rendering_doesnt_raise_on_escaped_tainted_attr
|
||||
with_taint_mode(:error) do
|
||||
tpl = Liquid::Template.parse('{{ product.user_input | escape }}')
|
||||
tpl.render!('product' => ProductDrop.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_drop_does_only_respond_to_whitelisted_methods
|
||||
assert_equal("", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new))
|
||||
assert_equal("", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new))
|
||||
@@ -249,7 +281,7 @@ class DropsTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_invokable_methods
|
||||
assert_equal(%w(to_liquid catchall context texts).to_set, ProductDrop.invokable_methods)
|
||||
assert_equal(%w(to_liquid catchall user_input context texts).to_set, ProductDrop.invokable_methods)
|
||||
assert_equal(%w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods)
|
||||
assert_equal(%w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods)
|
||||
assert_equal(%w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods)
|
||||
|
||||
@@ -238,7 +238,7 @@ class ParseTreeVisitorTest < Minitest::Test
|
||||
def traversal(template)
|
||||
ParseTreeVisitor
|
||||
.for(Template.parse(template).root)
|
||||
.add_callback_for(VariableLookup) { |node| node.name } # rubocop:disable Style/SymbolProc
|
||||
.add_callback_for(VariableLookup, &:name)
|
||||
end
|
||||
|
||||
def visit(template)
|
||||
|
||||
27
test/integration/registers/disabled_tags_test.rb
Normal file
27
test/integration/registers/disabled_tags_test.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class DisabledTagsTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
class DisableRaw < Block
|
||||
disable_tags "raw"
|
||||
end
|
||||
|
||||
class DisableRawEcho < Block
|
||||
disable_tags "raw", "echo"
|
||||
end
|
||||
|
||||
def test_disables_raw
|
||||
with_custom_tag('disable', DisableRaw) do
|
||||
assert_template_result 'raw usage is not allowed in this contextfoo', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
|
||||
end
|
||||
end
|
||||
|
||||
def test_disables_echo_and_raw
|
||||
with_custom_tag('disable', DisableRawEcho) do
|
||||
assert_template_result 'raw usage is not allowed in this contextecho usage is not allowed in this context', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -43,22 +43,15 @@ class SecurityTest < Minitest::Test
|
||||
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
|
||||
end
|
||||
|
||||
def test_does_not_permanently_add_filters_to_symbol_table
|
||||
def test_does_not_add_filters_to_symbol_table
|
||||
current_symbols = Symbol.all_symbols
|
||||
|
||||
# MRI imprecisely marks objects found on the C stack, which can result
|
||||
# in uninitialized memory being marked. This can even result in the test failing
|
||||
# deterministically for a given compilation of ruby. Using a separate thread will
|
||||
# keep these writes of the symbol pointer on a separate stack that will be garbage
|
||||
# collected after Thread#join.
|
||||
Thread.new do
|
||||
test = %( {{ "some_string" | a_bad_filter }} )
|
||||
Template.parse(test).render!
|
||||
nil
|
||||
end.join
|
||||
test = %( {{ "some_string" | a_bad_filter }} )
|
||||
|
||||
GC.start
|
||||
template = Template.parse(test)
|
||||
assert_equal([], (Symbol.all_symbols - current_symbols))
|
||||
|
||||
template.render!
|
||||
assert_equal([], (Symbol.all_symbols - current_symbols))
|
||||
end
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class TagDisableableTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
class DisableRaw < Block
|
||||
disable_tags "raw"
|
||||
end
|
||||
|
||||
class DisableRawEcho < Block
|
||||
disable_tags "raw", "echo"
|
||||
end
|
||||
|
||||
class DisableableRaw < Liquid::Raw
|
||||
prepend Liquid::Tag::Disableable
|
||||
end
|
||||
|
||||
class DisableableEcho < Liquid::Echo
|
||||
prepend Liquid::Tag::Disableable
|
||||
end
|
||||
|
||||
def test_disables_raw
|
||||
with_disableable_tags do
|
||||
with_custom_tag('disable', DisableRaw) do
|
||||
output = Template.parse('{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}').render
|
||||
assert_equal('Liquid error: raw usage is not allowed in this contextfoo', output)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_disables_echo_and_raw
|
||||
with_disableable_tags do
|
||||
with_custom_tag('disable', DisableRawEcho) do
|
||||
output = Template.parse('{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}').render
|
||||
assert_equal('Liquid error: raw usage is not allowed in this contextLiquid error: echo usage is not allowed in this context', output)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_disableable_tags
|
||||
with_custom_tag('raw', DisableableRaw) do
|
||||
with_custom_tag('echo', DisableableEcho) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -42,6 +42,34 @@ class RenderTagTest < Minitest::Test
|
||||
assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}")
|
||||
end
|
||||
|
||||
if taint_supported?
|
||||
def test_render_sets_the_correct_template_name_for_errors
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
|
||||
|
||||
with_taint_mode :error do
|
||||
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
|
||||
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
|
||||
template.render(context)
|
||||
|
||||
assert_equal [Liquid::TaintedError], template.errors.map(&:class)
|
||||
assert_equal 'snippet', template.errors.first.template_name
|
||||
end
|
||||
end
|
||||
|
||||
def test_render_sets_the_correct_template_name_for_warnings
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
|
||||
|
||||
with_taint_mode :warn do
|
||||
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
|
||||
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
|
||||
template.render(context)
|
||||
|
||||
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
|
||||
assert_equal 'snippet', context.warnings.first.template_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_render_does_not_mutate_parent_scope
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
|
||||
assert_template_result('', "{% render 'snippet' %}{{ inner }}")
|
||||
@@ -127,10 +155,7 @@ class RenderTagTest < Minitest::Test
|
||||
'test_include' => '{% include "foo" %}'
|
||||
)
|
||||
|
||||
exc = assert_raises(Liquid::DisabledError) do
|
||||
Liquid::Template.parse('{% render "test_include" %}').render!
|
||||
end
|
||||
assert_equal('Liquid error: include usage is not allowed in this context', exc.message)
|
||||
assert_template_result('include usage is not allowed in this context', '{% render "test_include" %}')
|
||||
end
|
||||
|
||||
def test_includes_will_not_render_inside_nested_sibling_tags
|
||||
@@ -140,8 +165,7 @@ class RenderTagTest < Minitest::Test
|
||||
'test_include' => '{% include "foo" %}'
|
||||
)
|
||||
|
||||
output = Liquid::Template.parse('{% render "nested_render_with_sibling_include" %}').render
|
||||
assert_equal('Liquid error: include usage is not allowed in this contextLiquid error: include usage is not allowed in this context', output)
|
||||
assert_template_result('include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}')
|
||||
end
|
||||
|
||||
def test_render_tag_with
|
||||
|
||||
@@ -361,4 +361,12 @@ class TemplateTest < Minitest::Test
|
||||
result = t.render('x' => 1, 'y' => 5)
|
||||
assert_equal('12345', result)
|
||||
end
|
||||
|
||||
unless taint_supported?
|
||||
def test_taint_mode
|
||||
assert_raises(NotImplementedError) do
|
||||
Template.taint_mode = :warn
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,6 +32,10 @@ module Minitest
|
||||
def fixture(name)
|
||||
File.join(File.expand_path(__dir__), "fixtures", name)
|
||||
end
|
||||
|
||||
def self.taint_supported?
|
||||
Object.new.taint.tainted?
|
||||
end
|
||||
end
|
||||
|
||||
module Assertions
|
||||
@@ -89,6 +93,14 @@ module Minitest
|
||||
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters)
|
||||
end
|
||||
|
||||
def with_taint_mode(mode)
|
||||
old_mode = Liquid::Template.taint_mode
|
||||
Liquid::Template.taint_mode = mode
|
||||
yield
|
||||
ensure
|
||||
Liquid::Template.taint_mode = old_mode
|
||||
end
|
||||
|
||||
def with_error_mode(mode)
|
||||
old_mode = Liquid::Template.error_mode
|
||||
Liquid::Template.error_mode = mode
|
||||
@@ -98,17 +110,10 @@ module Minitest
|
||||
end
|
||||
|
||||
def with_custom_tag(tag_name, tag_class)
|
||||
old_tag = Liquid::Template.tags[tag_name]
|
||||
begin
|
||||
Liquid::Template.register_tag(tag_name, tag_class)
|
||||
yield
|
||||
ensure
|
||||
if old_tag
|
||||
Liquid::Template.tags[tag_name] = old_tag
|
||||
else
|
||||
Liquid::Template.tags.delete(tag_name)
|
||||
end
|
||||
end
|
||||
Liquid::Template.register_tag(tag_name, tag_class)
|
||||
yield
|
||||
ensure
|
||||
Liquid::Template.tags.delete(tag_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -147,13 +147,13 @@ class ContextUnitTest < Minitest::Test
|
||||
|
||||
context = Context.new
|
||||
context.add_filters(filter)
|
||||
assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))
|
||||
assert_equal('hi? hi!', context.invoke('hi', 'hi?'))
|
||||
|
||||
context = Context.new
|
||||
assert_equal('hi?', context.invoke(:hi, 'hi?'))
|
||||
assert_equal('hi?', context.invoke('hi', 'hi?'))
|
||||
|
||||
context.add_filters(filter)
|
||||
assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))
|
||||
assert_equal('hi? hi!', context.invoke('hi', 'hi?'))
|
||||
end
|
||||
|
||||
def test_only_intended_filters_make_it_there
|
||||
@@ -563,35 +563,6 @@ class ContextUnitTest < Minitest::Test
|
||||
assert_equal('my filter result', template.render(subcontext))
|
||||
end
|
||||
|
||||
def test_disables_tag_specified
|
||||
context = Context.new
|
||||
context.with_disabled_tags(%w(foo bar)) do
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal true, context.tag_disabled?("bar")
|
||||
assert_equal false, context.tag_disabled?("unknown")
|
||||
end
|
||||
end
|
||||
|
||||
def test_disables_nested_tags
|
||||
context = Context.new
|
||||
context.with_disabled_tags(["foo"]) do
|
||||
context.with_disabled_tags(["foo"]) do
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal false, context.tag_disabled?("bar")
|
||||
end
|
||||
context.with_disabled_tags(["bar"]) do
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal true, context.tag_disabled?("bar")
|
||||
context.with_disabled_tags(["foo"]) do
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal true, context.tag_disabled?("bar")
|
||||
end
|
||||
end
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal false, context.tag_disabled?("bar")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_no_object_allocations
|
||||
|
||||
36
test/unit/registers/disabled_tags_unit_test.rb
Normal file
36
test/unit/registers/disabled_tags_unit_test.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class DisabledTagsUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_disables_tag_specified
|
||||
register = DisabledTags.new
|
||||
register.disable(%w(foo bar)) do
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal true, register.disabled?("bar")
|
||||
assert_equal false, register.disabled?("unknown")
|
||||
end
|
||||
end
|
||||
|
||||
def test_disables_nested_tags
|
||||
register = DisabledTags.new
|
||||
register.disable(["foo"]) do
|
||||
register.disable(["foo"]) do
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal false, register.disabled?("bar")
|
||||
end
|
||||
register.disable(["bar"]) do
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal true, register.disabled?("bar")
|
||||
register.disable(["foo"]) do
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal true, register.disabled?("bar")
|
||||
end
|
||||
end
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal false, register.disabled?("bar")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,152 +5,244 @@ require 'test_helper'
|
||||
class StaticRegistersUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_set
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
def set
|
||||
static_register = StaticRegisters.new
|
||||
static_register[nil] = true
|
||||
static_register[1] = :one
|
||||
static_register[:one] = "one"
|
||||
static_register["two"] = "three"
|
||||
static_register["two"] = 3
|
||||
static_register[false] = nil
|
||||
|
||||
assert_equal(1, static_register[:a])
|
||||
assert_equal(22, static_register[:b])
|
||||
assert_equal(33, static_register[:c])
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.registers)
|
||||
|
||||
static_register
|
||||
end
|
||||
|
||||
def test_get_missing_key
|
||||
static_register = StaticRegisters.new
|
||||
def test_get
|
||||
static_register = set
|
||||
|
||||
assert_nil(static_register[:missing])
|
||||
assert_equal(true, static_register[nil])
|
||||
assert_equal(:one, static_register[1])
|
||||
assert_equal("one", static_register[:one])
|
||||
assert_equal(3, static_register["two"])
|
||||
assert_nil(static_register[false])
|
||||
assert_nil(static_register["unknown"])
|
||||
end
|
||||
|
||||
def test_delete
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
static_register = set
|
||||
|
||||
assert_nil(static_register.delete(:a))
|
||||
assert_equal(true, static_register.delete(nil))
|
||||
assert_equal(:one, static_register.delete(1))
|
||||
assert_equal("one", static_register.delete(:one))
|
||||
assert_equal(3, static_register.delete("two"))
|
||||
assert_nil(static_register.delete(false))
|
||||
assert_nil(static_register.delete("unknown"))
|
||||
|
||||
assert_equal(22, static_register.delete(:b))
|
||||
|
||||
assert_equal(33, static_register.delete(:c))
|
||||
assert_nil(static_register[:c])
|
||||
|
||||
assert_nil(static_register.delete(:d))
|
||||
assert_equal({}, static_register.registers)
|
||||
end
|
||||
|
||||
def test_fetch
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
static_register = set
|
||||
|
||||
assert_equal(1, static_register.fetch(:a))
|
||||
assert_equal(1, static_register.fetch(:a, "default"))
|
||||
assert_equal(22, static_register.fetch(:b))
|
||||
assert_equal(22, static_register.fetch(:b, "default"))
|
||||
assert_equal(33, static_register.fetch(:c))
|
||||
assert_equal(33, static_register.fetch(:c, "default"))
|
||||
assert_equal(true, static_register.fetch(nil))
|
||||
assert_equal(:one, static_register.fetch(1))
|
||||
assert_equal("one", static_register.fetch(:one))
|
||||
assert_equal(3, static_register.fetch("two"))
|
||||
assert_nil(static_register.fetch(false))
|
||||
assert_nil(static_register.fetch("unknown"))
|
||||
end
|
||||
|
||||
assert_raises(KeyError) do
|
||||
static_register.fetch(:d)
|
||||
end
|
||||
assert_equal("default", static_register.fetch(:d, "default"))
|
||||
def test_fetch_default
|
||||
static_register = StaticRegisters.new
|
||||
|
||||
result = static_register.fetch(:d) { "default" }
|
||||
assert_equal("default", result)
|
||||
|
||||
result = static_register.fetch(:d, "default 1") { "default 2" }
|
||||
assert_equal("default 2", result)
|
||||
assert_equal(true, static_register.fetch(nil, true))
|
||||
assert_equal(:one, static_register.fetch(1, :one))
|
||||
assert_equal("one", static_register.fetch(:one, "one"))
|
||||
assert_equal(3, static_register.fetch("two", 3))
|
||||
assert_nil(static_register.fetch(false, nil))
|
||||
end
|
||||
|
||||
def test_key
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
static_register = set
|
||||
|
||||
assert_equal(true, static_register.key?(:a))
|
||||
assert_equal(true, static_register.key?(:b))
|
||||
assert_equal(true, static_register.key?(:c))
|
||||
assert_equal(false, static_register.key?(:d))
|
||||
assert_equal(true, static_register.key?(nil))
|
||||
assert_equal(true, static_register.key?(1))
|
||||
assert_equal(true, static_register.key?(:one))
|
||||
assert_equal(true, static_register.key?("two"))
|
||||
assert_equal(true, static_register.key?(false))
|
||||
assert_equal(false, static_register.key?("unknown"))
|
||||
assert_equal(false, static_register.key?(true))
|
||||
end
|
||||
|
||||
def set_with_static
|
||||
static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
|
||||
static_register[nil] = false
|
||||
static_register["two"] = 4
|
||||
static_register[true] = "foo"
|
||||
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
|
||||
assert_equal({ nil => false, "two" => 4, true => "foo" }, static_register.registers)
|
||||
|
||||
static_register
|
||||
end
|
||||
|
||||
def test_get_with_static
|
||||
static_register = set_with_static
|
||||
|
||||
assert_equal(false, static_register[nil])
|
||||
assert_equal(:one, static_register[1])
|
||||
assert_equal("one", static_register[:one])
|
||||
assert_equal(4, static_register["two"])
|
||||
assert_equal("foo", static_register[true])
|
||||
assert_nil(static_register[false])
|
||||
end
|
||||
|
||||
def test_delete_with_static
|
||||
static_register = set_with_static
|
||||
|
||||
assert_equal(false, static_register.delete(nil))
|
||||
assert_equal(4, static_register.delete("two"))
|
||||
assert_equal("foo", static_register.delete(true))
|
||||
assert_nil(static_register.delete("unknown"))
|
||||
assert_nil(static_register.delete(:one))
|
||||
|
||||
assert_equal({}, static_register.registers)
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
|
||||
end
|
||||
|
||||
def test_fetch_with_static
|
||||
static_register = set_with_static
|
||||
|
||||
assert_equal(false, static_register.fetch(nil))
|
||||
assert_equal(:one, static_register.fetch(1))
|
||||
assert_equal("one", static_register.fetch(:one))
|
||||
assert_equal(4, static_register.fetch("two"))
|
||||
assert_equal("foo", static_register.fetch(true))
|
||||
assert_nil(static_register.fetch(false))
|
||||
end
|
||||
|
||||
def test_key_with_static
|
||||
static_register = set_with_static
|
||||
|
||||
assert_equal(true, static_register.key?(nil))
|
||||
assert_equal(true, static_register.key?(1))
|
||||
assert_equal(true, static_register.key?(:one))
|
||||
assert_equal(true, static_register.key?("two"))
|
||||
assert_equal(true, static_register.key?(false))
|
||||
assert_equal(false, static_register.key?("unknown"))
|
||||
assert_equal(true, static_register.key?(true))
|
||||
end
|
||||
|
||||
def test_static_register_can_be_frozen
|
||||
static_register = StaticRegisters.new(a: 1)
|
||||
static_register = set_with_static
|
||||
|
||||
static_register.static.freeze
|
||||
static = static_register.static.freeze
|
||||
|
||||
assert_raises(RuntimeError) do
|
||||
static_register.static[:a] = "foo"
|
||||
static["two"] = "foo"
|
||||
end
|
||||
|
||||
assert_raises(RuntimeError) do
|
||||
static_register.static[:b] = "foo"
|
||||
static["unknown"] = "foo"
|
||||
end
|
||||
|
||||
assert_raises(RuntimeError) do
|
||||
static_register.static.delete(:a)
|
||||
end
|
||||
|
||||
assert_raises(RuntimeError) do
|
||||
static_register.static.delete(:c)
|
||||
static.delete("two")
|
||||
end
|
||||
end
|
||||
|
||||
def test_new_static_retains_static
|
||||
static_register = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register[:b] = 22
|
||||
static_register[:c] = 33
|
||||
static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
|
||||
static_register["one"] = 1
|
||||
static_register["two"] = 2
|
||||
static_register["three"] = 3
|
||||
|
||||
new_static_register = StaticRegisters.new(static_register)
|
||||
new_static_register[:b] = 222
|
||||
new_register = StaticRegisters.new(static_register)
|
||||
assert_equal({}, new_register.registers)
|
||||
|
||||
newest_static_register = StaticRegisters.new(new_static_register)
|
||||
newest_static_register[:c] = 333
|
||||
new_register["one"] = 4
|
||||
new_register["two"] = 5
|
||||
new_register["three"] = 6
|
||||
|
||||
assert_equal(1, static_register[:a])
|
||||
assert_equal(22, static_register[:b])
|
||||
assert_equal(33, static_register[:c])
|
||||
newest_register = StaticRegisters.new(new_register)
|
||||
assert_equal({}, newest_register.registers)
|
||||
|
||||
assert_equal(1, new_static_register[:a])
|
||||
assert_equal(222, new_static_register[:b])
|
||||
assert_nil(new_static_register[:c])
|
||||
newest_register["one"] = 7
|
||||
newest_register["two"] = 8
|
||||
newest_register["three"] = 9
|
||||
|
||||
assert_equal(1, newest_static_register[:a])
|
||||
assert_equal(2, newest_static_register[:b])
|
||||
assert_equal(333, newest_static_register[:c])
|
||||
assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers)
|
||||
assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers)
|
||||
assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers)
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, new_register.static)
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, newest_register.static)
|
||||
end
|
||||
|
||||
def test_multiple_instances_are_unique
|
||||
static_register_1 = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register_1[:b] = 22
|
||||
static_register_1[:c] = 33
|
||||
static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
|
||||
static_register["one"] = 1
|
||||
static_register["two"] = 2
|
||||
static_register["three"] = 3
|
||||
|
||||
static_register_2 = StaticRegisters.new(a: 10, b: 20)
|
||||
static_register_2[:b] = 220
|
||||
static_register_2[:c] = 330
|
||||
new_register = StaticRegisters.new(foo: :bar)
|
||||
assert_equal({}, new_register.registers)
|
||||
|
||||
assert_equal({ a: 1, b: 2 }, static_register_1.static)
|
||||
assert_equal(1, static_register_1[:a])
|
||||
assert_equal(22, static_register_1[:b])
|
||||
assert_equal(33, static_register_1[:c])
|
||||
new_register["one"] = 4
|
||||
new_register["two"] = 5
|
||||
new_register["three"] = 6
|
||||
|
||||
assert_equal({ a: 10, b: 20 }, static_register_2.static)
|
||||
assert_equal(10, static_register_2[:a])
|
||||
assert_equal(220, static_register_2[:b])
|
||||
assert_equal(330, static_register_2[:c])
|
||||
newest_register = StaticRegisters.new(bar: :foo)
|
||||
assert_equal({}, newest_register.registers)
|
||||
|
||||
newest_register["one"] = 7
|
||||
newest_register["two"] = 8
|
||||
newest_register["three"] = 9
|
||||
|
||||
assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers)
|
||||
assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers)
|
||||
assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers)
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
|
||||
assert_equal({ foo: :bar }, new_register.static)
|
||||
assert_equal({ bar: :foo }, newest_register.static)
|
||||
end
|
||||
|
||||
def test_initialization_reused_static_same_memory_object
|
||||
static_register_1 = StaticRegisters.new(a: 1, b: 2)
|
||||
static_register_1[:b] = 22
|
||||
static_register_1[:c] = 33
|
||||
def test_can_update_static_directly_and_updates_all_instances
|
||||
static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
|
||||
static_register["one"] = 1
|
||||
static_register["two"] = 2
|
||||
static_register["three"] = 3
|
||||
|
||||
static_register_2 = StaticRegisters.new(static_register_1)
|
||||
new_register = StaticRegisters.new(static_register)
|
||||
assert_equal({}, new_register.registers)
|
||||
|
||||
assert_equal(1, static_register_2[:a])
|
||||
assert_equal(2, static_register_2[:b])
|
||||
assert_nil(static_register_2[:c])
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
|
||||
|
||||
static_register_1.static[:b] = 222
|
||||
static_register_1.static[:c] = 333
|
||||
new_register["one"] = 4
|
||||
new_register["two"] = 5
|
||||
new_register["three"] = 6
|
||||
new_register.static["four"] = 10
|
||||
|
||||
assert_same(static_register_1.static, static_register_2.static)
|
||||
newest_register = StaticRegisters.new(new_register)
|
||||
assert_equal({}, newest_register.registers)
|
||||
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 10 }, new_register.static)
|
||||
|
||||
newest_register["one"] = 7
|
||||
newest_register["two"] = 8
|
||||
newest_register["three"] = 9
|
||||
new_register.static["four"] = 5
|
||||
new_register.static["five"] = 15
|
||||
|
||||
assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers)
|
||||
assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers)
|
||||
assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers)
|
||||
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, newest_register.static)
|
||||
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, static_register.static)
|
||||
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, new_register.static)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -82,16 +82,6 @@ class StrainerFactoryUnitTest < Minitest::Test
|
||||
assert_equal("has_method?", strainer.invoke("invoke", "has_method?", "invoke"))
|
||||
end
|
||||
|
||||
def test_strainer_uses_a_class_cache_to_avoid_method_cache_invalidation
|
||||
a = Module.new
|
||||
b = Module.new
|
||||
strainer = StrainerFactory.create(@context, [a, b])
|
||||
assert_kind_of(StrainerTemplate, strainer)
|
||||
assert_kind_of(a, strainer)
|
||||
assert_kind_of(b, strainer)
|
||||
assert_kind_of(Liquid::StandardFilters, strainer)
|
||||
end
|
||||
|
||||
def test_add_global_filter_clears_cache
|
||||
assert_equal('input', StrainerFactory.create(@context).invoke('late_added_filter', 'input'))
|
||||
StrainerFactory.add_global_filter(LateAddedFilter)
|
||||
|
||||
@@ -10,73 +10,9 @@ class StrainerTemplateUnitTest < Minitest::Test
|
||||
s = c.strainer
|
||||
wrong_filter = ->(v) { v.reverse }
|
||||
|
||||
exception = assert_raises(TypeError) do
|
||||
exception = assert_raises(ArgumentError) do
|
||||
s.class.add_filter(wrong_filter)
|
||||
end
|
||||
assert_equal(exception.message, "wrong argument type Proc (expected Module)")
|
||||
end
|
||||
|
||||
module PrivateMethodOverrideFilter
|
||||
private
|
||||
|
||||
def public_filter
|
||||
"overriden as private"
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_raises_when_module_privately_overrides_registered_public_methods
|
||||
strainer = Context.new.strainer
|
||||
|
||||
error = assert_raises(Liquid::MethodOverrideError) do
|
||||
strainer.class.add_filter(PrivateMethodOverrideFilter)
|
||||
end
|
||||
assert_equal('Liquid error: Filter overrides registered public methods as non public: public_filter', error.message)
|
||||
end
|
||||
|
||||
module ProtectedMethodOverrideFilter
|
||||
protected
|
||||
|
||||
def public_filter
|
||||
"overriden as protected"
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_raises_when_module_overrides_registered_public_method_as_protected
|
||||
strainer = Context.new.strainer
|
||||
|
||||
error = assert_raises(Liquid::MethodOverrideError) do
|
||||
strainer.class.add_filter(ProtectedMethodOverrideFilter)
|
||||
end
|
||||
assert_equal('Liquid error: Filter overrides registered public methods as non public: public_filter', error.message)
|
||||
end
|
||||
|
||||
module PublicMethodOverrideFilter
|
||||
def public_filter
|
||||
"public"
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
|
||||
strainer = Context.new.strainer
|
||||
with_global_filter do
|
||||
strainer.class.add_filter(PublicMethodOverrideFilter)
|
||||
assert(strainer.class.send(:filter_methods).include?(:public_filter))
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_does_not_include_already_included_module
|
||||
mod = Module.new do
|
||||
class << self
|
||||
attr_accessor :include_count
|
||||
def included(_mod)
|
||||
self.include_count += 1
|
||||
end
|
||||
end
|
||||
self.include_count = 0
|
||||
end
|
||||
strainer = Context.new.strainer
|
||||
strainer.class.add_filter(mod)
|
||||
strainer.class.add_filter(mod)
|
||||
assert_equal(1, mod.include_count)
|
||||
assert_equal(exception.message, "Liquid error: wrong argument type Proc (expected Liquid::Filter)")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -77,11 +77,4 @@ class TemplateUnitTest < Minitest::Test
|
||||
ensure
|
||||
Template.tags.delete('fake')
|
||||
end
|
||||
|
||||
class TemplateSubclass < Liquid::Template
|
||||
end
|
||||
|
||||
def test_template_inheritance
|
||||
assert_equal("foo", TemplateSubclass.parse("foo").render)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,75 +13,75 @@ class VariableUnitTest < Minitest::Test
|
||||
def test_filters
|
||||
var = create_variable('hello | textileze')
|
||||
assert_equal(VariableLookup.new('hello'), var.name)
|
||||
assert_equal([[:textileze, []]], var.filters)
|
||||
assert_equal([['textileze', []]], var.filters)
|
||||
|
||||
var = create_variable('hello | textileze | paragraph')
|
||||
assert_equal(VariableLookup.new('hello'), var.name)
|
||||
assert_equal([[:textileze, []], [:paragraph, []]], var.filters)
|
||||
assert_equal([['textileze', []], ['paragraph', []]], var.filters)
|
||||
|
||||
var = create_variable(%( hello | strftime: '%Y'))
|
||||
assert_equal(VariableLookup.new('hello'), var.name)
|
||||
assert_equal([[:strftime, ['%Y']]], var.filters)
|
||||
assert_equal([['strftime', ['%Y']]], var.filters)
|
||||
|
||||
var = create_variable(%( 'typo' | link_to: 'Typo', true ))
|
||||
assert_equal('typo', var.name)
|
||||
assert_equal([[:link_to, ['Typo', true]]], var.filters)
|
||||
assert_equal([['link_to', ['Typo', true]]], var.filters)
|
||||
|
||||
var = create_variable(%( 'typo' | link_to: 'Typo', false ))
|
||||
assert_equal('typo', var.name)
|
||||
assert_equal([[:link_to, ['Typo', false]]], var.filters)
|
||||
assert_equal([['link_to', ['Typo', false]]], var.filters)
|
||||
|
||||
var = create_variable(%( 'foo' | repeat: 3 ))
|
||||
assert_equal('foo', var.name)
|
||||
assert_equal([[:repeat, [3]]], var.filters)
|
||||
assert_equal([['repeat', [3]]], var.filters)
|
||||
|
||||
var = create_variable(%( 'foo' | repeat: 3, 3 ))
|
||||
assert_equal('foo', var.name)
|
||||
assert_equal([[:repeat, [3, 3]]], var.filters)
|
||||
assert_equal([['repeat', [3, 3]]], var.filters)
|
||||
|
||||
var = create_variable(%( 'foo' | repeat: 3, 3, 3 ))
|
||||
assert_equal('foo', var.name)
|
||||
assert_equal([[:repeat, [3, 3, 3]]], var.filters)
|
||||
assert_equal([['repeat', [3, 3, 3]]], var.filters)
|
||||
|
||||
var = create_variable(%( hello | strftime: '%Y, okay?'))
|
||||
assert_equal(VariableLookup.new('hello'), var.name)
|
||||
assert_equal([[:strftime, ['%Y, okay?']]], var.filters)
|
||||
assert_equal([['strftime', ['%Y, okay?']]], var.filters)
|
||||
|
||||
var = create_variable(%( hello | things: "%Y, okay?", 'the other one'))
|
||||
assert_equal(VariableLookup.new('hello'), var.name)
|
||||
assert_equal([[:things, ['%Y, okay?', 'the other one']]], var.filters)
|
||||
assert_equal([['things', ['%Y, okay?', 'the other one']]], var.filters)
|
||||
end
|
||||
|
||||
def test_filter_with_date_parameter
|
||||
var = create_variable(%( '2006-06-06' | date: "%m/%d/%Y"))
|
||||
assert_equal('2006-06-06', var.name)
|
||||
assert_equal([[:date, ['%m/%d/%Y']]], var.filters)
|
||||
assert_equal([['date', ['%m/%d/%Y']]], var.filters)
|
||||
end
|
||||
|
||||
def test_filters_without_whitespace
|
||||
var = create_variable('hello | textileze | paragraph')
|
||||
assert_equal(VariableLookup.new('hello'), var.name)
|
||||
assert_equal([[:textileze, []], [:paragraph, []]], var.filters)
|
||||
assert_equal([['textileze', []], ['paragraph', []]], var.filters)
|
||||
|
||||
var = create_variable('hello|textileze|paragraph')
|
||||
assert_equal(VariableLookup.new('hello'), var.name)
|
||||
assert_equal([[:textileze, []], [:paragraph, []]], var.filters)
|
||||
assert_equal([['textileze', []], ['paragraph', []]], var.filters)
|
||||
|
||||
var = create_variable("hello|replace:'foo','bar'|textileze")
|
||||
assert_equal(VariableLookup.new('hello'), var.name)
|
||||
assert_equal([[:replace, ['foo', 'bar']], [:textileze, []]], var.filters)
|
||||
assert_equal([['replace', ['foo', 'bar']], ['textileze', []]], var.filters)
|
||||
end
|
||||
|
||||
def test_symbol
|
||||
var = create_variable("http://disney.com/logo.gif | image: 'med' ", error_mode: :lax)
|
||||
assert_equal(VariableLookup.new('http://disney.com/logo.gif'), var.name)
|
||||
assert_equal([[:image, ['med']]], var.filters)
|
||||
assert_equal([['image', ['med']]], var.filters)
|
||||
end
|
||||
|
||||
def test_string_to_filter
|
||||
var = create_variable("'http://disney.com/logo.gif' | image: 'med' ")
|
||||
assert_equal('http://disney.com/logo.gif', var.name)
|
||||
assert_equal([[:image, ['med']]], var.filters)
|
||||
assert_equal([['image', ['med']]], var.filters)
|
||||
end
|
||||
|
||||
def test_string_single_quoted
|
||||
@@ -128,13 +128,13 @@ class VariableUnitTest < Minitest::Test
|
||||
def test_filter_with_keyword_arguments
|
||||
var = create_variable(%( hello | things: greeting: "world", farewell: 'goodbye'))
|
||||
assert_equal(VariableLookup.new('hello'), var.name)
|
||||
assert_equal([[:things, [], { 'greeting' => 'world', 'farewell' => 'goodbye' }]], var.filters)
|
||||
assert_equal([['things', [], { 'greeting' => 'world', 'farewell' => 'goodbye' }]], var.filters)
|
||||
end
|
||||
|
||||
def test_lax_filter_argument_parsing
|
||||
var = create_variable(%( number_of_comments | pluralize: 'comment': 'comments' ), error_mode: :lax)
|
||||
assert_equal(VariableLookup.new('number_of_comments'), var.name)
|
||||
assert_equal([[:pluralize, ['comment', 'comments']]], var.filters)
|
||||
assert_equal([['pluralize', ['comment', 'comments']]], var.filters)
|
||||
end
|
||||
|
||||
def test_strict_filter_argument_parsing
|
||||
|
||||
Reference in New Issue
Block a user