liquid 5.13.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 +4 -0
- data/README.md +7 -0
- data/lib/liquid/block_body.rb +3 -1
- data/lib/liquid/range_slice.rb +44 -0
- data/lib/liquid/tags/for.rb +3 -2
- data/lib/liquid/tags/table_row.rb +5 -1
- data/lib/liquid/template.rb +9 -0
- data/lib/liquid/utils.rb +69 -0
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +1 -0
- metadata +3 -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
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
|
|
@@ -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
|
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
|
|
@@ -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
|
@@ -189,12 +189,20 @@ module Liquid
|
|
|
189
189
|
|
|
190
190
|
context.template_name ||= name
|
|
191
191
|
|
|
192
|
+
previous_error_mode = context.registers.static[:template_error_mode]
|
|
193
|
+
context.registers.static[:template_error_mode] = @error_mode
|
|
194
|
+
|
|
192
195
|
begin
|
|
193
196
|
# render the nodelist.
|
|
194
197
|
@root.render_to_output_buffer(context, output || +'')
|
|
195
198
|
rescue Liquid::MemoryError => e
|
|
196
199
|
context.handle_error(e)
|
|
197
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
|
|
198
206
|
@errors = context.errors
|
|
199
207
|
end
|
|
200
208
|
end
|
|
@@ -226,6 +234,7 @@ module Liquid
|
|
|
226
234
|
end
|
|
227
235
|
|
|
228
236
|
@warnings = parse_context.warnings
|
|
237
|
+
@error_mode = parse_context.error_mode
|
|
229
238
|
parse_context
|
|
230
239
|
end
|
|
231
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/version.rb
CHANGED
data/lib/liquid.rb
CHANGED
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,6 +103,7 @@ 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
|
|
108
109
|
- lib/liquid/self_drop.rb
|
|
@@ -160,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
160
161
|
- !ruby/object:Gem::Version
|
|
161
162
|
version: 1.3.7
|
|
162
163
|
requirements: []
|
|
163
|
-
rubygems_version: 4.0.
|
|
164
|
+
rubygems_version: 4.0.21
|
|
164
165
|
specification_version: 4
|
|
165
166
|
summary: A secure, non-evaling end user template engine with aesthetic markup.
|
|
166
167
|
test_files: []
|