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.
- checksums.yaml +4 -4
- data/History.md +98 -58
- data/README.md +31 -0
- data/lib/liquid/block.rb +31 -124
- data/lib/liquid/block_body.rb +75 -59
- data/lib/liquid/condition.rb +23 -22
- data/lib/liquid/context.rb +50 -46
- 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 +15 -3
- data/lib/liquid/extensions.rb +13 -7
- data/lib/liquid/file_system.rb +11 -11
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +5 -5
- data/lib/liquid/interrupts.rb +1 -2
- data/lib/liquid/lexer.rb +6 -4
- data/lib/liquid/locales/en.yml +5 -1
- data/lib/liquid/parse_context.rb +37 -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 +121 -61
- data/lib/liquid/strainer.rb +14 -7
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +17 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +1 -1
- data/lib/liquid/tags/case.rb +19 -12
- data/lib/liquid/tags/comment.rb +2 -2
- data/lib/liquid/tags/cycle.rb +6 -6
- data/lib/liquid/tags/decrement.rb +1 -4
- data/lib/liquid/tags/for.rb +95 -75
- data/lib/liquid/tags/if.rb +49 -44
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +61 -52
- data/lib/liquid/tags/raw.rb +32 -4
- data/lib/liquid/tags/table_row.rb +12 -30
- 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/utils.rb +52 -8
- data/lib/liquid/variable.rb +46 -45
- data/lib/liquid/variable_lookup.rb +7 -5
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +9 -7
- data/test/integration/assign_test.rb +8 -8
- data/test/integration/blank_test.rb +14 -14
- 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 +99 -46
- 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/parsing_quirks_test.rb +15 -13
- data/test/integration/render_profiling_test.rb +20 -20
- data/test/integration/security_test.rb +9 -7
- data/test/integration/standard_filter_test.rb +179 -40
- 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 +133 -98
- data/test/integration/tags/if_else_tag_test.rb +75 -77
- data/test/integration/tags/include_tag_test.rb +34 -30
- 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 +190 -49
- data/test/integration/trim_mode_test.rb +525 -0
- data/test/integration/variable_test.rb +23 -13
- data/test/test_helper.rb +33 -5
- data/test/unit/block_unit_test.rb +8 -5
- data/test/unit/condition_unit_test.rb +86 -77
- data/test/unit/context_unit_test.rb +48 -57
- 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 +11 -8
- 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 +80 -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 +19 -14
- 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/{MIT-LICENSE → LICENSE} +0 -0
data/lib/liquid/strainer.rb
CHANGED
@@ -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
|
-
|
26
|
-
|
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: #{
|
29
|
+
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
|
31
30
|
unless self.class.include?(filter)
|
32
|
-
|
33
|
-
|
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.
|
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
|
-
|
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' %}
|
@@ -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.
|
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)
|
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
@@ -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.
|
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
|
46
|
+
return block.attachment.render(context) if execute_else_block
|
41
47
|
elsif block.evaluate(context)
|
42
48
|
execute_else_block = false
|
43
|
-
output <<
|
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
|
-
|
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(
|
63
|
-
@blocks
|
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
|
-
|
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(
|
80
|
+
block.attach(BlockBody.new)
|
74
81
|
@blocks << block
|
75
82
|
end
|
76
83
|
end
|
data/lib/liquid/tags/comment.rb
CHANGED
data/lib/liquid/tags/cycle.rb
CHANGED
@@ -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 =
|
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
|
36
|
+
key = context.evaluate(@name)
|
37
37
|
iteration = context.registers[:cycle][key]
|
38
|
-
result = context
|
38
|
+
result = context.evaluate(@variables[iteration])
|
39
39
|
iteration += 1
|
40
|
-
iteration = 0
|
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
|
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
|
#
|
@@ -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
|
-
@
|
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
|
-
|
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
|
-
@
|
68
|
+
@else_block = BlockBody.new
|
66
69
|
end
|
67
70
|
|
68
71
|
def render(context)
|
69
|
-
|
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
|
-
|
78
|
-
context
|
74
|
+
if segment.empty?
|
75
|
+
render_else(context)
|
79
76
|
else
|
80
|
-
context
|
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
|
-
|
132
|
-
@
|
133
|
-
@
|
134
|
-
@
|
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
|
-
|
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))
|
147
|
-
|
148
|
-
@name = "#{@variable_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
|
-
|
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
|
166
|
-
|
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
|
170
|
-
|
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
|
|