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 '&lt;strong&gt;', @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 '&lt;strong&gt;', @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


Reply via email to