liquid 2.6.3 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
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)