liquid 3.0.6 → 4.0.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +98 -58
  3. data/README.md +31 -0
  4. data/lib/liquid/block.rb +31 -124
  5. data/lib/liquid/block_body.rb +75 -59
  6. data/lib/liquid/condition.rb +23 -22
  7. data/lib/liquid/context.rb +50 -46
  8. data/lib/liquid/document.rb +19 -9
  9. data/lib/liquid/drop.rb +17 -16
  10. data/lib/liquid/errors.rb +20 -24
  11. data/lib/liquid/expression.rb +15 -3
  12. data/lib/liquid/extensions.rb +13 -7
  13. data/lib/liquid/file_system.rb +11 -11
  14. data/lib/liquid/forloop_drop.rb +42 -0
  15. data/lib/liquid/i18n.rb +5 -5
  16. data/lib/liquid/interrupts.rb +1 -2
  17. data/lib/liquid/lexer.rb +6 -4
  18. data/lib/liquid/locales/en.yml +5 -1
  19. data/lib/liquid/parse_context.rb +37 -0
  20. data/lib/liquid/parser_switching.rb +4 -4
  21. data/lib/liquid/profiler/hooks.rb +7 -7
  22. data/lib/liquid/profiler.rb +18 -19
  23. data/lib/liquid/range_lookup.rb +16 -1
  24. data/lib/liquid/resource_limits.rb +23 -0
  25. data/lib/liquid/standardfilters.rb +121 -61
  26. data/lib/liquid/strainer.rb +14 -7
  27. data/lib/liquid/tablerowloop_drop.rb +62 -0
  28. data/lib/liquid/tag.rb +9 -8
  29. data/lib/liquid/tags/assign.rb +17 -4
  30. data/lib/liquid/tags/break.rb +0 -3
  31. data/lib/liquid/tags/capture.rb +1 -1
  32. data/lib/liquid/tags/case.rb +19 -12
  33. data/lib/liquid/tags/comment.rb +2 -2
  34. data/lib/liquid/tags/cycle.rb +6 -6
  35. data/lib/liquid/tags/decrement.rb +1 -4
  36. data/lib/liquid/tags/for.rb +95 -75
  37. data/lib/liquid/tags/if.rb +49 -44
  38. data/lib/liquid/tags/ifchanged.rb +0 -2
  39. data/lib/liquid/tags/include.rb +61 -52
  40. data/lib/liquid/tags/raw.rb +32 -4
  41. data/lib/liquid/tags/table_row.rb +12 -30
  42. data/lib/liquid/tags/unless.rb +3 -4
  43. data/lib/liquid/template.rb +42 -54
  44. data/lib/liquid/tokenizer.rb +31 -0
  45. data/lib/liquid/utils.rb +52 -8
  46. data/lib/liquid/variable.rb +46 -45
  47. data/lib/liquid/variable_lookup.rb +7 -5
  48. data/lib/liquid/version.rb +1 -1
  49. data/lib/liquid.rb +9 -7
  50. data/test/integration/assign_test.rb +8 -8
  51. data/test/integration/blank_test.rb +14 -14
  52. data/test/integration/context_test.rb +2 -2
  53. data/test/integration/document_test.rb +19 -0
  54. data/test/integration/drop_test.rb +42 -40
  55. data/test/integration/error_handling_test.rb +99 -46
  56. data/test/integration/filter_test.rb +60 -20
  57. data/test/integration/hash_ordering_test.rb +9 -9
  58. data/test/integration/output_test.rb +26 -27
  59. data/test/integration/parsing_quirks_test.rb +15 -13
  60. data/test/integration/render_profiling_test.rb +20 -20
  61. data/test/integration/security_test.rb +9 -7
  62. data/test/integration/standard_filter_test.rb +179 -40
  63. data/test/integration/tags/break_tag_test.rb +1 -2
  64. data/test/integration/tags/continue_tag_test.rb +0 -1
  65. data/test/integration/tags/for_tag_test.rb +133 -98
  66. data/test/integration/tags/if_else_tag_test.rb +75 -77
  67. data/test/integration/tags/include_tag_test.rb +34 -30
  68. data/test/integration/tags/increment_tag_test.rb +10 -11
  69. data/test/integration/tags/raw_tag_test.rb +7 -1
  70. data/test/integration/tags/standard_tag_test.rb +121 -122
  71. data/test/integration/tags/statements_test.rb +3 -5
  72. data/test/integration/tags/table_row_test.rb +20 -19
  73. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  74. data/test/integration/template_test.rb +190 -49
  75. data/test/integration/trim_mode_test.rb +525 -0
  76. data/test/integration/variable_test.rb +23 -13
  77. data/test/test_helper.rb +33 -5
  78. data/test/unit/block_unit_test.rb +8 -5
  79. data/test/unit/condition_unit_test.rb +86 -77
  80. data/test/unit/context_unit_test.rb +48 -57
  81. data/test/unit/file_system_unit_test.rb +3 -3
  82. data/test/unit/i18n_unit_test.rb +2 -2
  83. data/test/unit/lexer_unit_test.rb +11 -8
  84. data/test/unit/parser_unit_test.rb +2 -2
  85. data/test/unit/regexp_unit_test.rb +1 -1
  86. data/test/unit/strainer_unit_test.rb +80 -1
  87. data/test/unit/tag_unit_test.rb +7 -2
  88. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  89. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  90. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  91. data/test/unit/template_unit_test.rb +14 -5
  92. data/test/unit/tokenizer_unit_test.rb +24 -7
  93. data/test/unit/variable_unit_test.rb +60 -43
  94. metadata +19 -14
  95. data/lib/liquid/module_ex.rb +0 -62
  96. data/lib/liquid/token.rb +0 -18
  97. data/test/unit/module_ex_unit_test.rb +0 -87
  98. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -1,7 +1,6 @@
1
1
  require 'set'
2
2
 
3
3
  module Liquid
4
-
5
4
  # Strainer is the parent class for the filters system.
6
5
  # New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
7
6
  #
@@ -22,19 +21,25 @@ module Liquid
22
21
  @context = context
23
22
  end
24
23
 
25
- def self.filter_methods
26
- @filter_methods
24
+ class << self
25
+ attr_reader :filter_methods
27
26
  end
28
27
 
29
28
  def self.add_filter(filter)
30
- raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module)
29
+ raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
31
30
  unless self.class.include?(filter)
32
- self.send(:include, filter)
33
- @filter_methods.merge(filter.public_instance_methods.map(&:to_s))
31
+ invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
32
+ if invokable_non_public_methods.any?
33
+ raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
34
+ else
35
+ send(:include, filter)
36
+ @filter_methods.merge(filter.public_instance_methods.map(&:to_s))
37
+ end
34
38
  end
35
39
  end
36
40
 
37
41
  def self.global_filter(filter)
42
+ @@strainer_class_cache.clear
38
43
  @@global_strainer.add_filter(filter)
39
44
  end
40
45
 
@@ -49,11 +54,13 @@ module Liquid
49
54
  def invoke(method, *args)
50
55
  if self.class.invokable?(method)
51
56
  send(method, *args)
57
+ elsif @context && @context.strict_filters
58
+ raise Liquid::UndefinedFilter, "undefined filter #{method}"
52
59
  else
53
60
  args.first
54
61
  end
55
62
  rescue ::ArgumentError => e
56
- raise Liquid::ArgumentError.new(e.message)
63
+ raise Liquid::ArgumentError, e.message, e.backtrace
57
64
  end
58
65
  end
59
66
  end
@@ -0,0 +1,62 @@
1
+ module Liquid
2
+ class TablerowloopDrop < Drop
3
+ def initialize(length, cols)
4
+ @length = length
5
+ @row = 1
6
+ @col = 1
7
+ @cols = cols
8
+ @index = 0
9
+ end
10
+
11
+ attr_reader :length, :col, :row
12
+
13
+ def index
14
+ @index + 1
15
+ end
16
+
17
+ def index0
18
+ @index
19
+ end
20
+
21
+ def col0
22
+ @col - 1
23
+ end
24
+
25
+ def rindex
26
+ @length - @index
27
+ end
28
+
29
+ def rindex0
30
+ @length - @index - 1
31
+ end
32
+
33
+ def first
34
+ @index == 0
35
+ end
36
+
37
+ def last
38
+ @index == @length - 1
39
+ end
40
+
41
+ def col_first
42
+ @col == 1
43
+ end
44
+
45
+ def col_last
46
+ @col == @cols
47
+ end
48
+
49
+ protected
50
+
51
+ def increment!
52
+ @index += 1
53
+
54
+ if @col == @cols
55
+ @col = 1
56
+ @row += 1
57
+ else
58
+ @col += 1
59
+ end
60
+ end
61
+ end
62
+ end
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' %}
@@ -15,8 +14,7 @@ module Liquid
15
14
  super
16
15
  if markup =~ Syntax
17
16
  @to = $1
18
- @from = Variable.new($2,options)
19
- @from.line_number = line_number
17
+ @from = Variable.new($2, options)
20
18
  else
21
19
  raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
22
20
  end
@@ -25,13 +23,28 @@ module Liquid
25
23
  def render(context)
26
24
  val = @from.render(context)
27
25
  context.scopes.last[@to] = val
28
- context.increment_used_resources(:assign_score_current, val)
26
+ context.resource_limits.assign_score += assign_score_of(val)
29
27
  ''.freeze
30
28
  end
31
29
 
32
30
  def blank?
33
31
  true
34
32
  end
33
+
34
+ private
35
+
36
+ def assign_score_of(val)
37
+ if val.instance_of?(String)
38
+ val.length
39
+ elsif val.instance_of?(Array) || val.instance_of?(Hash)
40
+ sum = 1
41
+ # Uses #each to avoid extra allocations.
42
+ val.each { |child| sum += assign_score_of(child) }
43
+ sum
44
+ else
45
+ 1
46
+ end
47
+ end
35
48
  end
36
49
 
37
50
  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
 
@@ -8,18 +8,24 @@ module Liquid
8
8
  @blocks = []
9
9
 
10
10
  if markup =~ Syntax
11
- @left = $1
11
+ @left = Expression.parse($1)
12
12
  else
13
13
  raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
14
14
  end
15
15
  end
16
16
 
17
+ def parse(tokens)
18
+ body = BlockBody.new
19
+ while parse_body(body, tokens)
20
+ body = @blocks.last.attachment
21
+ end
22
+ end
23
+
17
24
  def nodelist
18
- @blocks.flat_map(&:attachment)
25
+ @blocks.map(&:attachment)
19
26
  end
20
27
 
21
28
  def unknown_tag(tag, markup, tokens)
22
- @nodelist = []
23
29
  case tag
24
30
  when 'when'.freeze
25
31
  record_when_condition(markup)
@@ -37,10 +43,10 @@ module Liquid
37
43
  output = ''
38
44
  @blocks.each do |block|
39
45
  if block.else?
40
- return render_all(block.attachment, context) if execute_else_block
46
+ return block.attachment.render(context) if execute_else_block
41
47
  elsif block.evaluate(context)
42
48
  execute_else_block = false
43
- output << render_all(block.attachment, context)
49
+ output << block.attachment.render(context)
44
50
  end
45
51
  end
46
52
  output
@@ -50,27 +56,28 @@ module Liquid
50
56
  private
51
57
 
52
58
  def record_when_condition(markup)
59
+ body = BlockBody.new
60
+
53
61
  while markup
54
- # Create a new nodelist and assign it to the new block
55
- if not markup =~ WhenSyntax
62
+ unless markup =~ WhenSyntax
56
63
  raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
57
64
  end
58
65
 
59
66
  markup = $2
60
67
 
61
- block = Condition.new(@left, '=='.freeze, $1)
62
- block.attach(@nodelist)
63
- @blocks.push(block)
68
+ block = Condition.new(@left, '=='.freeze, Expression.parse($1))
69
+ block.attach(body)
70
+ @blocks << block
64
71
  end
65
72
  end
66
73
 
67
74
  def record_else_condition(markup)
68
- if not markup.strip.empty?
75
+ unless markup.strip.empty?
69
76
  raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
70
77
  end
71
78
 
72
79
  block = ElseCondition.new
73
- block.attach(@nodelist)
80
+ block.attach(BlockBody.new)
74
81
  @blocks << block
75
82
  end
76
83
  end
@@ -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?
@@ -20,10 +20,10 @@ module Liquid
20
20
  case markup
21
21
  when NamedSyntax
22
22
  @variables = variables_from_string($2)
23
- @name = $1
23
+ @name = Expression.parse($1)
24
24
  when SimpleSyntax
25
25
  @variables = variables_from_string(markup)
26
- @name = "'#{@variables.to_s}'"
26
+ @name = @variables.to_s
27
27
  else
28
28
  raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
29
29
  end
@@ -33,11 +33,11 @@ module Liquid
33
33
  context.registers[:cycle] ||= Hash.new(0)
34
34
 
35
35
  context.stack do
36
- key = context[@name]
36
+ key = context.evaluate(@name)
37
37
  iteration = context.registers[:cycle][key]
38
- result = context[@variables[iteration]]
38
+ result = context.evaluate(@variables[iteration])
39
39
  iteration += 1
40
- iteration = 0 if iteration >= @variables.size
40
+ iteration = 0 if iteration >= @variables.size
41
41
  context.registers[:cycle][key] = iteration
42
42
  result
43
43
  end
@@ -48,7 +48,7 @@ module Liquid
48
48
  def variables_from_string(markup)
49
49
  markup.split(',').collect do |var|
50
50
  var =~ /\s*(#{QuotedFragment})\s*/o
51
- $1 ? $1 : nil
51
+ $1 ? Expression.parse($1) : nil
52
52
  end.compact
53
53
  end
54
54
  end
@@ -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
  #
@@ -42,85 +41,41 @@ 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
49
  def initialize(tag_name, markup, options)
50
50
  super
51
+ @from = @limit = nil
51
52
  parse_with_selected_parser(markup)
52
- @nodelist = @for_block = []
53
+ @for_block = BlockBody.new
54
+ @else_block = nil
55
+ end
56
+
57
+ def parse(tokens)
58
+ return unless parse_body(@for_block, tokens)
59
+ parse_body(@else_block, tokens)
53
60
  end
54
61
 
55
62
  def nodelist
56
- if @else_block
57
- @for_block + @else_block
58
- else
59
- @for_block
60
- end
63
+ @else_block ? [@for_block, @else_block] : [@for_block]
61
64
  end
62
65
 
63
66
  def unknown_tag(tag, markup, tokens)
64
67
  return super unless tag == 'else'.freeze
65
- @nodelist = @else_block = []
68
+ @else_block = BlockBody.new
66
69
  end
67
70
 
68
71
  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)
73
-
74
- # Maintains Ruby 1.8.7 String#each behaviour on 1.9
75
- return render_else(context) unless iterable?(collection)
72
+ segment = collection_segment(context)
76
73
 
77
- from = if @attributes['offset'.freeze] == 'continue'.freeze
78
- context.registers[:for][@name].to_i
74
+ if segment.empty?
75
+ render_else(context)
79
76
  else
80
- context[@attributes['offset'.freeze]].to_i
77
+ render_segment(context, segment)
81
78
  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
122
- end
123
- result
124
79
  end
125
80
 
126
81
  protected
@@ -128,12 +83,12 @@ module Liquid
128
83
  def lax_parse(markup)
129
84
  if markup =~ Syntax
130
85
  @variable_name = $1
131
- @collection_name = $2
132
- @name = "#{$1}-#{$2}"
133
- @reversed = $3
134
- @attributes = {}
86
+ collection_name = $2
87
+ @reversed = !!$3
88
+ @name = "#{@variable_name}-#{collection_name}"
89
+ @collection_name = Expression.parse(collection_name)
135
90
  markup.scan(TagAttributes) do |key, value|
136
- @attributes[key] = value
91
+ set_attribute(key, value)
137
92
  end
138
93
  else
139
94
  raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
@@ -143,31 +98,96 @@ module Liquid
143
98
  def strict_parse(markup)
144
99
  p = Parser.new(markup)
145
100
  @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}"
101
+ raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
102
+ collection_name = p.expression
103
+ @name = "#{@variable_name}-#{collection_name}"
104
+ @collection_name = Expression.parse(collection_name)
149
105
  @reversed = p.id?('reversed'.freeze)
150
106
 
151
- @attributes = {}
152
107
  while p.look(:id) && p.look(:colon, 1)
153
108
  unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
154
109
  raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
155
110
  end
156
111
  p.consume
157
- val = p.expression
158
- @attributes[attribute] = val
112
+ set_attribute(attribute, p.expression)
159
113
  end
160
114
  p.consume(:end_of_string)
161
115
  end
162
116
 
163
117
  private
164
118
 
165
- def render_else(context)
166
- return @else_block ? [render_all(@else_block, context)] : ''.freeze
119
+ def collection_segment(context)
120
+ offsets = context.registers[:for] ||= Hash.new(0)
121
+
122
+ from = if @from == :continue
123
+ offsets[@name].to_i
124
+ else
125
+ context.evaluate(@from).to_i
126
+ end
127
+
128
+ collection = context.evaluate(@collection_name)
129
+ collection = collection.to_a if collection.is_a?(Range)
130
+
131
+ limit = context.evaluate(@limit)
132
+ to = limit ? limit.to_i + from : nil
133
+
134
+ segment = Utils.slice_collection(collection, from, to)
135
+ segment.reverse! if @reversed
136
+
137
+ offsets[@name] = from + segment.length
138
+
139
+ segment
167
140
  end
168
141
 
169
- def iterable?(collection)
170
- collection.respond_to?(:each) || Utils.non_blank_string?(collection)
142
+ def render_segment(context, segment)
143
+ for_stack = context.registers[:for_stack] ||= []
144
+ length = segment.length
145
+
146
+ result = ''
147
+
148
+ context.stack do
149
+ loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
150
+
151
+ for_stack.push(loop_vars)
152
+
153
+ begin
154
+ context['forloop'.freeze] = loop_vars
155
+
156
+ segment.each_with_index do |item, index|
157
+ context[@variable_name] = item
158
+ result << @for_block.render(context)
159
+ loop_vars.send(:increment!)
160
+
161
+ # Handle any interrupts if they exist.
162
+ if context.interrupt?
163
+ interrupt = context.pop_interrupt
164
+ break if interrupt.is_a? BreakInterrupt
165
+ next if interrupt.is_a? ContinueInterrupt
166
+ end
167
+ end
168
+ ensure
169
+ for_stack.pop
170
+ end
171
+ end
172
+
173
+ result
174
+ end
175
+
176
+ def set_attribute(key, expr)
177
+ case key
178
+ when 'offset'.freeze
179
+ @from = if expr == 'continue'.freeze
180
+ :continue
181
+ else
182
+ Expression.parse(expr)
183
+ end
184
+ when 'limit'.freeze
185
+ @limit = Expression.parse(expr)
186
+ end
187
+ end
188
+
189
+ def render_else(context)
190
+ @else_block ? @else_block.render(context) : ''.freeze
171
191
  end
172
192
  end
173
193