liquid 4.0.3 → 5.4.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 +89 -0
- data/README.md +10 -4
- data/lib/liquid/block.rb +31 -14
- data/lib/liquid/block_body.rb +169 -57
- data/lib/liquid/condition.rb +48 -21
- data/lib/liquid/context.rb +111 -52
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +4 -2
- data/lib/liquid/errors.rb +20 -18
- data/lib/liquid/expression.rb +28 -32
- data/lib/liquid/extensions.rb +2 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +54 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +30 -23
- data/lib/liquid/locales/en.yml +8 -5
- data/lib/liquid/parse_context.rb +20 -4
- data/lib/liquid/parse_tree_visitor.rb +2 -2
- data/lib/liquid/parser.rb +30 -18
- data/lib/liquid/parser_switching.rb +17 -3
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/range_lookup.rb +13 -3
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +551 -114
- data/lib/liquid/strainer_factory.rb +41 -0
- data/lib/liquid/strainer_template.rb +62 -0
- data/lib/liquid/tablerowloop_drop.rb +64 -5
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tag.rb +28 -6
- data/lib/liquid/tags/assign.rb +36 -18
- data/lib/liquid/tags/break.rb +16 -3
- data/lib/liquid/tags/capture.rb +24 -18
- data/lib/liquid/tags/case.rb +61 -27
- data/lib/liquid/tags/comment.rb +18 -3
- data/lib/liquid/tags/continue.rb +16 -12
- data/lib/liquid/tags/cycle.rb +37 -25
- data/lib/liquid/tags/decrement.rb +22 -20
- data/lib/liquid/tags/echo.rb +41 -0
- data/lib/liquid/tags/for.rb +90 -87
- data/lib/liquid/tags/if.rb +50 -32
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +49 -60
- data/lib/liquid/tags/increment.rb +23 -17
- data/lib/liquid/tags/inline_comment.rb +43 -0
- data/lib/liquid/tags/raw.rb +25 -11
- data/lib/liquid/tags/render.rb +109 -0
- data/lib/liquid/tags/table_row.rb +45 -19
- data/lib/liquid/tags/unless.rb +38 -19
- data/lib/liquid/template.rb +52 -72
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +18 -10
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +13 -3
- data/lib/liquid/variable.rb +49 -44
- data/lib/liquid/variable_lookup.rb +18 -10
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +18 -6
- metadata +20 -108
- data/lib/liquid/strainer.rb +0 -66
- data/lib/liquid/truffle.rb +0 -5
- data/test/fixtures/en_locale.yml +0 -9
- data/test/integration/assign_test.rb +0 -48
- data/test/integration/blank_test.rb +0 -106
- data/test/integration/block_test.rb +0 -12
- data/test/integration/capture_test.rb +0 -50
- data/test/integration/context_test.rb +0 -32
- data/test/integration/document_test.rb +0 -19
- data/test/integration/drop_test.rb +0 -273
- data/test/integration/error_handling_test.rb +0 -260
- data/test/integration/filter_test.rb +0 -178
- data/test/integration/hash_ordering_test.rb +0 -23
- data/test/integration/output_test.rb +0 -123
- data/test/integration/parse_tree_visitor_test.rb +0 -247
- data/test/integration/parsing_quirks_test.rb +0 -122
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -80
- data/test/integration/standard_filter_test.rb +0 -776
- data/test/integration/tags/break_tag_test.rb +0 -15
- data/test/integration/tags/continue_tag_test.rb +0 -15
- data/test/integration/tags/for_tag_test.rb +0 -410
- data/test/integration/tags/if_else_tag_test.rb +0 -188
- data/test/integration/tags/include_tag_test.rb +0 -253
- data/test/integration/tags/increment_tag_test.rb +0 -23
- data/test/integration/tags/raw_tag_test.rb +0 -31
- data/test/integration/tags/standard_tag_test.rb +0 -296
- data/test/integration/tags/statements_test.rb +0 -111
- data/test/integration/tags/table_row_test.rb +0 -64
- data/test/integration/tags/unless_else_tag_test.rb +0 -26
- data/test/integration/template_test.rb +0 -332
- data/test/integration/trim_mode_test.rb +0 -529
- data/test/integration/variable_test.rb +0 -96
- data/test/test_helper.rb +0 -116
- data/test/truffle/truffle_test.rb +0 -9
- data/test/unit/block_unit_test.rb +0 -58
- data/test/unit/condition_unit_test.rb +0 -166
- data/test/unit/context_unit_test.rb +0 -489
- data/test/unit/file_system_unit_test.rb +0 -35
- data/test/unit/i18n_unit_test.rb +0 -37
- data/test/unit/lexer_unit_test.rb +0 -51
- data/test/unit/parser_unit_test.rb +0 -82
- data/test/unit/regexp_unit_test.rb +0 -44
- data/test/unit/strainer_unit_test.rb +0 -164
- data/test/unit/tag_unit_test.rb +0 -21
- data/test/unit/tags/case_tag_unit_test.rb +0 -10
- data/test/unit/tags/for_tag_unit_test.rb +0 -13
- data/test/unit/tags/if_tag_unit_test.rb +0 -8
- data/test/unit/template_unit_test.rb +0 -78
- data/test/unit/tokenizer_unit_test.rb +0 -55
- data/test/unit/variable_unit_test.rb +0 -162
data/lib/liquid/tags/continue.rb
CHANGED
@@ -1,18 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
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 %}
|
11
12
|
class Continue < Tag
|
12
|
-
|
13
|
-
|
13
|
+
INTERRUPT = ContinueInterrupt.new.freeze
|
14
|
+
|
15
|
+
def render_to_output_buffer(context, output)
|
16
|
+
context.push_interrupt(INTERRUPT)
|
17
|
+
output
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
17
|
-
Template.register_tag('continue'
|
21
|
+
Template.register_tag('continue', Continue)
|
18
22
|
end
|
data/lib/liquid/tags/cycle.rb
CHANGED
@@ -1,16 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# <div class="blue"> Item three </div>
|
11
|
-
# <div class="red"> Item four </div>
|
12
|
-
# <div class="green"> Item five</div>
|
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.
|
13
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, ... %}
|
14
17
|
class Cycle < Tag
|
15
18
|
SimpleSyntax = /\A#{QuotedFragment}+/o
|
16
19
|
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
@@ -21,28 +24,37 @@ module Liquid
|
|
21
24
|
super
|
22
25
|
case markup
|
23
26
|
when NamedSyntax
|
24
|
-
@variables = variables_from_string(
|
25
|
-
@name
|
27
|
+
@variables = variables_from_string(Regexp.last_match(2))
|
28
|
+
@name = parse_expression(Regexp.last_match(1))
|
26
29
|
when SimpleSyntax
|
27
30
|
@variables = variables_from_string(markup)
|
28
|
-
@name
|
31
|
+
@name = @variables.to_s
|
29
32
|
else
|
30
|
-
raise SyntaxError
|
33
|
+
raise SyntaxError, options[:locale].t("errors.syntax.cycle")
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
34
|
-
def
|
37
|
+
def render_to_output_buffer(context, output)
|
35
38
|
context.registers[:cycle] ||= {}
|
36
39
|
|
37
|
-
context.
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
45
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
|
46
58
|
end
|
47
59
|
|
48
60
|
private
|
@@ -50,7 +62,7 @@ module Liquid
|
|
50
62
|
def variables_from_string(markup)
|
51
63
|
markup.split(',').collect do |var|
|
52
64
|
var =~ /\s*(#{QuotedFragment})\s*/o
|
53
|
-
|
65
|
+
Regexp.last_match(1) ? parse_expression(Regexp.last_match(1)) : nil
|
54
66
|
end.compact
|
55
67
|
end
|
56
68
|
|
@@ -1,35 +1,37 @@
|
|
1
|
-
|
2
|
-
# decrement is used in a place where one needs to insert a counter
|
3
|
-
# into a template, and needs the counter to survive across
|
4
|
-
# multiple instantiations of the template.
|
5
|
-
# NOTE: decrement is a pre-decrement, --i,
|
6
|
-
# while increment is post: i++.
|
7
|
-
#
|
8
|
-
# (To achieve the survival, the application must keep the context)
|
9
|
-
#
|
10
|
-
# if the variable does not exist, it is created with value 0.
|
1
|
+
# frozen_string_literal: true
|
11
2
|
|
12
|
-
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
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.
|
19
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.
|
20
21
|
class Decrement < Tag
|
21
22
|
def initialize(tag_name, markup, options)
|
22
23
|
super
|
23
24
|
@variable = markup.strip
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
+
def render_to_output_buffer(context, output)
|
27
28
|
value = context.environments.first[@variable] ||= 0
|
28
29
|
value -= 1
|
29
30
|
context.environments.first[@variable] = value
|
30
|
-
value.to_s
|
31
|
+
output << value.to_s
|
32
|
+
output
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
34
|
-
Template.register_tag('decrement'
|
36
|
+
Template.register_tag('decrement', Decrement)
|
35
37
|
end
|
@@ -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
|
data/lib/liquid/tags/for.rb
CHANGED
@@ -1,48 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# {% for item in collection %}
|
12
|
-
# <div {% if forloop.first %}class="first"{% endif %}>
|
13
|
-
# Item {{ forloop.index }}: {{ item.name }}
|
14
|
-
# </div>
|
15
|
-
# {% else %}
|
16
|
-
# There is nothing in the collection.
|
17
|
-
# {% endfor %}
|
18
|
-
#
|
19
|
-
# You can also define a limit and offset much like SQL. Remember
|
20
|
-
# that offset starts at 0 for the first item.
|
21
|
-
#
|
22
|
-
# {% for item in collection limit:5 offset:10 %}
|
23
|
-
# {{ item.name }}
|
24
|
-
# {% end %}
|
25
|
-
#
|
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`)
|
27
|
-
#
|
28
|
-
# == Available variables:
|
29
|
-
#
|
30
|
-
# forloop.name:: 'item-collection'
|
31
|
-
# forloop.length:: Length of the loop
|
32
|
-
# forloop.index:: The current item's position in the collection;
|
33
|
-
# forloop.index starts at 1.
|
34
|
-
# This is helpful for non-programmers who start believe
|
35
|
-
# the first item in an array is 1, not 0.
|
36
|
-
# forloop.index0:: The current item's position in the collection
|
37
|
-
# where the first item is 0
|
38
|
-
# forloop.rindex:: Number of items remaining in the loop
|
39
|
-
# (length - index) where 1 is the last item.
|
40
|
-
# forloop.rindex0:: Number of items remaining in the loop
|
41
|
-
# where 0 is the last item.
|
42
|
-
# forloop.first:: Returns true if the item is the first item.
|
43
|
-
# forloop.last:: Returns true if the item is the last item.
|
44
|
-
# forloop.parentloop:: Provides access to the parent loop, if present.
|
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
28
|
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
48
29
|
|
@@ -52,13 +33,20 @@ module Liquid
|
|
52
33
|
super
|
53
34
|
@from = @limit = nil
|
54
35
|
parse_with_selected_parser(markup)
|
55
|
-
@for_block =
|
36
|
+
@for_block = new_body
|
56
37
|
@else_block = nil
|
57
38
|
end
|
58
39
|
|
59
40
|
def parse(tokens)
|
60
|
-
|
61
|
-
|
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
|
62
50
|
end
|
63
51
|
|
64
52
|
def nodelist
|
@@ -66,49 +54,53 @@ module Liquid
|
|
66
54
|
end
|
67
55
|
|
68
56
|
def unknown_tag(tag, markup, tokens)
|
69
|
-
return super unless tag == 'else'
|
70
|
-
@else_block =
|
57
|
+
return super unless tag == 'else'
|
58
|
+
@else_block = new_body
|
71
59
|
end
|
72
60
|
|
73
|
-
def
|
61
|
+
def render_to_output_buffer(context, output)
|
74
62
|
segment = collection_segment(context)
|
75
63
|
|
76
64
|
if segment.empty?
|
77
|
-
render_else(context)
|
65
|
+
render_else(context, output)
|
78
66
|
else
|
79
|
-
render_segment(context, segment)
|
67
|
+
render_segment(context, output, segment)
|
80
68
|
end
|
69
|
+
|
70
|
+
output
|
81
71
|
end
|
82
72
|
|
83
73
|
protected
|
84
74
|
|
85
75
|
def lax_parse(markup)
|
86
76
|
if markup =~ Syntax
|
87
|
-
@variable_name
|
88
|
-
collection_name
|
89
|
-
@reversed
|
90
|
-
@name
|
91
|
-
@collection_name =
|
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)
|
92
82
|
markup.scan(TagAttributes) do |key, value|
|
93
83
|
set_attribute(key, value)
|
94
84
|
end
|
95
85
|
else
|
96
|
-
raise SyntaxError
|
86
|
+
raise SyntaxError, options[:locale].t("errors.syntax.for")
|
97
87
|
end
|
98
88
|
end
|
99
89
|
|
100
90
|
def strict_parse(markup)
|
101
91
|
p = Parser.new(markup)
|
102
92
|
@variable_name = p.consume(:id)
|
103
|
-
raise SyntaxError
|
104
|
-
|
105
|
-
|
106
|
-
@collection_name =
|
107
|
-
|
93
|
+
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
|
94
|
+
|
95
|
+
collection_name = p.expression
|
96
|
+
@collection_name = parse_expression(collection_name)
|
97
|
+
|
98
|
+
@name = "#{@variable_name}-#{collection_name}"
|
99
|
+
@reversed = p.id?('reversed')
|
108
100
|
|
109
101
|
while p.look(:id) && p.look(:colon, 1)
|
110
|
-
unless attribute = p.id?('limit'
|
111
|
-
raise SyntaxError
|
102
|
+
unless (attribute = p.id?('limit') || p.id?('offset'))
|
103
|
+
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
|
112
104
|
end
|
113
105
|
p.consume
|
114
106
|
set_attribute(attribute, p.expression)
|
@@ -124,14 +116,23 @@ module Liquid
|
|
124
116
|
from = if @from == :continue
|
125
117
|
offsets[@name].to_i
|
126
118
|
else
|
127
|
-
context.evaluate(@from)
|
119
|
+
from_value = context.evaluate(@from)
|
120
|
+
if from_value.nil?
|
121
|
+
0
|
122
|
+
else
|
123
|
+
Utils.to_integer(from_value)
|
124
|
+
end
|
128
125
|
end
|
129
126
|
|
130
127
|
collection = context.evaluate(@collection_name)
|
131
128
|
collection = collection.to_a if collection.is_a?(Range)
|
132
129
|
|
133
|
-
|
134
|
-
to =
|
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
|
135
136
|
|
136
137
|
segment = Utils.slice_collection(collection, from, to)
|
137
138
|
segment.reverse! if @reversed
|
@@ -141,11 +142,9 @@ module Liquid
|
|
141
142
|
segment
|
142
143
|
end
|
143
144
|
|
144
|
-
def render_segment(context, segment)
|
145
|
+
def render_segment(context, output, segment)
|
145
146
|
for_stack = context.registers[:for_stack] ||= []
|
146
|
-
length
|
147
|
-
|
148
|
-
result = ''
|
147
|
+
length = segment.length
|
149
148
|
|
150
149
|
context.stack do
|
151
150
|
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
|
@@ -153,43 +152,47 @@ module Liquid
|
|
153
152
|
for_stack.push(loop_vars)
|
154
153
|
|
155
154
|
begin
|
156
|
-
context['forloop'
|
155
|
+
context['forloop'] = loop_vars
|
157
156
|
|
158
157
|
segment.each do |item|
|
159
158
|
context[@variable_name] = item
|
160
|
-
|
159
|
+
@for_block.render_to_output_buffer(context, output)
|
161
160
|
loop_vars.send(:increment!)
|
162
161
|
|
163
162
|
# Handle any interrupts if they exist.
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
end
|
163
|
+
next unless context.interrupt?
|
164
|
+
interrupt = context.pop_interrupt
|
165
|
+
break if interrupt.is_a?(BreakInterrupt)
|
166
|
+
next if interrupt.is_a?(ContinueInterrupt)
|
169
167
|
end
|
170
168
|
ensure
|
171
169
|
for_stack.pop
|
172
170
|
end
|
173
171
|
end
|
174
172
|
|
175
|
-
|
173
|
+
output
|
176
174
|
end
|
177
175
|
|
178
176
|
def set_attribute(key, expr)
|
179
177
|
case key
|
180
|
-
when 'offset'
|
181
|
-
@from = if expr == 'continue'
|
178
|
+
when 'offset'
|
179
|
+
@from = if expr == 'continue'
|
180
|
+
Usage.increment('for_offset_continue')
|
182
181
|
:continue
|
183
182
|
else
|
184
|
-
|
183
|
+
parse_expression(expr)
|
185
184
|
end
|
186
|
-
when 'limit'
|
187
|
-
@limit =
|
185
|
+
when 'limit'
|
186
|
+
@limit = parse_expression(expr)
|
188
187
|
end
|
189
188
|
end
|
190
189
|
|
191
|
-
def render_else(context)
|
192
|
-
|
190
|
+
def render_else(context, output)
|
191
|
+
if @else_block
|
192
|
+
@else_block.render_to_output_buffer(context, output)
|
193
|
+
else
|
194
|
+
output
|
195
|
+
end
|
193
196
|
end
|
194
197
|
|
195
198
|
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
@@ -199,5 +202,5 @@ module Liquid
|
|
199
202
|
end
|
200
203
|
end
|
201
204
|
|
202
|
-
Template.register_tag('for'
|
205
|
+
Template.register_tag('for', For)
|
203
206
|
end
|
data/lib/liquid/tags/if.rb
CHANGED
@@ -1,25 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
4
|
+
# @liquid_public_docs
|
5
|
+
# @liquid_type tag
|
6
|
+
# @liquid_category conditional
|
7
|
+
# @liquid_name if
|
8
|
+
# @liquid_summary
|
9
|
+
# Renders an expression if a specific condition is `true`.
|
10
|
+
# @liquid_syntax
|
11
|
+
# {% if condition %}
|
12
|
+
# expression
|
8
13
|
# {% endif %}
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
14
|
+
# @liquid_syntax_keyword condition The condition to evaluate.
|
15
|
+
# @liquid_syntax_keyword expression The expression to render if the condition is met.
|
12
16
|
class If < Block
|
13
|
-
Syntax
|
17
|
+
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
|
14
18
|
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
15
|
-
BOOLEAN_OPERATORS
|
19
|
+
BOOLEAN_OPERATORS = %w(and or).freeze
|
16
20
|
|
17
21
|
attr_reader :blocks
|
18
22
|
|
19
23
|
def initialize(tag_name, markup, options)
|
20
24
|
super
|
21
25
|
@blocks = []
|
22
|
-
push_block('if'
|
26
|
+
push_block('if', markup)
|
23
27
|
end
|
24
28
|
|
25
29
|
def nodelist
|
@@ -29,53 +33,67 @@ module Liquid
|
|
29
33
|
def parse(tokens)
|
30
34
|
while parse_body(@blocks.last.attachment, tokens)
|
31
35
|
end
|
36
|
+
@blocks.reverse_each do |block|
|
37
|
+
block.attachment.remove_blank_strings if blank?
|
38
|
+
block.attachment.freeze
|
39
|
+
end
|
32
40
|
end
|
33
41
|
|
42
|
+
ELSE_TAG_NAMES = ['elsif', 'else'].freeze
|
43
|
+
private_constant :ELSE_TAG_NAMES
|
44
|
+
|
34
45
|
def unknown_tag(tag, markup, tokens)
|
35
|
-
if
|
46
|
+
if ELSE_TAG_NAMES.include?(tag)
|
36
47
|
push_block(tag, markup)
|
37
48
|
else
|
38
49
|
super
|
39
50
|
end
|
40
51
|
end
|
41
52
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
53
|
+
def render_to_output_buffer(context, output)
|
54
|
+
@blocks.each do |block|
|
55
|
+
result = Liquid::Utils.to_liquid_value(
|
56
|
+
block.evaluate(context)
|
57
|
+
)
|
58
|
+
|
59
|
+
if result
|
60
|
+
return block.attachment.render_to_output_buffer(context, output)
|
48
61
|
end
|
49
|
-
''.freeze
|
50
62
|
end
|
63
|
+
|
64
|
+
output
|
51
65
|
end
|
52
66
|
|
53
67
|
private
|
54
68
|
|
55
69
|
def push_block(tag, markup)
|
56
|
-
block = if tag == 'else'
|
70
|
+
block = if tag == 'else'
|
57
71
|
ElseCondition.new
|
58
72
|
else
|
59
73
|
parse_with_selected_parser(markup)
|
60
74
|
end
|
61
75
|
|
62
76
|
@blocks.push(block)
|
63
|
-
block.attach(
|
77
|
+
block.attach(new_body)
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_expression(markup)
|
81
|
+
Condition.parse_expression(parse_context, markup)
|
64
82
|
end
|
65
83
|
|
66
84
|
def lax_parse(markup)
|
67
85
|
expressions = markup.scan(ExpressionsAndOperators)
|
68
|
-
raise
|
86
|
+
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
|
69
87
|
|
70
|
-
condition = Condition.new(
|
88
|
+
condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
|
71
89
|
|
72
90
|
until expressions.empty?
|
73
91
|
operator = expressions.pop.to_s.strip
|
74
92
|
|
75
|
-
raise
|
93
|
+
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax
|
76
94
|
|
77
|
-
new_condition = Condition.new(
|
78
|
-
raise
|
95
|
+
new_condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
|
96
|
+
raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
|
79
97
|
new_condition.send(operator, condition)
|
80
98
|
condition = new_condition
|
81
99
|
end
|
@@ -93,7 +111,7 @@ module Liquid
|
|
93
111
|
def parse_binary_comparisons(p)
|
94
112
|
condition = parse_comparison(p)
|
95
113
|
first_condition = condition
|
96
|
-
while op = (p.id?('and'
|
114
|
+
while (op = (p.id?('and') || p.id?('or')))
|
97
115
|
child_condition = parse_comparison(p)
|
98
116
|
condition.send(op, child_condition)
|
99
117
|
condition = child_condition
|
@@ -102,9 +120,9 @@ module Liquid
|
|
102
120
|
end
|
103
121
|
|
104
122
|
def parse_comparison(p)
|
105
|
-
a =
|
106
|
-
if op = p.consume?(:comparison)
|
107
|
-
b =
|
123
|
+
a = parse_expression(p.expression)
|
124
|
+
if (op = p.consume?(:comparison))
|
125
|
+
b = parse_expression(p.expression)
|
108
126
|
Condition.new(a, op, b)
|
109
127
|
else
|
110
128
|
Condition.new(a)
|
@@ -118,5 +136,5 @@ module Liquid
|
|
118
136
|
end
|
119
137
|
end
|
120
138
|
|
121
|
-
Template.register_tag('if'
|
139
|
+
Template.register_tag('if', If)
|
122
140
|
end
|