liquid 5.4.0 → 5.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +4 -0
- data/README.md +3 -3
- data/lib/liquid/block.rb +8 -4
- data/lib/liquid/block_body.rb +17 -2
- data/lib/liquid/condition.rb +9 -4
- data/lib/liquid/context.rb +8 -4
- data/lib/liquid/drop.rb +4 -0
- data/lib/liquid/errors.rb +16 -15
- data/lib/liquid/expression.rb +4 -1
- data/lib/liquid/forloop_drop.rb +2 -5
- data/lib/liquid/lexer.rb +2 -3
- data/lib/liquid/locales/en.yml +1 -0
- data/lib/liquid/partial_cache.rb +12 -3
- data/lib/liquid/range_lookup.rb +11 -1
- data/lib/liquid/standardfilters.rb +81 -18
- data/lib/liquid/tablerowloop_drop.rb +1 -1
- data/lib/liquid/tag/disabler.rb +0 -8
- data/lib/liquid/tag.rb +10 -3
- data/lib/liquid/tags/assign.rb +1 -1
- data/lib/liquid/tags/break.rb +1 -1
- data/lib/liquid/tags/case.rb +1 -1
- data/lib/liquid/tags/comment.rb +60 -1
- data/lib/liquid/tags/continue.rb +1 -1
- data/lib/liquid/tags/cycle.rb +1 -1
- data/lib/liquid/tags/decrement.rb +8 -5
- data/lib/liquid/tags/echo.rb +2 -2
- data/lib/liquid/tags/for.rb +5 -5
- data/lib/liquid/tags/if.rb +1 -1
- data/lib/liquid/tags/include.rb +8 -6
- data/lib/liquid/tags/increment.rb +8 -5
- data/lib/liquid/tags/inline_comment.rb +0 -13
- data/lib/liquid/tags/raw.rb +2 -2
- data/lib/liquid/tags/render.rb +14 -10
- data/lib/liquid/tags/table_row.rb +12 -4
- data/lib/liquid/tags/unless.rb +3 -3
- data/lib/liquid/template.rb +9 -1
- data/lib/liquid/tokenizer.rb +9 -3
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d5699469b4a46eb15532bf2d5000925eeeff4f2eb5fef683d53bf09f0ec8825
|
4
|
+
data.tar.gz: df2bebc1a371dc250c768d46996c861fc22db9a1e2afc77f2f12247baa9a2e71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e953d07e402f2325e56953a1cce6f4d334a183548fd70db1a472695b491f3f2efb6318625c1582e708362f5d2cff81f932d9c0d30c3da23d63fdfdab12b50725
|
7
|
+
data.tar.gz: d6abf81f05029169f17da65b9c93af90c96c93fccd6179cff3a02d050f5736e177c2686348b1d62af4caa432e6332ffc19c37c86004d139e422876049856fc97
|
data/History.md
CHANGED
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
[![Build
|
1
|
+
[![Build status](https://github.com/Shopify/liquid/actions/workflows/liquid.yml/badge.svg)](https://github.com/Shopify/liquid/actions/workflows/liquid.yml)
|
2
2
|
[![Inline docs](http://inch-ci.org/github/Shopify/liquid.svg?branch=master)](http://inch-ci.org/github/Shopify/liquid)
|
3
3
|
|
4
4
|
# Liquid template engine
|
5
5
|
|
6
6
|
* [Contributing guidelines](CONTRIBUTING.md)
|
7
7
|
* [Version history](History.md)
|
8
|
-
* [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
|
8
|
+
* [Liquid documentation from Shopify](https://shopify.dev/docs/api/liquid)
|
9
9
|
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
|
10
10
|
* [Website](http://liquidmarkup.org/)
|
11
11
|
|
@@ -111,4 +111,4 @@ template.render!({ 'x' => 1}, { strict_variables: true })
|
|
111
111
|
|
112
112
|
To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
|
113
113
|
|
114
|
-
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
|
114
|
+
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
|
data/lib/liquid/block.rb
CHANGED
@@ -36,13 +36,17 @@ module Liquid
|
|
36
36
|
# @api private
|
37
37
|
def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
|
38
38
|
if tag == 'else'
|
39
|
-
raise SyntaxError, parse_context.locale.t(
|
40
|
-
|
39
|
+
raise SyntaxError, parse_context.locale.t(
|
40
|
+
"errors.syntax.unexpected_else",
|
41
|
+
block_name: block_name,
|
42
|
+
)
|
41
43
|
elsif tag.start_with?('end')
|
42
|
-
raise SyntaxError, parse_context.locale.t(
|
44
|
+
raise SyntaxError, parse_context.locale.t(
|
45
|
+
"errors.syntax.invalid_delimiter",
|
43
46
|
tag: tag,
|
44
47
|
block_name: block_name,
|
45
|
-
block_delimiter: block_delimiter
|
48
|
+
block_delimiter: block_delimiter,
|
49
|
+
)
|
46
50
|
else
|
47
51
|
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
|
48
52
|
end
|
data/lib/liquid/block_body.rb
CHANGED
@@ -6,6 +6,7 @@ module Liquid
|
|
6
6
|
class BlockBody
|
7
7
|
LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
|
8
8
|
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
9
|
+
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*)?#{WhitespaceControl}?#{TagEnd}\z/om
|
9
10
|
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
10
11
|
WhitespaceOrNothing = /\A\s*\z/
|
11
12
|
TAGSTART = "{%"
|
@@ -45,6 +46,12 @@ module Liquid
|
|
45
46
|
end
|
46
47
|
tag_name = Regexp.last_match(1)
|
47
48
|
markup = Regexp.last_match(2)
|
49
|
+
|
50
|
+
if tag_name == 'liquid'
|
51
|
+
parse_context.line_number -= 1
|
52
|
+
next parse_liquid_tag(markup, parse_context)
|
53
|
+
end
|
54
|
+
|
48
55
|
unless (tag = registered_tags[tag_name])
|
49
56
|
# end parsing if we reach an unknown tag and let the caller decide
|
50
57
|
# determine how to proceed
|
@@ -109,14 +116,22 @@ module Liquid
|
|
109
116
|
end
|
110
117
|
end
|
111
118
|
|
112
|
-
private def
|
119
|
+
private def handle_invalid_tag_token(token, parse_context)
|
120
|
+
if token.end_with?('%}')
|
121
|
+
yield token, token
|
122
|
+
else
|
123
|
+
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private def parse_for_document(tokenizer, parse_context, &block)
|
113
128
|
while (token = tokenizer.shift)
|
114
129
|
next if token.empty?
|
115
130
|
case
|
116
131
|
when token.start_with?(TAGSTART)
|
117
132
|
whitespace_handler(token, parse_context)
|
118
133
|
unless token =~ FullToken
|
119
|
-
|
134
|
+
return handle_invalid_tag_token(token, parse_context, &block)
|
120
135
|
end
|
121
136
|
tag_name = Regexp.last_match(2)
|
122
137
|
markup = Regexp.last_match(4)
|
data/lib/liquid/condition.rb
CHANGED
@@ -24,6 +24,9 @@ module Liquid
|
|
24
24
|
else
|
25
25
|
false
|
26
26
|
end
|
27
|
+
rescue Encoding::CompatibilityError
|
28
|
+
# "✅".b.include?("✅") raises Encoding::CompatibilityError despite being materially equal
|
29
|
+
left.b.include?(right.b)
|
27
30
|
end,
|
28
31
|
}
|
29
32
|
|
@@ -69,9 +72,9 @@ module Liquid
|
|
69
72
|
|
70
73
|
case condition.child_relation
|
71
74
|
when :or
|
72
|
-
break if result
|
75
|
+
break if Liquid::Utils.to_liquid_value(result)
|
73
76
|
when :and
|
74
|
-
break unless result
|
77
|
+
break unless Liquid::Utils.to_liquid_value(result)
|
75
78
|
else
|
76
79
|
break
|
77
80
|
end
|
@@ -159,8 +162,10 @@ module Liquid
|
|
159
162
|
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
160
163
|
def children
|
161
164
|
[
|
162
|
-
@node.left,
|
163
|
-
@node.
|
165
|
+
@node.left,
|
166
|
+
@node.right,
|
167
|
+
@node.child_condition,
|
168
|
+
@node.attachment
|
164
169
|
].compact
|
165
170
|
end
|
166
171
|
end
|
data/lib/liquid/context.rb
CHANGED
@@ -26,7 +26,7 @@ module Liquid
|
|
26
26
|
@environments = [environments]
|
27
27
|
@environments.flatten!
|
28
28
|
|
29
|
-
@static_environments = [static_environments].
|
29
|
+
@static_environments = [static_environments].flatten(1).freeze
|
30
30
|
@scopes = [(outer_scope || {})]
|
31
31
|
@registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
|
32
32
|
@errors = []
|
@@ -144,7 +144,7 @@ module Liquid
|
|
144
144
|
self.class.build(
|
145
145
|
resource_limits: resource_limits,
|
146
146
|
static_environments: static_environments,
|
147
|
-
registers: Registers.new(registers)
|
147
|
+
registers: Registers.new(registers),
|
148
148
|
).tap do |subcontext|
|
149
149
|
subcontext.base_scope_depth = base_scope_depth + 1
|
150
150
|
subcontext.exception_renderer = exception_renderer
|
@@ -197,10 +197,14 @@ module Liquid
|
|
197
197
|
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
|
198
198
|
end
|
199
199
|
|
200
|
-
variable
|
200
|
+
# update variable's context before invoking #to_liquid
|
201
201
|
variable.context = self if variable.respond_to?(:context=)
|
202
202
|
|
203
|
-
variable
|
203
|
+
liquid_variable = variable.to_liquid
|
204
|
+
|
205
|
+
liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)
|
206
|
+
|
207
|
+
liquid_variable
|
204
208
|
end
|
205
209
|
|
206
210
|
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
|
data/lib/liquid/drop.rb
CHANGED
data/lib/liquid/errors.rb
CHANGED
@@ -40,19 +40,20 @@ module Liquid
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
ArgumentError
|
44
|
-
ContextError
|
45
|
-
FileSystemError
|
46
|
-
StandardError
|
47
|
-
SyntaxError
|
48
|
-
StackLevelError
|
49
|
-
MemoryError
|
50
|
-
ZeroDivisionError
|
51
|
-
FloatDomainError
|
52
|
-
UndefinedVariable
|
53
|
-
UndefinedDropMethod
|
54
|
-
UndefinedFilter
|
55
|
-
MethodOverrideError
|
56
|
-
DisabledError
|
57
|
-
InternalError
|
43
|
+
ArgumentError = Class.new(Error)
|
44
|
+
ContextError = Class.new(Error)
|
45
|
+
FileSystemError = Class.new(Error)
|
46
|
+
StandardError = Class.new(Error)
|
47
|
+
SyntaxError = Class.new(Error)
|
48
|
+
StackLevelError = Class.new(Error)
|
49
|
+
MemoryError = Class.new(Error)
|
50
|
+
ZeroDivisionError = Class.new(Error)
|
51
|
+
FloatDomainError = Class.new(Error)
|
52
|
+
UndefinedVariable = Class.new(Error)
|
53
|
+
UndefinedDropMethod = Class.new(Error)
|
54
|
+
UndefinedFilter = Class.new(Error)
|
55
|
+
MethodOverrideError = Class.new(Error)
|
56
|
+
DisabledError = Class.new(Error)
|
57
|
+
InternalError = Class.new(Error)
|
58
|
+
TemplateEncodingError = Class.new(Error)
|
58
59
|
end
|
data/lib/liquid/expression.rb
CHANGED
data/lib/liquid/forloop_drop.rb
CHANGED
@@ -5,7 +5,7 @@ module Liquid
|
|
5
5
|
# @liquid_type object
|
6
6
|
# @liquid_name forloop
|
7
7
|
# @liquid_summary
|
8
|
-
# Information about a parent [`for` loop](/api/liquid/tags
|
8
|
+
# Information about a parent [`for` loop](/docs/api/liquid/tags/for).
|
9
9
|
class ForloopDrop < Drop
|
10
10
|
def initialize(name, length, parentloop)
|
11
11
|
@name = name
|
@@ -30,10 +30,7 @@ module Liquid
|
|
30
30
|
# @liquid_return [forloop]
|
31
31
|
attr_reader :parentloop
|
32
32
|
|
33
|
-
|
34
|
-
Usage.increment('forloop_drop_name')
|
35
|
-
@name
|
36
|
-
end
|
33
|
+
attr_reader :name
|
37
34
|
|
38
35
|
# @liquid_public_docs
|
39
36
|
# @liquid_summary
|
data/lib/liquid/lexer.rb
CHANGED
@@ -18,6 +18,7 @@ module Liquid
|
|
18
18
|
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
|
19
19
|
SINGLE_STRING_LITERAL = /'[^\']*'/
|
20
20
|
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
21
|
+
STRING_LITERAL = Regexp.union(SINGLE_STRING_LITERAL, DOUBLE_STRING_LITERAL)
|
21
22
|
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
22
23
|
DOTDOT = /\.\./
|
23
24
|
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
|
@@ -35,9 +36,7 @@ module Liquid
|
|
35
36
|
break if @ss.eos?
|
36
37
|
tok = if (t = @ss.scan(COMPARISON_OPERATOR))
|
37
38
|
[:comparison, t]
|
38
|
-
elsif (t = @ss.scan(
|
39
|
-
[:string, t]
|
40
|
-
elsif (t = @ss.scan(DOUBLE_STRING_LITERAL))
|
39
|
+
elsif (t = @ss.scan(STRING_LITERAL))
|
41
40
|
[:string, t]
|
42
41
|
elsif (t = @ss.scan(NUMBER_LITERAL))
|
43
42
|
[:number, t]
|
data/lib/liquid/locales/en.yml
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
|
16
16
|
inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character"
|
17
17
|
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
18
|
+
invalid_template_encoding: "Invalid template encoding"
|
18
19
|
render: "Syntax error in tag 'render' - Template name must be a quoted string"
|
19
20
|
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
20
21
|
tag_never_closed: "'%{block_name}' tag was never closed"
|
data/lib/liquid/partial_cache.rb
CHANGED
@@ -4,7 +4,8 @@ module Liquid
|
|
4
4
|
class PartialCache
|
5
5
|
def self.load(template_name, context:, parse_context:)
|
6
6
|
cached_partials = context.registers[:cached_partials]
|
7
|
-
|
7
|
+
cache_key = "#{template_name}:#{parse_context.error_mode}"
|
8
|
+
cached = cached_partials[cache_key]
|
8
9
|
return cached if cached
|
9
10
|
|
10
11
|
file_system = context.registers[:file_system]
|
@@ -15,8 +16,16 @@ module Liquid
|
|
15
16
|
template_factory = context.registers[:template_factory]
|
16
17
|
template = template_factory.for(template_name)
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
begin
|
20
|
+
partial = template.parse(source, parse_context)
|
21
|
+
rescue Liquid::Error => e
|
22
|
+
e.template_name = template&.name || template_name
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
|
26
|
+
partial.name ||= template_name
|
27
|
+
|
28
|
+
cached_partials[cache_key] = partial
|
20
29
|
ensure
|
21
30
|
parse_context.partial = false
|
22
31
|
end
|
data/lib/liquid/range_lookup.rb
CHANGED
@@ -8,7 +8,17 @@ module Liquid
|
|
8
8
|
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
9
9
|
new(start_obj, end_obj)
|
10
10
|
else
|
11
|
-
|
11
|
+
begin
|
12
|
+
start_obj.to_i..end_obj.to_i
|
13
|
+
rescue NoMethodError
|
14
|
+
invalid_expr = start_markup unless start_obj.respond_to?(:to_i)
|
15
|
+
invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)
|
16
|
+
if invalid_expr
|
17
|
+
raise Liquid::SyntaxError, "Invalid expression type '#{invalid_expr}' in range expression"
|
18
|
+
end
|
19
|
+
|
20
|
+
raise
|
21
|
+
end
|
12
22
|
end
|
13
23
|
end
|
14
24
|
|
@@ -6,7 +6,14 @@ require 'bigdecimal'
|
|
6
6
|
|
7
7
|
module Liquid
|
8
8
|
module StandardFilters
|
9
|
-
|
9
|
+
MAX_I32 = (1 << 31) - 1
|
10
|
+
private_constant :MAX_I32
|
11
|
+
|
12
|
+
MIN_I64 = -(1 << 63)
|
13
|
+
MAX_I64 = (1 << 63) - 1
|
14
|
+
I64_RANGE = MIN_I64..MAX_I64
|
15
|
+
private_constant :MIN_I64, :MAX_I64, :I64_RANGE
|
16
|
+
|
10
17
|
HTML_ESCAPE = {
|
11
18
|
'&' => '&',
|
12
19
|
'>' => '>',
|
@@ -18,10 +25,23 @@ module Liquid
|
|
18
25
|
STRIP_HTML_BLOCKS = Regexp.union(
|
19
26
|
%r{<script.*?</script>}m,
|
20
27
|
/<!--.*?-->/m,
|
21
|
-
%r{<style.*?</style>}m
|
28
|
+
%r{<style.*?</style>}m,
|
22
29
|
)
|
23
30
|
STRIP_HTML_TAGS = /<.*?>/m
|
24
31
|
|
32
|
+
class << self
|
33
|
+
def try_coerce_encoding(input, encoding:)
|
34
|
+
original_encoding = input.encoding
|
35
|
+
if input.encoding != encoding
|
36
|
+
input.force_encoding(encoding)
|
37
|
+
unless input.valid_encoding?
|
38
|
+
input.force_encoding(original_encoding)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
input
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
25
45
|
# @liquid_public_docs
|
26
46
|
# @liquid_type filter
|
27
47
|
# @liquid_category array
|
@@ -62,7 +82,7 @@ module Liquid
|
|
62
82
|
# @liquid_type filter
|
63
83
|
# @liquid_category string
|
64
84
|
# @liquid_summary
|
65
|
-
# Capitalizes the first word in a string.
|
85
|
+
# Capitalizes the first word in a string and downcases the remaining characters.
|
66
86
|
# @liquid_syntax string | capitalize
|
67
87
|
# @liquid_return [string]
|
68
88
|
def capitalize(input)
|
@@ -73,7 +93,7 @@ module Liquid
|
|
73
93
|
# @liquid_type filter
|
74
94
|
# @liquid_category string
|
75
95
|
# @liquid_summary
|
76
|
-
# Escapes a
|
96
|
+
# Escapes special characters in HTML, such as `<>`, `'`, and `&`, and converts characters into escape sequences. The filter doesn't effect characters within the string that don’t have a corresponding escape sequence.".
|
77
97
|
# @liquid_syntax string | escape
|
78
98
|
# @liquid_return [string]
|
79
99
|
def escape(input)
|
@@ -143,7 +163,8 @@ module Liquid
|
|
143
163
|
# @liquid_syntax string | base64_decode
|
144
164
|
# @liquid_return [string]
|
145
165
|
def base64_decode(input)
|
146
|
-
|
166
|
+
input = input.to_s
|
167
|
+
StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding)
|
147
168
|
rescue ::ArgumentError
|
148
169
|
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
|
149
170
|
end
|
@@ -167,7 +188,8 @@ module Liquid
|
|
167
188
|
# @liquid_syntax string | base64_url_safe_decode
|
168
189
|
# @liquid_return [string]
|
169
190
|
def base64_url_safe_decode(input)
|
170
|
-
|
191
|
+
input = input.to_s
|
192
|
+
StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding)
|
171
193
|
rescue ::ArgumentError
|
172
194
|
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
|
173
195
|
end
|
@@ -186,10 +208,19 @@ module Liquid
|
|
186
208
|
offset = Utils.to_integer(offset)
|
187
209
|
length = length ? Utils.to_integer(length) : 1
|
188
210
|
|
189
|
-
|
190
|
-
input.
|
191
|
-
|
192
|
-
|
211
|
+
begin
|
212
|
+
if input.is_a?(Array)
|
213
|
+
input.slice(offset, length) || []
|
214
|
+
else
|
215
|
+
input.to_s.slice(offset, length) || ''
|
216
|
+
end
|
217
|
+
rescue RangeError
|
218
|
+
if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
|
219
|
+
raise # unexpected error
|
220
|
+
end
|
221
|
+
offset = offset.clamp(I64_RANGE)
|
222
|
+
length = length.clamp(I64_RANGE)
|
223
|
+
retry
|
193
224
|
end
|
194
225
|
end
|
195
226
|
|
@@ -239,9 +270,9 @@ module Liquid
|
|
239
270
|
wordlist = begin
|
240
271
|
input.split(" ", words + 1)
|
241
272
|
rescue RangeError
|
242
|
-
|
243
|
-
|
244
|
-
raise
|
273
|
+
# integer too big for String#split, but we can semantically assume no truncation is needed
|
274
|
+
return input if words + 1 > MAX_I32
|
275
|
+
raise # unexpected error
|
245
276
|
end
|
246
277
|
return input if wordlist.length <= words
|
247
278
|
|
@@ -599,7 +630,7 @@ module Liquid
|
|
599
630
|
# @liquid_description
|
600
631
|
# > Note:
|
601
632
|
# > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
|
602
|
-
# > [`uniq` filter](/api/liquid/filters
|
633
|
+
# > [`uniq` filter](/docs/api/liquid/filters/uniq).
|
603
634
|
# @liquid_syntax array | concat: array
|
604
635
|
# @liquid_return [array[untyped]]
|
605
636
|
def concat(input, array)
|
@@ -741,7 +772,7 @@ module Liquid
|
|
741
772
|
# @liquid_type filter
|
742
773
|
# @liquid_category math
|
743
774
|
# @liquid_summary
|
744
|
-
# Divides a number by a given number.
|
775
|
+
# Divides a number by a given number. The `divided_by` filter produces a result of the same type as the divisor. This means if you divide by an integer, the result will be an integer, and if you divide by a float, the result will be a float.
|
745
776
|
# @liquid_syntax number | divided_by: number
|
746
777
|
# @liquid_return [number]
|
747
778
|
def divided_by(input, operand)
|
@@ -841,9 +872,9 @@ module Liquid
|
|
841
872
|
# @liquid_summary
|
842
873
|
# Sets a default value for any variable whose value is one of the following:
|
843
874
|
#
|
844
|
-
# - [`empty`](/api/liquid/basics#empty)
|
845
|
-
# - [`false`](/api/liquid/basics#truthy-and-falsy)
|
846
|
-
# - [`nil`](/api/liquid/basics#nil)
|
875
|
+
# - [`empty`](/docs/api/liquid/basics#empty)
|
876
|
+
# - [`false`](/docs/api/liquid/basics#truthy-and-falsy)
|
877
|
+
# - [`nil`](/docs/api/liquid/basics#nil)
|
847
878
|
# @liquid_syntax variable | default: variable
|
848
879
|
# @liquid_return [untyped]
|
849
880
|
# @liquid_optional_param allow_false [boolean] Whether to use false values instead of the default.
|
@@ -853,6 +884,36 @@ module Liquid
|
|
853
884
|
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
854
885
|
end
|
855
886
|
|
887
|
+
# @liquid_public_docs
|
888
|
+
# @liquid_type filter
|
889
|
+
# @liquid_category array
|
890
|
+
# @liquid_summary
|
891
|
+
# Returns the sum of all elements in an array.
|
892
|
+
# @liquid_syntax array | sum
|
893
|
+
# @liquid_return [number]
|
894
|
+
def sum(input, property = nil)
|
895
|
+
ary = InputIterator.new(input, context)
|
896
|
+
return 0 if ary.empty?
|
897
|
+
|
898
|
+
values_for_sum = ary.map do |item|
|
899
|
+
if property.nil?
|
900
|
+
item
|
901
|
+
elsif item.respond_to?(:[])
|
902
|
+
item[property]
|
903
|
+
else
|
904
|
+
0
|
905
|
+
end
|
906
|
+
rescue TypeError
|
907
|
+
raise_property_error(property)
|
908
|
+
end
|
909
|
+
|
910
|
+
result = InputIterator.new(values_for_sum, context).sum do |item|
|
911
|
+
Utils.to_number(item)
|
912
|
+
end
|
913
|
+
|
914
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
915
|
+
end
|
916
|
+
|
856
917
|
private
|
857
918
|
|
858
919
|
attr_reader :context
|
@@ -883,6 +944,8 @@ module Liquid
|
|
883
944
|
def nil_safe_casecmp(a, b)
|
884
945
|
if !a.nil? && !b.nil?
|
885
946
|
a.to_s.casecmp(b.to_s)
|
947
|
+
elsif a.nil? && b.nil?
|
948
|
+
0
|
886
949
|
else
|
887
950
|
a.nil? ? 1 : -1
|
888
951
|
end
|
@@ -5,7 +5,7 @@ module Liquid
|
|
5
5
|
# @liquid_type object
|
6
6
|
# @liquid_name tablerowloop
|
7
7
|
# @liquid_summary
|
8
|
-
# Information about a parent [`tablerow` loop](/api/liquid/tags
|
8
|
+
# Information about a parent [`tablerow` loop](/docs/api/liquid/tags/tablerow).
|
9
9
|
class TablerowloopDrop < Drop
|
10
10
|
def initialize(length, cols)
|
11
11
|
@length = length
|
data/lib/liquid/tag/disabler.rb
CHANGED
@@ -3,14 +3,6 @@
|
|
3
3
|
module Liquid
|
4
4
|
class Tag
|
5
5
|
module Disabler
|
6
|
-
module ClassMethods
|
7
|
-
attr_reader :disabled_tags
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.prepended(base)
|
11
|
-
base.extend(ClassMethods)
|
12
|
-
end
|
13
|
-
|
14
6
|
def render_to_output_buffer(context, output)
|
15
7
|
context.with_disabled_tags(self.class.disabled_tags) do
|
16
8
|
super
|
data/lib/liquid/tag.rb
CHANGED
@@ -14,12 +14,18 @@ module Liquid
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def disable_tags(*tag_names)
|
17
|
-
|
18
|
-
|
17
|
+
tag_names += disabled_tags
|
18
|
+
define_singleton_method(:disabled_tags) { tag_names }
|
19
19
|
prepend(Disabler)
|
20
20
|
end
|
21
21
|
|
22
22
|
private :new
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def disabled_tags
|
27
|
+
[]
|
28
|
+
end
|
23
29
|
end
|
24
30
|
|
25
31
|
def initialize(tag_name, markup, parse_context)
|
@@ -48,7 +54,8 @@ module Liquid
|
|
48
54
|
# of the `render_to_output_buffer` method will become the default and the `render`
|
49
55
|
# method will be removed.
|
50
56
|
def render_to_output_buffer(context, output)
|
51
|
-
|
57
|
+
render_result = render(context)
|
58
|
+
output << render_result if render_result
|
52
59
|
output
|
53
60
|
end
|
54
61
|
|
data/lib/liquid/tags/assign.rb
CHANGED
@@ -8,7 +8,7 @@ module Liquid
|
|
8
8
|
# @liquid_summary
|
9
9
|
# Creates a new variable.
|
10
10
|
# @liquid_description
|
11
|
-
# You can create variables of any [basic type](/api/liquid/basics#types), [object](/api/liquid/objects), or object property.
|
11
|
+
# You can create variables of any [basic type](/docs/api/liquid/basics#types), [object](/docs/api/liquid/objects), or object property.
|
12
12
|
# @liquid_syntax
|
13
13
|
# {% assign variable_name = value %}
|
14
14
|
# @liquid_syntax_keyword variable_name The name of the variable being created.
|
data/lib/liquid/tags/break.rb
CHANGED
@@ -15,7 +15,7 @@ module Liquid
|
|
15
15
|
# @liquid_category iteration
|
16
16
|
# @liquid_name break
|
17
17
|
# @liquid_summary
|
18
|
-
# Stops a [`for` loop](/api/liquid/tags
|
18
|
+
# Stops a [`for` loop](/docs/api/liquid/tags/for) from iterating.
|
19
19
|
# @liquid_syntax
|
20
20
|
# {% break %}
|
21
21
|
class Break < Tag
|
data/lib/liquid/tags/case.rb
CHANGED
data/lib/liquid/tags/comment.rb
CHANGED
@@ -8,7 +8,7 @@ module Liquid
|
|
8
8
|
# @liquid_summary
|
9
9
|
# Prevents an expression from being rendered or output.
|
10
10
|
# @liquid_description
|
11
|
-
# Any text inside `comment` tags won't be output, and any Liquid code
|
11
|
+
# Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed.
|
12
12
|
# @liquid_syntax
|
13
13
|
# {% comment %}
|
14
14
|
# content
|
@@ -25,6 +25,65 @@ module Liquid
|
|
25
25
|
def blank?
|
26
26
|
true
|
27
27
|
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def parse_body(body, tokenizer)
|
32
|
+
if parse_context.depth >= MAX_DEPTH
|
33
|
+
raise StackLevelError, "Nesting too deep"
|
34
|
+
end
|
35
|
+
|
36
|
+
parse_context.depth += 1
|
37
|
+
comment_tag_depth = 1
|
38
|
+
|
39
|
+
begin
|
40
|
+
# Consume tokens without creating child nodes.
|
41
|
+
# The children tag doesn't require to be a valid Liquid except the comment and raw tag.
|
42
|
+
# The child comment and raw tag must be closed.
|
43
|
+
while (token = tokenizer.send(:shift))
|
44
|
+
tag_name = if tokenizer.for_liquid_tag
|
45
|
+
next if token.empty? || token.match?(BlockBody::WhitespaceOrNothing)
|
46
|
+
|
47
|
+
tag_name_match = BlockBody::LiquidTagToken.match(token)
|
48
|
+
|
49
|
+
next if tag_name_match.nil?
|
50
|
+
|
51
|
+
tag_name_match[1]
|
52
|
+
else
|
53
|
+
token =~ BlockBody::FullToken
|
54
|
+
Regexp.last_match(2)
|
55
|
+
end
|
56
|
+
|
57
|
+
case tag_name
|
58
|
+
when "raw"
|
59
|
+
parse_raw_tag_body(tokenizer)
|
60
|
+
when "comment"
|
61
|
+
comment_tag_depth += 1
|
62
|
+
when "endcomment"
|
63
|
+
comment_tag_depth -= 1
|
64
|
+
end
|
65
|
+
|
66
|
+
if comment_tag_depth.zero?
|
67
|
+
parse_context.trim_whitespace = (token[-3] == WhitespaceControl) unless tokenizer.for_liquid_tag
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
raise_tag_never_closed(block_name)
|
73
|
+
ensure
|
74
|
+
parse_context.depth -= 1
|
75
|
+
end
|
76
|
+
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_raw_tag_body(tokenizer)
|
81
|
+
while (token = tokenizer.send(:shift))
|
82
|
+
return if token =~ BlockBody::FullTokenPossiblyInvalid && "endraw" == Regexp.last_match(2)
|
83
|
+
end
|
84
|
+
|
85
|
+
raise_tag_never_closed("raw")
|
86
|
+
end
|
28
87
|
end
|
29
88
|
|
30
89
|
Template.register_tag('comment', Comment)
|
data/lib/liquid/tags/continue.rb
CHANGED
@@ -6,7 +6,7 @@ module Liquid
|
|
6
6
|
# @liquid_category iteration
|
7
7
|
# @liquid_name continue
|
8
8
|
# @liquid_summary
|
9
|
-
# Causes a [`for` loop](/api/liquid/tags
|
9
|
+
# Causes a [`for` loop](/docs/api/liquid/tags/for) to skip to the next iteration.
|
10
10
|
# @liquid_syntax
|
11
11
|
# {% continue %}
|
12
12
|
class Continue < Tag
|
data/lib/liquid/tags/cycle.rb
CHANGED
@@ -6,7 +6,7 @@ module Liquid
|
|
6
6
|
# @liquid_category iteration
|
7
7
|
# @liquid_name cycle
|
8
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
|
9
|
+
# Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/docs/api/liquid/tags/for).
|
10
10
|
# @liquid_description
|
11
11
|
# The `cycle` tag must be used inside a `for` loop.
|
12
12
|
#
|
@@ -12,22 +12,25 @@ module Liquid
|
|
12
12
|
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
13
13
|
# [snippets](/themes/architecture#snippets) included in the file.
|
14
14
|
#
|
15
|
-
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/api/liquid/tags
|
16
|
-
# and [`capture`](/api/liquid/tags
|
15
|
+
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
|
16
|
+
# and [`capture`](/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](/docs/api/liquid/tags/increment) share
|
17
17
|
# variables.
|
18
18
|
# @liquid_syntax
|
19
19
|
# {% decrement variable_name %}
|
20
20
|
# @liquid_syntax_keyword variable_name The name of the variable being decremented.
|
21
21
|
class Decrement < Tag
|
22
|
+
attr_reader :variable_name
|
23
|
+
|
22
24
|
def initialize(tag_name, markup, options)
|
23
25
|
super
|
24
|
-
@
|
26
|
+
@variable_name = markup.strip
|
25
27
|
end
|
26
28
|
|
27
29
|
def render_to_output_buffer(context, output)
|
28
|
-
|
30
|
+
counter_environment = context.environments.first
|
31
|
+
value = counter_environment[@variable_name] || 0
|
29
32
|
value -= 1
|
30
|
-
|
33
|
+
counter_environment[@variable_name] = value
|
31
34
|
output << value.to_s
|
32
35
|
output
|
33
36
|
end
|
data/lib/liquid/tags/echo.rb
CHANGED
@@ -9,10 +9,10 @@ module Liquid
|
|
9
9
|
# Outputs an expression.
|
10
10
|
# @liquid_description
|
11
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
|
12
|
+
# bracket method, you can use the `echo` tag inside [`liquid` tags](/docs/api/liquid/tags/liquid).
|
13
13
|
#
|
14
14
|
# > Tip:
|
15
|
-
# > You can use [filters](/api/liquid/filters) on expressions inside `echo` tags.
|
15
|
+
# > You can use [filters](/docs/api/liquid/filters) on expressions inside `echo` tags.
|
16
16
|
# @liquid_syntax
|
17
17
|
# {% liquid
|
18
18
|
# echo expression
|
data/lib/liquid/tags/for.rb
CHANGED
@@ -9,10 +9,10 @@ module Liquid
|
|
9
9
|
# Renders an expression for every item in an array.
|
10
10
|
# @liquid_description
|
11
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
|
12
|
+
# [`paginate` tag](/docs/api/liquid/tags/paginate) to split the items over multiple pages.
|
13
13
|
#
|
14
14
|
# > Tip:
|
15
|
-
# > Every `for` loop has an associated [`forloop` object](/api/liquid/objects
|
15
|
+
# > Every `for` loop has an associated [`forloop` object](/docs/api/liquid/objects/forloop) with information about the loop.
|
16
16
|
# @liquid_syntax
|
17
17
|
# {% for variable in array %}
|
18
18
|
# expression
|
@@ -98,11 +98,12 @@ module Liquid
|
|
98
98
|
@name = "#{@variable_name}-#{collection_name}"
|
99
99
|
@reversed = p.id?('reversed')
|
100
100
|
|
101
|
-
while p.look(:
|
101
|
+
while p.look(:comma) || p.look(:id)
|
102
|
+
p.consume?(:comma)
|
102
103
|
unless (attribute = p.id?('limit') || p.id?('offset'))
|
103
104
|
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
|
104
105
|
end
|
105
|
-
p.consume
|
106
|
+
p.consume(:colon)
|
106
107
|
set_attribute(attribute, p.expression)
|
107
108
|
end
|
108
109
|
p.consume(:end_of_string)
|
@@ -177,7 +178,6 @@ module Liquid
|
|
177
178
|
case key
|
178
179
|
when 'offset'
|
179
180
|
@from = if expr == 'continue'
|
180
|
-
Usage.increment('for_offset_continue')
|
181
181
|
:continue
|
182
182
|
else
|
183
183
|
parse_expression(expr)
|
data/lib/liquid/tags/if.rb
CHANGED
data/lib/liquid/tags/include.rb
CHANGED
@@ -8,7 +8,7 @@ module Liquid
|
|
8
8
|
# @liquid_summary
|
9
9
|
# Renders a [snippet](/themes/architecture#snippets).
|
10
10
|
# @liquid_description
|
11
|
-
# Inside the snippet, you can access and alter variables that are [created](/api/liquid/tags
|
11
|
+
# Inside the snippet, you can access and alter variables that are [created](/docs/api/liquid/tags/variable-tags) outside of the
|
12
12
|
# snippet.
|
13
13
|
# @liquid_syntax
|
14
14
|
# {% include 'filename' %}
|
@@ -16,7 +16,7 @@ module Liquid
|
|
16
16
|
# @liquid_deprecated
|
17
17
|
# Deprecated because the way that variables are handled reduces performance and makes code harder to both read and maintain.
|
18
18
|
#
|
19
|
-
# The `include` tag has been replaced by [`render`](/api/liquid/tags
|
19
|
+
# The `include` tag has been replaced by [`render`](/docs/api/liquid/tags/render).
|
20
20
|
class Include < Tag
|
21
21
|
prepend Tag::Disableable
|
22
22
|
|
@@ -52,12 +52,12 @@ module Liquid
|
|
52
52
|
|
53
53
|
def render_to_output_buffer(context, output)
|
54
54
|
template_name = context.evaluate(@template_name_expr)
|
55
|
-
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
|
55
|
+
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name.is_a?(String)
|
56
56
|
|
57
57
|
partial = PartialCache.load(
|
58
58
|
template_name,
|
59
59
|
context: context,
|
60
|
-
parse_context: parse_context
|
60
|
+
parse_context: parse_context,
|
61
61
|
)
|
62
62
|
|
63
63
|
context_variable_name = @alias_name || template_name.split('/').last
|
@@ -70,9 +70,11 @@ module Liquid
|
|
70
70
|
|
71
71
|
old_template_name = context.template_name
|
72
72
|
old_partial = context.partial
|
73
|
+
|
73
74
|
begin
|
74
|
-
context.template_name =
|
75
|
-
context.partial
|
75
|
+
context.template_name = partial.name
|
76
|
+
context.partial = true
|
77
|
+
|
76
78
|
context.stack do
|
77
79
|
@attributes.each do |key, value|
|
78
80
|
context[key] = context.evaluate(value)
|
@@ -12,21 +12,24 @@ module Liquid
|
|
12
12
|
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
13
13
|
# [snippets](/themes/architecture#snippets) included in the file.
|
14
14
|
#
|
15
|
-
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/api/liquid/tags
|
16
|
-
# and [`capture`](/api/liquid/tags
|
15
|
+
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
|
16
|
+
# and [`capture`](/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](/docs/api/liquid/tags/decrement) share
|
17
17
|
# variables.
|
18
18
|
# @liquid_syntax
|
19
19
|
# {% increment variable_name %}
|
20
20
|
# @liquid_syntax_keyword variable_name The name of the variable being incremented.
|
21
21
|
class Increment < Tag
|
22
|
+
attr_reader :variable_name
|
23
|
+
|
22
24
|
def initialize(tag_name, markup, options)
|
23
25
|
super
|
24
|
-
@
|
26
|
+
@variable_name = markup.strip
|
25
27
|
end
|
26
28
|
|
27
29
|
def render_to_output_buffer(context, output)
|
28
|
-
|
29
|
-
|
30
|
+
counter_environment = context.environments.first
|
31
|
+
value = counter_environment[@variable_name] || 0
|
32
|
+
counter_environment[@variable_name] = value + 1
|
30
33
|
|
31
34
|
output << value.to_s
|
32
35
|
output
|
@@ -1,19 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Liquid
|
4
|
-
# @liquid_public_docs
|
5
|
-
# @liquid_type tag
|
6
|
-
# @liquid_category syntax
|
7
|
-
# @liquid_name inline_comment
|
8
|
-
# @liquid_summary
|
9
|
-
# Prevents an expression from being rendered or output.
|
10
|
-
# @liquid_description
|
11
|
-
# Any text inside an `inline_comment` tag won't be rendered or output.
|
12
|
-
#
|
13
|
-
# You can create multi-line inline comments. However, each line must begin with a `#`.
|
14
|
-
# @liquid_syntax
|
15
|
-
# {% # content %}
|
16
|
-
# @liquid_syntax_keyword content The content of the comment.
|
17
4
|
class InlineComment < Tag
|
18
5
|
def initialize(tag_name, markup, options)
|
19
6
|
super
|
data/lib/liquid/tags/raw.rb
CHANGED
@@ -14,7 +14,6 @@ module Liquid
|
|
14
14
|
# @liquid_syntax_keyword expression The expression to be output without being rendered.
|
15
15
|
class Raw < Block
|
16
16
|
Syntax = /\A\s*\z/
|
17
|
-
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
18
17
|
|
19
18
|
def initialize(tag_name, markup, parse_context)
|
20
19
|
super
|
@@ -25,7 +24,8 @@ module Liquid
|
|
25
24
|
def parse(tokens)
|
26
25
|
@body = +''
|
27
26
|
while (token = tokens.shift)
|
28
|
-
if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
|
27
|
+
if token =~ BlockBody::FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
|
28
|
+
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
29
29
|
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
|
30
30
|
return
|
31
31
|
end
|
data/lib/liquid/tags/render.rb
CHANGED
@@ -8,19 +8,19 @@ module Liquid
|
|
8
8
|
# @liquid_summary
|
9
9
|
# Renders a [snippet](/themes/architecture#snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
|
10
10
|
# @liquid_description
|
11
|
-
# Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags
|
12
|
-
# of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags#render-passing-variables-to-
|
11
|
+
# Inside snippets and app blocks, you can't directly access variables that are [created](/docs/api/liquid/tags/variable-tags) outside
|
12
|
+
# of the snippet or app block. However, you can [specify variables as parameters](/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet)
|
13
13
|
# to pass outside variables to snippets.
|
14
14
|
#
|
15
15
|
# While you can't directly access created variables, you can access global objects, as well as any objects that are
|
16
16
|
# directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
|
17
|
-
# can access the [`product` object](/api/liquid/objects
|
18
|
-
# can access the [`section` object](/api/liquid/objects
|
17
|
+
# can access the [`product` object](/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
|
18
|
+
# can access the [`section` object](/docs/api/liquid/objects/section).
|
19
19
|
#
|
20
20
|
# Outside a snippet or app block, you can't access variables created inside the snippet or app block.
|
21
21
|
#
|
22
22
|
# > Note:
|
23
|
-
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags
|
23
|
+
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/docs/api/liquid/tags/include)
|
24
24
|
# > inside the snippet.
|
25
25
|
# @liquid_syntax
|
26
26
|
# {% render 'filename' %}
|
@@ -31,7 +31,7 @@ module Liquid
|
|
31
31
|
|
32
32
|
disable_tags "include"
|
33
33
|
|
34
|
-
attr_reader :template_name_expr, :variable_name_expr, :attributes
|
34
|
+
attr_reader :template_name_expr, :variable_name_expr, :attributes, :alias_name
|
35
35
|
|
36
36
|
def initialize(tag_name, markup, options)
|
37
37
|
super
|
@@ -45,7 +45,7 @@ module Liquid
|
|
45
45
|
@alias_name = Regexp.last_match(6)
|
46
46
|
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
47
47
|
@template_name_expr = parse_expression(template_name)
|
48
|
-
@
|
48
|
+
@is_for_loop = (with_or_for == FOR)
|
49
49
|
|
50
50
|
@attributes = {}
|
51
51
|
markup.scan(TagAttributes) do |key, value|
|
@@ -53,6 +53,10 @@ module Liquid
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
+
def for_loop?
|
57
|
+
@is_for_loop
|
58
|
+
end
|
59
|
+
|
56
60
|
def render_to_output_buffer(context, output)
|
57
61
|
render_tag(context, output)
|
58
62
|
end
|
@@ -65,14 +69,14 @@ module Liquid
|
|
65
69
|
partial = PartialCache.load(
|
66
70
|
template_name,
|
67
71
|
context: context,
|
68
|
-
parse_context: parse_context
|
72
|
+
parse_context: parse_context,
|
69
73
|
)
|
70
74
|
|
71
75
|
context_variable_name = @alias_name || template_name.split('/').last
|
72
76
|
|
73
77
|
render_partial_func = ->(var, forloop) {
|
74
78
|
inner_context = context.new_isolated_subcontext
|
75
|
-
inner_context.template_name =
|
79
|
+
inner_context.template_name = partial.name
|
76
80
|
inner_context.partial = true
|
77
81
|
inner_context['forloop'] = forloop if forloop
|
78
82
|
|
@@ -85,7 +89,7 @@ module Liquid
|
|
85
89
|
}
|
86
90
|
|
87
91
|
variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
|
88
|
-
if @
|
92
|
+
if @is_for_loop && variable.respond_to?(:each) && variable.respond_to?(:count)
|
89
93
|
forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
|
90
94
|
variable.each { |var| render_partial_func.call(var, forloop) }
|
91
95
|
else
|
@@ -11,7 +11,7 @@ module Liquid
|
|
11
11
|
# The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
|
12
12
|
#
|
13
13
|
# > Tip:
|
14
|
-
# > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects
|
14
|
+
# > Every `tablerow` loop has an associated [`tablerowloop` object](/docs/api/liquid/objects/tablerowloop) with information about the loop.
|
15
15
|
# @liquid_syntax
|
16
16
|
# {% tablerow variable in array %}
|
17
17
|
# expression
|
@@ -45,13 +45,13 @@ module Liquid
|
|
45
45
|
def render_to_output_buffer(context, output)
|
46
46
|
(collection = context.evaluate(@collection_name)) || (return '')
|
47
47
|
|
48
|
-
from = @attributes.key?('offset') ? context.evaluate(@attributes['offset'])
|
49
|
-
to
|
48
|
+
from = @attributes.key?('offset') ? to_integer(context.evaluate(@attributes['offset'])) : 0
|
49
|
+
to = @attributes.key?('limit') ? from + to_integer(context.evaluate(@attributes['limit'])) : nil
|
50
50
|
|
51
51
|
collection = Utils.slice_collection(collection, from, to)
|
52
52
|
length = collection.length
|
53
53
|
|
54
|
-
cols = context.evaluate(@attributes['cols'])
|
54
|
+
cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length
|
55
55
|
|
56
56
|
output << "<tr class=\"row1\">\n"
|
57
57
|
context.stack do
|
@@ -82,6 +82,14 @@ module Liquid
|
|
82
82
|
super + @node.attributes.values + [@node.collection_name]
|
83
83
|
end
|
84
84
|
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def to_integer(value)
|
89
|
+
value.to_i
|
90
|
+
rescue NoMethodError
|
91
|
+
raise Liquid::ArgumentError, "invalid integer"
|
92
|
+
end
|
85
93
|
end
|
86
94
|
|
87
95
|
Template.register_tag('tablerow', TableRow)
|
data/lib/liquid/tags/unless.rb
CHANGED
@@ -11,7 +11,7 @@ module Liquid
|
|
11
11
|
# Renders an expression unless a specific condition is `true`.
|
12
12
|
# @liquid_description
|
13
13
|
# > Tip:
|
14
|
-
# > Similar to the [`if` tag](/api/liquid/tags
|
14
|
+
# > Similar to the [`if` tag](/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
|
15
15
|
# @liquid_syntax
|
16
16
|
# {% unless condition %}
|
17
17
|
# expression
|
@@ -23,7 +23,7 @@ module Liquid
|
|
23
23
|
# First condition is interpreted backwards ( if not )
|
24
24
|
first_block = @blocks.first
|
25
25
|
result = Liquid::Utils.to_liquid_value(
|
26
|
-
first_block.evaluate(context)
|
26
|
+
first_block.evaluate(context),
|
27
27
|
)
|
28
28
|
|
29
29
|
unless result
|
@@ -33,7 +33,7 @@ module Liquid
|
|
33
33
|
# After the first condition unless works just like if
|
34
34
|
@blocks[1..-1].each do |block|
|
35
35
|
result = Liquid::Utils.to_liquid_value(
|
36
|
-
block.evaluate(context)
|
36
|
+
block.evaluate(context),
|
37
37
|
)
|
38
38
|
|
39
39
|
if result
|
data/lib/liquid/template.rb
CHANGED
@@ -15,7 +15,7 @@ module Liquid
|
|
15
15
|
# template.render('user_name' => 'bob')
|
16
16
|
#
|
17
17
|
class Template
|
18
|
-
attr_accessor :root
|
18
|
+
attr_accessor :root, :name
|
19
19
|
attr_reader :resource_limits, :warnings
|
20
20
|
|
21
21
|
class TagRegistry
|
@@ -107,6 +107,12 @@ module Liquid
|
|
107
107
|
# Returns self for easy chaining
|
108
108
|
def parse(source, options = {})
|
109
109
|
parse_context = configure_options(options)
|
110
|
+
source = source.to_s.to_str
|
111
|
+
|
112
|
+
unless source.valid_encoding?
|
113
|
+
raise TemplateEncodingError, parse_context.locale.t("errors.syntax.invalid_template_encoding")
|
114
|
+
end
|
115
|
+
|
110
116
|
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
|
111
117
|
@root = Document.parse(tokenizer, parse_context)
|
112
118
|
self
|
@@ -189,6 +195,8 @@ module Liquid
|
|
189
195
|
@profiler = context.profiler = Liquid::Profiler.new
|
190
196
|
end
|
191
197
|
|
198
|
+
context.template_name ||= name
|
199
|
+
|
192
200
|
begin
|
193
201
|
# render the nodelist.
|
194
202
|
@root.render_to_output_buffer(context, output || +'')
|
data/lib/liquid/tokenizer.rb
CHANGED
@@ -5,14 +5,18 @@ module Liquid
|
|
5
5
|
attr_reader :line_number, :for_liquid_tag
|
6
6
|
|
7
7
|
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
|
8
|
-
@source = source
|
8
|
+
@source = source
|
9
9
|
@line_number = line_number || (line_numbers ? 1 : nil)
|
10
10
|
@for_liquid_tag = for_liquid_tag
|
11
|
+
@offset = 0
|
11
12
|
@tokens = tokenize
|
12
13
|
end
|
13
14
|
|
14
15
|
def shift
|
15
|
-
|
16
|
+
token = @tokens[@offset]
|
17
|
+
return nil unless token
|
18
|
+
|
19
|
+
@offset += 1
|
16
20
|
|
17
21
|
if @line_number
|
18
22
|
@line_number += @for_liquid_tag ? 1 : token.count("\n")
|
@@ -31,7 +35,9 @@ module Liquid
|
|
31
35
|
tokens = @source.split(TemplateParser)
|
32
36
|
|
33
37
|
# removes the rogue empty element at the beginning of the array
|
34
|
-
|
38
|
+
if tokens[0]&.empty?
|
39
|
+
@offset += 1
|
40
|
+
end
|
35
41
|
|
36
42
|
tokens
|
37
43
|
end
|
data/lib/liquid/version.rb
CHANGED
data/lib/liquid.rb
CHANGED
@@ -41,7 +41,7 @@ module Liquid
|
|
41
41
|
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
|
42
42
|
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
43
43
|
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
44
|
-
VariableParser = /\[[^\]]
|
44
|
+
VariableParser = /\[(?>[^\[\]]+|\g<0>)*\]|#{VariableSegment}+\??/o
|
45
45
|
|
46
46
|
RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
|
47
47
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: liquid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tobias Lütke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -130,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: 1.3.7
|
132
132
|
requirements: []
|
133
|
-
rubygems_version: 3.
|
133
|
+
rubygems_version: 3.5.6
|
134
134
|
signing_key:
|
135
135
|
specification_version: 4
|
136
136
|
summary: A secure, non-evaling end user template engine with aesthetic markup.
|