Hello community, here is the log from the commit of package rubygem-liquid for openSUSE:Factory checked in at 2019-01-21 10:25:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-liquid (Old) and /work/SRC/openSUSE:Factory/.rubygem-liquid.new.28833 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-liquid" Mon Jan 21 10:25:27 2019 rev:3 rq:656368 version:4.0.1 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-liquid/rubygem-liquid.changes 2016-12-29 22:45:50.175780395 +0100 +++ /work/SRC/openSUSE:Factory/.rubygem-liquid.new.28833/rubygem-liquid.changes 2019-01-21 10:25:27.913743256 +0100 @@ -1,0 +2,6 @@ +Sat Dec 8 16:24:49 UTC 2018 - Stephan Kulow <[email protected]> + +- updated to version 4.0.1 + see installed History.md + +------------------------------------------------------------------- Old: ---- liquid-4.0.0.gem New: ---- liquid-4.0.1.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-liquid.spec ++++++ --- /var/tmp/diff_new_pack.8KugJc/_old 2019-01-21 10:25:28.273742860 +0100 +++ /var/tmp/diff_new_pack.8KugJc/_new 2019-01-21 10:25:28.273742860 +0100 @@ -1,7 +1,7 @@ # # spec file for package rubygem-liquid # -# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -12,7 +12,7 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ # @@ -24,7 +24,7 @@ # Name: rubygem-liquid -Version: 4.0.0 +Version: 4.0.1 Release: 0 %define mod_name liquid %define mod_full_name %{mod_name}-%{version} @@ -33,7 +33,7 @@ BuildRequires: %{rubygem gem2rpm} BuildRequires: ruby-macros >= 5 Url: http://www.liquidmarkup.org -Source: http://rubygems.org/gems/%{mod_full_name}.gem +Source: https://rubygems.org/gems/%{mod_full_name}.gem Source1: gem2rpm.yml Summary: A secure, non-evaling end user template engine with aesthetic markup License: MIT ++++++ liquid-4.0.0.gem -> liquid-4.0.1.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/History.md new/History.md --- old/History.md 2016-12-14 17:59:11.000000000 +0100 +++ new/History.md 2018-10-09 11:14:52.000000000 +0200 @@ -1,6 +1,6 @@ # Liquid Change Log -## 4.0.0 / not yet released / branch "master" +## 4.0.0 / 2016-12-14 / branch "4-0-stable" ### Changed * Render an opaque internal error by default for non-Liquid::Error (#835) [Dylan Thacker-Smith] @@ -20,10 +20,13 @@ * Add concat filter to concatenate arrays (#429) [Diogo Beato] * Ruby 1.9 support dropped (#491) [Justin Li] * Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith] -* Remove support for `liquid_methods` +* Remove `liquid_methods` (See https://github.com/Shopify/liquid/pull/568 for replacement) * Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande] ### Fixed + +* Fix variable names being detected as an operator when starting with contains (#788) [Michael Angell] +* Fix include tag used with strict_variables (#828) [QuickPay] * Fix map filter when value is a Proc (#672) [Guillaume Malette] * Fix truncate filter when value is not a string (#672) [Guillaume Malette] * Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/README.md new/README.md --- old/README.md 2016-12-14 17:59:11.000000000 +0100 +++ new/README.md 2018-10-09 11:14:52.000000000 +0200 @@ -42,6 +42,8 @@ ## How to use Liquid +Install Liquid by adding `gem 'liquid'` to your gemfile. + Liquid supports a very simple API based around the Liquid::Template class. For standard use you can just pass it the content of a file and call render with a parameters hash. Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/block.rb new/lib/liquid/block.rb --- old/lib/liquid/block.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/block.rb 2018-10-09 11:14:52.000000000 +0200 @@ -1,5 +1,7 @@ module Liquid class Block < Tag + MAX_DEPTH = 100 + def initialize(tag_name, markup, options) super @blank = true @@ -24,12 +26,12 @@ end def unknown_tag(tag, _params, _tokens) - case tag - when 'else'.freeze + if tag == 'else'.freeze raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_else".freeze, block_name: block_name)) - when 'end'.freeze + elsif tag.start_with?('end'.freeze) raise SyntaxError.new(parse_context.locale.t("errors.syntax.invalid_delimiter".freeze, + tag: tag, block_name: block_name, block_delimiter: block_delimiter)) else @@ -48,17 +50,25 @@ protected def parse_body(body, tokens) - body.parse(tokens, parse_context) do |end_tag_name, end_tag_params| - @blank &&= body.blank? - - return false if end_tag_name == block_delimiter - unless end_tag_name - raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) + if parse_context.depth >= MAX_DEPTH + raise StackLevelError, "Nesting too deep".freeze + end + parse_context.depth += 1 + begin + body.parse(tokens, parse_context) do |end_tag_name, end_tag_params| + @blank &&= body.blank? + + return false if end_tag_name == block_delimiter + unless end_tag_name + raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) + end + + # this tag is not registered with the system + # pass it to the current block for special handling or error reporting + unknown_tag(end_tag_name, end_tag_params, tokens) end - - # this tag is not registered with the system - # pass it to the current block for special handling or error reporting - unknown_tag(end_tag_name, end_tag_params, tokens) + ensure + parse_context.depth -= 1 end true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/block_body.rb new/lib/liquid/block_body.rb --- old/lib/liquid/block_body.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/block_body.rb 2018-10-09 11:14:52.000000000 +0200 @@ -2,6 +2,7 @@ class BlockBody FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om + WhitespaceOrNothing = /\A\s*\z/ TAGSTART = "{%".freeze VARSTART = "{{".freeze @@ -15,38 +16,35 @@ def parse(tokenizer, parse_context) parse_context.line_number = tokenizer.line_number while token = tokenizer.shift - unless token.empty? - case - when token.start_with?(TAGSTART) - whitespace_handler(token, parse_context) - if token =~ FullToken - tag_name = $1 - markup = $2 - # fetch the tag from registered blocks - if tag = registered_tags[tag_name] - new_tag = tag.parse(tag_name, markup, tokenizer, parse_context) - @blank &&= new_tag.blank? - @nodelist << new_tag - else - # end parsing if we reach an unknown tag and let the caller decide - # determine how to proceed - return yield tag_name, markup - end - else - raise_missing_tag_terminator(token, parse_context) - end - when token.start_with?(VARSTART) - whitespace_handler(token, parse_context) - @nodelist << create_variable(token, parse_context) - @blank = false - else - if parse_context.trim_whitespace - token.lstrip! - end - parse_context.trim_whitespace = false - @nodelist << token - @blank &&= !!(token =~ /\A\s*\z/) + next if token.empty? + case + when token.start_with?(TAGSTART) + whitespace_handler(token, parse_context) + unless token =~ FullToken + raise_missing_tag_terminator(token, parse_context) end + tag_name = $1 + markup = $2 + # fetch the tag from registered blocks + unless tag = registered_tags[tag_name] + # end parsing if we reach an unknown tag and let the caller decide + # determine how to proceed + return yield tag_name, markup + end + new_tag = tag.parse(tag_name, markup, tokenizer, parse_context) + @blank &&= new_tag.blank? + @nodelist << new_tag + when token.start_with?(VARSTART) + whitespace_handler(token, parse_context) + @nodelist << create_variable(token, parse_context) + @blank = false + else + if parse_context.trim_whitespace + token.lstrip! + end + parse_context.trim_whitespace = false + @nodelist << token + @blank &&= !!(token =~ WhitespaceOrNothing) end parse_context.line_number = tokenizer.line_number end @@ -72,32 +70,27 @@ output = [] context.resource_limits.render_score += @nodelist.length - @nodelist.each do |token| - # Break out if we have any unhanded interrupts. - break if context.interrupt? - - begin + idx = 0 + while node = @nodelist[idx] + case node + when String + check_resources(context, node) + output << node + when Variable + render_node_to_output(node, output, context) + when Block + render_node_to_output(node, output, context, node.blank?) + break if context.interrupt? # might have happened in a for-block + when Continue, Break # If we get an Interrupt that means the block must stop processing. An # Interrupt is any command that stops block execution such as {% break %} # or {% continue %} - if token.is_a?(Continue) || token.is_a?(Break) - context.push_interrupt(token.interrupt) - break - end - - node_output = render_node(token, context) - - unless token.is_a?(Block) && token.blank? - output << node_output - end - rescue MemoryError => e - raise e - rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e - context.handle_error(e, token.line_number, token.raw) - output << nil - rescue ::StandardError => e - output << context.handle_error(e, token.line_number, token.raw) + context.push_interrupt(node.interrupt) + break + else # Other non-Block tags + render_node_to_output(node, output, context) end + idx += 1 end output.join @@ -105,15 +98,25 @@ private - def render_node(node, context) - node_output = (node.respond_to?(:render) ? node.render(context) : node) + def render_node_to_output(node, output, context, skip_output = false) + node_output = node.render(context) node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s + check_resources(context, node_output) + output << node_output unless skip_output + rescue MemoryError => e + raise e + rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e + context.handle_error(e, node.line_number) + output << nil + rescue ::StandardError => e + line_number = node.is_a?(String) ? nil : node.line_number + output << context.handle_error(e, line_number) + end + def check_resources(context, node_output) context.resource_limits.render_length += node_output.length - if context.resource_limits.reached? - raise MemoryError.new("Memory limits exceeded".freeze) - end - node_output + return unless context.resource_limits.reached? + raise MemoryError.new("Memory limits exceeded".freeze) end def create_variable(token, parse_context) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/condition.rb new/lib/liquid/condition.rb --- old/lib/liquid/condition.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/condition.rb 2018-10-09 11:14:52.000000000 +0200 @@ -41,16 +41,22 @@ end def evaluate(context = Context.new) - result = interpret_condition(left, right, operator, context) - - case @child_relation - when :or - result || @child_condition.evaluate(context) - when :and - result && @child_condition.evaluate(context) - else - result + condition = self + result = nil + loop do + result = interpret_condition(condition.left, condition.right, condition.operator, context) + + case condition.child_relation + when :or + break if result + when :and + break unless result + else + break + end + condition = condition.child_condition end + result end def or(condition) @@ -75,6 +81,10 @@ "#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>" end + protected + + attr_reader :child_relation, :child_condition + private def equal_variables(left, right) @@ -110,7 +120,7 @@ if operation.respond_to?(:call) operation.call(self, left, right) - elsif left.respond_to?(operation) && right.respond_to?(operation) + elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash) begin left.send(operation, right) rescue ::ArgumentError => e diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/context.rb new/lib/liquid/context.rb --- old/lib/liquid/context.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/context.rb 2018-10-09 11:14:52.000000000 +0200 @@ -74,7 +74,7 @@ @interrupts.pop end - def handle_error(e, line_number = nil, raw_token = nil) + def handle_error(e, line_number = nil) e = internal_error unless e.is_a?(Liquid::Error) e.template_name ||= template_name e.line_number ||= line_number @@ -89,7 +89,7 @@ # Push new local scope on the stack. use <tt>Context#stack</tt> instead def push(new_scope = {}) @scopes.unshift(new_scope) - raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100 + raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH end # Merge a hash of variables in the current local scope @@ -160,7 +160,7 @@ end # Fetches an object starting at the local scope and then moving up the hierachy - def find_variable(key) + def find_variable(key, raise_on_not_found: true) # This was changed from find() to find_index() because this is a very hot # path and find_index() is optimized in MRI to reduce object allocation index = @scopes.find_index { |s| s.key?(key) } @@ -170,8 +170,10 @@ if scope.nil? @environments.each do |e| - variable = lookup_and_evaluate(e, key) - unless variable.nil? + variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found) + # When lookup returned a value OR there is no value but the lookup also did not raise + # then it is the value we are looking for. + if !variable.nil? || @strict_variables && raise_on_not_found scope = e break end @@ -179,7 +181,7 @@ end scope ||= @environments.last || @scopes.last - variable ||= lookup_and_evaluate(scope, key) + variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found) variable = variable.to_liquid variable.context = self if variable.respond_to?(:context=) @@ -187,8 +189,8 @@ variable end - def lookup_and_evaluate(obj, key) - if @strict_variables && obj.respond_to?(:key?) && !obj.key?(key) + def lookup_and_evaluate(obj, key, raise_on_not_found: true) + if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key) raise Liquid::UndefinedVariable, "undefined variable #{key}" end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/expression.rb new/lib/liquid/expression.rb --- old/lib/liquid/expression.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/expression.rb 2018-10-09 11:14:52.000000000 +0200 @@ -21,20 +21,24 @@ 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze } + SINGLE_QUOTED_STRING = /\A'(.*)'\z/m + DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m + INTEGERS_REGEX = /\A(-?\d+)\z/ + FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/ + RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/ + def self.parse(markup) if LITERALS.key?(markup) LITERALS[markup] else case markup - when /\A'(.*)'\z/m # Single quoted strings - $1 - when /\A"(.*)"\z/m # Double quoted strings + when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING $1 - when /\A(-?\d+)\z/ # Integer and floats + when INTEGERS_REGEX $1.to_i - when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges + when RANGES_REGEX RangeLookup.parse($1, $2) - when /\A(-?\d[\d\.]+)\z/ # Floats + when FLOATS_REGEX $1.to_f else VariableLookup.parse(markup) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/extensions.rb new/lib/liquid/extensions.rb --- old/lib/liquid/extensions.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/extensions.rb 2018-10-09 11:14:52.000000000 +0200 @@ -7,6 +7,12 @@ end end +class Symbol # :nodoc: + def to_liquid + to_s + end +end + class Array # :nodoc: def to_liquid self diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/lexer.rb new/lib/liquid/lexer.rb --- old/lib/liquid/lexer.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/lexer.rb 2018-10-09 11:14:52.000000000 +0200 @@ -18,17 +18,19 @@ DOUBLE_STRING_LITERAL = /"[^\"]*"/ NUMBER_LITERAL = /-?\d+(\.\d+)?/ DOTDOT = /\.\./ - COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains/ + COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/ + WHITESPACE_OR_NOTHING = /\s*/ def initialize(input) - @ss = StringScanner.new(input.rstrip) + @ss = StringScanner.new(input) end def tokenize @output = [] until @ss.eos? - @ss.skip(/\s*/) + @ss.skip(WHITESPACE_OR_NOTHING) + break if @ss.eos? tok = case when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t] when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/locales/en.yml new/lib/liquid/locales/en.yml --- old/lib/liquid/locales/en.yml 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/locales/en.yml 2018-10-09 11:14:52.000000000 +0200 @@ -14,7 +14,7 @@ if: "Syntax Error in tag 'if' - Valid syntax: if [expression]" include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]" unknown_tag: "Unknown tag '%{tag}'" - invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}" + invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}" unexpected_else: "%{block_name} tag does not expect 'else' tag" unexpected_outer_tag: "Unexpected outer '%{tag}' tag" tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/parse_context.rb new/lib/liquid/parse_context.rb --- old/lib/liquid/parse_context.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/parse_context.rb 2018-10-09 11:14:52.000000000 +0200 @@ -1,12 +1,13 @@ module Liquid class ParseContext - attr_accessor :locale, :line_number, :trim_whitespace + attr_accessor :locale, :line_number, :trim_whitespace, :depth attr_reader :partial, :warnings, :error_mode def initialize(options = {}) @template_options = options ? options.dup : {} @locale = @template_options[:locale] ||= I18n.new @warnings = [] + self.depth = 0 self.partial = false end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/profiler/hooks.rb new/lib/liquid/profiler/hooks.rb --- old/lib/liquid/profiler/hooks.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/profiler/hooks.rb 2018-10-09 11:14:52.000000000 +0200 @@ -1,13 +1,13 @@ module Liquid class BlockBody - def render_node_with_profiling(node, context) + def render_node_with_profiling(node, output, context, skip_output = false) Profiler.profile_node_render(node) do - render_node_without_profiling(node, context) + render_node_without_profiling(node, output, context, skip_output) end end - alias_method :render_node_without_profiling, :render_node - alias_method :render_node, :render_node_with_profiling + alias_method :render_node_without_profiling, :render_node_to_output + alias_method :render_node_to_output, :render_node_with_profiling end class Include < Tag diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/standardfilters.rb new/lib/liquid/standardfilters.rb --- old/lib/liquid/standardfilters.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/standardfilters.rb 2018-10-09 11:14:52.000000000 +0200 @@ -33,7 +33,7 @@ end def escape(input) - CGI.escapeHTML(input).untaint unless input.nil? + CGI.escapeHTML(input.to_s).untaint unless input.nil? end alias_method :h, :escape @@ -42,11 +42,11 @@ end def url_encode(input) - CGI.escape(input) unless input.nil? + CGI.escape(input.to_s) unless input.nil? end def url_decode(input) - CGI.unescape(input) unless input.nil? + CGI.unescape(input.to_s) unless input.nil? end def slice(input, offset, length = nil) @@ -121,17 +121,23 @@ def sort(input, property = nil) ary = InputIterator.new(input) if property.nil? - ary.sort + ary.sort do |a, b| + if !a.nil? && !b.nil? + a <=> b + else + a.nil? ? 1 : -1 + end + end elsif ary.empty? # The next two cases assume a non-empty array. [] - elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? + elsif ary.all? { |el| el.respond_to?(:[]) } ary.sort do |a, b| a = a[property] b = b[property] - if a && b + if !a.nil? && !b.nil? a <=> b else - a ? -1 : 1 + a.nil? ? 1 : -1 end end end @@ -143,11 +149,25 @@ ary = InputIterator.new(input) if property.nil? - ary.sort { |a, b| a.casecmp(b) } + ary.sort do |a, b| + if !a.nil? && !b.nil? + a.to_s.casecmp(b.to_s) + else + a.nil? ? 1 : -1 + end + end elsif ary.empty? # The next two cases assume a non-empty array. [] - elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? - ary.sort { |a, b| a[property].casecmp(b[property]) } + elsif ary.all? { |el| el.respond_to?(:[]) } + ary.sort do |a, b| + a = a[property] + b = b[property] + if !a.nil? && !b.nil? + a.to_s.casecmp(b.to_s) + else + a.nil? ? 1 : -1 + end + end end end @@ -353,6 +373,22 @@ raise Liquid::FloatDomainError, e.message end + def at_least(input, n) + min_value = Utils.to_number(n) + + result = Utils.to_number(input) + result = min_value if min_value > result + result.is_a?(BigDecimal) ? result.to_f : result + end + + def at_most(input, n) + max_value = Utils.to_number(n) + + result = Utils.to_number(input) + result = max_value if max_value < result + result.is_a?(BigDecimal) ? result.to_f : result + end + def default(input, default_value = ''.freeze) if !input || input.respond_to?(:empty?) && input.empty? default_value @@ -384,7 +420,7 @@ end def join(glue) - to_a.join(glue) + to_a.join(glue.to_s) end def concat(args) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/strainer.rb new/lib/liquid/strainer.rb --- old/lib/liquid/strainer.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/strainer.rb 2018-10-09 11:14:52.000000000 +0200 @@ -27,7 +27,7 @@ def self.add_filter(filter) raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module) - unless self.class.include?(filter) + unless self.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(', ')}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/tags/cycle.rb new/lib/liquid/tags/cycle.rb --- old/lib/liquid/tags/cycle.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/tags/cycle.rb 2018-10-09 11:14:52.000000000 +0200 @@ -30,11 +30,11 @@ end def render(context) - context.registers[:cycle] ||= Hash.new(0) + context.registers[:cycle] ||= {} context.stack do key = context.evaluate(@name) - iteration = context.registers[:cycle][key] + iteration = context.registers[:cycle][key].to_i result = context.evaluate(@variables[iteration]) iteration += 1 iteration = 0 if iteration >= @variables.size diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/tags/for.rb new/lib/liquid/tags/for.rb --- old/lib/liquid/tags/for.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/tags/for.rb 2018-10-09 11:14:52.000000000 +0200 @@ -23,7 +23,7 @@ # {{ item.name }} # {% end %} # - # To reverse the for loop simply use {% for item in collection reversed %} + # To reverse the for loop simply use {% for item in collection reversed %} (note that the flag's spelling is different to the filter `reverse`) # # == Available variables: # @@ -46,6 +46,9 @@ class For < Block Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o + attr_reader :collection_name + attr_reader :variable_name + def initialize(tag_name, markup, options) super @from = @limit = nil @@ -117,7 +120,7 @@ private def collection_segment(context) - offsets = context.registers[:for] ||= Hash.new(0) + offsets = context.registers[:for] ||= {} from = if @from == :continue offsets[@name].to_i @@ -153,7 +156,7 @@ begin context['forloop'.freeze] = loop_vars - segment.each_with_index do |item, index| + segment.each do |item| context[@variable_name] = item result << @for_block.render(context) loop_vars.send(:increment!) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/tags/if.rb new/lib/liquid/tags/if.rb --- old/lib/liquid/tags/if.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/tags/if.rb 2018-10-09 11:14:52.000000000 +0200 @@ -83,17 +83,20 @@ def strict_parse(markup) p = Parser.new(markup) - condition = parse_binary_comparison(p) + condition = parse_binary_comparisons(p) p.consume(:end_of_string) condition end - def parse_binary_comparison(p) + def parse_binary_comparisons(p) condition = parse_comparison(p) - if op = (p.id?('and'.freeze) || p.id?('or'.freeze)) - condition.send(op, parse_binary_comparison(p)) + first_condition = condition + while op = (p.id?('and'.freeze) || p.id?('or'.freeze)) + child_condition = parse_comparison(p) + condition.send(op, child_condition) + condition = child_condition end - condition + first_condition end def parse_comparison(p) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/tags/include.rb new/lib/liquid/tags/include.rb --- old/lib/liquid/tags/include.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/tags/include.rb 2018-10-09 11:14:52.000000000 +0200 @@ -50,7 +50,7 @@ variable = if @variable_name_expr context.evaluate(@variable_name_expr) else - context.find_variable(template_name) + context.find_variable(template_name, raise_on_not_found: false) end old_template_name = context.template_name diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/tags/table_row.rb new/lib/liquid/tags/table_row.rb --- old/lib/liquid/tags/table_row.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/tags/table_row.rb 2018-10-09 11:14:52.000000000 +0200 @@ -33,7 +33,7 @@ tablerowloop = Liquid::TablerowloopDrop.new(length, cols) context['tablerowloop'.freeze] = tablerowloop - collection.each_with_index do |item, index| + collection.each do |item| context[@variable_name] = item result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/utils.rb new/lib/liquid/utils.rb --- old/lib/liquid/utils.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/utils.rb 2018-10-09 11:14:52.000000000 +0200 @@ -46,11 +46,11 @@ def self.to_number(obj) case obj when Float - BigDecimal.new(obj.to_s) + BigDecimal(obj.to_s) when Numeric obj when String - (obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i + (obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal(obj) : obj.to_i else if obj.respond_to?(:to_number) obj.to_number diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/variable.rb new/lib/liquid/variable.rb --- old/lib/liquid/variable.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/variable.rb 2018-10-09 11:14:52.000000000 +0200 @@ -10,10 +10,16 @@ # {{ user | link }} # class Variable + FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o + FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o + JustTagAttributes = /\A#{TagAttributes}\z/o + MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om + attr_accessor :filters, :name, :line_number attr_reader :parse_context alias_method :options, :parse_context + include ParserSwitching def initialize(markup, parse_context) @@ -35,17 +41,17 @@ def lax_parse(markup) @filters = [] - return unless markup =~ /(#{QuotedFragment})(.*)/om + return unless markup =~ MarkupWithQuotedFragment name_markup = $1 filter_markup = $2 @name = Expression.parse(name_markup) - if filter_markup =~ /#{FilterSeparator}\s*(.*)/om + if filter_markup =~ FilterMarkupRegex filters = $1.scan(FilterParser) filters.each do |f| next unless f =~ /\w+/ filtername = Regexp.last_match(0) - filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten + filterargs = f.scan(FilterArgsRegex).flatten @filters << parse_filter_expressions(filtername, filterargs) end end @@ -91,7 +97,7 @@ filter_args = [] keyword_args = {} unparsed_args.each do |a| - if matches = a.match(/\A#{TagAttributes}\z/o) + if matches = a.match(JustTagAttributes) keyword_args[matches[1]] = Expression.parse(matches[2]) else filter_args << Expression.parse(a) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/liquid/version.rb new/lib/liquid/version.rb --- old/lib/liquid/version.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/lib/liquid/version.rb 2018-10-09 11:14:52.000000000 +0200 @@ -1,4 +1,4 @@ # encoding: utf-8 module Liquid - VERSION = "4.0.0" + VERSION = "4.0.1" end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2016-12-14 17:59:11.000000000 +0100 +++ new/metadata 2018-10-09 11:14:52.000000000 +0200 @@ -1,14 +1,14 @@ --- !ruby/object:Gem::Specification name: liquid version: !ruby/object:Gem::Version - version: 4.0.0 + version: 4.0.1 platform: ruby authors: - Tobias Lütke autorequire: bindir: bin cert_chain: [] -date: 2016-12-14 00:00:00.000000000 Z +date: 2018-10-09 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rake @@ -102,6 +102,7 @@ - test/fixtures/en_locale.yml - test/integration/assign_test.rb - test/integration/blank_test.rb +- test/integration/block_test.rb - test/integration/capture_test.rb - test/integration/context_test.rb - test/integration/document_test.rb @@ -165,54 +166,55 @@ version: 1.3.7 requirements: [] rubyforge_project: -rubygems_version: 2.4.5 +rubygems_version: 2.7.6 signing_key: specification_version: 4 summary: A secure, non-evaling end user template engine with aesthetic markup. test_files: -- test/fixtures/en_locale.yml -- test/integration/assign_test.rb +- test/unit/lexer_unit_test.rb +- test/unit/block_unit_test.rb +- test/unit/variable_unit_test.rb +- test/unit/parser_unit_test.rb +- test/unit/tags/if_tag_unit_test.rb +- test/unit/tags/case_tag_unit_test.rb +- test/unit/tags/for_tag_unit_test.rb +- test/unit/context_unit_test.rb +- test/unit/tokenizer_unit_test.rb +- test/unit/tag_unit_test.rb +- test/unit/i18n_unit_test.rb +- test/unit/template_unit_test.rb +- test/unit/condition_unit_test.rb +- test/unit/file_system_unit_test.rb +- test/unit/regexp_unit_test.rb +- test/unit/strainer_unit_test.rb +- test/integration/output_test.rb +- test/integration/hash_ordering_test.rb +- test/integration/variable_test.rb - test/integration/blank_test.rb -- test/integration/capture_test.rb +- test/integration/assign_test.rb +- test/integration/trim_mode_test.rb - test/integration/context_test.rb -- test/integration/document_test.rb -- test/integration/drop_test.rb -- test/integration/error_handling_test.rb -- test/integration/filter_test.rb -- test/integration/hash_ordering_test.rb -- test/integration/output_test.rb -- test/integration/parsing_quirks_test.rb -- test/integration/render_profiling_test.rb -- test/integration/security_test.rb -- test/integration/standard_filter_test.rb -- test/integration/tags/break_tag_test.rb -- test/integration/tags/continue_tag_test.rb +- test/integration/capture_test.rb +- test/integration/tags/increment_tag_test.rb - test/integration/tags/for_tag_test.rb -- test/integration/tags/if_else_tag_test.rb +- test/integration/tags/standard_tag_test.rb +- test/integration/tags/table_row_test.rb - test/integration/tags/include_tag_test.rb -- test/integration/tags/increment_tag_test.rb - test/integration/tags/raw_tag_test.rb -- test/integration/tags/standard_tag_test.rb - test/integration/tags/statements_test.rb -- test/integration/tags/table_row_test.rb +- test/integration/tags/if_else_tag_test.rb - test/integration/tags/unless_else_tag_test.rb +- test/integration/tags/continue_tag_test.rb +- test/integration/tags/break_tag_test.rb +- test/integration/block_test.rb +- test/integration/standard_filter_test.rb +- test/integration/drop_test.rb +- test/integration/error_handling_test.rb - test/integration/template_test.rb -- test/integration/trim_mode_test.rb -- test/integration/variable_test.rb +- test/integration/document_test.rb +- test/integration/security_test.rb +- test/integration/render_profiling_test.rb +- test/integration/parsing_quirks_test.rb +- test/integration/filter_test.rb +- test/fixtures/en_locale.yml - test/test_helper.rb -- test/unit/block_unit_test.rb -- test/unit/condition_unit_test.rb -- test/unit/context_unit_test.rb -- test/unit/file_system_unit_test.rb -- test/unit/i18n_unit_test.rb -- test/unit/lexer_unit_test.rb -- test/unit/parser_unit_test.rb -- test/unit/regexp_unit_test.rb -- test/unit/strainer_unit_test.rb -- test/unit/tag_unit_test.rb -- test/unit/tags/case_tag_unit_test.rb -- test/unit/tags/for_tag_unit_test.rb -- test/unit/tags/if_tag_unit_test.rb -- test/unit/template_unit_test.rb -- test/unit/tokenizer_unit_test.rb -- test/unit/variable_unit_test.rb diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/integration/block_test.rb new/test/integration/block_test.rb --- old/test/integration/block_test.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/test/integration/block_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -0,0 +1,12 @@ +require 'test_helper' + +class BlockTest < Minitest::Test + include Liquid + + def test_unexpected_end_tag + exc = assert_raises(SyntaxError) do + Template.parse("{% if true %}{% endunless %}") + end + assert_equal exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif" + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/integration/parsing_quirks_test.rb new/test/integration/parsing_quirks_test.rb --- old/test/integration/parsing_quirks_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/integration/parsing_quirks_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -115,4 +115,8 @@ assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}") end end + + def test_contains_in_id + assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true) + end end # ParsingQuirksTest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/integration/security_test.rb new/test/integration/security_test.rb --- old/test/integration/security_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/integration/security_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -63,4 +63,18 @@ assert_equal [], (Symbol.all_symbols - current_symbols) end + + def test_max_depth_nested_blocks_does_not_raise_exception + depth = Liquid::Block::MAX_DEPTH + code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth + assert_equal "rendered", Template.parse(code).render! + end + + def test_more_than_max_depth_nested_blocks_raises_exception + depth = Liquid::Block::MAX_DEPTH + 1 + code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth + assert_raises(Liquid::StackLevelError) do + Template.parse(code).render! + end + end end # SecurityTest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/integration/standard_filter_test.rb new/test/integration/standard_filter_test.rb --- old/test/integration/standard_filter_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/integration/standard_filter_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -128,8 +128,16 @@ def test_escape assert_equal '<strong>', @filters.escape('<strong>') - assert_equal nil, @filters.escape(nil) + assert_equal '1', @filters.escape(1) + assert_equal '2001-02-03', @filters.escape(Date.new(2001, 2, 3)) + assert_nil @filters.escape(nil) + end + + def test_h assert_equal '<strong>', @filters.h('<strong>') + assert_equal '1', @filters.h(1) + assert_equal '2001-02-03', @filters.h(Date.new(2001, 2, 3)) + assert_nil @filters.h(nil) end def test_escape_once @@ -138,14 +146,18 @@ def test_url_encode assert_equal 'foo%2B1%40example.com', @filters.url_encode('[email protected]') - assert_equal nil, @filters.url_encode(nil) + assert_equal '1', @filters.url_encode(1) + assert_equal '2001-02-03', @filters.url_encode(Date.new(2001, 2, 3)) + assert_nil @filters.url_encode(nil) end def test_url_decode assert_equal 'foo bar', @filters.url_decode('foo+bar') assert_equal 'foo bar', @filters.url_decode('foo%20bar') assert_equal '[email protected]', @filters.url_decode('foo%2B1%40example.com') - assert_equal nil, @filters.url_decode(nil) + assert_equal '1', @filters.url_decode(1) + assert_equal '2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)) + assert_nil @filters.url_decode(nil) end def test_truncatewords @@ -170,6 +182,7 @@ def test_join assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4]) assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ') + assert_equal '1121314', @filters.join([1, 2, 3, 4], 1) end def test_sort @@ -177,6 +190,11 @@ assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a") end + def test_sort_with_nils + assert_equal [1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1]) + assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a") + end + def test_sort_when_property_is_sometimes_missing_puts_nils_last input = [ { "price" => 4, "handle" => "alpha" }, @@ -195,6 +213,57 @@ assert_equal expectation, @filters.sort(input, "price") end + def test_sort_natural + assert_equal ["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"]) + assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a") + end + + def test_sort_natural_with_nils + assert_equal ["a", "B", "c", "D", nil], @filters.sort_natural([nil, "c", "D", "a", "B"]) + assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a") + end + + def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last + input = [ + { "price" => "4", "handle" => "alpha" }, + { "handle" => "beta" }, + { "price" => "1", "handle" => "gamma" }, + { "handle" => "delta" }, + { "price" => 2, "handle" => "epsilon" } + ] + expectation = [ + { "price" => "1", "handle" => "gamma" }, + { "price" => 2, "handle" => "epsilon" }, + { "price" => "4", "handle" => "alpha" }, + { "handle" => "delta" }, + { "handle" => "beta" } + ] + assert_equal expectation, @filters.sort_natural(input, "price") + end + + def test_sort_natural_case_check + input = [ + { "key" => "X" }, + { "key" => "Y" }, + { "key" => "Z" }, + { "fake" => "t" }, + { "key" => "a" }, + { "key" => "b" }, + { "key" => "c" } + ] + expectation = [ + { "key" => "a" }, + { "key" => "b" }, + { "key" => "c" }, + { "key" => "X" }, + { "key" => "Y" }, + { "key" => "Z" }, + { "fake" => "t" } + ] + assert_equal expectation, @filters.sort_natural(input, "key") + assert_equal ["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"]) + end + def test_sort_empty_array assert_equal [], @filters.sort([], "a") end @@ -329,7 +398,7 @@ assert_equal "#{Date.today.year}", @filters.date('today', '%Y') assert_equal "#{Date.today.year}", @filters.date('Today', '%Y') - assert_equal nil, @filters.date(nil, "%B") + assert_nil @filters.date(nil, "%B") assert_equal '', @filters.date('', "%B") @@ -342,8 +411,8 @@ def test_first_last assert_equal 1, @filters.first([1, 2, 3]) assert_equal 3, @filters.last([1, 2, 3]) - assert_equal nil, @filters.first([]) - assert_equal nil, @filters.last([]) + assert_nil @filters.first([]) + assert_nil @filters.last([]) end def test_replace @@ -483,6 +552,28 @@ assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4) end + def test_at_most + assert_template_result "4", "{{ 5 | at_most:4 }}" + assert_template_result "5", "{{ 5 | at_most:5 }}" + assert_template_result "5", "{{ 5 | at_most:6 }}" + + assert_template_result "4.5", "{{ 4.5 | at_most:5 }}" + assert_template_result "5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6) + assert_template_result "4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4) + assert_template_result "4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4) + end + + def test_at_least + assert_template_result "5", "{{ 5 | at_least:4 }}" + assert_template_result "5", "{{ 5 | at_least:5 }}" + assert_template_result "6", "{{ 5 | at_least:6 }}" + + assert_template_result "5", "{{ 4.5 | at_least:5 }}" + assert_template_result "6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6) + assert_template_result "5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4) + assert_template_result "6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6) + end + def test_append assigns = { 'a' => 'bc', 'b' => 'd' } assert_template_result('bcd', "{{ a | append: 'd'}}", assigns) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/integration/tags/for_tag_test.rb new/test/integration/tags/for_tag_test.rb --- old/test/integration/tags/for_tag_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/integration/tags/for_tag_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -159,7 +159,7 @@ assert_template_result(expected, markup, assigns) end - def test_pause_resume_BIG_limit + def test_pause_resume_big_limit assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } } markup = <<-MKUP {%for i in array.items limit:3 %}{{i}}{%endfor%} @@ -178,7 +178,7 @@ assert_template_result(expected, markup, assigns) end - def test_pause_resume_BIG_offset + def test_pause_resume_big_offset assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } } markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%} next diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/integration/tags/include_tag_test.rb new/test/integration/tags/include_tag_test.rb --- old/test/integration/tags/include_tag_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/integration/tags/include_tag_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -137,7 +137,7 @@ Liquid::Template.file_system = infinite_file_system.new - assert_raises(Liquid::StackLevelError, SystemStackError) do + assert_raises(Liquid::StackLevelError) do Template.parse("{% include 'loop' %}").render! end end @@ -235,4 +235,11 @@ assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' } end + + def test_including_with_strict_variables + template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn) + template.render(nil, strict_variables: true) + + assert_equal [], template.errors + end end # IncludeTagTest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/integration/template_test.rb new/test/integration/template_test.rb --- old/test/integration/template_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/integration/template_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -261,6 +261,15 @@ assert_equal 'Liquid error: undefined variable d', t.errors[2].message end + def test_nil_value_does_not_raise + Liquid::Template.error_mode = :strict + t = Template.parse("some{{x}}thing") + result = t.render!({ 'x' => nil }, strict_variables: true) + + assert_equal 0, t.errors.count + assert_equal 'something', result + end + def test_undefined_variables_raise t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/integration/trim_mode_test.rb new/test/integration/trim_mode_test.rb --- old/test/integration/trim_mode_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/integration/trim_mode_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -496,6 +496,10 @@ assert_template_result(expected, text) end + def test_right_trim_followed_by_tag + assert_template_result('ab c', '{{ "a" -}}{{ "b" }} c') + end + def test_raw_output whitespace = ' ' text = <<-END_TEMPLATE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/integration/variable_test.rb new/test/integration/variable_test.rb --- old/test/integration/variable_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/integration/variable_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -89,4 +89,8 @@ def test_multiline_variable assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked') end + + def test_render_symbol + assert_template_result 'bar', '{{ foo }}', 'foo' => :bar + end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/test_helper.rb new/test/test_helper.rb --- old/test/test_helper.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/test_helper.rb 2018-10-09 11:14:52.000000000 +0200 @@ -2,7 +2,6 @@ ENV["MT_NO_EXPECTATIONS"] = "1" require 'minitest/autorun' -require 'spy/integration' $LOAD_PATH.unshift(File.join(File.expand_path(__dir__), '..', 'lib')) require 'liquid.rb' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/unit/condition_unit_test.rb new/test/unit/condition_unit_test.rb --- old/test/unit/condition_unit_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/unit/condition_unit_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -64,6 +64,14 @@ assert_evaluates_argument_error '1', '<=', 0 end + def test_hash_compare_backwards_compatibility + assert_nil Condition.new({}, '>', 2).evaluate + assert_nil Condition.new(2, '>', {}).evaluate + assert_equal false, Condition.new({}, '==', 2).evaluate + assert_equal true, Condition.new({ 'a' => 1 }, '==', { 'a' => 1 }).evaluate + assert_equal true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate + end + def test_contains_works_on_arrays @context = Liquid::Context.new @context['array'] = [1, 2, 3, 4, 5] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/unit/context_unit_test.rb new/test/unit/context_unit_test.rb --- old/test/unit/context_unit_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/unit/context_unit_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -70,10 +70,6 @@ @context = Liquid::Context.new end - def teardown - Spy.teardown - end - def test_variables @context['string'] = 'string' assert_equal 'string', @context['string'] @@ -98,12 +94,12 @@ assert_equal false, @context['bool'] @context['nil'] = nil - assert_equal nil, @context['nil'] - assert_equal nil, @context['nil'] + assert_nil @context['nil'] + assert_nil @context['nil'] end def test_variables_not_existing - assert_equal nil, @context['does_not_exist'] + assert_nil @context['does_not_exist'] end def test_scoping @@ -185,7 +181,7 @@ @context['test'] = 'test' assert_equal 'test', @context['test'] @context.pop - assert_equal nil, @context['test'] + assert_nil @context['test'] end def test_hierachical_data @@ -300,7 +296,7 @@ @context['hash'] = { 'first' => 'Hello' } assert_equal 1, @context['array.first'] - assert_equal nil, @context['array["first"]'] + assert_nil @context['array["first"]'] assert_equal 'Hello', @context['hash["first"]'] end @@ -450,14 +446,10 @@ assert_equal @context, @context['category'].context end - def test_use_empty_instead_of_any_in_interrupt_handling_to_avoid_lots_of_unnecessary_object_allocations - mock_any = Spy.on_instance_method(Array, :any?) - mock_empty = Spy.on_instance_method(Array, :empty?) - - @context.interrupt? - - refute mock_any.has_been_called? - assert mock_empty.has_been_called? + def test_interrupt_avoids_object_allocations + assert_no_object_allocations do + @context.interrupt? + end end def test_context_initialization_with_a_proc_in_environment @@ -480,4 +472,18 @@ context = Context.new assert_equal 'hi', context.apply_global_filter('hi') end + + private + + def assert_no_object_allocations + unless RUBY_ENGINE == 'ruby' + skip "stackprof needed to count object allocations" + end + require 'stackprof' + + profile = StackProf.run(mode: :object) do + yield + end + assert_equal 0, profile[:samples] + end end # ContextTest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/unit/lexer_unit_test.rb new/test/unit/lexer_unit_test.rb --- old/test/unit/lexer_unit_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/unit/lexer_unit_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -19,7 +19,7 @@ end def test_comparison - tokens = Lexer.new('== <> contains').tokenize + tokens = Lexer.new('== <> contains ').tokenize assert_equal [[:comparison, '=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]], tokens end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/unit/strainer_unit_test.rb new/test/unit/strainer_unit_test.rb --- old/test/unit/strainer_unit_test.rb 2016-12-14 17:59:11.000000000 +0100 +++ new/test/unit/strainer_unit_test.rb 2018-10-09 11:14:52.000000000 +0200 @@ -145,4 +145,20 @@ Strainer.global_filter(LateAddedFilter) assert_equal 'filtered', Strainer.create(nil).invoke('late_added_filter', 'input') 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 + end end # StrainerTest
