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.
- checksums.yaml +5 -5
- data/History.md +154 -58
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/README.md +33 -0
- data/lib/liquid/block.rb +42 -125
- data/lib/liquid/block_body.rb +99 -79
- data/lib/liquid/condition.rb +52 -32
- data/lib/liquid/context.rb +57 -51
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +17 -16
- data/lib/liquid/errors.rb +20 -24
- data/lib/liquid/expression.rb +26 -10
- data/lib/liquid/extensions.rb +19 -7
- data/lib/liquid/file_system.rb +11 -11
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +6 -6
- data/lib/liquid/interrupts.rb +1 -2
- data/lib/liquid/lexer.rb +12 -8
- data/lib/liquid/locales/en.yml +6 -2
- data/lib/liquid/parse_context.rb +38 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser_switching.rb +4 -4
- data/lib/liquid/profiler/hooks.rb +7 -7
- data/lib/liquid/profiler.rb +18 -19
- data/lib/liquid/range_lookup.rb +16 -1
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +207 -61
- data/lib/liquid/strainer.rb +15 -8
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +25 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +1 -1
- data/lib/liquid/tags/case.rb +27 -12
- data/lib/liquid/tags/comment.rb +2 -2
- data/lib/liquid/tags/cycle.rb +16 -8
- data/lib/liquid/tags/decrement.rb +1 -4
- data/lib/liquid/tags/for.rb +103 -75
- data/lib/liquid/tags/if.rb +60 -44
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +71 -51
- data/lib/liquid/tags/raw.rb +32 -4
- data/lib/liquid/tags/table_row.rb +21 -31
- data/lib/liquid/tags/unless.rb +3 -4
- data/lib/liquid/template.rb +42 -54
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/truffle.rb +5 -0
- data/lib/liquid/utils.rb +52 -8
- data/lib/liquid/variable.rb +59 -46
- data/lib/liquid/variable_lookup.rb +14 -6
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +10 -7
- data/test/integration/assign_test.rb +8 -8
- data/test/integration/blank_test.rb +14 -14
- data/test/integration/block_test.rb +12 -0
- data/test/integration/context_test.rb +2 -2
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +42 -40
- data/test/integration/error_handling_test.rb +96 -43
- data/test/integration/filter_test.rb +60 -20
- data/test/integration/hash_ordering_test.rb +9 -9
- data/test/integration/output_test.rb +26 -27
- data/test/integration/parse_tree_visitor_test.rb +247 -0
- data/test/integration/parsing_quirks_test.rb +19 -13
- data/test/integration/render_profiling_test.rb +20 -20
- data/test/integration/security_test.rb +23 -7
- data/test/integration/standard_filter_test.rb +426 -46
- data/test/integration/tags/break_tag_test.rb +1 -2
- data/test/integration/tags/continue_tag_test.rb +0 -1
- data/test/integration/tags/for_tag_test.rb +135 -100
- data/test/integration/tags/if_else_tag_test.rb +75 -77
- data/test/integration/tags/include_tag_test.rb +50 -31
- data/test/integration/tags/increment_tag_test.rb +10 -11
- data/test/integration/tags/raw_tag_test.rb +7 -1
- data/test/integration/tags/standard_tag_test.rb +121 -122
- data/test/integration/tags/statements_test.rb +3 -5
- data/test/integration/tags/table_row_test.rb +20 -19
- data/test/integration/tags/unless_else_tag_test.rb +6 -6
- data/test/integration/template_test.rb +199 -49
- data/test/integration/trim_mode_test.rb +529 -0
- data/test/integration/variable_test.rb +27 -13
- data/test/test_helper.rb +33 -6
- data/test/truffle/truffle_test.rb +9 -0
- data/test/unit/block_unit_test.rb +8 -5
- data/test/unit/condition_unit_test.rb +94 -77
- data/test/unit/context_unit_test.rb +69 -72
- data/test/unit/file_system_unit_test.rb +3 -3
- data/test/unit/i18n_unit_test.rb +2 -2
- data/test/unit/lexer_unit_test.rb +12 -9
- data/test/unit/parser_unit_test.rb +2 -2
- data/test/unit/regexp_unit_test.rb +1 -1
- data/test/unit/strainer_unit_test.rb +96 -1
- data/test/unit/tag_unit_test.rb +7 -2
- data/test/unit/tags/case_tag_unit_test.rb +1 -1
- data/test/unit/tags/for_tag_unit_test.rb +2 -2
- data/test/unit/tags/if_tag_unit_test.rb +1 -1
- data/test/unit/template_unit_test.rb +14 -5
- data/test/unit/tokenizer_unit_test.rb +24 -7
- data/test/unit/variable_unit_test.rb +60 -43
- metadata +62 -50
- data/lib/liquid/module_ex.rb +0 -62
- data/lib/liquid/token.rb +0 -18
- 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
|
-
|
4
|
-
|
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,
|
8
|
+
def parse(tag_name, markup, tokenizer, options)
|
9
9
|
tag = new(tag_name, markup, options)
|
10
|
-
tag.parse(
|
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,
|
17
|
+
def initialize(tag_name, markup, parse_context)
|
18
18
|
@tag_name = tag_name
|
19
19
|
@markup = markup
|
20
|
-
@
|
20
|
+
@parse_context = parse_context
|
21
|
+
@line_number = parse_context.line_number
|
21
22
|
end
|
22
23
|
|
23
|
-
def parse(
|
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(
|
35
|
+
def render(_context)
|
35
36
|
''.freeze
|
36
37
|
end
|
37
38
|
|
data/lib/liquid/tags/assign.rb
CHANGED
@@ -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.
|
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)
|
data/lib/liquid/tags/break.rb
CHANGED
@@ -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)
|
data/lib/liquid/tags/capture.rb
CHANGED
data/lib/liquid/tags/case.rb
CHANGED
@@ -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.
|
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
|
48
|
+
return block.attachment.render(context) if execute_else_block
|
41
49
|
elsif block.evaluate(context)
|
42
50
|
execute_else_block = false
|
43
|
-
output <<
|
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
|
-
|
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(
|
63
|
-
@blocks
|
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
|
-
|
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(
|
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)
|
data/lib/liquid/tags/comment.rb
CHANGED
data/lib/liquid/tags/cycle.rb
CHANGED
@@ -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 =
|
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] ||=
|
35
|
+
context.registers[:cycle] ||= {}
|
34
36
|
|
35
37
|
context.stack do
|
36
|
-
key = context
|
37
|
-
iteration = context.registers[:cycle][key]
|
38
|
-
result = context
|
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
|
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
|
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)
|
data/lib/liquid/tags/for.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
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
|
-
@
|
70
|
+
@else_block = BlockBody.new
|
66
71
|
end
|
67
72
|
|
68
73
|
def render(context)
|
69
|
-
|
70
|
-
|
71
|
-
collection = context[@collection_name]
|
72
|
-
collection = collection.to_a if collection.is_a?(Range)
|
74
|
+
segment = collection_segment(context)
|
73
75
|
|
74
|
-
|
75
|
-
|
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
|
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
|
-
|
132
|
-
@
|
133
|
-
@
|
134
|
-
@
|
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
|
-
|
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))
|
147
|
-
|
148
|
-
@name = "#{@variable_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
|
-
|
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
|
-
|
192
|
+
@else_block ? @else_block.render(context) : ''.freeze
|
167
193
|
end
|
168
194
|
|
169
|
-
|
170
|
-
|
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
|
|