liquid 3.0.6 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
 - data/History.md +243 -58
 - data/README.md +43 -4
 - data/lib/liquid/block.rb +57 -123
 - data/lib/liquid/block_body.rb +217 -85
 - data/lib/liquid/condition.rb +92 -45
 - data/lib/liquid/context.rb +154 -89
 - data/lib/liquid/document.rb +57 -9
 - data/lib/liquid/drop.rb +20 -17
 - data/lib/liquid/errors.rb +27 -29
 - data/lib/liquid/expression.rb +32 -20
 - data/lib/liquid/extensions.rb +21 -7
 - data/lib/liquid/file_system.rb +17 -15
 - data/lib/liquid/forloop_drop.rb +92 -0
 - data/lib/liquid/i18n.rb +10 -8
 - data/lib/liquid/interrupts.rb +4 -3
 - data/lib/liquid/lexer.rb +37 -26
 - data/lib/liquid/locales/en.yml +13 -6
 - data/lib/liquid/parse_context.rb +54 -0
 - data/lib/liquid/parse_tree_visitor.rb +42 -0
 - data/lib/liquid/parser.rb +30 -18
 - data/lib/liquid/parser_switching.rb +20 -6
 - data/lib/liquid/partial_cache.rb +24 -0
 - data/lib/liquid/profiler/hooks.rb +26 -14
 - data/lib/liquid/profiler.rb +72 -92
 - data/lib/liquid/range_lookup.rb +28 -3
 - data/lib/liquid/registers.rb +51 -0
 - data/lib/liquid/resource_limits.rb +62 -0
 - data/lib/liquid/standardfilters.rb +715 -132
 - data/lib/liquid/strainer_factory.rb +41 -0
 - data/lib/liquid/strainer_template.rb +62 -0
 - data/lib/liquid/tablerowloop_drop.rb +121 -0
 - data/lib/liquid/tag/disableable.rb +22 -0
 - data/lib/liquid/tag/disabler.rb +21 -0
 - data/lib/liquid/tag.rb +35 -12
 - data/lib/liquid/tags/assign.rb +57 -18
 - data/lib/liquid/tags/break.rb +15 -5
 - data/lib/liquid/tags/capture.rb +24 -18
 - data/lib/liquid/tags/case.rb +79 -30
 - data/lib/liquid/tags/comment.rb +19 -4
 - data/lib/liquid/tags/continue.rb +16 -12
 - data/lib/liquid/tags/cycle.rb +47 -27
 - data/lib/liquid/tags/decrement.rb +23 -24
 - data/lib/liquid/tags/echo.rb +41 -0
 - data/lib/liquid/tags/for.rb +155 -124
 - data/lib/liquid/tags/if.rb +97 -63
 - data/lib/liquid/tags/ifchanged.rb +11 -12
 - data/lib/liquid/tags/include.rb +82 -73
 - data/lib/liquid/tags/increment.rb +23 -17
 - data/lib/liquid/tags/inline_comment.rb +43 -0
 - data/lib/liquid/tags/raw.rb +50 -8
 - data/lib/liquid/tags/render.rb +109 -0
 - data/lib/liquid/tags/table_row.rb +57 -41
 - data/lib/liquid/tags/unless.rb +38 -20
 - data/lib/liquid/template.rb +71 -103
 - data/lib/liquid/template_factory.rb +9 -0
 - data/lib/liquid/tokenizer.rb +39 -0
 - data/lib/liquid/usage.rb +8 -0
 - data/lib/liquid/utils.rb +63 -9
 - data/lib/liquid/variable.rb +74 -56
 - data/lib/liquid/variable_lookup.rb +31 -15
 - data/lib/liquid/version.rb +3 -1
 - data/lib/liquid.rb +27 -12
 - metadata +30 -106
 - data/lib/liquid/module_ex.rb +0 -62
 - data/lib/liquid/strainer.rb +0 -59
 - data/lib/liquid/token.rb +0 -18
 - data/test/fixtures/en_locale.yml +0 -9
 - data/test/integration/assign_test.rb +0 -48
 - data/test/integration/blank_test.rb +0 -106
 - data/test/integration/capture_test.rb +0 -50
 - data/test/integration/context_test.rb +0 -32
 - data/test/integration/drop_test.rb +0 -271
 - data/test/integration/error_handling_test.rb +0 -207
 - data/test/integration/filter_test.rb +0 -138
 - data/test/integration/hash_ordering_test.rb +0 -23
 - data/test/integration/output_test.rb +0 -124
 - data/test/integration/parsing_quirks_test.rb +0 -116
 - data/test/integration/render_profiling_test.rb +0 -154
 - data/test/integration/security_test.rb +0 -64
 - data/test/integration/standard_filter_test.rb +0 -396
 - data/test/integration/tags/break_tag_test.rb +0 -16
 - data/test/integration/tags/continue_tag_test.rb +0 -16
 - data/test/integration/tags/for_tag_test.rb +0 -375
 - data/test/integration/tags/if_else_tag_test.rb +0 -190
 - data/test/integration/tags/include_tag_test.rb +0 -234
 - data/test/integration/tags/increment_tag_test.rb +0 -24
 - data/test/integration/tags/raw_tag_test.rb +0 -25
 - data/test/integration/tags/standard_tag_test.rb +0 -297
 - data/test/integration/tags/statements_test.rb +0 -113
 - data/test/integration/tags/table_row_test.rb +0 -63
 - data/test/integration/tags/unless_else_tag_test.rb +0 -26
 - data/test/integration/template_test.rb +0 -182
 - data/test/integration/variable_test.rb +0 -82
 - data/test/test_helper.rb +0 -89
 - data/test/unit/block_unit_test.rb +0 -55
 - data/test/unit/condition_unit_test.rb +0 -149
 - data/test/unit/context_unit_test.rb +0 -492
 - data/test/unit/file_system_unit_test.rb +0 -35
 - data/test/unit/i18n_unit_test.rb +0 -37
 - data/test/unit/lexer_unit_test.rb +0 -48
 - data/test/unit/module_ex_unit_test.rb +0 -87
 - data/test/unit/parser_unit_test.rb +0 -82
 - data/test/unit/regexp_unit_test.rb +0 -44
 - data/test/unit/strainer_unit_test.rb +0 -69
 - data/test/unit/tag_unit_test.rb +0 -16
 - data/test/unit/tags/case_tag_unit_test.rb +0 -10
 - data/test/unit/tags/for_tag_unit_test.rb +0 -13
 - data/test/unit/tags/if_tag_unit_test.rb +0 -8
 - data/test/unit/template_unit_test.rb +0 -69
 - data/test/unit/tokenizer_unit_test.rb +0 -38
 - data/test/unit/variable_unit_test.rb +0 -145
 - /data/{MIT-LICENSE → LICENSE} +0 -0
 
    
        data/lib/liquid/tags/comment.rb
    CHANGED
    
    | 
         @@ -1,10 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module Liquid
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @liquid_public_docs
         
     | 
| 
      
 5 
     | 
    
         
            +
              # @liquid_type tag
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @liquid_category syntax
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @liquid_name comment
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @liquid_summary
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   Prevents an expression from being rendered or output.
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @liquid_description
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   Any text inside `comment` tags won't be output, and any Liquid code won't be rendered.
         
     | 
| 
      
 12 
     | 
    
         
            +
              # @liquid_syntax
         
     | 
| 
      
 13 
     | 
    
         
            +
              #   {% comment %}
         
     | 
| 
      
 14 
     | 
    
         
            +
              #     content
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   {% endcomment %}
         
     | 
| 
      
 16 
     | 
    
         
            +
              # @liquid_syntax_keyword content The content of the comment.
         
     | 
| 
       2 
17 
     | 
    
         
             
              class Comment < Block
         
     | 
| 
       3 
     | 
    
         
            -
                def  
     | 
| 
       4 
     | 
    
         
            -
                   
     | 
| 
      
 18 
     | 
    
         
            +
                def render_to_output_buffer(_context, output)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  output
         
     | 
| 
       5 
20 
     | 
    
         
             
                end
         
     | 
| 
       6 
21 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
                def unknown_tag( 
     | 
| 
      
 22 
     | 
    
         
            +
                def unknown_tag(_tag, _markup, _tokens)
         
     | 
| 
       8 
23 
     | 
    
         
             
                end
         
     | 
| 
       9 
24 
     | 
    
         | 
| 
       10 
25 
     | 
    
         
             
                def blank?
         
     | 
| 
         @@ -12,5 +27,5 @@ module Liquid 
     | 
|
| 
       12 
27 
     | 
    
         
             
                end
         
     | 
| 
       13 
28 
     | 
    
         
             
              end
         
     | 
| 
       14 
29 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
              Template.register_tag('comment' 
     | 
| 
      
 30 
     | 
    
         
            +
              Template.register_tag('comment', Comment)
         
     | 
| 
       16 
31 
     | 
    
         
             
            end
         
     | 
    
        data/lib/liquid/tags/continue.rb
    CHANGED
    
    | 
         @@ -1,18 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module Liquid
         
     | 
| 
       2 
     | 
    
         
            -
              #  
     | 
| 
       3 
     | 
    
         
            -
              #
         
     | 
| 
       4 
     | 
    
         
            -
              #  
     | 
| 
       5 
     | 
    
         
            -
              # 
     | 
| 
       6 
     | 
    
         
            -
              # 
     | 
| 
       7 
     | 
    
         
            -
              # 
     | 
| 
       8 
     | 
    
         
            -
              # 
     | 
| 
       9 
     | 
    
         
            -
              # 
     | 
| 
       10 
     | 
    
         
            -
              #
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @liquid_public_docs
         
     | 
| 
      
 5 
     | 
    
         
            +
              # @liquid_type tag
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @liquid_category iteration
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @liquid_name continue
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @liquid_summary
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   Causes a [`for` loop](/api/liquid/tags#for) to skip to the next iteration.
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @liquid_syntax
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   {% continue %}
         
     | 
| 
       11 
12 
     | 
    
         
             
              class Continue < Tag
         
     | 
| 
       12 
     | 
    
         
            -
                 
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
      
 13 
     | 
    
         
            +
                INTERRUPT = ContinueInterrupt.new.freeze
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def render_to_output_buffer(context, output)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  context.push_interrupt(INTERRUPT)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  output
         
     | 
| 
       14 
18 
     | 
    
         
             
                end
         
     | 
| 
       15 
19 
     | 
    
         
             
              end
         
     | 
| 
       16 
20 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
              Template.register_tag('continue' 
     | 
| 
      
 21 
     | 
    
         
            +
              Template.register_tag('continue', Continue)
         
     | 
| 
       18 
22 
     | 
    
         
             
            end
         
     | 
    
        data/lib/liquid/tags/cycle.rb
    CHANGED
    
    | 
         @@ -1,46 +1,60 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module Liquid
         
     | 
| 
       2 
     | 
    
         
            -
              #  
     | 
| 
       3 
     | 
    
         
            -
              #
         
     | 
| 
       4 
     | 
    
         
            -
              # 
     | 
| 
       5 
     | 
    
         
            -
              # 
     | 
| 
       6 
     | 
    
         
            -
              # 
     | 
| 
       7 
     | 
    
         
            -
              #
         
     | 
| 
       8 
     | 
    
         
            -
              # 
     | 
| 
       9 
     | 
    
         
            -
              # 
     | 
| 
       10 
     | 
    
         
            -
              #    <div class="blue"> Item three </div>
         
     | 
| 
       11 
     | 
    
         
            -
              #    <div class="red"> Item four </div>
         
     | 
| 
       12 
     | 
    
         
            -
              #    <div class="green"> Item five</div>
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @liquid_public_docs
         
     | 
| 
      
 5 
     | 
    
         
            +
              # @liquid_type tag
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @liquid_category iteration
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @liquid_name cycle
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @liquid_summary
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/api/liquid/tags#for).
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @liquid_description
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   The `cycle` tag must be used inside a `for` loop.
         
     | 
| 
       13 
12 
     | 
    
         
             
              #
         
     | 
| 
      
 13 
     | 
    
         
            +
              #   > Tip:
         
     | 
| 
      
 14 
     | 
    
         
            +
              #   > Use the `cycle` tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table.
         
     | 
| 
      
 15 
     | 
    
         
            +
              # @liquid_syntax
         
     | 
| 
      
 16 
     | 
    
         
            +
              #   {% cycle string, string, ... %}
         
     | 
| 
       14 
17 
     | 
    
         
             
              class Cycle < Tag
         
     | 
| 
       15 
18 
     | 
    
         
             
                SimpleSyntax = /\A#{QuotedFragment}+/o
         
     | 
| 
       16 
19 
     | 
    
         
             
                NamedSyntax  = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
         
     | 
| 
       17 
20 
     | 
    
         | 
| 
      
 21 
     | 
    
         
            +
                attr_reader :variables
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
       18 
23 
     | 
    
         
             
                def initialize(tag_name, markup, options)
         
     | 
| 
       19 
24 
     | 
    
         
             
                  super
         
     | 
| 
       20 
25 
     | 
    
         
             
                  case markup
         
     | 
| 
       21 
26 
     | 
    
         
             
                  when NamedSyntax
         
     | 
| 
       22 
     | 
    
         
            -
                    @variables = variables_from_string( 
     | 
| 
       23 
     | 
    
         
            -
                    @name 
     | 
| 
      
 27 
     | 
    
         
            +
                    @variables = variables_from_string(Regexp.last_match(2))
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @name      = parse_expression(Regexp.last_match(1))
         
     | 
| 
       24 
29 
     | 
    
         
             
                  when SimpleSyntax
         
     | 
| 
       25 
30 
     | 
    
         
             
                    @variables = variables_from_string(markup)
         
     | 
| 
       26 
     | 
    
         
            -
                    @name 
     | 
| 
      
 31 
     | 
    
         
            +
                    @name      = @variables.to_s
         
     | 
| 
       27 
32 
     | 
    
         
             
                  else
         
     | 
| 
       28 
     | 
    
         
            -
                    raise SyntaxError 
     | 
| 
      
 33 
     | 
    
         
            +
                    raise SyntaxError, options[:locale].t("errors.syntax.cycle")
         
     | 
| 
       29 
34 
     | 
    
         
             
                  end
         
     | 
| 
       30 
35 
     | 
    
         
             
                end
         
     | 
| 
       31 
36 
     | 
    
         | 
| 
       32 
     | 
    
         
            -
                def  
     | 
| 
       33 
     | 
    
         
            -
                  context.registers[:cycle] ||=  
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
                  context. 
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                     
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
      
 37 
     | 
    
         
            +
                def render_to_output_buffer(context, output)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  context.registers[:cycle] ||= {}
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  key       = context.evaluate(@name)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  iteration = context.registers[:cycle][key].to_i
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  val = context.evaluate(@variables[iteration])
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  if val.is_a?(Array)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    val = val.join
         
     | 
| 
      
 47 
     | 
    
         
            +
                  elsif !val.is_a?(String)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    val = val.to_s
         
     | 
| 
       43 
49 
     | 
    
         
             
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  output << val
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  iteration += 1
         
     | 
| 
      
 54 
     | 
    
         
            +
                  iteration = 0 if iteration >= @variables.size
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  context.registers[:cycle][key] = iteration
         
     | 
| 
      
 57 
     | 
    
         
            +
                  output
         
     | 
| 
       44 
58 
     | 
    
         
             
                end
         
     | 
| 
       45 
59 
     | 
    
         | 
| 
       46 
60 
     | 
    
         
             
                private
         
     | 
| 
         @@ -48,9 +62,15 @@ module Liquid 
     | 
|
| 
       48 
62 
     | 
    
         
             
                def variables_from_string(markup)
         
     | 
| 
       49 
63 
     | 
    
         
             
                  markup.split(',').collect do |var|
         
     | 
| 
       50 
64 
     | 
    
         
             
                    var =~ /\s*(#{QuotedFragment})\s*/o
         
     | 
| 
       51 
     | 
    
         
            -
                     
     | 
| 
      
 65 
     | 
    
         
            +
                    Regexp.last_match(1) ? parse_expression(Regexp.last_match(1)) : nil
         
     | 
| 
       52 
66 
     | 
    
         
             
                  end.compact
         
     | 
| 
       53 
67 
     | 
    
         
             
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                class ParseTreeVisitor < Liquid::ParseTreeVisitor
         
     | 
| 
      
 70 
     | 
    
         
            +
                  def children
         
     | 
| 
      
 71 
     | 
    
         
            +
                    Array(@node.variables)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
       54 
74 
     | 
    
         
             
              end
         
     | 
| 
       55 
75 
     | 
    
         | 
| 
       56 
76 
     | 
    
         
             
              Template.register_tag('cycle', Cycle)
         
     | 
| 
         @@ -1,38 +1,37 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
              # decrement is used in a place where one needs to insert a counter
         
     | 
| 
       4 
     | 
    
         
            -
              #     into a template, and needs the counter to survive across
         
     | 
| 
       5 
     | 
    
         
            -
              #     multiple instantiations of the template.
         
     | 
| 
       6 
     | 
    
         
            -
              #     NOTE: decrement is a pre-decrement, --i,
         
     | 
| 
       7 
     | 
    
         
            -
              #           while increment is post:      i++.
         
     | 
| 
       8 
     | 
    
         
            -
              #
         
     | 
| 
       9 
     | 
    
         
            -
              #     (To achieve the survival, the application must keep the context)
         
     | 
| 
       10 
     | 
    
         
            -
              #
         
     | 
| 
       11 
     | 
    
         
            -
              #     if the variable does not exist, it is created with value 0.
         
     | 
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       12 
2 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
              #
         
     | 
| 
       15 
     | 
    
         
            -
              #  
     | 
| 
       16 
     | 
    
         
            -
              #
         
     | 
| 
       17 
     | 
    
         
            -
              # 
     | 
| 
       18 
     | 
    
         
            -
              # 
     | 
| 
       19 
     | 
    
         
            -
              # 
     | 
| 
      
 3 
     | 
    
         
            +
            module Liquid
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @liquid_public_docs
         
     | 
| 
      
 5 
     | 
    
         
            +
              # @liquid_type tag
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @liquid_category variable
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @liquid_name decrement
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @liquid_summary
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   Creates a new variable, with a default value of -1, that's decreased by 1 with each subsequent call.
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @liquid_description
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
         
     | 
| 
      
 12 
     | 
    
         
            +
              #   or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
         
     | 
| 
      
 13 
     | 
    
         
            +
              #   [snippets](/themes/architecture#snippets) included in the file.
         
     | 
| 
       20 
14 
     | 
    
         
             
              #
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/api/liquid/tags#assign)
         
     | 
| 
      
 16 
     | 
    
         
            +
              #   and [`capture`](/api/liquid/tags#capture). However, `decrement` and [`increment`](/api/liquid/tags#increment) share
         
     | 
| 
      
 17 
     | 
    
         
            +
              #   variables.
         
     | 
| 
      
 18 
     | 
    
         
            +
              # @liquid_syntax
         
     | 
| 
      
 19 
     | 
    
         
            +
              #   {% decrement variable_name %}
         
     | 
| 
      
 20 
     | 
    
         
            +
              # @liquid_syntax_keyword variable_name The name of the variable being decremented.
         
     | 
| 
       21 
21 
     | 
    
         
             
              class Decrement < Tag
         
     | 
| 
       22 
22 
     | 
    
         
             
                def initialize(tag_name, markup, options)
         
     | 
| 
       23 
23 
     | 
    
         
             
                  super
         
     | 
| 
       24 
24 
     | 
    
         
             
                  @variable = markup.strip
         
     | 
| 
       25 
25 
     | 
    
         
             
                end
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
     | 
    
         
            -
                def  
     | 
| 
      
 27 
     | 
    
         
            +
                def render_to_output_buffer(context, output)
         
     | 
| 
       28 
28 
     | 
    
         
             
                  value = context.environments.first[@variable] ||= 0
         
     | 
| 
       29 
     | 
    
         
            -
                  value  
     | 
| 
      
 29 
     | 
    
         
            +
                  value -= 1
         
     | 
| 
       30 
30 
     | 
    
         
             
                  context.environments.first[@variable] = value
         
     | 
| 
       31 
     | 
    
         
            -
                  value.to_s
         
     | 
| 
      
 31 
     | 
    
         
            +
                  output << value.to_s
         
     | 
| 
      
 32 
     | 
    
         
            +
                  output
         
     | 
| 
       32 
33 
     | 
    
         
             
                end
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                private
         
     | 
| 
       35 
34 
     | 
    
         
             
              end
         
     | 
| 
       36 
35 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
              Template.register_tag('decrement' 
     | 
| 
      
 36 
     | 
    
         
            +
              Template.register_tag('decrement', Decrement)
         
     | 
| 
       38 
37 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,41 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Liquid
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @liquid_public_docs
         
     | 
| 
      
 5 
     | 
    
         
            +
              # @liquid_type tag
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @liquid_category syntax
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @liquid_name echo
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @liquid_summary
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   Outputs an expression.
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @liquid_description
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly
         
     | 
| 
      
 12 
     | 
    
         
            +
              #   bracket method, you can use the `echo` tag inside [`liquid` tags](/api/liquid/tags#liquid).
         
     | 
| 
      
 13 
     | 
    
         
            +
              #
         
     | 
| 
      
 14 
     | 
    
         
            +
              #   > Tip:
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   > You can use [filters](/api/liquid/filters) on expressions inside `echo` tags.
         
     | 
| 
      
 16 
     | 
    
         
            +
              # @liquid_syntax
         
     | 
| 
      
 17 
     | 
    
         
            +
              #   {% liquid
         
     | 
| 
      
 18 
     | 
    
         
            +
              #     echo expression
         
     | 
| 
      
 19 
     | 
    
         
            +
              #   %}
         
     | 
| 
      
 20 
     | 
    
         
            +
              # @liquid_syntax_keyword expression The expression to be output.
         
     | 
| 
      
 21 
     | 
    
         
            +
              class Echo < Tag
         
     | 
| 
      
 22 
     | 
    
         
            +
                attr_reader :variable
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def initialize(tag_name, markup, parse_context)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  super
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @variable = Variable.new(markup, parse_context)
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def render(context)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @variable.render_to_output_buffer(context, +'')
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                class ParseTreeVisitor < Liquid::ParseTreeVisitor
         
     | 
| 
      
 34 
     | 
    
         
            +
                  def children
         
     | 
| 
      
 35 
     | 
    
         
            +
                    [@node.variable]
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              Template.register_tag('echo', Echo)
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/liquid/tags/for.rb
    CHANGED
    
    | 
         @@ -1,175 +1,206 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
              #  
     | 
| 
       5 
     | 
    
         
            -
              #
         
     | 
| 
       6 
     | 
    
         
            -
              #  
     | 
| 
       7 
     | 
    
         
            -
              # 
     | 
| 
       8 
     | 
    
         
            -
              # 
     | 
| 
       9 
     | 
    
         
            -
              # 
     | 
| 
       10 
     | 
    
         
            -
              #
         
     | 
| 
       11 
     | 
    
         
            -
              #  
     | 
| 
       12 
     | 
    
         
            -
              # 
     | 
| 
       13 
     | 
    
         
            -
              #      <div {% if forloop.first %}class="first"{% endif %}>
         
     | 
| 
       14 
     | 
    
         
            -
              #        Item {{ forloop.index }}: {{ item.name }}
         
     | 
| 
       15 
     | 
    
         
            -
              #      </div>
         
     | 
| 
       16 
     | 
    
         
            -
              #    {% else %}
         
     | 
| 
       17 
     | 
    
         
            -
              #      There is nothing in the collection.
         
     | 
| 
       18 
     | 
    
         
            -
              #    {% endfor %}
         
     | 
| 
       19 
     | 
    
         
            -
              #
         
     | 
| 
       20 
     | 
    
         
            -
              # You can also define a limit and offset much like SQL.  Remember
         
     | 
| 
       21 
     | 
    
         
            -
              # that offset starts at 0 for the first item.
         
     | 
| 
       22 
     | 
    
         
            -
              #
         
     | 
| 
       23 
     | 
    
         
            -
              #    {% for item in collection limit:5 offset:10 %}
         
     | 
| 
       24 
     | 
    
         
            -
              #      {{ item.name }}
         
     | 
| 
       25 
     | 
    
         
            -
              #    {% end %}
         
     | 
| 
       26 
     | 
    
         
            -
              #
         
     | 
| 
       27 
     | 
    
         
            -
              #  To reverse the for loop simply use {% for item in collection reversed %}
         
     | 
| 
       28 
     | 
    
         
            -
              #
         
     | 
| 
       29 
     | 
    
         
            -
              # == Available variables:
         
     | 
| 
       30 
     | 
    
         
            -
              #
         
     | 
| 
       31 
     | 
    
         
            -
              # forloop.name:: 'item-collection'
         
     | 
| 
       32 
     | 
    
         
            -
              # forloop.length:: Length of the loop
         
     | 
| 
       33 
     | 
    
         
            -
              # forloop.index:: The current item's position in the collection;
         
     | 
| 
       34 
     | 
    
         
            -
              #                 forloop.index starts at 1.
         
     | 
| 
       35 
     | 
    
         
            -
              #                 This is helpful for non-programmers who start believe
         
     | 
| 
       36 
     | 
    
         
            -
              #                 the first item in an array is 1, not 0.
         
     | 
| 
       37 
     | 
    
         
            -
              # forloop.index0:: The current item's position in the collection
         
     | 
| 
       38 
     | 
    
         
            -
              #                  where the first item is 0
         
     | 
| 
       39 
     | 
    
         
            -
              # forloop.rindex:: Number of items remaining in the loop
         
     | 
| 
       40 
     | 
    
         
            -
              #                  (length - index) where 1 is the last item.
         
     | 
| 
       41 
     | 
    
         
            -
              # forloop.rindex0:: Number of items remaining in the loop
         
     | 
| 
       42 
     | 
    
         
            -
              #                   where 0 is the last item.
         
     | 
| 
       43 
     | 
    
         
            -
              # forloop.first:: Returns true if the item is the first item.
         
     | 
| 
       44 
     | 
    
         
            -
              # forloop.last:: Returns true if the item is the last item.
         
     | 
| 
      
 3 
     | 
    
         
            +
            module Liquid
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @liquid_public_docs
         
     | 
| 
      
 5 
     | 
    
         
            +
              # @liquid_type tag
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @liquid_category iteration
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @liquid_name for
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @liquid_summary
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   Renders an expression for every item in an array.
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @liquid_description
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the
         
     | 
| 
      
 12 
     | 
    
         
            +
              #   [`paginate` tag](/api/liquid/tags#paginate) to split the items over multiple pages.
         
     | 
| 
       45 
13 
     | 
    
         
             
              #
         
     | 
| 
      
 14 
     | 
    
         
            +
              #   > Tip:
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   > Every `for` loop has an associated [`forloop` object](/api/liquid/objects#forloop) with information about the loop.
         
     | 
| 
      
 16 
     | 
    
         
            +
              # @liquid_syntax
         
     | 
| 
      
 17 
     | 
    
         
            +
              #   {% for variable in array %}
         
     | 
| 
      
 18 
     | 
    
         
            +
              #     expression
         
     | 
| 
      
 19 
     | 
    
         
            +
              #   {% endfor %}
         
     | 
| 
      
 20 
     | 
    
         
            +
              # @liquid_syntax_keyword variable The current item in the array.
         
     | 
| 
      
 21 
     | 
    
         
            +
              # @liquid_syntax_keyword array The array to iterate over.
         
     | 
| 
      
 22 
     | 
    
         
            +
              # @liquid_syntax_keyword expression The expression to render for each iteration.
         
     | 
| 
      
 23 
     | 
    
         
            +
              # @liquid_optional_param limit [number] The number of iterations to perform.
         
     | 
| 
      
 24 
     | 
    
         
            +
              # @liquid_optional_param offset [number] The 1-based index to start iterating at.
         
     | 
| 
      
 25 
     | 
    
         
            +
              # @liquid_optional_param range [untyped] A custom numeric range to iterate over.
         
     | 
| 
      
 26 
     | 
    
         
            +
              # @liquid_optional_param reversed [untyped] Iterate in reverse order.
         
     | 
| 
       46 
27 
     | 
    
         
             
              class For < Block
         
     | 
| 
       47 
28 
     | 
    
         
             
                Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
         
     | 
| 
       48 
29 
     | 
    
         | 
| 
      
 30 
     | 
    
         
            +
                attr_reader :collection_name, :variable_name, :limit, :from
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
       49 
32 
     | 
    
         
             
                def initialize(tag_name, markup, options)
         
     | 
| 
       50 
33 
     | 
    
         
             
                  super
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @from = @limit = nil
         
     | 
| 
       51 
35 
     | 
    
         
             
                  parse_with_selected_parser(markup)
         
     | 
| 
       52 
     | 
    
         
            -
                  @ 
     | 
| 
      
 36 
     | 
    
         
            +
                  @for_block = new_body
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @else_block = nil
         
     | 
| 
       53 
38 
     | 
    
         
             
                end
         
     | 
| 
       54 
39 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
                def  
     | 
| 
       56 
     | 
    
         
            -
                  if @ 
     | 
| 
       57 
     | 
    
         
            -
                    @ 
     | 
| 
       58 
     | 
    
         
            -
                  else
         
     | 
| 
       59 
     | 
    
         
            -
                    @for_block
         
     | 
| 
      
 40 
     | 
    
         
            +
                def parse(tokens)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  if parse_body(@for_block, tokens)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    parse_body(@else_block, tokens)
         
     | 
| 
       60 
43 
     | 
    
         
             
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  if blank?
         
     | 
| 
      
 45 
     | 
    
         
            +
                    @else_block&.remove_blank_strings
         
     | 
| 
      
 46 
     | 
    
         
            +
                    @for_block.remove_blank_strings
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                  @else_block&.freeze
         
     | 
| 
      
 49 
     | 
    
         
            +
                  @for_block.freeze
         
     | 
| 
       61 
50 
     | 
    
         
             
                end
         
     | 
| 
       62 
51 
     | 
    
         | 
| 
       63 
     | 
    
         
            -
                def  
     | 
| 
       64 
     | 
    
         
            -
                   
     | 
| 
       65 
     | 
    
         
            -
                  @nodelist = @else_block = []
         
     | 
| 
      
 52 
     | 
    
         
            +
                def nodelist
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @else_block ? [@for_block, @else_block] : [@for_block]
         
     | 
| 
       66 
54 
     | 
    
         
             
                end
         
     | 
| 
       67 
55 
     | 
    
         | 
| 
       68 
     | 
    
         
            -
                def  
     | 
| 
       69 
     | 
    
         
            -
                   
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
                  collection = collection.to_a if collection.is_a?(Range)
         
     | 
| 
      
 56 
     | 
    
         
            +
                def unknown_tag(tag, markup, tokens)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  return super unless tag == 'else'
         
     | 
| 
      
 58 
     | 
    
         
            +
                  @else_block = new_body
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
       73 
60 
     | 
    
         | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
                   
     | 
| 
      
 61 
     | 
    
         
            +
                def render_to_output_buffer(context, output)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  segment = collection_segment(context)
         
     | 
| 
       76 
63 
     | 
    
         | 
| 
       77 
     | 
    
         
            -
                   
     | 
| 
       78 
     | 
    
         
            -
                    context 
     | 
| 
      
 64 
     | 
    
         
            +
                  if segment.empty?
         
     | 
| 
      
 65 
     | 
    
         
            +
                    render_else(context, output)
         
     | 
| 
       79 
66 
     | 
    
         
             
                  else
         
     | 
| 
       80 
     | 
    
         
            -
                    context 
     | 
| 
      
 67 
     | 
    
         
            +
                    render_segment(context, output, segment)
         
     | 
| 
       81 
68 
     | 
    
         
             
                  end
         
     | 
| 
       82 
69 
     | 
    
         | 
| 
       83 
     | 
    
         
            -
                   
     | 
| 
       84 
     | 
    
         
            -
                  to    = limit ? limit.to_i + from : nil
         
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
                  segment = Utils.slice_collection(collection, from, to)
         
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
                  return render_else(context) if segment.empty?
         
     | 
| 
       89 
     | 
    
         
            -
             
     | 
| 
       90 
     | 
    
         
            -
                  segment.reverse! if @reversed
         
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
                  result = ''
         
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
                  length = segment.length
         
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
     | 
    
         
            -
                  # Store our progress through the collection for the continue flag
         
     | 
| 
       97 
     | 
    
         
            -
                  context.registers[:for][@name] = from + segment.length
         
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
     | 
    
         
            -
                  context.stack do
         
     | 
| 
       100 
     | 
    
         
            -
                    segment.each_with_index do |item, index|
         
     | 
| 
       101 
     | 
    
         
            -
                      context[@variable_name] = item
         
     | 
| 
       102 
     | 
    
         
            -
                      context['forloop'.freeze] = {
         
     | 
| 
       103 
     | 
    
         
            -
                        'name'.freeze    => @name,
         
     | 
| 
       104 
     | 
    
         
            -
                        'length'.freeze  => length,
         
     | 
| 
       105 
     | 
    
         
            -
                        'index'.freeze   => index + 1,
         
     | 
| 
       106 
     | 
    
         
            -
                        'index0'.freeze  => index,
         
     | 
| 
       107 
     | 
    
         
            -
                        'rindex'.freeze  => length - index,
         
     | 
| 
       108 
     | 
    
         
            -
                        'rindex0'.freeze => length - index - 1,
         
     | 
| 
       109 
     | 
    
         
            -
                        'first'.freeze   => (index == 0),
         
     | 
| 
       110 
     | 
    
         
            -
                        'last'.freeze    => (index == length - 1)
         
     | 
| 
       111 
     | 
    
         
            -
                      }
         
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
     | 
    
         
            -
                      result << render_all(@for_block, context)
         
     | 
| 
       114 
     | 
    
         
            -
             
     | 
| 
       115 
     | 
    
         
            -
                      # Handle any interrupts if they exist.
         
     | 
| 
       116 
     | 
    
         
            -
                      if context.has_interrupt?
         
     | 
| 
       117 
     | 
    
         
            -
                        interrupt = context.pop_interrupt
         
     | 
| 
       118 
     | 
    
         
            -
                        break if interrupt.is_a? BreakInterrupt
         
     | 
| 
       119 
     | 
    
         
            -
                        next if interrupt.is_a? ContinueInterrupt
         
     | 
| 
       120 
     | 
    
         
            -
                      end
         
     | 
| 
       121 
     | 
    
         
            -
                    end
         
     | 
| 
       122 
     | 
    
         
            -
                  end
         
     | 
| 
       123 
     | 
    
         
            -
                  result
         
     | 
| 
      
 70 
     | 
    
         
            +
                  output
         
     | 
| 
       124 
71 
     | 
    
         
             
                end
         
     | 
| 
       125 
72 
     | 
    
         | 
| 
       126 
73 
     | 
    
         
             
                protected
         
     | 
| 
       127 
74 
     | 
    
         | 
| 
       128 
75 
     | 
    
         
             
                def lax_parse(markup)
         
     | 
| 
       129 
76 
     | 
    
         
             
                  if markup =~ Syntax
         
     | 
| 
       130 
     | 
    
         
            -
                    @variable_name 
     | 
| 
       131 
     | 
    
         
            -
                     
     | 
| 
       132 
     | 
    
         
            -
                    @ 
     | 
| 
       133 
     | 
    
         
            -
                    @ 
     | 
| 
       134 
     | 
    
         
            -
                    @ 
     | 
| 
      
 77 
     | 
    
         
            +
                    @variable_name   = Regexp.last_match(1)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    collection_name  = Regexp.last_match(2)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    @reversed        = !!Regexp.last_match(3)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    @name            = "#{@variable_name}-#{collection_name}"
         
     | 
| 
      
 81 
     | 
    
         
            +
                    @collection_name = parse_expression(collection_name)
         
     | 
| 
       135 
82 
     | 
    
         
             
                    markup.scan(TagAttributes) do |key, value|
         
     | 
| 
       136 
     | 
    
         
            -
                       
     | 
| 
      
 83 
     | 
    
         
            +
                      set_attribute(key, value)
         
     | 
| 
       137 
84 
     | 
    
         
             
                    end
         
     | 
| 
       138 
85 
     | 
    
         
             
                  else
         
     | 
| 
       139 
     | 
    
         
            -
                    raise SyntaxError 
     | 
| 
      
 86 
     | 
    
         
            +
                    raise SyntaxError, options[:locale].t("errors.syntax.for")
         
     | 
| 
       140 
87 
     | 
    
         
             
                  end
         
     | 
| 
       141 
88 
     | 
    
         
             
                end
         
     | 
| 
       142 
89 
     | 
    
         | 
| 
       143 
90 
     | 
    
         
             
                def strict_parse(markup)
         
     | 
| 
       144 
91 
     | 
    
         
             
                  p = Parser.new(markup)
         
     | 
| 
       145 
92 
     | 
    
         
             
                  @variable_name = p.consume(:id)
         
     | 
| 
       146 
     | 
    
         
            -
                  raise SyntaxError 
     | 
| 
       147 
     | 
    
         
            -
             
     | 
| 
       148 
     | 
    
         
            -
                   
     | 
| 
       149 
     | 
    
         
            -
                  @ 
     | 
| 
      
 93 
     | 
    
         
            +
                  raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  collection_name  = p.expression
         
     | 
| 
      
 96 
     | 
    
         
            +
                  @collection_name = parse_expression(collection_name)
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                  @name     = "#{@variable_name}-#{collection_name}"
         
     | 
| 
      
 99 
     | 
    
         
            +
                  @reversed = p.id?('reversed')
         
     | 
| 
       150 
100 
     | 
    
         | 
| 
       151 
     | 
    
         
            -
                  @attributes = {}
         
     | 
| 
       152 
101 
     | 
    
         
             
                  while p.look(:id) && p.look(:colon, 1)
         
     | 
| 
       153 
     | 
    
         
            -
                    unless attribute = p.id?('limit' 
     | 
| 
       154 
     | 
    
         
            -
                      raise SyntaxError 
     | 
| 
      
 102 
     | 
    
         
            +
                    unless (attribute = p.id?('limit') || p.id?('offset'))
         
     | 
| 
      
 103 
     | 
    
         
            +
                      raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
         
     | 
| 
       155 
104 
     | 
    
         
             
                    end
         
     | 
| 
       156 
105 
     | 
    
         
             
                    p.consume
         
     | 
| 
       157 
     | 
    
         
            -
                     
     | 
| 
       158 
     | 
    
         
            -
                    @attributes[attribute] = val
         
     | 
| 
      
 106 
     | 
    
         
            +
                    set_attribute(attribute, p.expression)
         
     | 
| 
       159 
107 
     | 
    
         
             
                  end
         
     | 
| 
       160 
108 
     | 
    
         
             
                  p.consume(:end_of_string)
         
     | 
| 
       161 
109 
     | 
    
         
             
                end
         
     | 
| 
       162 
110 
     | 
    
         | 
| 
       163 
111 
     | 
    
         
             
                private
         
     | 
| 
       164 
112 
     | 
    
         | 
| 
       165 
     | 
    
         
            -
                def  
     | 
| 
       166 
     | 
    
         
            -
                   
     | 
| 
      
 113 
     | 
    
         
            +
                def collection_segment(context)
         
     | 
| 
      
 114 
     | 
    
         
            +
                  offsets = context.registers[:for] ||= {}
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                  from = if @from == :continue
         
     | 
| 
      
 117 
     | 
    
         
            +
                    offsets[@name].to_i
         
     | 
| 
      
 118 
     | 
    
         
            +
                  else
         
     | 
| 
      
 119 
     | 
    
         
            +
                    from_value = context.evaluate(@from)
         
     | 
| 
      
 120 
     | 
    
         
            +
                    if from_value.nil?
         
     | 
| 
      
 121 
     | 
    
         
            +
                      0
         
     | 
| 
      
 122 
     | 
    
         
            +
                    else
         
     | 
| 
      
 123 
     | 
    
         
            +
                      Utils.to_integer(from_value)
         
     | 
| 
      
 124 
     | 
    
         
            +
                    end
         
     | 
| 
      
 125 
     | 
    
         
            +
                  end
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                  collection = context.evaluate(@collection_name)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  collection = collection.to_a if collection.is_a?(Range)
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                  limit_value = context.evaluate(@limit)
         
     | 
| 
      
 131 
     | 
    
         
            +
                  to = if limit_value.nil?
         
     | 
| 
      
 132 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 133 
     | 
    
         
            +
                  else
         
     | 
| 
      
 134 
     | 
    
         
            +
                    Utils.to_integer(limit_value) + from
         
     | 
| 
      
 135 
     | 
    
         
            +
                  end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                  segment = Utils.slice_collection(collection, from, to)
         
     | 
| 
      
 138 
     | 
    
         
            +
                  segment.reverse! if @reversed
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                  offsets[@name] = from + segment.length
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                  segment
         
     | 
| 
      
 143 
     | 
    
         
            +
                end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                def render_segment(context, output, segment)
         
     | 
| 
      
 146 
     | 
    
         
            +
                  for_stack = context.registers[:for_stack] ||= []
         
     | 
| 
      
 147 
     | 
    
         
            +
                  length    = segment.length
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  context.stack do
         
     | 
| 
      
 150 
     | 
    
         
            +
                    loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                    for_stack.push(loop_vars)
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 155 
     | 
    
         
            +
                      context['forloop'] = loop_vars
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                      segment.each do |item|
         
     | 
| 
      
 158 
     | 
    
         
            +
                        context[@variable_name] = item
         
     | 
| 
      
 159 
     | 
    
         
            +
                        @for_block.render_to_output_buffer(context, output)
         
     | 
| 
      
 160 
     | 
    
         
            +
                        loop_vars.send(:increment!)
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                        # Handle any interrupts if they exist.
         
     | 
| 
      
 163 
     | 
    
         
            +
                        next unless context.interrupt?
         
     | 
| 
      
 164 
     | 
    
         
            +
                        interrupt = context.pop_interrupt
         
     | 
| 
      
 165 
     | 
    
         
            +
                        break if interrupt.is_a?(BreakInterrupt)
         
     | 
| 
      
 166 
     | 
    
         
            +
                        next if interrupt.is_a?(ContinueInterrupt)
         
     | 
| 
      
 167 
     | 
    
         
            +
                      end
         
     | 
| 
      
 168 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 169 
     | 
    
         
            +
                      for_stack.pop
         
     | 
| 
      
 170 
     | 
    
         
            +
                    end
         
     | 
| 
      
 171 
     | 
    
         
            +
                  end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                  output
         
     | 
| 
      
 174 
     | 
    
         
            +
                end
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                def set_attribute(key, expr)
         
     | 
| 
      
 177 
     | 
    
         
            +
                  case key
         
     | 
| 
      
 178 
     | 
    
         
            +
                  when 'offset'
         
     | 
| 
      
 179 
     | 
    
         
            +
                    @from = if expr == 'continue'
         
     | 
| 
      
 180 
     | 
    
         
            +
                      Usage.increment('for_offset_continue')
         
     | 
| 
      
 181 
     | 
    
         
            +
                      :continue
         
     | 
| 
      
 182 
     | 
    
         
            +
                    else
         
     | 
| 
      
 183 
     | 
    
         
            +
                      parse_expression(expr)
         
     | 
| 
      
 184 
     | 
    
         
            +
                    end
         
     | 
| 
      
 185 
     | 
    
         
            +
                  when 'limit'
         
     | 
| 
      
 186 
     | 
    
         
            +
                    @limit = parse_expression(expr)
         
     | 
| 
      
 187 
     | 
    
         
            +
                  end
         
     | 
| 
       167 
188 
     | 
    
         
             
                end
         
     | 
| 
       168 
189 
     | 
    
         | 
| 
       169 
     | 
    
         
            -
                def  
     | 
| 
       170 
     | 
    
         
            -
                   
     | 
| 
      
 190 
     | 
    
         
            +
                def render_else(context, output)
         
     | 
| 
      
 191 
     | 
    
         
            +
                  if @else_block
         
     | 
| 
      
 192 
     | 
    
         
            +
                    @else_block.render_to_output_buffer(context, output)
         
     | 
| 
      
 193 
     | 
    
         
            +
                  else
         
     | 
| 
      
 194 
     | 
    
         
            +
                    output
         
     | 
| 
      
 195 
     | 
    
         
            +
                  end
         
     | 
| 
      
 196 
     | 
    
         
            +
                end
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
                class ParseTreeVisitor < Liquid::ParseTreeVisitor
         
     | 
| 
      
 199 
     | 
    
         
            +
                  def children
         
     | 
| 
      
 200 
     | 
    
         
            +
                    (super + [@node.limit, @node.from, @node.collection_name]).compact
         
     | 
| 
      
 201 
     | 
    
         
            +
                  end
         
     | 
| 
       171 
202 
     | 
    
         
             
                end
         
     | 
| 
       172 
203 
     | 
    
         
             
              end
         
     | 
| 
       173 
204 
     | 
    
         | 
| 
       174 
     | 
    
         
            -
              Template.register_tag('for' 
     | 
| 
      
 205 
     | 
    
         
            +
              Template.register_tag('for', For)
         
     | 
| 
       175 
206 
     | 
    
         
             
            end
         
     |