liquid 5.12.0 → 5.14.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 +16 -0
- data/README.md +7 -0
- data/lib/liquid/block_body.rb +3 -1
- data/lib/liquid/context.rb +19 -1
- data/lib/liquid/expression.rb +2 -0
- data/lib/liquid/parse_context.rb +1 -1
- data/lib/liquid/parser.rb +5 -1
- data/lib/liquid/range_slice.rb +44 -0
- data/lib/liquid/self_drop.rb +54 -0
- data/lib/liquid/standardfilters.rb +16 -7
- data/lib/liquid/tags/assign.rb +25 -0
- data/lib/liquid/tags/capture.rb +18 -0
- data/lib/liquid/tags/decrement.rb +16 -0
- data/lib/liquid/tags/for.rb +3 -2
- data/lib/liquid/tags/include.rb +13 -4
- data/lib/liquid/tags/increment.rb +16 -0
- data/lib/liquid/tags/table_row.rb +5 -1
- data/lib/liquid/template.rb +13 -2
- data/lib/liquid/utils.rb +69 -0
- data/lib/liquid/variable.rb +4 -0
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 160a429434128c743e92aa594ff0c0da4442ff24eaafec960b3d3bc877bd623d
|
|
4
|
+
data.tar.gz: 86b6682d426999c6f1528c3898671414f0f96ebf955498535e30171c639e380f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8f9fb1adebc5974b7f4e0eca38947335b359cb30fc6c6ae4058516573d760695c5ab8ef2cbf1f4cc2226d23d8a27126faf407b5da7ccc244a0b976491f0a3328
|
|
7
|
+
data.tar.gz: d7d200ba0a5b62d4e86bcf197f7f7a06ed2b448c25e73e196787164892b517b24d3fc4fa3f6296d03945f06755d8004590b8dc1bd0df13743ba4f8ba805e2939
|
data/History.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Liquid Change Log
|
|
2
2
|
|
|
3
|
+
## 5.14.0
|
|
4
|
+
|
|
5
|
+
* Avoid materializing integer ranges in `for` and `tablerow` loops, and account for each visited range item [Ian Ker-Seymer]
|
|
6
|
+
|
|
7
|
+
## 5.13.0
|
|
8
|
+
|
|
9
|
+
* Add TruffleRuby in CI [Benoit Daloze]
|
|
10
|
+
* Skip slow test raising many exceptions on non-CRuby [Benoit Daloze]
|
|
11
|
+
* Reject bare-bracket syntax in strict2 and introduce `self` keyword by [Alok Swamy]
|
|
12
|
+
* Add strict2_parse to assign and capture tags by [Alok Swamy]
|
|
13
|
+
* Add strict2_parse to increment and decrement tags by [Alok Swamy]
|
|
14
|
+
* Update liquid-spec adapters for `missing_features` [Ian Ker-Seymer]
|
|
15
|
+
* Prevent `SelfDrop` context mutation across render boundaries [Guilherme Carreiro]
|
|
16
|
+
* Fix `SelfDrop` equality [Guilherme Carreiro]
|
|
17
|
+
* Let environment `self` shadow `SelfDrop` [Ian Ker-Seymer]
|
|
18
|
+
|
|
3
19
|
## 5.11.0
|
|
4
20
|
* Revert the Inline Snippets tag (#2001), treat its inclusion in the latest Liquid release as a bug, and allow for feedback on RFC#1916 to better support Liquid developers [Guilherme Carreiro]
|
|
5
21
|
* Rename the `:rigid` error mode to `:strict2` and display a warning when users attempt to use the `:rigid` mode [Guilherme Carreiro]
|
data/README.md
CHANGED
|
@@ -149,6 +149,13 @@ template.render!({ 'x' => 1}, { strict_variables: true })
|
|
|
149
149
|
#=> Liquid::UndefinedVariable: Liquid error: undefined variable y
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
### Resource limits
|
|
153
|
+
|
|
154
|
+
`render_score_limit` and `cumulative_render_score_limit` account for each item visited by
|
|
155
|
+
integer-range `for` and `tablerow` loops, including loops with empty bodies. This bounds range
|
|
156
|
+
iteration work when a score limit is configured; `render_length_limit` only bounds generated
|
|
157
|
+
output and does not by itself limit CPU work for output-free loops.
|
|
158
|
+
|
|
152
159
|
### Usage tracking
|
|
153
160
|
|
|
154
161
|
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.
|
data/lib/liquid/block_body.rb
CHANGED
|
@@ -99,7 +99,9 @@ module Liquid
|
|
|
99
99
|
context.handle_error(exc, line_number)
|
|
100
100
|
else
|
|
101
101
|
error_message = context.handle_error(exc, line_number)
|
|
102
|
-
|
|
102
|
+
error_mode = context.registers.static[:template_error_mode]
|
|
103
|
+
suppress_error_text = blank_tag && error_mode != :strict2 && error_mode != :rigid
|
|
104
|
+
unless suppress_error_text # blank-tag suppression is kept for backwards compatibility outside strict2
|
|
103
105
|
output << error_message
|
|
104
106
|
end
|
|
105
107
|
end
|
data/lib/liquid/context.rb
CHANGED
|
@@ -187,6 +187,15 @@ module Liquid
|
|
|
187
187
|
find_variable(key, raise_on_not_found: false) != nil
|
|
188
188
|
end
|
|
189
189
|
|
|
190
|
+
# Checks whether a variable is defined in any scope, including nil-valued keys.
|
|
191
|
+
# Unlike #key?, this uses Hash#key? so that variables explicitly set to nil
|
|
192
|
+
# are still considered defined.
|
|
193
|
+
def variable_defined?(key)
|
|
194
|
+
@scopes.any? { |s| s.key?(key) } ||
|
|
195
|
+
@environments.any? { |e| e.key?(key) } ||
|
|
196
|
+
@static_environments.any? { |e| e.key?(key) }
|
|
197
|
+
end
|
|
198
|
+
|
|
190
199
|
def evaluate(object)
|
|
191
200
|
object.respond_to?(:evaluate) ? object.evaluate(self) : object
|
|
192
201
|
end
|
|
@@ -197,12 +206,21 @@ module Liquid
|
|
|
197
206
|
# path and find_index() is optimized in MRI to reduce object allocation
|
|
198
207
|
index = @scopes.find_index { |s| s.key?(key) }
|
|
199
208
|
|
|
209
|
+
fallback_to_self_drop = key == Expression::SELF && index.nil?
|
|
210
|
+
|
|
200
211
|
variable = if index
|
|
201
212
|
lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
|
|
202
213
|
else
|
|
203
|
-
try_variable_find_in_environments(
|
|
214
|
+
try_variable_find_in_environments(
|
|
215
|
+
key,
|
|
216
|
+
raise_on_not_found: raise_on_not_found && !fallback_to_self_drop,
|
|
217
|
+
)
|
|
204
218
|
end
|
|
205
219
|
|
|
220
|
+
# `self` resolves to a SelfDrop (enabling `self['var']` lookups),
|
|
221
|
+
# but only after the normal environment lookup doesn't find a value.
|
|
222
|
+
return @self_drop ||= SelfDrop.new(self) if fallback_to_self_drop && variable.nil?
|
|
223
|
+
|
|
206
224
|
# update variable's context before invoking #to_liquid
|
|
207
225
|
variable.context = self if variable.respond_to?(:context=)
|
|
208
226
|
|
data/lib/liquid/expression.rb
CHANGED
data/lib/liquid/parse_context.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Liquid
|
|
|
38
38
|
|
|
39
39
|
def new_parser(input)
|
|
40
40
|
@string_scanner.string = input
|
|
41
|
-
Parser.new(@string_scanner)
|
|
41
|
+
Parser.new(@string_scanner, reject_bare_brackets: @error_mode == :strict2 || @error_mode == :rigid)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def new_tokenizer(source, start_line_number: nil, for_liquid_tag: false)
|
data/lib/liquid/parser.rb
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Liquid
|
|
4
4
|
class Parser
|
|
5
|
-
def initialize(input)
|
|
5
|
+
def initialize(input, reject_bare_brackets: false)
|
|
6
6
|
ss = input.is_a?(StringScanner) ? input : StringScanner.new(input)
|
|
7
7
|
@tokens = Lexer.tokenize(ss)
|
|
8
8
|
@p = 0 # pointer to current location
|
|
9
|
+
@reject_bare_brackets = reject_bare_brackets
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def jump(point)
|
|
@@ -53,6 +54,9 @@ module Liquid
|
|
|
53
54
|
str = consume
|
|
54
55
|
str << variable_lookups
|
|
55
56
|
when :open_square
|
|
57
|
+
if @reject_bare_brackets
|
|
58
|
+
raise SyntaxError, "Bare bracket access is not allowed. Use #{Expression::SELF}['...'] instead"
|
|
59
|
+
end
|
|
56
60
|
str = consume.dup
|
|
57
61
|
str << expression
|
|
58
62
|
str << consume(:close_square)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liquid
|
|
4
|
+
class RangeSlice
|
|
5
|
+
attr_reader :length
|
|
6
|
+
|
|
7
|
+
def initialize(range, from, to, resource_limits)
|
|
8
|
+
range_length = range.end - range.begin
|
|
9
|
+
range_length += 1 unless range.exclude_end?
|
|
10
|
+
range_length = 0 if range_length.negative?
|
|
11
|
+
|
|
12
|
+
start = [from, 0].max
|
|
13
|
+
finish = [to || range_length, range_length].min
|
|
14
|
+
|
|
15
|
+
@first = range.begin + start
|
|
16
|
+
@length = [finish - start, 0].max
|
|
17
|
+
@direction = 1
|
|
18
|
+
@resource_limits = resource_limits
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def empty?
|
|
22
|
+
@length.zero?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def each
|
|
26
|
+
return enum_for(:each) unless block_given?
|
|
27
|
+
|
|
28
|
+
value = @first
|
|
29
|
+
@length.times do
|
|
30
|
+
@resource_limits.increment_render_score(1)
|
|
31
|
+
yield value
|
|
32
|
+
value += @direction
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reverse!
|
|
37
|
+
unless empty?
|
|
38
|
+
@first += @direction * (@length - 1)
|
|
39
|
+
@direction = -@direction
|
|
40
|
+
end
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liquid
|
|
4
|
+
# @liquid_public_docs
|
|
5
|
+
# @liquid_type object
|
|
6
|
+
# @liquid_name self
|
|
7
|
+
# @liquid_summary
|
|
8
|
+
# Provides access to variables through the current scope chain.
|
|
9
|
+
# @liquid_description
|
|
10
|
+
# The `self` object resolves variables through the normal lookup hierarchy
|
|
11
|
+
# (local > file > global) without exposing filters, interrupts, errors,
|
|
12
|
+
# or other context internals. It's used when bare bracket notation
|
|
13
|
+
# (`['variable']`) needs to be replaced with an explicit variable lookup.
|
|
14
|
+
#
|
|
15
|
+
# If `self` is explicitly assigned as a local variable (e.g. `{% assign self = 'value' %}`),
|
|
16
|
+
# then the local value takes precedence over the `self` object.
|
|
17
|
+
# @liquid_access global
|
|
18
|
+
class SelfDrop < Drop
|
|
19
|
+
def initialize(self_context)
|
|
20
|
+
super()
|
|
21
|
+
@self_context = self_context
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def [](key)
|
|
25
|
+
@self_context.find_variable(key)
|
|
26
|
+
rescue UndefinedVariable
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def key?(key)
|
|
31
|
+
@self_context.variable_defined?(key)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_liquid
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ==(other)
|
|
39
|
+
other.is_a?(SelfDrop) && other.self_context.equal?(@self_context)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
alias_method :eql?, :==
|
|
43
|
+
|
|
44
|
+
def hash
|
|
45
|
+
@self_context.object_id.hash
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
protected
|
|
49
|
+
|
|
50
|
+
attr_reader :self_context
|
|
51
|
+
|
|
52
|
+
undef context=
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -8,10 +8,19 @@ module Liquid
|
|
|
8
8
|
MAX_I32 = (1 << 31) - 1
|
|
9
9
|
private_constant :MAX_I32
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
supports_64bit_indices = begin
|
|
12
|
+
[][1 << 33, 1 << 33]
|
|
13
|
+
true
|
|
14
|
+
rescue RangeError
|
|
15
|
+
false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
INDEX_RANGE = if supports_64bit_indices
|
|
19
|
+
(-(1 << 63))..((1 << 63) - 1)
|
|
20
|
+
else
|
|
21
|
+
(-(1 << 31))..((1 << 31) - 1)
|
|
22
|
+
end
|
|
23
|
+
private_constant :INDEX_RANGE
|
|
15
24
|
|
|
16
25
|
HTML_ESCAPE = {
|
|
17
26
|
'&' => '&',
|
|
@@ -214,11 +223,11 @@ module Liquid
|
|
|
214
223
|
Utils.to_s(input).slice(offset, length) || ''
|
|
215
224
|
end
|
|
216
225
|
rescue RangeError
|
|
217
|
-
if
|
|
226
|
+
if INDEX_RANGE.cover?(length) && INDEX_RANGE.cover?(offset)
|
|
218
227
|
raise # unexpected error
|
|
219
228
|
end
|
|
220
|
-
offset = offset.clamp(
|
|
221
|
-
length = length.clamp(
|
|
229
|
+
offset = offset.clamp(INDEX_RANGE)
|
|
230
|
+
length = length.clamp(INDEX_RANGE)
|
|
222
231
|
retry
|
|
223
232
|
end
|
|
224
233
|
end
|
data/lib/liquid/tags/assign.rb
CHANGED
|
@@ -18,6 +18,8 @@ module Liquid
|
|
|
18
18
|
# @liquid_syntax_keyword variable_name The name of the variable being created.
|
|
19
19
|
# @liquid_syntax_keyword value The value you want to assign to the variable.
|
|
20
20
|
class Assign < Tag
|
|
21
|
+
include ParserSwitching
|
|
22
|
+
|
|
21
23
|
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
|
22
24
|
|
|
23
25
|
# @api private
|
|
@@ -29,6 +31,10 @@ module Liquid
|
|
|
29
31
|
|
|
30
32
|
def initialize(tag_name, markup, parse_context)
|
|
31
33
|
super
|
|
34
|
+
parse_with_selected_parser(markup)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def lax_parse(markup)
|
|
32
38
|
if markup =~ Syntax
|
|
33
39
|
@to = Regexp.last_match(1)
|
|
34
40
|
@from = Variable.new(Regexp.last_match(2), parse_context)
|
|
@@ -37,6 +43,25 @@ module Liquid
|
|
|
37
43
|
end
|
|
38
44
|
end
|
|
39
45
|
|
|
46
|
+
def strict_parse(markup)
|
|
47
|
+
lax_parse(markup)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def strict2_parse(markup)
|
|
51
|
+
unless markup =~ Syntax
|
|
52
|
+
self.class.raise_syntax_error(parse_context)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
lhs = Regexp.last_match(1).strip
|
|
56
|
+
rhs = Regexp.last_match(2)
|
|
57
|
+
|
|
58
|
+
p = @parse_context.new_parser(lhs)
|
|
59
|
+
@to = p.consume(:id)
|
|
60
|
+
p.consume(:end_of_string)
|
|
61
|
+
|
|
62
|
+
@from = Variable.new(rhs, parse_context)
|
|
63
|
+
end
|
|
64
|
+
|
|
40
65
|
def render_to_output_buffer(context, output)
|
|
41
66
|
val = @from.render(context)
|
|
42
67
|
context.scopes.last[@to] = val
|
data/lib/liquid/tags/capture.rb
CHANGED
|
@@ -20,10 +20,18 @@ module Liquid
|
|
|
20
20
|
# @liquid_syntax_keyword variable The name of the variable being created.
|
|
21
21
|
# @liquid_syntax_keyword value The value you want to assign to the variable.
|
|
22
22
|
class Capture < Block
|
|
23
|
+
include ParserSwitching
|
|
24
|
+
|
|
23
25
|
Syntax = /(#{VariableSignature}+)/o
|
|
24
26
|
|
|
27
|
+
attr_reader :to
|
|
28
|
+
|
|
25
29
|
def initialize(tag_name, markup, options)
|
|
26
30
|
super
|
|
31
|
+
parse_with_selected_parser(markup)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def lax_parse(markup)
|
|
27
35
|
if markup =~ Syntax
|
|
28
36
|
@to = Regexp.last_match(1)
|
|
29
37
|
else
|
|
@@ -31,6 +39,16 @@ module Liquid
|
|
|
31
39
|
end
|
|
32
40
|
end
|
|
33
41
|
|
|
42
|
+
def strict_parse(markup)
|
|
43
|
+
lax_parse(markup)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def strict2_parse(markup)
|
|
47
|
+
p = @parse_context.new_parser(markup.strip)
|
|
48
|
+
@to = p.consume(:id)
|
|
49
|
+
p.consume(:end_of_string)
|
|
50
|
+
end
|
|
51
|
+
|
|
34
52
|
def render_to_output_buffer(context, output)
|
|
35
53
|
context.resource_limits.with_capture do
|
|
36
54
|
capture_output = render(context)
|
|
@@ -23,13 +23,29 @@ module Liquid
|
|
|
23
23
|
# {% decrement variable_name %}
|
|
24
24
|
# @liquid_syntax_keyword variable_name The name of the variable being decremented.
|
|
25
25
|
class Decrement < Tag
|
|
26
|
+
include ParserSwitching
|
|
27
|
+
|
|
26
28
|
attr_reader :variable_name
|
|
27
29
|
|
|
28
30
|
def initialize(tag_name, markup, options)
|
|
29
31
|
super
|
|
32
|
+
parse_with_selected_parser(markup)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def lax_parse(markup)
|
|
30
36
|
@variable_name = markup.strip
|
|
31
37
|
end
|
|
32
38
|
|
|
39
|
+
def strict_parse(markup)
|
|
40
|
+
lax_parse(markup)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def strict2_parse(markup)
|
|
44
|
+
p = @parse_context.new_parser(markup.strip)
|
|
45
|
+
@variable_name = p.consume(:id)
|
|
46
|
+
p.consume(:end_of_string)
|
|
47
|
+
end
|
|
48
|
+
|
|
33
49
|
def render_to_output_buffer(context, output)
|
|
34
50
|
counter_environment = context.environments.first
|
|
35
51
|
value = counter_environment[@variable_name] || 0
|
data/lib/liquid/tags/for.rb
CHANGED
|
@@ -130,7 +130,6 @@ module Liquid
|
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
collection = context.evaluate(@collection_name)
|
|
133
|
-
collection = collection.to_a if collection.is_a?(Range)
|
|
134
133
|
|
|
135
134
|
limit_value = context.evaluate(@limit)
|
|
136
135
|
to = if limit_value.nil?
|
|
@@ -139,7 +138,9 @@ module Liquid
|
|
|
139
138
|
Utils.to_integer(limit_value) + from
|
|
140
139
|
end
|
|
141
140
|
|
|
142
|
-
segment = Utils.
|
|
141
|
+
segment = Utils.slice_collection_for_iteration(
|
|
142
|
+
collection, from, to, context.resource_limits, use_range_to_a: true
|
|
143
|
+
)
|
|
143
144
|
segment.reverse! if @reversed
|
|
144
145
|
|
|
145
146
|
offsets[@name] = from + segment.length
|
data/lib/liquid/tags/include.rb
CHANGED
|
@@ -20,7 +20,8 @@ module Liquid
|
|
|
20
20
|
class Include < Tag
|
|
21
21
|
prepend Tag::Disableable
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
FOR = 'for'
|
|
24
|
+
SYNTAX = /(#{QuotedFragment}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
|
24
25
|
Syntax = SYNTAX
|
|
25
26
|
|
|
26
27
|
attr_reader :template_name_expr, :variable_name_expr, :attributes
|
|
@@ -84,12 +85,18 @@ module Liquid
|
|
|
84
85
|
alias_method :parse_context, :options
|
|
85
86
|
private :parse_context
|
|
86
87
|
|
|
88
|
+
def for_loop?
|
|
89
|
+
@is_for_loop
|
|
90
|
+
end
|
|
91
|
+
|
|
87
92
|
def strict2_parse(markup)
|
|
88
93
|
p = @parse_context.new_parser(markup)
|
|
89
94
|
|
|
90
95
|
@template_name_expr = safe_parse_expression(p)
|
|
91
|
-
|
|
96
|
+
with_or_for = p.id?("for") || p.id?("with")
|
|
97
|
+
@variable_name_expr = safe_parse_expression(p) if with_or_for
|
|
92
98
|
@alias_name = p.consume(:id) if p.id?("as")
|
|
99
|
+
@is_for_loop = (with_or_for == FOR)
|
|
93
100
|
|
|
94
101
|
p.consume?(:comma)
|
|
95
102
|
|
|
@@ -111,11 +118,13 @@ module Liquid
|
|
|
111
118
|
def lax_parse(markup)
|
|
112
119
|
if markup =~ SYNTAX
|
|
113
120
|
template_name = Regexp.last_match(1)
|
|
114
|
-
|
|
121
|
+
with_or_for = Regexp.last_match(3)
|
|
122
|
+
variable_name = Regexp.last_match(4)
|
|
115
123
|
|
|
116
|
-
@alias_name = Regexp.last_match(
|
|
124
|
+
@alias_name = Regexp.last_match(6)
|
|
117
125
|
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
|
118
126
|
@template_name_expr = parse_expression(template_name)
|
|
127
|
+
@is_for_loop = (with_or_for == FOR)
|
|
119
128
|
@attributes = {}
|
|
120
129
|
|
|
121
130
|
markup.scan(TagAttributes) do |key, value|
|
|
@@ -23,13 +23,29 @@ module Liquid
|
|
|
23
23
|
# {% increment variable_name %}
|
|
24
24
|
# @liquid_syntax_keyword variable_name The name of the variable being incremented.
|
|
25
25
|
class Increment < Tag
|
|
26
|
+
include ParserSwitching
|
|
27
|
+
|
|
26
28
|
attr_reader :variable_name
|
|
27
29
|
|
|
28
30
|
def initialize(tag_name, markup, options)
|
|
29
31
|
super
|
|
32
|
+
parse_with_selected_parser(markup)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def lax_parse(markup)
|
|
30
36
|
@variable_name = markup.strip
|
|
31
37
|
end
|
|
32
38
|
|
|
39
|
+
def strict_parse(markup)
|
|
40
|
+
lax_parse(markup)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def strict2_parse(markup)
|
|
44
|
+
p = @parse_context.new_parser(markup.strip)
|
|
45
|
+
@variable_name = p.consume(:id)
|
|
46
|
+
p.consume(:end_of_string)
|
|
47
|
+
end
|
|
48
|
+
|
|
33
49
|
def render_to_output_buffer(context, output)
|
|
34
50
|
counter_environment = context.environments.first
|
|
35
51
|
value = counter_environment[@variable_name] || 0
|
|
@@ -85,12 +85,13 @@ module Liquid
|
|
|
85
85
|
from = @attributes.key?('offset') ? to_integer(context.evaluate(@attributes['offset'])) : 0
|
|
86
86
|
to = @attributes.key?('limit') ? from + to_integer(context.evaluate(@attributes['limit'])) : nil
|
|
87
87
|
|
|
88
|
-
collection = Utils.
|
|
88
|
+
collection = Utils.slice_collection_for_iteration(collection, from, to, context.resource_limits, allow_endless: true)
|
|
89
89
|
length = collection.length
|
|
90
90
|
|
|
91
91
|
cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length
|
|
92
92
|
|
|
93
93
|
output << "<tr class=\"row1\">\n"
|
|
94
|
+
context.resource_limits.increment_write_score(output)
|
|
94
95
|
context.stack do
|
|
95
96
|
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
|
96
97
|
context['tablerowloop'] = tablerowloop
|
|
@@ -101,6 +102,7 @@ module Liquid
|
|
|
101
102
|
output << "<td class=\"col#{tablerowloop.col}\">"
|
|
102
103
|
super
|
|
103
104
|
output << '</td>'
|
|
105
|
+
context.resource_limits.increment_write_score(output)
|
|
104
106
|
|
|
105
107
|
# Handle any interrupts if they exist.
|
|
106
108
|
if context.interrupt?
|
|
@@ -110,6 +112,7 @@ module Liquid
|
|
|
110
112
|
|
|
111
113
|
if tablerowloop.col_last && !tablerowloop.last
|
|
112
114
|
output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
|
115
|
+
context.resource_limits.increment_write_score(output)
|
|
113
116
|
end
|
|
114
117
|
|
|
115
118
|
tablerowloop.send(:increment!)
|
|
@@ -117,6 +120,7 @@ module Liquid
|
|
|
117
120
|
end
|
|
118
121
|
|
|
119
122
|
output << "</tr>\n"
|
|
123
|
+
context.resource_limits.increment_write_score(output)
|
|
120
124
|
output
|
|
121
125
|
end
|
|
122
126
|
|
data/lib/liquid/template.rb
CHANGED
|
@@ -151,8 +151,10 @@ module Liquid
|
|
|
151
151
|
|
|
152
152
|
c
|
|
153
153
|
when Liquid::Drop
|
|
154
|
-
drop
|
|
155
|
-
|
|
154
|
+
drop = args.shift
|
|
155
|
+
c = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
|
|
156
|
+
drop.context = c if drop.respond_to?(:context=)
|
|
157
|
+
c
|
|
156
158
|
when Hash
|
|
157
159
|
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
|
|
158
160
|
when nil
|
|
@@ -187,12 +189,20 @@ module Liquid
|
|
|
187
189
|
|
|
188
190
|
context.template_name ||= name
|
|
189
191
|
|
|
192
|
+
previous_error_mode = context.registers.static[:template_error_mode]
|
|
193
|
+
context.registers.static[:template_error_mode] = @error_mode
|
|
194
|
+
|
|
190
195
|
begin
|
|
191
196
|
# render the nodelist.
|
|
192
197
|
@root.render_to_output_buffer(context, output || +'')
|
|
193
198
|
rescue Liquid::MemoryError => e
|
|
194
199
|
context.handle_error(e)
|
|
195
200
|
ensure
|
|
201
|
+
if previous_error_mode
|
|
202
|
+
context.registers.static[:template_error_mode] = previous_error_mode
|
|
203
|
+
else
|
|
204
|
+
context.registers.static.delete(:template_error_mode)
|
|
205
|
+
end
|
|
196
206
|
@errors = context.errors
|
|
197
207
|
end
|
|
198
208
|
end
|
|
@@ -224,6 +234,7 @@ module Liquid
|
|
|
224
234
|
end
|
|
225
235
|
|
|
226
236
|
@warnings = parse_context.warnings
|
|
237
|
+
@error_mode = parse_context.error_mode
|
|
227
238
|
parse_context
|
|
228
239
|
end
|
|
229
240
|
|
data/lib/liquid/utils.rb
CHANGED
|
@@ -13,6 +13,26 @@ module Liquid
|
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# This is intentionally separate from slice_collection, whose Array-returning
|
|
17
|
+
# behavior is used outside of the iteration tags.
|
|
18
|
+
def self.slice_collection_for_iteration(
|
|
19
|
+
collection, from, to, resource_limits, allow_endless: false, use_range_to_a: false
|
|
20
|
+
)
|
|
21
|
+
if integer_range?(collection)
|
|
22
|
+
RangeSlice.new(collection, from, to, resource_limits)
|
|
23
|
+
elsif collection.is_a?(Range)
|
|
24
|
+
if use_range_to_a && range_method_overridden?(collection, :to_a)
|
|
25
|
+
# For historically honored custom Range#to_a. Charge the resulting
|
|
26
|
+
# selection before buffering it, just as for a custom #each.
|
|
27
|
+
slice_collection_for_iteration_using_each(collection.to_a, from, to, resource_limits)
|
|
28
|
+
else
|
|
29
|
+
slice_range_using_each(collection, from, to, resource_limits, allow_endless: allow_endless)
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
slice_collection(collection, from, to)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
16
36
|
def self.slice_collection_using_each(collection, from, to)
|
|
17
37
|
segments = []
|
|
18
38
|
index = 0
|
|
@@ -38,6 +58,55 @@ module Liquid
|
|
|
38
58
|
segments
|
|
39
59
|
end
|
|
40
60
|
|
|
61
|
+
# Arithmetic slicing must not bypass a Range subclass's custom #each.
|
|
62
|
+
def self.integer_range?(collection)
|
|
63
|
+
collection.instance_of?(Range) && collection.begin.is_a?(Integer) && collection.end.is_a?(Integer)
|
|
64
|
+
end
|
|
65
|
+
private_class_method :integer_range?
|
|
66
|
+
|
|
67
|
+
# Preserve support for Ruby-supplied string ranges and custom Range#each.
|
|
68
|
+
# Their selected length cannot be inferred from integer bounds, but the tags
|
|
69
|
+
# need it before rendering for loop metadata, continuation offsets, and columns.
|
|
70
|
+
# Buffer the selection so we do not have to replay a potentially custom iterator.
|
|
71
|
+
def self.slice_range_using_each(collection, from, to, resource_limits, allow_endless:)
|
|
72
|
+
# TableRow historically accepted an endless subclass when its custom #each
|
|
73
|
+
# was finite, while For historically raised through Range#to_a.
|
|
74
|
+
if collection.end.nil? && !(allow_endless && (!to.nil? || range_method_overridden?(collection, :each)))
|
|
75
|
+
raise RangeError, "cannot convert endless range to an array"
|
|
76
|
+
end
|
|
77
|
+
if collection.begin.nil? && !range_method_overridden?(collection, :each)
|
|
78
|
+
raise TypeError, "can't iterate from NilClass"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
slice_collection_for_iteration_using_each(collection, from, to, resource_limits)
|
|
82
|
+
end
|
|
83
|
+
private_class_method :slice_range_using_each
|
|
84
|
+
|
|
85
|
+
# Custom Range#each can make a nominally beginless range finite; standard
|
|
86
|
+
# beginless ranges were rejected before reaching this budgeted traversal.
|
|
87
|
+
def self.slice_collection_for_iteration_using_each(collection, from, to, resource_limits)
|
|
88
|
+
return [] if to && to <= from
|
|
89
|
+
|
|
90
|
+
segments = []
|
|
91
|
+
index = 0
|
|
92
|
+
collection.each do |item|
|
|
93
|
+
break if to && to <= index
|
|
94
|
+
|
|
95
|
+
# Charge preparation, including skipped offsets, before buffering; checking
|
|
96
|
+
# only while rendering the buffered values would leave this work unbudgeted.
|
|
97
|
+
resource_limits.increment_render_score(1)
|
|
98
|
+
segments << item if from <= index
|
|
99
|
+
index += 1
|
|
100
|
+
end
|
|
101
|
+
segments
|
|
102
|
+
end
|
|
103
|
+
private_class_method :slice_collection_for_iteration_using_each
|
|
104
|
+
|
|
105
|
+
def self.range_method_overridden?(collection, method_name)
|
|
106
|
+
collection.method(method_name).owner != Range.instance_method(method_name).owner
|
|
107
|
+
end
|
|
108
|
+
private_class_method :range_method_overridden?
|
|
109
|
+
|
|
41
110
|
def self.to_integer(num)
|
|
42
111
|
return num if num.is_a?(Integer)
|
|
43
112
|
num = num.to_s
|
data/lib/liquid/variable.rb
CHANGED
data/lib/liquid/version.rb
CHANGED
data/lib/liquid.rb
CHANGED
|
@@ -65,6 +65,7 @@ require 'liquid/lexer'
|
|
|
65
65
|
require 'liquid/parser'
|
|
66
66
|
require 'liquid/i18n'
|
|
67
67
|
require 'liquid/drop'
|
|
68
|
+
require 'liquid/self_drop'
|
|
68
69
|
require 'liquid/tablerowloop_drop'
|
|
69
70
|
require 'liquid/forloop_drop'
|
|
70
71
|
require 'liquid/extensions'
|
|
@@ -82,6 +83,7 @@ require 'liquid/resource_limits'
|
|
|
82
83
|
require 'liquid/expression'
|
|
83
84
|
require 'liquid/template'
|
|
84
85
|
require 'liquid/condition'
|
|
86
|
+
require 'liquid/range_slice'
|
|
85
87
|
require 'liquid/utils'
|
|
86
88
|
require 'liquid/tokenizer'
|
|
87
89
|
require 'liquid/parse_context'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: liquid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tobias Lütke
|
|
@@ -103,8 +103,10 @@ files:
|
|
|
103
103
|
- lib/liquid/profiler.rb
|
|
104
104
|
- lib/liquid/profiler/hooks.rb
|
|
105
105
|
- lib/liquid/range_lookup.rb
|
|
106
|
+
- lib/liquid/range_slice.rb
|
|
106
107
|
- lib/liquid/registers.rb
|
|
107
108
|
- lib/liquid/resource_limits.rb
|
|
109
|
+
- lib/liquid/self_drop.rb
|
|
108
110
|
- lib/liquid/standardfilters.rb
|
|
109
111
|
- lib/liquid/strainer_template.rb
|
|
110
112
|
- lib/liquid/tablerowloop_drop.rb
|
|
@@ -159,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
159
161
|
- !ruby/object:Gem::Version
|
|
160
162
|
version: 1.3.7
|
|
161
163
|
requirements: []
|
|
162
|
-
rubygems_version: 4.0.
|
|
164
|
+
rubygems_version: 4.0.21
|
|
163
165
|
specification_version: 4
|
|
164
166
|
summary: A secure, non-evaling end user template engine with aesthetic markup.
|
|
165
167
|
test_files: []
|