liquid 2.6.3 → 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.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +272 -26
  3. data/README.md +67 -3
  4. data/lib/liquid/block.rb +62 -94
  5. data/lib/liquid/block_body.rb +255 -0
  6. data/lib/liquid/condition.rb +96 -38
  7. data/lib/liquid/context.rb +172 -154
  8. data/lib/liquid/document.rb +57 -9
  9. data/lib/liquid/drop.rb +33 -14
  10. data/lib/liquid/errors.rb +56 -10
  11. data/lib/liquid/expression.rb +45 -0
  12. data/lib/liquid/extensions.rb +21 -7
  13. data/lib/liquid/file_system.rb +27 -14
  14. data/lib/liquid/forloop_drop.rb +92 -0
  15. data/lib/liquid/i18n.rb +41 -0
  16. data/lib/liquid/interrupts.rb +3 -2
  17. data/lib/liquid/lexer.rb +62 -0
  18. data/lib/liquid/locales/en.yml +29 -0
  19. data/lib/liquid/parse_context.rb +54 -0
  20. data/lib/liquid/parse_tree_visitor.rb +42 -0
  21. data/lib/liquid/parser.rb +102 -0
  22. data/lib/liquid/parser_switching.rb +45 -0
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +35 -0
  25. data/lib/liquid/profiler.rb +139 -0
  26. data/lib/liquid/range_lookup.rb +47 -0
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +62 -0
  29. data/lib/liquid/standardfilters.rb +789 -118
  30. data/lib/liquid/strainer_factory.rb +41 -0
  31. data/lib/liquid/strainer_template.rb +62 -0
  32. data/lib/liquid/tablerowloop_drop.rb +121 -0
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +49 -10
  36. data/lib/liquid/tags/assign.rb +61 -19
  37. data/lib/liquid/tags/break.rb +14 -4
  38. data/lib/liquid/tags/capture.rb +29 -21
  39. data/lib/liquid/tags/case.rb +80 -31
  40. data/lib/liquid/tags/comment.rb +24 -2
  41. data/lib/liquid/tags/continue.rb +14 -13
  42. data/lib/liquid/tags/cycle.rb +50 -32
  43. data/lib/liquid/tags/decrement.rb +24 -26
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +164 -100
  46. data/lib/liquid/tags/if.rb +105 -44
  47. data/lib/liquid/tags/ifchanged.rb +10 -11
  48. data/lib/liquid/tags/include.rb +85 -65
  49. data/lib/liquid/tags/increment.rb +24 -22
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +50 -11
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +88 -0
  54. data/lib/liquid/tags/unless.rb +37 -21
  55. data/lib/liquid/template.rb +124 -46
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +39 -0
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +68 -5
  60. data/lib/liquid/variable.rb +128 -32
  61. data/lib/liquid/variable_lookup.rb +96 -0
  62. data/lib/liquid/version.rb +3 -1
  63. data/lib/liquid.rb +36 -13
  64. metadata +69 -77
  65. data/lib/extras/liquid_view.rb +0 -51
  66. data/lib/liquid/htmltags.rb +0 -73
  67. data/lib/liquid/module_ex.rb +0 -62
  68. data/lib/liquid/strainer.rb +0 -53
  69. data/test/liquid/assign_test.rb +0 -21
  70. data/test/liquid/block_test.rb +0 -58
  71. data/test/liquid/capture_test.rb +0 -40
  72. data/test/liquid/condition_test.rb +0 -127
  73. data/test/liquid/context_test.rb +0 -478
  74. data/test/liquid/drop_test.rb +0 -180
  75. data/test/liquid/error_handling_test.rb +0 -81
  76. data/test/liquid/file_system_test.rb +0 -29
  77. data/test/liquid/filter_test.rb +0 -125
  78. data/test/liquid/hash_ordering_test.rb +0 -25
  79. data/test/liquid/module_ex_test.rb +0 -87
  80. data/test/liquid/output_test.rb +0 -116
  81. data/test/liquid/parsing_quirks_test.rb +0 -52
  82. data/test/liquid/regexp_test.rb +0 -44
  83. data/test/liquid/security_test.rb +0 -64
  84. data/test/liquid/standard_filter_test.rb +0 -263
  85. data/test/liquid/strainer_test.rb +0 -52
  86. data/test/liquid/tags/break_tag_test.rb +0 -16
  87. data/test/liquid/tags/continue_tag_test.rb +0 -16
  88. data/test/liquid/tags/for_tag_test.rb +0 -297
  89. data/test/liquid/tags/html_tag_test.rb +0 -63
  90. data/test/liquid/tags/if_else_tag_test.rb +0 -166
  91. data/test/liquid/tags/include_tag_test.rb +0 -166
  92. data/test/liquid/tags/increment_tag_test.rb +0 -24
  93. data/test/liquid/tags/raw_tag_test.rb +0 -24
  94. data/test/liquid/tags/standard_tag_test.rb +0 -295
  95. data/test/liquid/tags/statements_test.rb +0 -134
  96. data/test/liquid/tags/unless_else_tag_test.rb +0 -26
  97. data/test/liquid/template_test.rb +0 -146
  98. data/test/liquid/variable_test.rb +0 -186
  99. data/test/test_helper.rb +0 -29
  100. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -1,7 +1,29 @@
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 render(context)
4
- ''
18
+ def render_to_output_buffer(_context, output)
19
+ output
20
+ end
21
+
22
+ def unknown_tag(_tag, _markup, _tokens)
23
+ end
24
+
25
+ def blank?
26
+ true
5
27
  end
6
28
  end
7
29
 
@@ -1,20 +1,21 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
- # Continue tag to be used to break out of a for loop.
4
- #
5
- # == Basic Usage:
6
- # {% for item in collection %}
7
- # {% if item.condition %}
8
- # {% continue %}
9
- # {% endif %}
10
- # {% endfor %}
11
- #
3
+ module Liquid
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 %}
12
12
  class Continue < Tag
13
+ INTERRUPT = ContinueInterrupt.new.freeze
13
14
 
14
- def interrupt
15
- ContinueInterrupt.new
15
+ def render_to_output_buffer(context, output)
16
+ context.push_interrupt(INTERRUPT)
17
+ output
16
18
  end
17
-
18
19
  end
19
20
 
20
21
  Template.register_tag('continue', Continue)
@@ -1,47 +1,60 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
- # Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
4
- #
5
- # {% for item in items %}
6
- # <div class="{% cycle 'red', 'green', 'blue' %}"> {{ item }} </div>
7
- # {% end %}
8
- #
9
- # <div class="red"> Item one </div>
10
- # <div class="green"> Item two </div>
11
- # <div class="blue"> Item three </div>
12
- # <div class="red"> Item four </div>
13
- # <div class="green"> Item five</div>
3
+ module Liquid
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.
14
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, ... %}
15
17
  class Cycle < Tag
16
- SimpleSyntax = /^#{QuotedFragment}+/o
17
- NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
18
+ SimpleSyntax = /\A#{QuotedFragment}+/o
19
+ NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
18
20
 
19
- def initialize(tag_name, markup, tokens)
21
+ attr_reader :variables
22
+
23
+ def initialize(tag_name, markup, options)
24
+ super
20
25
  case markup
21
26
  when NamedSyntax
22
- @variables = variables_from_string($2)
23
- @name = $1
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 = "'#{@variables.to_s}'"
31
+ @name = @variables.to_s
27
32
  else
28
- raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
33
+ raise SyntaxError, options[:locale].t("errors.syntax.cycle")
29
34
  end
30
- super
31
35
  end
32
36
 
33
- def render(context)
34
- context.registers[:cycle] ||= Hash.new(0)
35
-
36
- context.stack do
37
- key = context[@name]
38
- iteration = context.registers[:cycle][key]
39
- result = context[@variables[iteration]]
40
- iteration += 1
41
- iteration = 0 if iteration >= @variables.size
42
- context.registers[:cycle][key] = iteration
43
- result
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
44
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
45
58
  end
46
59
 
47
60
  private
@@ -49,10 +62,15 @@ module Liquid
49
62
  def variables_from_string(markup)
50
63
  markup.split(',').collect do |var|
51
64
  var =~ /\s*(#{QuotedFragment})\s*/o
52
- $1 ? $1 : nil
65
+ Regexp.last_match(1) ? parse_expression(Regexp.last_match(1)) : nil
53
66
  end.compact
54
67
  end
55
68
 
69
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
70
+ def children
71
+ Array(@node.variables)
72
+ end
73
+ end
56
74
  end
57
75
 
58
76
  Template.register_tag('cycle', Cycle)
@@ -1,38 +1,36 @@
1
- module Liquid
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
- # Hello: {% decrement variable %}
14
- #
15
- # gives you:
16
- #
17
- # Hello: -1
18
- # Hello: -2
19
- # Hello: -3
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
- def initialize(tag_name, markup, tokens)
23
- @variable = markup.strip
24
-
22
+ def initialize(tag_name, markup, options)
25
23
  super
24
+ @variable = markup.strip
26
25
  end
27
26
 
28
- def render(context)
27
+ def render_to_output_buffer(context, output)
29
28
  value = context.environments.first[@variable] ||= 0
30
- value = value - 1
29
+ value -= 1
31
30
  context.environments.first[@variable] = value
32
- value.to_s
31
+ output << value.to_s
32
+ output
33
33
  end
34
-
35
- private
36
34
  end
37
35
 
38
36
  Template.register_tag('decrement', Decrement)
@@ -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
@@ -1,141 +1,205 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
- # "For" iterates over an array or collection.
4
- # Several useful variables are available to you within the loop.
5
- #
6
- # == Basic usage:
7
- # {% for item in collection %}
8
- # {{ forloop.index }}: {{ item.name }}
9
- # {% endfor %}
10
- #
11
- # == Advanced usage:
12
- # {% for item in collection %}
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
- Syntax = /\A(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
28
+ Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
48
29
 
49
- def initialize(tag_name, markup, tokens)
50
- if markup =~ Syntax
51
- @variable_name = $1
52
- @collection_name = $2
53
- @name = "#{$1}-#{$2}"
54
- @reversed = $3
55
- @attributes = {}
56
- markup.scan(TagAttributes) do |key, value|
57
- @attributes[key] = value
58
- end
59
- else
60
- raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
61
- end
30
+ attr_reader :collection_name, :variable_name, :limit, :from
62
31
 
63
- @nodelist = @for_block = []
32
+ def initialize(tag_name, markup, options)
64
33
  super
34
+ @from = @limit = nil
35
+ parse_with_selected_parser(markup)
36
+ @for_block = new_body
37
+ @else_block = nil
38
+ end
39
+
40
+ def parse(tokens)
41
+ if parse_body(@for_block, tokens)
42
+ parse_body(@else_block, tokens)
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
50
+ end
51
+
52
+ def nodelist
53
+ @else_block ? [@for_block, @else_block] : [@for_block]
65
54
  end
66
55
 
67
56
  def unknown_tag(tag, markup, tokens)
68
57
  return super unless tag == 'else'
69
- @nodelist = @else_block = []
58
+ @else_block = new_body
70
59
  end
71
60
 
72
- def render(context)
73
- context.registers[:for] ||= Hash.new(0)
61
+ def render_to_output_buffer(context, output)
62
+ segment = collection_segment(context)
74
63
 
75
- collection = context[@collection_name]
76
- collection = collection.to_a if collection.is_a?(Range)
64
+ if segment.empty?
65
+ render_else(context, output)
66
+ else
67
+ render_segment(context, output, segment)
68
+ end
69
+
70
+ output
71
+ end
77
72
 
78
- # Maintains Ruby 1.8.7 String#each behaviour on 1.9
79
- return render_else(context) unless iterable?(collection)
73
+ protected
80
74
 
81
- from = if @attributes['offset'] == 'continue'
82
- context.registers[:for][@name].to_i
75
+ def lax_parse(markup)
76
+ if markup =~ Syntax
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)
82
+ markup.scan(TagAttributes) do |key, value|
83
+ set_attribute(key, value)
84
+ end
83
85
  else
84
- context[@attributes['offset']].to_i
86
+ raise SyntaxError, options[:locale].t("errors.syntax.for")
85
87
  end
88
+ end
86
89
 
87
- limit = context[@attributes['limit']]
88
- to = limit ? limit.to_i + from : nil
90
+ def strict_parse(markup)
91
+ p = Parser.new(markup)
92
+ @variable_name = p.consume(:id)
93
+ raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
89
94
 
95
+ collection_name = p.expression
96
+ @collection_name = parse_expression(collection_name)
90
97
 
91
- segment = Utils.slice_collection_using_each(collection, from, to)
98
+ @name = "#{@variable_name}-#{collection_name}"
99
+ @reversed = p.id?('reversed')
92
100
 
93
- return render_else(context) if segment.empty?
101
+ while p.look(:id) && p.look(:colon, 1)
102
+ unless (attribute = p.id?('limit') || p.id?('offset'))
103
+ raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
104
+ end
105
+ p.consume
106
+ set_attribute(attribute, p.expression)
107
+ end
108
+ p.consume(:end_of_string)
109
+ end
110
+
111
+ private
112
+
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
94
136
 
137
+ segment = Utils.slice_collection(collection, from, to)
95
138
  segment.reverse! if @reversed
96
139
 
97
- result = ''
140
+ offsets[@name] = from + segment.length
98
141
 
99
- length = segment.length
142
+ segment
143
+ end
100
144
 
101
- # Store our progress through the collection for the continue flag
102
- context.registers[:for][@name] = from + segment.length
145
+ def render_segment(context, output, segment)
146
+ for_stack = context.registers[:for_stack] ||= []
147
+ length = segment.length
103
148
 
104
149
  context.stack do
105
- segment.each_with_index do |item, index|
106
- context[@variable_name] = item
107
- context['forloop'] = {
108
- 'name' => @name,
109
- 'length' => length,
110
- 'index' => index + 1,
111
- 'index0' => index,
112
- 'rindex' => length - index,
113
- 'rindex0' => length - index - 1,
114
- 'first' => (index == 0),
115
- 'last' => (index == length - 1) }
116
-
117
- result << render_all(@for_block, context)
118
-
119
- # Handle any interrupts if they exist.
120
- if context.has_interrupt?
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?
121
164
  interrupt = context.pop_interrupt
122
- break if interrupt.is_a? BreakInterrupt
123
- next if interrupt.is_a? ContinueInterrupt
165
+ break if interrupt.is_a?(BreakInterrupt)
166
+ next if interrupt.is_a?(ContinueInterrupt)
124
167
  end
168
+ ensure
169
+ for_stack.pop
125
170
  end
126
171
  end
127
- result
172
+
173
+ output
128
174
  end
129
175
 
130
- private
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
188
+ end
131
189
 
132
- def render_else(context)
133
- return @else_block ? [render_all(@else_block, context)] : ''
190
+ def render_else(context, output)
191
+ if @else_block
192
+ @else_block.render_to_output_buffer(context, output)
193
+ else
194
+ output
134
195
  end
196
+ end
135
197
 
136
- def iterable?(collection)
137
- collection.respond_to?(:each) || Utils.non_blank_string?(collection)
198
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
199
+ def children
200
+ (super + [@node.limit, @node.from, @node.collection_name]).compact
138
201
  end
202
+ end
139
203
  end
140
204
 
141
205
  Template.register_tag('for', For)