liquid 4.0.3 → 5.1.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 +54 -0
- data/README.md +6 -0
- data/lib/liquid/block.rb +31 -14
- data/lib/liquid/block_body.rb +166 -54
- data/lib/liquid/condition.rb +41 -20
- data/lib/liquid/context.rb +107 -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 +29 -34
- data/lib/liquid/extensions.rb +2 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +11 -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 +3 -1
- 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/register.rb +6 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +95 -46
- data/lib/liquid/static_registers.rb +44 -0
- data/lib/liquid/strainer_factory.rb +36 -0
- data/lib/liquid/strainer_template.rb +53 -0
- data/lib/liquid/tablerowloop_drop.rb +6 -4
- 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 +24 -10
- data/lib/liquid/tags/break.rb +8 -3
- data/lib/liquid/tags/capture.rb +11 -8
- data/lib/liquid/tags/case.rb +40 -27
- data/lib/liquid/tags/comment.rb +5 -3
- data/lib/liquid/tags/continue.rb +8 -3
- data/lib/liquid/tags/cycle.rb +25 -14
- data/lib/liquid/tags/decrement.rb +6 -3
- data/lib/liquid/tags/echo.rb +34 -0
- data/lib/liquid/tags/for.rb +68 -44
- data/lib/liquid/tags/if.rb +39 -23
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +34 -47
- data/lib/liquid/tags/increment.rb +7 -3
- data/lib/liquid/tags/raw.rb +14 -11
- data/lib/liquid/tags/render.rb +84 -0
- data/lib/liquid/tags/table_row.rb +23 -19
- data/lib/liquid/tags/unless.rb +23 -15
- data/lib/liquid/template.rb +53 -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 +46 -41
- data/lib/liquid/variable_lookup.rb +11 -6
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +17 -5
- data/test/integration/assign_test.rb +74 -5
- data/test/integration/blank_test.rb +11 -8
- data/test/integration/block_test.rb +47 -1
- data/test/integration/capture_test.rb +18 -10
- data/test/integration/context_test.rb +609 -5
- data/test/integration/document_test.rb +4 -2
- data/test/integration/drop_test.rb +67 -83
- data/test/integration/error_handling_test.rb +73 -61
- data/test/integration/expression_test.rb +46 -0
- data/test/integration/filter_test.rb +53 -42
- data/test/integration/hash_ordering_test.rb +5 -3
- data/test/integration/output_test.rb +26 -24
- data/test/integration/parsing_quirks_test.rb +19 -7
- data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
- data/test/integration/security_test.rb +30 -21
- data/test/integration/standard_filter_test.rb +385 -281
- data/test/integration/tag/disableable_test.rb +59 -0
- data/test/integration/tag_test.rb +45 -0
- data/test/integration/tags/break_tag_test.rb +4 -2
- data/test/integration/tags/continue_tag_test.rb +4 -2
- data/test/integration/tags/echo_test.rb +13 -0
- data/test/integration/tags/for_tag_test.rb +107 -51
- data/test/integration/tags/if_else_tag_test.rb +5 -3
- data/test/integration/tags/include_tag_test.rb +70 -54
- data/test/integration/tags/increment_tag_test.rb +4 -2
- data/test/integration/tags/liquid_tag_test.rb +116 -0
- data/test/integration/tags/raw_tag_test.rb +14 -11
- data/test/integration/tags/render_tag_test.rb +213 -0
- data/test/integration/tags/standard_tag_test.rb +38 -31
- data/test/integration/tags/statements_test.rb +23 -21
- data/test/integration/tags/table_row_test.rb +2 -0
- data/test/integration/tags/unless_else_tag_test.rb +4 -2
- data/test/integration/template_test.rb +132 -124
- data/test/integration/trim_mode_test.rb +78 -44
- data/test/integration/variable_test.rb +74 -32
- data/test/test_helper.rb +113 -22
- data/test/unit/block_unit_test.rb +19 -24
- data/test/unit/condition_unit_test.rb +79 -77
- data/test/unit/file_system_unit_test.rb +6 -4
- data/test/unit/i18n_unit_test.rb +7 -5
- data/test/unit/lexer_unit_test.rb +11 -9
- data/test/{integration → unit}/parse_tree_visitor_test.rb +16 -2
- data/test/unit/parser_unit_test.rb +37 -35
- data/test/unit/partial_cache_unit_test.rb +128 -0
- data/test/unit/regexp_unit_test.rb +17 -15
- data/test/unit/static_registers_unit_test.rb +156 -0
- data/test/unit/strainer_factory_unit_test.rb +100 -0
- data/test/unit/strainer_template_unit_test.rb +82 -0
- data/test/unit/tag_unit_test.rb +5 -3
- data/test/unit/tags/case_tag_unit_test.rb +3 -1
- data/test/unit/tags/for_tag_unit_test.rb +4 -2
- data/test/unit/tags/if_tag_unit_test.rb +3 -1
- data/test/unit/template_factory_unit_test.rb +12 -0
- data/test/unit/template_unit_test.rb +19 -10
- data/test/unit/tokenizer_unit_test.rb +26 -19
- data/test/unit/variable_unit_test.rb +51 -49
- metadata +76 -50
- data/lib/liquid/strainer.rb +0 -66
- data/lib/liquid/truffle.rb +0 -5
- data/test/truffle/truffle_test.rb +0 -9
- data/test/unit/context_unit_test.rb +0 -489
- data/test/unit/strainer_unit_test.rb +0 -164
@@ -1,20 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cgi'
|
4
|
+
require 'base64'
|
2
5
|
require 'bigdecimal'
|
3
6
|
|
4
7
|
module Liquid
|
5
8
|
module StandardFilters
|
9
|
+
MAX_INT = (1 << 31) - 1
|
6
10
|
HTML_ESCAPE = {
|
7
|
-
'&'
|
8
|
-
'>'
|
9
|
-
'<'
|
10
|
-
'"'
|
11
|
-
"'"
|
11
|
+
'&' => '&',
|
12
|
+
'>' => '>',
|
13
|
+
'<' => '<',
|
14
|
+
'"' => '"',
|
15
|
+
"'" => ''',
|
12
16
|
}.freeze
|
13
17
|
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
14
|
-
STRIP_HTML_BLOCKS
|
15
|
-
|
18
|
+
STRIP_HTML_BLOCKS = Regexp.union(
|
19
|
+
%r{<script.*?</script>}m,
|
16
20
|
/<!--.*?-->/m,
|
17
|
-
|
21
|
+
%r{<style.*?</style>}m
|
18
22
|
)
|
19
23
|
STRIP_HTML_TAGS = /<.*?>/m
|
20
24
|
|
@@ -39,7 +43,7 @@ module Liquid
|
|
39
43
|
end
|
40
44
|
|
41
45
|
def escape(input)
|
42
|
-
CGI.escapeHTML(input.to_s)
|
46
|
+
CGI.escapeHTML(input.to_s) unless input.nil?
|
43
47
|
end
|
44
48
|
alias_method :h, :escape
|
45
49
|
|
@@ -60,6 +64,26 @@ module Liquid
|
|
60
64
|
result
|
61
65
|
end
|
62
66
|
|
67
|
+
def base64_encode(input)
|
68
|
+
Base64.strict_encode64(input.to_s)
|
69
|
+
end
|
70
|
+
|
71
|
+
def base64_decode(input)
|
72
|
+
Base64.strict_decode64(input.to_s)
|
73
|
+
rescue ::ArgumentError
|
74
|
+
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
|
75
|
+
end
|
76
|
+
|
77
|
+
def base64_url_safe_encode(input)
|
78
|
+
Base64.urlsafe_encode64(input.to_s)
|
79
|
+
end
|
80
|
+
|
81
|
+
def base64_url_safe_decode(input)
|
82
|
+
Base64.urlsafe_decode64(input.to_s)
|
83
|
+
rescue ::ArgumentError
|
84
|
+
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
|
85
|
+
end
|
86
|
+
|
63
87
|
def slice(input, offset, length = nil)
|
64
88
|
offset = Utils.to_integer(offset)
|
65
89
|
length = length ? Utils.to_integer(length) : 1
|
@@ -72,23 +96,36 @@ module Liquid
|
|
72
96
|
end
|
73
97
|
|
74
98
|
# Truncate a string down to x characters
|
75
|
-
def truncate(input, length = 50, truncate_string = "..."
|
99
|
+
def truncate(input, length = 50, truncate_string = "...")
|
76
100
|
return if input.nil?
|
77
101
|
input_str = input.to_s
|
78
|
-
length
|
102
|
+
length = Utils.to_integer(length)
|
103
|
+
|
79
104
|
truncate_string_str = truncate_string.to_s
|
105
|
+
|
80
106
|
l = length - truncate_string_str.length
|
81
107
|
l = 0 if l < 0
|
82
|
-
|
108
|
+
|
109
|
+
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
|
83
110
|
end
|
84
111
|
|
85
|
-
def truncatewords(input, words = 15, truncate_string = "..."
|
112
|
+
def truncatewords(input, words = 15, truncate_string = "...")
|
86
113
|
return if input.nil?
|
87
|
-
|
114
|
+
input = input.to_s
|
88
115
|
words = Utils.to_integer(words)
|
89
|
-
|
90
|
-
|
91
|
-
wordlist
|
116
|
+
words = 1 if words <= 0
|
117
|
+
|
118
|
+
wordlist = begin
|
119
|
+
input.split(" ", words + 1)
|
120
|
+
rescue RangeError
|
121
|
+
raise if words + 1 < MAX_INT
|
122
|
+
# e.g. integer #{words} too big to convert to `int'
|
123
|
+
raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
|
124
|
+
end
|
125
|
+
return input if wordlist.length <= words
|
126
|
+
|
127
|
+
wordlist.pop
|
128
|
+
wordlist.join(" ").concat(truncate_string.to_s)
|
92
129
|
end
|
93
130
|
|
94
131
|
# Split input string into an array of substrings separated by given pattern.
|
@@ -113,7 +150,7 @@ module Liquid
|
|
113
150
|
end
|
114
151
|
|
115
152
|
def strip_html(input)
|
116
|
-
empty
|
153
|
+
empty = ''
|
117
154
|
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
118
155
|
result.gsub!(STRIP_HTML_TAGS, empty)
|
119
156
|
result
|
@@ -121,18 +158,18 @@ module Liquid
|
|
121
158
|
|
122
159
|
# Remove all newlines from the string
|
123
160
|
def strip_newlines(input)
|
124
|
-
input.to_s.gsub(/\r?\n/, ''
|
161
|
+
input.to_s.gsub(/\r?\n/, '')
|
125
162
|
end
|
126
163
|
|
127
164
|
# Join elements of the array with certain character between them
|
128
|
-
def join(input, glue = ' '
|
129
|
-
InputIterator.new(input).join(glue)
|
165
|
+
def join(input, glue = ' ')
|
166
|
+
InputIterator.new(input, context).join(glue)
|
130
167
|
end
|
131
168
|
|
132
169
|
# Sort elements of the array
|
133
170
|
# provide optional property with which to sort an array of hashes or drops
|
134
171
|
def sort(input, property = nil)
|
135
|
-
ary = InputIterator.new(input)
|
172
|
+
ary = InputIterator.new(input, context)
|
136
173
|
|
137
174
|
return [] if ary.empty?
|
138
175
|
|
@@ -152,7 +189,7 @@ module Liquid
|
|
152
189
|
# Sort elements of an array ignoring case if strings
|
153
190
|
# provide optional property with which to sort an array of hashes or drops
|
154
191
|
def sort_natural(input, property = nil)
|
155
|
-
ary = InputIterator.new(input)
|
192
|
+
ary = InputIterator.new(input, context)
|
156
193
|
|
157
194
|
return [] if ary.empty?
|
158
195
|
|
@@ -172,7 +209,7 @@ module Liquid
|
|
172
209
|
# Filter the elements of an array to those with a certain property value.
|
173
210
|
# By default the target is any truthy value.
|
174
211
|
def where(input, property, target_value = nil)
|
175
|
-
ary = InputIterator.new(input)
|
212
|
+
ary = InputIterator.new(input, context)
|
176
213
|
|
177
214
|
if ary.empty?
|
178
215
|
[]
|
@@ -194,7 +231,7 @@ module Liquid
|
|
194
231
|
# Remove duplicate elements from an array
|
195
232
|
# provide optional property with which to determine uniqueness
|
196
233
|
def uniq(input, property = nil)
|
197
|
-
ary = InputIterator.new(input)
|
234
|
+
ary = InputIterator.new(input, context)
|
198
235
|
|
199
236
|
if property.nil?
|
200
237
|
ary.uniq
|
@@ -211,16 +248,16 @@ module Liquid
|
|
211
248
|
|
212
249
|
# Reverse the elements of an array
|
213
250
|
def reverse(input)
|
214
|
-
ary = InputIterator.new(input)
|
251
|
+
ary = InputIterator.new(input, context)
|
215
252
|
ary.reverse
|
216
253
|
end
|
217
254
|
|
218
255
|
# map/collect on a given property
|
219
256
|
def map(input, property)
|
220
|
-
InputIterator.new(input).map do |e|
|
257
|
+
InputIterator.new(input, context).map do |e|
|
221
258
|
e = e.call if e.is_a?(Proc)
|
222
259
|
|
223
|
-
if property == "to_liquid"
|
260
|
+
if property == "to_liquid"
|
224
261
|
e
|
225
262
|
elsif e.respond_to?(:[])
|
226
263
|
r = e[property]
|
@@ -234,7 +271,7 @@ module Liquid
|
|
234
271
|
# Remove nils within an array
|
235
272
|
# provide optional property with which to check for nil
|
236
273
|
def compact(input, property = nil)
|
237
|
-
ary = InputIterator.new(input)
|
274
|
+
ary = InputIterator.new(input, context)
|
238
275
|
|
239
276
|
if property.nil?
|
240
277
|
ary.compact
|
@@ -250,23 +287,23 @@ module Liquid
|
|
250
287
|
end
|
251
288
|
|
252
289
|
# Replace occurrences of a string with another
|
253
|
-
def replace(input, string, replacement = ''
|
290
|
+
def replace(input, string, replacement = '')
|
254
291
|
input.to_s.gsub(string.to_s, replacement.to_s)
|
255
292
|
end
|
256
293
|
|
257
294
|
# Replace the first occurrences of a string with another
|
258
|
-
def replace_first(input, string, replacement = ''
|
295
|
+
def replace_first(input, string, replacement = '')
|
259
296
|
input.to_s.sub(string.to_s, replacement.to_s)
|
260
297
|
end
|
261
298
|
|
262
299
|
# remove a substring
|
263
300
|
def remove(input, string)
|
264
|
-
input.to_s.gsub(string.to_s, ''
|
301
|
+
input.to_s.gsub(string.to_s, '')
|
265
302
|
end
|
266
303
|
|
267
304
|
# remove the first occurrences of a substring
|
268
305
|
def remove_first(input, string)
|
269
|
-
input.to_s.sub(string.to_s, ''
|
306
|
+
input.to_s.sub(string.to_s, '')
|
270
307
|
end
|
271
308
|
|
272
309
|
# add one string to another
|
@@ -276,9 +313,9 @@ module Liquid
|
|
276
313
|
|
277
314
|
def concat(input, array)
|
278
315
|
unless array.respond_to?(:to_ary)
|
279
|
-
raise ArgumentError
|
316
|
+
raise ArgumentError, "concat filter requires an array argument"
|
280
317
|
end
|
281
|
-
InputIterator.new(input).concat(array)
|
318
|
+
InputIterator.new(input, context).concat(array)
|
282
319
|
end
|
283
320
|
|
284
321
|
# prepend a string to another
|
@@ -288,7 +325,7 @@ module Liquid
|
|
288
325
|
|
289
326
|
# Add <br /> tags in front of all newlines in input string
|
290
327
|
def newline_to_br(input)
|
291
|
-
input.to_s.gsub(/\n/, "<br />\n"
|
328
|
+
input.to_s.gsub(/\r?\n/, "<br />\n")
|
292
329
|
end
|
293
330
|
|
294
331
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
@@ -325,7 +362,7 @@ module Liquid
|
|
325
362
|
def date(input, format)
|
326
363
|
return input if format.to_s.empty?
|
327
364
|
|
328
|
-
return input unless date = Utils.to_date(input)
|
365
|
+
return input unless (date = Utils.to_date(input))
|
329
366
|
|
330
367
|
date.strftime(format.to_s)
|
331
368
|
end
|
@@ -419,18 +456,28 @@ module Liquid
|
|
419
456
|
result.is_a?(BigDecimal) ? result.to_f : result
|
420
457
|
end
|
421
458
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
459
|
+
# Set a default value when the input is nil, false or empty
|
460
|
+
#
|
461
|
+
# Example:
|
462
|
+
# {{ product.title | default: "No Title" }}
|
463
|
+
#
|
464
|
+
# Use `allow_false` when an input should only be tested against nil or empty and not false.
|
465
|
+
#
|
466
|
+
# Example:
|
467
|
+
# {{ product.title | default: "No Title", allow_false: true }}
|
468
|
+
#
|
469
|
+
def default(input, default_value = '', options = {})
|
470
|
+
options = {} unless options.is_a?(Hash)
|
471
|
+
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
|
472
|
+
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
428
473
|
end
|
429
474
|
|
430
475
|
private
|
431
476
|
|
477
|
+
attr_reader :context
|
478
|
+
|
432
479
|
def raise_property_error(property)
|
433
|
-
raise Liquid::ArgumentError
|
480
|
+
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
|
434
481
|
end
|
435
482
|
|
436
483
|
def apply_operation(input, operand, operation)
|
@@ -457,8 +504,9 @@ module Liquid
|
|
457
504
|
class InputIterator
|
458
505
|
include Enumerable
|
459
506
|
|
460
|
-
def initialize(input)
|
461
|
-
@
|
507
|
+
def initialize(input, context)
|
508
|
+
@context = context
|
509
|
+
@input = if input.is_a?(Array)
|
462
510
|
input.flatten
|
463
511
|
elsif input.is_a?(Hash)
|
464
512
|
[input]
|
@@ -496,6 +544,7 @@ module Liquid
|
|
496
544
|
|
497
545
|
def each
|
498
546
|
@input.each do |e|
|
547
|
+
e.context = @context if e.respond_to?(:context=)
|
499
548
|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
500
549
|
end
|
501
550
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class StaticRegisters
|
5
|
+
attr_reader :static
|
6
|
+
|
7
|
+
def initialize(registers = {})
|
8
|
+
@static = registers.is_a?(StaticRegisters) ? registers.static : registers
|
9
|
+
@registers = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(key, value)
|
13
|
+
@registers[key] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
if @registers.key?(key)
|
18
|
+
@registers[key]
|
19
|
+
else
|
20
|
+
@static[key]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
@registers.delete(key)
|
26
|
+
end
|
27
|
+
|
28
|
+
UNDEFINED = Object.new
|
29
|
+
|
30
|
+
def fetch(key, default = UNDEFINED, &block)
|
31
|
+
if @registers.key?(key)
|
32
|
+
@registers.fetch(key)
|
33
|
+
elsif default != UNDEFINED
|
34
|
+
@static.fetch(key, default, &block)
|
35
|
+
else
|
36
|
+
@static.fetch(key, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def key?(key)
|
41
|
+
@registers.key?(key) || @static.key?(key)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
# StrainerFactory is the factory for the filters system.
|
5
|
+
module StrainerFactory
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def add_global_filter(filter)
|
9
|
+
strainer_class_cache.clear
|
10
|
+
global_filters << filter
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(context, filters = [])
|
14
|
+
strainer_from_cache(filters).new(context)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def global_filters
|
20
|
+
@global_filters ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def strainer_from_cache(filters)
|
24
|
+
strainer_class_cache[filters] ||= begin
|
25
|
+
klass = Class.new(StrainerTemplate)
|
26
|
+
global_filters.each { |f| klass.add_filter(f) }
|
27
|
+
filters.each { |f| klass.add_filter(f) }
|
28
|
+
klass
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def strainer_class_cache
|
33
|
+
@strainer_class_cache ||= {}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Liquid
|
6
|
+
# StrainerTemplate is the computed class for the filters system.
|
7
|
+
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
|
8
|
+
#
|
9
|
+
# The Strainer only allows method calls defined in filters given to it via StrainerFactory.add_global_filter,
|
10
|
+
# Context#add_filters or Template.register_filter
|
11
|
+
class StrainerTemplate
|
12
|
+
def initialize(context)
|
13
|
+
@context = context
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def add_filter(filter)
|
18
|
+
return if include?(filter)
|
19
|
+
|
20
|
+
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
21
|
+
if invokable_non_public_methods.any?
|
22
|
+
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
23
|
+
end
|
24
|
+
|
25
|
+
include(filter)
|
26
|
+
|
27
|
+
filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
28
|
+
end
|
29
|
+
|
30
|
+
def invokable?(method)
|
31
|
+
filter_methods.include?(method.to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def filter_methods
|
37
|
+
@filter_methods ||= Set.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def invoke(method, *args)
|
42
|
+
if self.class.invokable?(method)
|
43
|
+
send(method, *args)
|
44
|
+
elsif @context.strict_filters
|
45
|
+
raise Liquid::UndefinedFilter, "undefined filter #{method}"
|
46
|
+
else
|
47
|
+
args.first
|
48
|
+
end
|
49
|
+
rescue ::ArgumentError => e
|
50
|
+
raise Liquid::ArgumentError, e.message, e.backtrace
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class TablerowloopDrop < Drop
|
3
5
|
def initialize(length, cols)
|
4
6
|
@length = length
|
5
|
-
@row
|
6
|
-
@col
|
7
|
-
@cols
|
8
|
-
@index
|
7
|
+
@row = 1
|
8
|
+
@col = 1
|
9
|
+
@cols = cols
|
10
|
+
@index = 0
|
9
11
|
end
|
10
12
|
|
11
13
|
attr_reader :length, :col, :row
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class Tag
|
5
|
+
module Disableable
|
6
|
+
def render_to_output_buffer(context, output)
|
7
|
+
if context.tag_disabled?(tag_name)
|
8
|
+
output << disabled_error(context)
|
9
|
+
return
|
10
|
+
end
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def disabled_error(context)
|
15
|
+
# raise then rescue the exception so that the Context#exception_renderer can re-raise it
|
16
|
+
raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
|
17
|
+
rescue DisabledError => exc
|
18
|
+
context.handle_error(exc, line_number)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class Tag
|
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
|
+
def render_to_output_buffer(context, output)
|
15
|
+
context.with_disabled_tags(self.class.disabled_tags) do
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/liquid/tag.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class Tag
|
3
5
|
attr_reader :nodelist, :tag_name, :line_number, :parse_context
|
@@ -5,20 +7,26 @@ module Liquid
|
|
5
7
|
include ParserSwitching
|
6
8
|
|
7
9
|
class << self
|
8
|
-
def parse(tag_name, markup, tokenizer,
|
9
|
-
tag = new(tag_name, markup,
|
10
|
+
def parse(tag_name, markup, tokenizer, parse_context)
|
11
|
+
tag = new(tag_name, markup, parse_context)
|
10
12
|
tag.parse(tokenizer)
|
11
13
|
tag
|
12
14
|
end
|
13
15
|
|
16
|
+
def disable_tags(*tag_names)
|
17
|
+
@disabled_tags ||= []
|
18
|
+
@disabled_tags.concat(tag_names)
|
19
|
+
prepend(Disabler)
|
20
|
+
end
|
21
|
+
|
14
22
|
private :new
|
15
23
|
end
|
16
24
|
|
17
25
|
def initialize(tag_name, markup, parse_context)
|
18
|
-
@tag_name
|
19
|
-
@markup
|
26
|
+
@tag_name = tag_name
|
27
|
+
@markup = markup
|
20
28
|
@parse_context = parse_context
|
21
|
-
@line_number
|
29
|
+
@line_number = parse_context.line_number
|
22
30
|
end
|
23
31
|
|
24
32
|
def parse(_tokens)
|
@@ -33,11 +41,25 @@ module Liquid
|
|
33
41
|
end
|
34
42
|
|
35
43
|
def render(_context)
|
36
|
-
''
|
44
|
+
''
|
45
|
+
end
|
46
|
+
|
47
|
+
# For backwards compatibility with custom tags. In a future release, the semantics
|
48
|
+
# of the `render_to_output_buffer` method will become the default and the `render`
|
49
|
+
# method will be removed.
|
50
|
+
def render_to_output_buffer(context, output)
|
51
|
+
output << render(context)
|
52
|
+
output
|
37
53
|
end
|
38
54
|
|
39
55
|
def blank?
|
40
56
|
false
|
41
57
|
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def parse_expression(markup)
|
62
|
+
parse_context.parse_expression(markup)
|
63
|
+
end
|
42
64
|
end
|
43
65
|
end
|
data/lib/liquid/tags/assign.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
# Assign sets a variable in your template.
|
3
5
|
#
|
@@ -10,23 +12,28 @@ module Liquid
|
|
10
12
|
class Assign < Tag
|
11
13
|
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
12
14
|
|
15
|
+
# @api private
|
16
|
+
def self.raise_syntax_error(parse_context)
|
17
|
+
raise Liquid::SyntaxError, parse_context.locale.t('errors.syntax.assign')
|
18
|
+
end
|
19
|
+
|
13
20
|
attr_reader :to, :from
|
14
21
|
|
15
|
-
def initialize(tag_name, markup,
|
22
|
+
def initialize(tag_name, markup, parse_context)
|
16
23
|
super
|
17
24
|
if markup =~ Syntax
|
18
|
-
@to
|
19
|
-
@from = Variable.new(
|
25
|
+
@to = Regexp.last_match(1)
|
26
|
+
@from = Variable.new(Regexp.last_match(2), parse_context)
|
20
27
|
else
|
21
|
-
|
28
|
+
self.class.raise_syntax_error(parse_context)
|
22
29
|
end
|
23
30
|
end
|
24
31
|
|
25
|
-
def
|
32
|
+
def render_to_output_buffer(context, output)
|
26
33
|
val = @from.render(context)
|
27
34
|
context.scopes.last[@to] = val
|
28
|
-
context.resource_limits.
|
29
|
-
|
35
|
+
context.resource_limits.increment_assign_score(assign_score_of(val))
|
36
|
+
output
|
30
37
|
end
|
31
38
|
|
32
39
|
def blank?
|
@@ -37,12 +44,19 @@ module Liquid
|
|
37
44
|
|
38
45
|
def assign_score_of(val)
|
39
46
|
if val.instance_of?(String)
|
40
|
-
val.
|
41
|
-
elsif val.instance_of?(Array)
|
47
|
+
val.bytesize
|
48
|
+
elsif val.instance_of?(Array)
|
42
49
|
sum = 1
|
43
50
|
# Uses #each to avoid extra allocations.
|
44
51
|
val.each { |child| sum += assign_score_of(child) }
|
45
52
|
sum
|
53
|
+
elsif val.instance_of?(Hash)
|
54
|
+
sum = 1
|
55
|
+
val.each do |key, entry_value|
|
56
|
+
sum += assign_score_of(key)
|
57
|
+
sum += assign_score_of(entry_value)
|
58
|
+
end
|
59
|
+
sum
|
46
60
|
else
|
47
61
|
1
|
48
62
|
end
|
@@ -55,5 +69,5 @@ module Liquid
|
|
55
69
|
end
|
56
70
|
end
|
57
71
|
|
58
|
-
Template.register_tag('assign'
|
72
|
+
Template.register_tag('assign', Assign)
|
59
73
|
end
|
data/lib/liquid/tags/break.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
# Break tag to be used to break out of a for loop.
|
3
5
|
#
|
@@ -9,10 +11,13 @@ module Liquid
|
|
9
11
|
# {% endfor %}
|
10
12
|
#
|
11
13
|
class Break < Tag
|
12
|
-
|
13
|
-
|
14
|
+
INTERRUPT = BreakInterrupt.new.freeze
|
15
|
+
|
16
|
+
def render_to_output_buffer(context, output)
|
17
|
+
context.push_interrupt(INTERRUPT)
|
18
|
+
output
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
17
|
-
Template.register_tag('break'
|
22
|
+
Template.register_tag('break', Break)
|
18
23
|
end
|