liquid 3.0.6 → 4.0.3

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 (103) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +154 -58
  3. data/{MIT-LICENSE → LICENSE} +0 -0
  4. data/README.md +33 -0
  5. data/lib/liquid/block.rb +42 -125
  6. data/lib/liquid/block_body.rb +99 -79
  7. data/lib/liquid/condition.rb +52 -32
  8. data/lib/liquid/context.rb +57 -51
  9. data/lib/liquid/document.rb +19 -9
  10. data/lib/liquid/drop.rb +17 -16
  11. data/lib/liquid/errors.rb +20 -24
  12. data/lib/liquid/expression.rb +26 -10
  13. data/lib/liquid/extensions.rb +19 -7
  14. data/lib/liquid/file_system.rb +11 -11
  15. data/lib/liquid/forloop_drop.rb +42 -0
  16. data/lib/liquid/i18n.rb +6 -6
  17. data/lib/liquid/interrupts.rb +1 -2
  18. data/lib/liquid/lexer.rb +12 -8
  19. data/lib/liquid/locales/en.yml +6 -2
  20. data/lib/liquid/parse_context.rb +38 -0
  21. data/lib/liquid/parse_tree_visitor.rb +42 -0
  22. data/lib/liquid/parser_switching.rb +4 -4
  23. data/lib/liquid/profiler/hooks.rb +7 -7
  24. data/lib/liquid/profiler.rb +18 -19
  25. data/lib/liquid/range_lookup.rb +16 -1
  26. data/lib/liquid/resource_limits.rb +23 -0
  27. data/lib/liquid/standardfilters.rb +207 -61
  28. data/lib/liquid/strainer.rb +15 -8
  29. data/lib/liquid/tablerowloop_drop.rb +62 -0
  30. data/lib/liquid/tag.rb +9 -8
  31. data/lib/liquid/tags/assign.rb +25 -4
  32. data/lib/liquid/tags/break.rb +0 -3
  33. data/lib/liquid/tags/capture.rb +1 -1
  34. data/lib/liquid/tags/case.rb +27 -12
  35. data/lib/liquid/tags/comment.rb +2 -2
  36. data/lib/liquid/tags/cycle.rb +16 -8
  37. data/lib/liquid/tags/decrement.rb +1 -4
  38. data/lib/liquid/tags/for.rb +103 -75
  39. data/lib/liquid/tags/if.rb +60 -44
  40. data/lib/liquid/tags/ifchanged.rb +0 -2
  41. data/lib/liquid/tags/include.rb +71 -51
  42. data/lib/liquid/tags/raw.rb +32 -4
  43. data/lib/liquid/tags/table_row.rb +21 -31
  44. data/lib/liquid/tags/unless.rb +3 -4
  45. data/lib/liquid/template.rb +42 -54
  46. data/lib/liquid/tokenizer.rb +31 -0
  47. data/lib/liquid/truffle.rb +5 -0
  48. data/lib/liquid/utils.rb +52 -8
  49. data/lib/liquid/variable.rb +59 -46
  50. data/lib/liquid/variable_lookup.rb +14 -6
  51. data/lib/liquid/version.rb +2 -1
  52. data/lib/liquid.rb +10 -7
  53. data/test/integration/assign_test.rb +8 -8
  54. data/test/integration/blank_test.rb +14 -14
  55. data/test/integration/block_test.rb +12 -0
  56. data/test/integration/context_test.rb +2 -2
  57. data/test/integration/document_test.rb +19 -0
  58. data/test/integration/drop_test.rb +42 -40
  59. data/test/integration/error_handling_test.rb +96 -43
  60. data/test/integration/filter_test.rb +60 -20
  61. data/test/integration/hash_ordering_test.rb +9 -9
  62. data/test/integration/output_test.rb +26 -27
  63. data/test/integration/parse_tree_visitor_test.rb +247 -0
  64. data/test/integration/parsing_quirks_test.rb +19 -13
  65. data/test/integration/render_profiling_test.rb +20 -20
  66. data/test/integration/security_test.rb +23 -7
  67. data/test/integration/standard_filter_test.rb +426 -46
  68. data/test/integration/tags/break_tag_test.rb +1 -2
  69. data/test/integration/tags/continue_tag_test.rb +0 -1
  70. data/test/integration/tags/for_tag_test.rb +135 -100
  71. data/test/integration/tags/if_else_tag_test.rb +75 -77
  72. data/test/integration/tags/include_tag_test.rb +50 -31
  73. data/test/integration/tags/increment_tag_test.rb +10 -11
  74. data/test/integration/tags/raw_tag_test.rb +7 -1
  75. data/test/integration/tags/standard_tag_test.rb +121 -122
  76. data/test/integration/tags/statements_test.rb +3 -5
  77. data/test/integration/tags/table_row_test.rb +20 -19
  78. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  79. data/test/integration/template_test.rb +199 -49
  80. data/test/integration/trim_mode_test.rb +529 -0
  81. data/test/integration/variable_test.rb +27 -13
  82. data/test/test_helper.rb +33 -6
  83. data/test/truffle/truffle_test.rb +9 -0
  84. data/test/unit/block_unit_test.rb +8 -5
  85. data/test/unit/condition_unit_test.rb +94 -77
  86. data/test/unit/context_unit_test.rb +69 -72
  87. data/test/unit/file_system_unit_test.rb +3 -3
  88. data/test/unit/i18n_unit_test.rb +2 -2
  89. data/test/unit/lexer_unit_test.rb +12 -9
  90. data/test/unit/parser_unit_test.rb +2 -2
  91. data/test/unit/regexp_unit_test.rb +1 -1
  92. data/test/unit/strainer_unit_test.rb +96 -1
  93. data/test/unit/tag_unit_test.rb +7 -2
  94. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  95. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  96. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  97. data/test/unit/template_unit_test.rb +14 -5
  98. data/test/unit/tokenizer_unit_test.rb +24 -7
  99. data/test/unit/variable_unit_test.rb +60 -43
  100. metadata +62 -50
  101. data/lib/liquid/module_ex.rb +0 -62
  102. data/lib/liquid/token.rb +0 -18
  103. data/test/unit/module_ex_unit_test.rb +0 -87
data/lib/liquid/tag.rb CHANGED
@@ -1,26 +1,27 @@
1
1
  module Liquid
2
2
  class Tag
3
- attr_accessor :options, :line_number
4
- attr_reader :nodelist, :warnings
3
+ attr_reader :nodelist, :tag_name, :line_number, :parse_context
4
+ alias_method :options, :parse_context
5
5
  include ParserSwitching
6
6
 
7
7
  class << self
8
- def parse(tag_name, markup, tokens, options)
8
+ def parse(tag_name, markup, tokenizer, options)
9
9
  tag = new(tag_name, markup, options)
10
- tag.parse(tokens)
10
+ tag.parse(tokenizer)
11
11
  tag
12
12
  end
13
13
 
14
14
  private :new
15
15
  end
16
16
 
17
- def initialize(tag_name, markup, options)
17
+ def initialize(tag_name, markup, parse_context)
18
18
  @tag_name = tag_name
19
19
  @markup = markup
20
- @options = options
20
+ @parse_context = parse_context
21
+ @line_number = parse_context.line_number
21
22
  end
22
23
 
23
- def parse(tokens)
24
+ def parse(_tokens)
24
25
  end
25
26
 
26
27
  def raw
@@ -31,7 +32,7 @@ module Liquid
31
32
  self.class.name.downcase
32
33
  end
33
34
 
34
- def render(context)
35
+ def render(_context)
35
36
  ''.freeze
36
37
  end
37
38
 
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Assign sets a variable in your template.
4
3
  #
5
4
  # {% assign foo = 'monkey' %}
@@ -11,12 +10,13 @@ module Liquid
11
10
  class Assign < Tag
12
11
  Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
13
12
 
13
+ attr_reader :to, :from
14
+
14
15
  def initialize(tag_name, markup, options)
15
16
  super
16
17
  if markup =~ Syntax
17
18
  @to = $1
18
- @from = Variable.new($2,options)
19
- @from.line_number = line_number
19
+ @from = Variable.new($2, options)
20
20
  else
21
21
  raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
22
22
  end
@@ -25,13 +25,34 @@ module Liquid
25
25
  def render(context)
26
26
  val = @from.render(context)
27
27
  context.scopes.last[@to] = val
28
- context.increment_used_resources(:assign_score_current, val)
28
+ context.resource_limits.assign_score += assign_score_of(val)
29
29
  ''.freeze
30
30
  end
31
31
 
32
32
  def blank?
33
33
  true
34
34
  end
35
+
36
+ private
37
+
38
+ def assign_score_of(val)
39
+ if val.instance_of?(String)
40
+ val.length
41
+ elsif val.instance_of?(Array) || val.instance_of?(Hash)
42
+ sum = 1
43
+ # Uses #each to avoid extra allocations.
44
+ val.each { |child| sum += assign_score_of(child) }
45
+ sum
46
+ else
47
+ 1
48
+ end
49
+ end
50
+
51
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
52
+ def children
53
+ [@node.from]
54
+ end
55
+ end
35
56
  end
36
57
 
37
58
  Template.register_tag('assign'.freeze, Assign)
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Break tag to be used to break out of a for loop.
4
3
  #
5
4
  # == Basic Usage:
@@ -10,11 +9,9 @@ module Liquid
10
9
  # {% endfor %}
11
10
  #
12
11
  class Break < Tag
13
-
14
12
  def interrupt
15
13
  BreakInterrupt.new
16
14
  end
17
-
18
15
  end
19
16
 
20
17
  Template.register_tag('break'.freeze, Break)
@@ -25,7 +25,7 @@ module Liquid
25
25
  def render(context)
26
26
  output = super
27
27
  context.scopes.last[@to] = output
28
- context.increment_used_resources(:assign_score_current, output)
28
+ context.resource_limits.assign_score += output.length
29
29
  ''.freeze
30
30
  end
31
31
 
@@ -3,23 +3,31 @@ module Liquid
3
3
  Syntax = /(#{QuotedFragment})/o
4
4
  WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
5
5
 
6
+ attr_reader :blocks, :left
7
+
6
8
  def initialize(tag_name, markup, options)
7
9
  super
8
10
  @blocks = []
9
11
 
10
12
  if markup =~ Syntax
11
- @left = $1
13
+ @left = Expression.parse($1)
12
14
  else
13
15
  raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
14
16
  end
15
17
  end
16
18
 
19
+ def parse(tokens)
20
+ body = BlockBody.new
21
+ while parse_body(body, tokens)
22
+ body = @blocks.last.attachment
23
+ end
24
+ end
25
+
17
26
  def nodelist
18
- @blocks.flat_map(&:attachment)
27
+ @blocks.map(&:attachment)
19
28
  end
20
29
 
21
30
  def unknown_tag(tag, markup, tokens)
22
- @nodelist = []
23
31
  case tag
24
32
  when 'when'.freeze
25
33
  record_when_condition(markup)
@@ -37,10 +45,10 @@ module Liquid
37
45
  output = ''
38
46
  @blocks.each do |block|
39
47
  if block.else?
40
- return render_all(block.attachment, context) if execute_else_block
48
+ return block.attachment.render(context) if execute_else_block
41
49
  elsif block.evaluate(context)
42
50
  execute_else_block = false
43
- output << render_all(block.attachment, context)
51
+ output << block.attachment.render(context)
44
52
  end
45
53
  end
46
54
  output
@@ -50,29 +58,36 @@ module Liquid
50
58
  private
51
59
 
52
60
  def record_when_condition(markup)
61
+ body = BlockBody.new
62
+
53
63
  while markup
54
- # Create a new nodelist and assign it to the new block
55
- if not markup =~ WhenSyntax
64
+ unless markup =~ WhenSyntax
56
65
  raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
57
66
  end
58
67
 
59
68
  markup = $2
60
69
 
61
- block = Condition.new(@left, '=='.freeze, $1)
62
- block.attach(@nodelist)
63
- @blocks.push(block)
70
+ block = Condition.new(@left, '=='.freeze, Expression.parse($1))
71
+ block.attach(body)
72
+ @blocks << block
64
73
  end
65
74
  end
66
75
 
67
76
  def record_else_condition(markup)
68
- if not markup.strip.empty?
77
+ unless markup.strip.empty?
69
78
  raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
70
79
  end
71
80
 
72
81
  block = ElseCondition.new
73
- block.attach(@nodelist)
82
+ block.attach(BlockBody.new)
74
83
  @blocks << block
75
84
  end
85
+
86
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
87
+ def children
88
+ [@node.left] + @node.blocks
89
+ end
90
+ end
76
91
  end
77
92
 
78
93
  Template.register_tag('case'.freeze, Case)
@@ -1,10 +1,10 @@
1
1
  module Liquid
2
2
  class Comment < Block
3
- def render(context)
3
+ def render(_context)
4
4
  ''.freeze
5
5
  end
6
6
 
7
- def unknown_tag(tag, markup, tokens)
7
+ def unknown_tag(_tag, _markup, _tokens)
8
8
  end
9
9
 
10
10
  def blank?
@@ -15,29 +15,31 @@ module Liquid
15
15
  SimpleSyntax = /\A#{QuotedFragment}+/o
16
16
  NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
17
17
 
18
+ attr_reader :variables
19
+
18
20
  def initialize(tag_name, markup, options)
19
21
  super
20
22
  case markup
21
23
  when NamedSyntax
22
24
  @variables = variables_from_string($2)
23
- @name = $1
25
+ @name = Expression.parse($1)
24
26
  when SimpleSyntax
25
27
  @variables = variables_from_string(markup)
26
- @name = "'#{@variables.to_s}'"
28
+ @name = @variables.to_s
27
29
  else
28
30
  raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
29
31
  end
30
32
  end
31
33
 
32
34
  def render(context)
33
- context.registers[:cycle] ||= Hash.new(0)
35
+ context.registers[:cycle] ||= {}
34
36
 
35
37
  context.stack do
36
- key = context[@name]
37
- iteration = context.registers[:cycle][key]
38
- result = context[@variables[iteration]]
38
+ key = context.evaluate(@name)
39
+ iteration = context.registers[:cycle][key].to_i
40
+ result = context.evaluate(@variables[iteration])
39
41
  iteration += 1
40
- iteration = 0 if iteration >= @variables.size
42
+ iteration = 0 if iteration >= @variables.size
41
43
  context.registers[:cycle][key] = iteration
42
44
  result
43
45
  end
@@ -48,9 +50,15 @@ module Liquid
48
50
  def variables_from_string(markup)
49
51
  markup.split(',').collect do |var|
50
52
  var =~ /\s*(#{QuotedFragment})\s*/o
51
- $1 ? $1 : nil
53
+ $1 ? Expression.parse($1) : nil
52
54
  end.compact
53
55
  end
56
+
57
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
58
+ def children
59
+ Array(@node.variables)
60
+ end
61
+ end
54
62
  end
55
63
 
56
64
  Template.register_tag('cycle', Cycle)
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # decrement is used in a place where one needs to insert a counter
4
3
  # into a template, and needs the counter to survive across
5
4
  # multiple instantiations of the template.
@@ -26,12 +25,10 @@ module Liquid
26
25
 
27
26
  def render(context)
28
27
  value = context.environments.first[@variable] ||= 0
29
- value = value - 1
28
+ value -= 1
30
29
  context.environments.first[@variable] = value
31
30
  value.to_s
32
31
  end
33
-
34
- private
35
32
  end
36
33
 
37
34
  Template.register_tag('decrement'.freeze, Decrement)
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # "For" iterates over an array or collection.
4
3
  # Several useful variables are available to you within the loop.
5
4
  #
@@ -24,7 +23,7 @@ module Liquid
24
23
  # {{ item.name }}
25
24
  # {% end %}
26
25
  #
27
- # To reverse the for loop simply use {% for item in collection reversed %}
26
+ # To reverse the for loop simply use {% for item in collection reversed %} (note that the flag's spelling is different to the filter `reverse`)
28
27
  #
29
28
  # == Available variables:
30
29
  #
@@ -42,85 +41,43 @@ module Liquid
42
41
  # where 0 is the last item.
43
42
  # forloop.first:: Returns true if the item is the first item.
44
43
  # forloop.last:: Returns true if the item is the last item.
44
+ # forloop.parentloop:: Provides access to the parent loop, if present.
45
45
  #
46
46
  class For < Block
47
47
  Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
48
48
 
49
+ attr_reader :collection_name, :variable_name, :limit, :from
50
+
49
51
  def initialize(tag_name, markup, options)
50
52
  super
53
+ @from = @limit = nil
51
54
  parse_with_selected_parser(markup)
52
- @nodelist = @for_block = []
55
+ @for_block = BlockBody.new
56
+ @else_block = nil
57
+ end
58
+
59
+ def parse(tokens)
60
+ return unless parse_body(@for_block, tokens)
61
+ parse_body(@else_block, tokens)
53
62
  end
54
63
 
55
64
  def nodelist
56
- if @else_block
57
- @for_block + @else_block
58
- else
59
- @for_block
60
- end
65
+ @else_block ? [@for_block, @else_block] : [@for_block]
61
66
  end
62
67
 
63
68
  def unknown_tag(tag, markup, tokens)
64
69
  return super unless tag == 'else'.freeze
65
- @nodelist = @else_block = []
70
+ @else_block = BlockBody.new
66
71
  end
67
72
 
68
73
  def render(context)
69
- context.registers[:for] ||= Hash.new(0)
70
-
71
- collection = context[@collection_name]
72
- collection = collection.to_a if collection.is_a?(Range)
74
+ segment = collection_segment(context)
73
75
 
74
- # Maintains Ruby 1.8.7 String#each behaviour on 1.9
75
- return render_else(context) unless iterable?(collection)
76
-
77
- from = if @attributes['offset'.freeze] == 'continue'.freeze
78
- context.registers[:for][@name].to_i
76
+ if segment.empty?
77
+ render_else(context)
79
78
  else
80
- context[@attributes['offset'.freeze]].to_i
81
- end
82
-
83
- limit = context[@attributes['limit'.freeze]]
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
79
+ render_segment(context, segment)
122
80
  end
123
- result
124
81
  end
125
82
 
126
83
  protected
@@ -128,12 +85,12 @@ module Liquid
128
85
  def lax_parse(markup)
129
86
  if markup =~ Syntax
130
87
  @variable_name = $1
131
- @collection_name = $2
132
- @name = "#{$1}-#{$2}"
133
- @reversed = $3
134
- @attributes = {}
88
+ collection_name = $2
89
+ @reversed = !!$3
90
+ @name = "#{@variable_name}-#{collection_name}"
91
+ @collection_name = Expression.parse(collection_name)
135
92
  markup.scan(TagAttributes) do |key, value|
136
- @attributes[key] = value
93
+ set_attribute(key, value)
137
94
  end
138
95
  else
139
96
  raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
@@ -143,31 +100,102 @@ module Liquid
143
100
  def strict_parse(markup)
144
101
  p = Parser.new(markup)
145
102
  @variable_name = p.consume(:id)
146
- raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
147
- @collection_name = p.expression
148
- @name = "#{@variable_name}-#{@collection_name}"
103
+ raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
104
+ collection_name = p.expression
105
+ @name = "#{@variable_name}-#{collection_name}"
106
+ @collection_name = Expression.parse(collection_name)
149
107
  @reversed = p.id?('reversed'.freeze)
150
108
 
151
- @attributes = {}
152
109
  while p.look(:id) && p.look(:colon, 1)
153
110
  unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
154
111
  raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
155
112
  end
156
113
  p.consume
157
- val = p.expression
158
- @attributes[attribute] = val
114
+ set_attribute(attribute, p.expression)
159
115
  end
160
116
  p.consume(:end_of_string)
161
117
  end
162
118
 
163
119
  private
164
120
 
121
+ def collection_segment(context)
122
+ offsets = context.registers[:for] ||= {}
123
+
124
+ from = if @from == :continue
125
+ offsets[@name].to_i
126
+ else
127
+ context.evaluate(@from).to_i
128
+ end
129
+
130
+ collection = context.evaluate(@collection_name)
131
+ collection = collection.to_a if collection.is_a?(Range)
132
+
133
+ limit = context.evaluate(@limit)
134
+ to = limit ? limit.to_i + from : nil
135
+
136
+ segment = Utils.slice_collection(collection, from, to)
137
+ segment.reverse! if @reversed
138
+
139
+ offsets[@name] = from + segment.length
140
+
141
+ segment
142
+ end
143
+
144
+ def render_segment(context, segment)
145
+ for_stack = context.registers[:for_stack] ||= []
146
+ length = segment.length
147
+
148
+ result = ''
149
+
150
+ context.stack do
151
+ loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
152
+
153
+ for_stack.push(loop_vars)
154
+
155
+ begin
156
+ context['forloop'.freeze] = loop_vars
157
+
158
+ segment.each do |item|
159
+ context[@variable_name] = item
160
+ result << @for_block.render(context)
161
+ loop_vars.send(:increment!)
162
+
163
+ # Handle any interrupts if they exist.
164
+ if context.interrupt?
165
+ interrupt = context.pop_interrupt
166
+ break if interrupt.is_a? BreakInterrupt
167
+ next if interrupt.is_a? ContinueInterrupt
168
+ end
169
+ end
170
+ ensure
171
+ for_stack.pop
172
+ end
173
+ end
174
+
175
+ result
176
+ end
177
+
178
+ def set_attribute(key, expr)
179
+ case key
180
+ when 'offset'.freeze
181
+ @from = if expr == 'continue'.freeze
182
+ :continue
183
+ else
184
+ Expression.parse(expr)
185
+ end
186
+ when 'limit'.freeze
187
+ @limit = Expression.parse(expr)
188
+ end
189
+ end
190
+
165
191
  def render_else(context)
166
- return @else_block ? [render_all(@else_block, context)] : ''.freeze
192
+ @else_block ? @else_block.render(context) : ''.freeze
167
193
  end
168
194
 
169
- def iterable?(collection)
170
- collection.respond_to?(:each) || Utils.non_blank_string?(collection)
195
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
196
+ def children
197
+ (super + [@node.limit, @node.from, @node.collection_name]).compact
198
+ end
171
199
  end
172
200
  end
173
201