liquid 5.0.0 → 5.2.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 +32 -0
- data/README.md +2 -2
- data/lib/liquid/block_body.rb +5 -3
- data/lib/liquid/condition.rb +3 -3
- data/lib/liquid/context.rb +1 -1
- data/lib/liquid/expression.rb +11 -10
- data/lib/liquid/parse_context.rb +4 -0
- data/lib/liquid/range_lookup.rb +8 -0
- data/lib/liquid/standardfilters.rb +93 -25
- data/lib/liquid/strainer_factory.rb +11 -10
- data/lib/liquid/strainer_template.rb +5 -0
- data/lib/liquid/tags/case.rb +8 -1
- data/lib/liquid/tags/echo.rb +8 -0
- data/lib/liquid/tags/if.rb +5 -1
- data/lib/liquid/tags/unless.rb +10 -2
- data/lib/liquid/template.rb +2 -5
- data/lib/liquid/tokenizer.rb +2 -2
- data/lib/liquid/utils.rb +8 -0
- data/lib/liquid/variable_lookup.rb +3 -0
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +2 -2
- data/test/integration/context_test.rb +1 -0
- data/test/integration/filter_kwarg_test.rb +24 -0
- data/test/integration/standard_filter_test.rb +86 -21
- data/test/integration/tags/include_tag_test.rb +2 -2
- data/test/integration/tags/render_tag_test.rb +7 -7
- data/test/integration/template_test.rb +14 -0
- data/test/integration/variable_test.rb +31 -0
- data/test/test_helper.rb +48 -10
- data/test/unit/parse_tree_visitor_test.rb +14 -0
- data/test/unit/strainer_factory_unit_test.rb +2 -1
- data/test/unit/strainer_template_unit_test.rb +1 -1
- data/test/unit/tokenizer_unit_test.rb +9 -4
- metadata +51 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e15bedf89a61f8c69c63cb18007fea0639ba1da3e06126dbcc569bea596c300
|
4
|
+
data.tar.gz: 8bee4895c2d61314d9e1bede8bf9839c5fd1771031452ee9a07cac47dcfa460b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da978fffe487c1256d718df59a34bdb86f5c8dc9dd66b85d8169dfef0ab5d0fbf3feffb932b1ea30bea71ef573ceee8d8c021b78ee12f2988f365c0d0fa39e87
|
7
|
+
data.tar.gz: 45360ea60c3059c64508caf7e14cf7fbac0dd6198c6fba7ac4a525653f8f508348223770174b2872f8e54cd5a80ab0bfa70d13ad62975cefe0dd93247e694480
|
data/History.md
CHANGED
@@ -1,5 +1,37 @@
|
|
1
1
|
# Liquid Change Log
|
2
2
|
|
3
|
+
## 5.2.0 2021-03-01
|
4
|
+
|
5
|
+
### Features
|
6
|
+
* Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
|
7
|
+
* Eagerly cache global filters (#1524) [Jean Boussier]
|
8
|
+
|
9
|
+
### Fixes
|
10
|
+
* Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]
|
11
|
+
* Allow dash in filter kwarg name for consistency with Liquid::C (#1518) [CP Clermont]
|
12
|
+
|
13
|
+
|
14
|
+
## 5.1.0 / 2021-09-09
|
15
|
+
|
16
|
+
### Features
|
17
|
+
* Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
|
18
|
+
* Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]
|
19
|
+
|
20
|
+
### Fixes
|
21
|
+
* Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
|
22
|
+
* Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]
|
23
|
+
* Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]
|
24
|
+
|
25
|
+
## 5.0.1 / 2021-03-24
|
26
|
+
|
27
|
+
### Fixes
|
28
|
+
* Add ParseTreeVisitor to Echo tag (#1414) [CP Clermont]
|
29
|
+
* Test with ruby 3.0 as the latest ruby version (#1398) [Dylan Thacker-Smith]
|
30
|
+
* Handle carriage return in newlines_to_br (#1391) [Unending]
|
31
|
+
|
32
|
+
### Performance Improvements
|
33
|
+
* Use split limit in truncatewords (#1361) [Dylan Thacker-Smith]
|
34
|
+
|
3
35
|
## 5.0.0 / 2021-01-06
|
4
36
|
|
5
37
|
### Features
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
* [Contributing guidelines](CONTRIBUTING.md)
|
7
7
|
* [Version history](History.md)
|
8
|
-
* [Liquid documentation from Shopify](
|
8
|
+
* [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
|
9
9
|
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
|
10
10
|
* [Website](http://liquidmarkup.org/)
|
11
11
|
|
@@ -56,7 +56,7 @@ For standard use you can just pass it the content of a file and call render with
|
|
56
56
|
|
57
57
|
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
|
58
58
|
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
|
59
|
-
it very hard to debug and can lead to unexpected behaviour.
|
59
|
+
it very hard to debug and can lead to unexpected behaviour.
|
60
60
|
|
61
61
|
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
|
62
62
|
when templates are invalid. You can enable this new parser like this:
|
data/lib/liquid/block_body.rb
CHANGED
@@ -99,7 +99,9 @@ module Liquid
|
|
99
99
|
end
|
100
100
|
|
101
101
|
private def parse_liquid_tag(markup, parse_context)
|
102
|
-
liquid_tag_tokenizer =
|
102
|
+
liquid_tag_tokenizer = parse_context.new_tokenizer(
|
103
|
+
markup, start_line_number: parse_context.line_number, for_liquid_tag: true
|
104
|
+
)
|
103
105
|
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|
|
104
106
|
if end_tag_name
|
105
107
|
BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)
|
@@ -229,8 +231,8 @@ module Liquid
|
|
229
231
|
end
|
230
232
|
|
231
233
|
def create_variable(token, parse_context)
|
232
|
-
token
|
233
|
-
markup =
|
234
|
+
if token =~ ContentOfVariable
|
235
|
+
markup = Regexp.last_match(1)
|
234
236
|
return Variable.new(markup, parse_context)
|
235
237
|
end
|
236
238
|
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
data/lib/liquid/condition.rb
CHANGED
@@ -8,7 +8,7 @@ module Liquid
|
|
8
8
|
# c = Condition.new(1, '==', 1)
|
9
9
|
# c.evaluate #=> true
|
10
10
|
#
|
11
|
-
class Condition
|
11
|
+
class Condition # :nodoc:
|
12
12
|
@@operators = {
|
13
13
|
'==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
14
14
|
'!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
@@ -134,8 +134,8 @@ module Liquid
|
|
134
134
|
# return this as the result.
|
135
135
|
return context.evaluate(left) if op.nil?
|
136
136
|
|
137
|
-
left = context.evaluate(left)
|
138
|
-
right = context.evaluate(right)
|
137
|
+
left = Liquid::Utils.to_liquid_value(context.evaluate(left))
|
138
|
+
right = Liquid::Utils.to_liquid_value(context.evaluate(right))
|
139
139
|
|
140
140
|
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
|
141
141
|
|
data/lib/liquid/context.rb
CHANGED
data/lib/liquid/expression.rb
CHANGED
@@ -10,21 +10,23 @@ module Liquid
|
|
10
10
|
'empty' => ''
|
11
11
|
}.freeze
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
|
16
|
-
FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
|
13
|
+
INTEGERS_REGEX = /\A(-?\d+)\z/
|
14
|
+
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
|
17
15
|
|
18
16
|
# Use an atomic group (?>...) to avoid pathological backtracing from
|
19
17
|
# malicious input as described in https://github.com/Shopify/liquid/issues/1357
|
20
|
-
RANGES_REGEX = /\A\
|
18
|
+
RANGES_REGEX = /\A\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\z/
|
21
19
|
|
22
20
|
def self.parse(markup)
|
21
|
+
return nil unless markup
|
22
|
+
|
23
|
+
markup = markup.strip
|
24
|
+
if (markup.start_with?('"') && markup.end_with?('"')) ||
|
25
|
+
(markup.start_with?("'") && markup.end_with?("'"))
|
26
|
+
return markup[1..-2]
|
27
|
+
end
|
28
|
+
|
23
29
|
case markup
|
24
|
-
when nil
|
25
|
-
nil
|
26
|
-
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
27
|
-
Regexp.last_match(1)
|
28
30
|
when INTEGERS_REGEX
|
29
31
|
Regexp.last_match(1).to_i
|
30
32
|
when RANGES_REGEX
|
@@ -32,7 +34,6 @@ module Liquid
|
|
32
34
|
when FLOATS_REGEX
|
33
35
|
Regexp.last_match(1).to_f
|
34
36
|
else
|
35
|
-
markup = markup.strip
|
36
37
|
if LITERALS.key?(markup)
|
37
38
|
LITERALS[markup]
|
38
39
|
else
|
data/lib/liquid/parse_context.rb
CHANGED
@@ -23,6 +23,10 @@ module Liquid
|
|
23
23
|
Liquid::BlockBody.new
|
24
24
|
end
|
25
25
|
|
26
|
+
def new_tokenizer(markup, start_line_number: nil, for_liquid_tag: false)
|
27
|
+
Tokenizer.new(markup, line_number: start_line_number, for_liquid_tag: for_liquid_tag)
|
28
|
+
end
|
29
|
+
|
26
30
|
def parse_expression(markup)
|
27
31
|
Expression.parse(markup)
|
28
32
|
end
|
data/lib/liquid/range_lookup.rb
CHANGED
@@ -12,6 +12,8 @@ module Liquid
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
attr_reader :start_obj, :end_obj
|
16
|
+
|
15
17
|
def initialize(start_obj, end_obj)
|
16
18
|
@start_obj = start_obj
|
17
19
|
@end_obj = end_obj
|
@@ -35,5 +37,11 @@ module Liquid
|
|
35
37
|
Utils.to_integer(input)
|
36
38
|
end
|
37
39
|
end
|
40
|
+
|
41
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
42
|
+
def children
|
43
|
+
[@node.start_obj, @node.end_obj]
|
44
|
+
end
|
45
|
+
end
|
38
46
|
end
|
39
47
|
end
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'cgi'
|
4
|
+
require 'base64'
|
4
5
|
require 'bigdecimal'
|
5
6
|
|
6
7
|
module Liquid
|
7
8
|
module StandardFilters
|
9
|
+
MAX_INT = (1 << 31) - 1
|
8
10
|
HTML_ESCAPE = {
|
9
11
|
'&' => '&',
|
10
12
|
'>' => '>',
|
@@ -62,6 +64,26 @@ module Liquid
|
|
62
64
|
result
|
63
65
|
end
|
64
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
|
+
|
65
87
|
def slice(input, offset, length = nil)
|
66
88
|
offset = Utils.to_integer(offset)
|
67
89
|
length = length ? Utils.to_integer(length) : 1
|
@@ -89,13 +111,21 @@ module Liquid
|
|
89
111
|
|
90
112
|
def truncatewords(input, words = 15, truncate_string = "...")
|
91
113
|
return if input.nil?
|
92
|
-
|
93
|
-
words
|
94
|
-
|
95
|
-
|
96
|
-
|
114
|
+
input = input.to_s
|
115
|
+
words = Utils.to_integer(words)
|
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
|
97
126
|
|
98
|
-
wordlist.
|
127
|
+
wordlist.pop
|
128
|
+
wordlist.join(" ").concat(truncate_string.to_s)
|
99
129
|
end
|
100
130
|
|
101
131
|
# Split input string into an array of substrings separated by given pattern.
|
@@ -183,17 +213,23 @@ module Liquid
|
|
183
213
|
|
184
214
|
if ary.empty?
|
185
215
|
[]
|
186
|
-
elsif
|
187
|
-
|
188
|
-
|
216
|
+
elsif target_value.nil?
|
217
|
+
ary.select do |item|
|
218
|
+
item[property]
|
189
219
|
rescue TypeError
|
190
220
|
raise_property_error(property)
|
221
|
+
rescue NoMethodError
|
222
|
+
return nil unless item.respond_to?(:[])
|
223
|
+
raise
|
191
224
|
end
|
192
|
-
|
193
|
-
|
194
|
-
|
225
|
+
else
|
226
|
+
ary.select do |item|
|
227
|
+
item[property] == target_value
|
195
228
|
rescue TypeError
|
196
229
|
raise_property_error(property)
|
230
|
+
rescue NoMethodError
|
231
|
+
return nil unless item.respond_to?(:[])
|
232
|
+
raise
|
197
233
|
end
|
198
234
|
end
|
199
235
|
end
|
@@ -207,11 +243,14 @@ module Liquid
|
|
207
243
|
ary.uniq
|
208
244
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
209
245
|
[]
|
210
|
-
|
211
|
-
|
212
|
-
|
246
|
+
else
|
247
|
+
ary.uniq do |item|
|
248
|
+
item[property]
|
213
249
|
rescue TypeError
|
214
250
|
raise_property_error(property)
|
251
|
+
rescue NoMethodError
|
252
|
+
return nil unless item.respond_to?(:[])
|
253
|
+
raise
|
215
254
|
end
|
216
255
|
end
|
217
256
|
end
|
@@ -247,11 +286,14 @@ module Liquid
|
|
247
286
|
ary.compact
|
248
287
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
249
288
|
[]
|
250
|
-
|
251
|
-
|
252
|
-
|
289
|
+
else
|
290
|
+
ary.reject do |item|
|
291
|
+
item[property].nil?
|
253
292
|
rescue TypeError
|
254
293
|
raise_property_error(property)
|
294
|
+
rescue NoMethodError
|
295
|
+
return nil unless item.respond_to?(:[])
|
296
|
+
raise
|
255
297
|
end
|
256
298
|
end
|
257
299
|
end
|
@@ -266,14 +308,34 @@ module Liquid
|
|
266
308
|
input.to_s.sub(string.to_s, replacement.to_s)
|
267
309
|
end
|
268
310
|
|
311
|
+
# Replace the last occurrences of a string with another
|
312
|
+
def replace_last(input, string, replacement)
|
313
|
+
input = input.to_s
|
314
|
+
string = string.to_s
|
315
|
+
replacement = replacement.to_s
|
316
|
+
|
317
|
+
start_index = input.rindex(string)
|
318
|
+
|
319
|
+
return input unless start_index
|
320
|
+
|
321
|
+
output = input.dup
|
322
|
+
output[start_index, string.length] = replacement
|
323
|
+
output
|
324
|
+
end
|
325
|
+
|
269
326
|
# remove a substring
|
270
327
|
def remove(input, string)
|
271
|
-
input
|
328
|
+
replace(input, string, '')
|
272
329
|
end
|
273
330
|
|
274
331
|
# remove the first occurrences of a substring
|
275
332
|
def remove_first(input, string)
|
276
|
-
input
|
333
|
+
replace_first(input, string, '')
|
334
|
+
end
|
335
|
+
|
336
|
+
# remove the last occurences of a substring
|
337
|
+
def remove_last(input, string)
|
338
|
+
replace_last(input, string, '')
|
277
339
|
end
|
278
340
|
|
279
341
|
# add one string to another
|
@@ -295,7 +357,7 @@ module Liquid
|
|
295
357
|
|
296
358
|
# Add <br /> tags in front of all newlines in input string
|
297
359
|
def newline_to_br(input)
|
298
|
-
input.to_s.gsub(/\n/, "<br />\n")
|
360
|
+
input.to_s.gsub(/\r?\n/, "<br />\n")
|
299
361
|
end
|
300
362
|
|
301
363
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
@@ -438,7 +500,7 @@ module Liquid
|
|
438
500
|
#
|
439
501
|
def default(input, default_value = '', options = {})
|
440
502
|
options = {} unless options.is_a?(Hash)
|
441
|
-
false_check = options['allow_false'] ? input.nil? : !input
|
503
|
+
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
|
442
504
|
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
443
505
|
end
|
444
506
|
|
@@ -456,10 +518,16 @@ module Liquid
|
|
456
518
|
end
|
457
519
|
|
458
520
|
def nil_safe_compare(a, b)
|
459
|
-
|
460
|
-
|
521
|
+
result = a <=> b
|
522
|
+
|
523
|
+
if result
|
524
|
+
result
|
525
|
+
elsif a.nil?
|
526
|
+
1
|
527
|
+
elsif b.nil?
|
528
|
+
-1
|
461
529
|
else
|
462
|
-
|
530
|
+
raise Liquid::ArgumentError, "cannot sort values of incompatible types"
|
463
531
|
end
|
464
532
|
end
|
465
533
|
|
@@ -7,25 +7,26 @@ module Liquid
|
|
7
7
|
|
8
8
|
def add_global_filter(filter)
|
9
9
|
strainer_class_cache.clear
|
10
|
-
|
10
|
+
GlobalCache.add_filter(filter)
|
11
11
|
end
|
12
12
|
|
13
13
|
def create(context, filters = [])
|
14
14
|
strainer_from_cache(filters).new(context)
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
GlobalCache = Class.new(StrainerTemplate)
|
18
18
|
|
19
|
-
|
20
|
-
@global_filters ||= []
|
21
|
-
end
|
19
|
+
private
|
22
20
|
|
23
21
|
def strainer_from_cache(filters)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
filters
|
28
|
-
|
22
|
+
if filters.empty?
|
23
|
+
GlobalCache
|
24
|
+
else
|
25
|
+
strainer_class_cache[filters] ||= begin
|
26
|
+
klass = Class.new(GlobalCache)
|
27
|
+
filters.each { |f| klass.add_filter(f) }
|
28
|
+
klass
|
29
|
+
end
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
data/lib/liquid/tags/case.rb
CHANGED
@@ -52,7 +52,14 @@ module Liquid
|
|
52
52
|
@blocks.each do |block|
|
53
53
|
if block.else?
|
54
54
|
block.attachment.render_to_output_buffer(context, output) if execute_else_block
|
55
|
-
|
55
|
+
next
|
56
|
+
end
|
57
|
+
|
58
|
+
result = Liquid::Utils.to_liquid_value(
|
59
|
+
block.evaluate(context)
|
60
|
+
)
|
61
|
+
|
62
|
+
if result
|
56
63
|
execute_else_block = false
|
57
64
|
block.attachment.render_to_output_buffer(context, output)
|
58
65
|
end
|
data/lib/liquid/tags/echo.rb
CHANGED
@@ -12,6 +12,8 @@ module Liquid
|
|
12
12
|
# {% echo user | link %}
|
13
13
|
#
|
14
14
|
class Echo < Tag
|
15
|
+
attr_reader :variable
|
16
|
+
|
15
17
|
def initialize(tag_name, markup, parse_context)
|
16
18
|
super
|
17
19
|
@variable = Variable.new(markup, parse_context)
|
@@ -20,6 +22,12 @@ module Liquid
|
|
20
22
|
def render(context)
|
21
23
|
@variable.render_to_output_buffer(context, +'')
|
22
24
|
end
|
25
|
+
|
26
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
27
|
+
def children
|
28
|
+
[@node.variable]
|
29
|
+
end
|
30
|
+
end
|
23
31
|
end
|
24
32
|
|
25
33
|
Template.register_tag('echo', Echo)
|
data/lib/liquid/tags/if.rb
CHANGED
@@ -50,7 +50,11 @@ module Liquid
|
|
50
50
|
|
51
51
|
def render_to_output_buffer(context, output)
|
52
52
|
@blocks.each do |block|
|
53
|
-
|
53
|
+
result = Liquid::Utils.to_liquid_value(
|
54
|
+
block.evaluate(context)
|
55
|
+
)
|
56
|
+
|
57
|
+
if result
|
54
58
|
return block.attachment.render_to_output_buffer(context, output)
|
55
59
|
end
|
56
60
|
end
|
data/lib/liquid/tags/unless.rb
CHANGED
@@ -11,13 +11,21 @@ module Liquid
|
|
11
11
|
def render_to_output_buffer(context, output)
|
12
12
|
# First condition is interpreted backwards ( if not )
|
13
13
|
first_block = @blocks.first
|
14
|
-
|
14
|
+
result = Liquid::Utils.to_liquid_value(
|
15
|
+
first_block.evaluate(context)
|
16
|
+
)
|
17
|
+
|
18
|
+
unless result
|
15
19
|
return first_block.attachment.render_to_output_buffer(context, output)
|
16
20
|
end
|
17
21
|
|
18
22
|
# After the first condition unless works just like if
|
19
23
|
@blocks[1..-1].each do |block|
|
20
|
-
|
24
|
+
result = Liquid::Utils.to_liquid_value(
|
25
|
+
block.evaluate(context)
|
26
|
+
)
|
27
|
+
|
28
|
+
if result
|
21
29
|
return block.attachment.render_to_output_buffer(context, output)
|
22
30
|
end
|
23
31
|
end
|
data/lib/liquid/template.rb
CHANGED
@@ -107,7 +107,8 @@ module Liquid
|
|
107
107
|
# Returns self for easy chaining
|
108
108
|
def parse(source, options = {})
|
109
109
|
parse_context = configure_options(options)
|
110
|
-
|
110
|
+
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
|
111
|
+
@root = Document.parse(tokenizer, parse_context)
|
111
112
|
self
|
112
113
|
end
|
113
114
|
|
@@ -223,10 +224,6 @@ module Liquid
|
|
223
224
|
parse_context
|
224
225
|
end
|
225
226
|
|
226
|
-
def tokenize(source)
|
227
|
-
Tokenizer.new(source, @line_numbers)
|
228
|
-
end
|
229
|
-
|
230
227
|
def apply_options_to_context(context, options)
|
231
228
|
context.add_filters(options[:filters]) if options[:filters]
|
232
229
|
context.global_filter = options[:global_filter] if options[:global_filter]
|
data/lib/liquid/tokenizer.rb
CHANGED
@@ -5,7 +5,7 @@ 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.to_s.to_str
|
9
9
|
@line_number = line_number || (line_numbers ? 1 : nil)
|
10
10
|
@for_liquid_tag = for_liquid_tag
|
11
11
|
@tokens = tokenize
|
@@ -24,7 +24,7 @@ module Liquid
|
|
24
24
|
private
|
25
25
|
|
26
26
|
def tokenize
|
27
|
-
return [] if @source.
|
27
|
+
return [] if @source.empty?
|
28
28
|
|
29
29
|
return @source.split("\n") if @for_liquid_tag
|
30
30
|
|
data/lib/liquid/utils.rb
CHANGED
@@ -81,5 +81,13 @@ module Liquid
|
|
81
81
|
rescue ::ArgumentError
|
82
82
|
nil
|
83
83
|
end
|
84
|
+
|
85
|
+
def self.to_liquid_value(obj)
|
86
|
+
# Enable "obj" to represent itself as a primitive value like integer, string, or boolean
|
87
|
+
return obj.to_liquid_value if obj.respond_to?(:to_liquid_value)
|
88
|
+
|
89
|
+
# Otherwise return the object itself
|
90
|
+
obj
|
91
|
+
end
|
84
92
|
end
|
85
93
|
end
|
@@ -40,6 +40,9 @@ module Liquid
|
|
40
40
|
@lookups.each_index do |i|
|
41
41
|
key = context.evaluate(@lookups[i])
|
42
42
|
|
43
|
+
# Cast "key" to its liquid value to enable it to act as a primitive value
|
44
|
+
key = Liquid::Utils.to_liquid_value(key)
|
45
|
+
|
43
46
|
# If object is a hash- or array-like object we look for the
|
44
47
|
# presence of the key and if its available we return it
|
45
48
|
if object.respond_to?(:[]) &&
|
data/lib/liquid/version.rb
CHANGED
data/lib/liquid.rb
CHANGED
@@ -36,7 +36,7 @@ module Liquid
|
|
36
36
|
VariableIncompleteEnd = /\}\}?/
|
37
37
|
QuotedString = /"[^"]*"|'[^']*'/
|
38
38
|
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
39
|
-
TagAttributes = /(\w
|
39
|
+
TagAttributes = /(\w[\w-]*)\s*\:\s*(#{QuotedFragment})/o
|
40
40
|
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
|
41
41
|
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
42
42
|
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
@@ -59,8 +59,8 @@ require 'liquid/forloop_drop'
|
|
59
59
|
require 'liquid/extensions'
|
60
60
|
require 'liquid/errors'
|
61
61
|
require 'liquid/interrupts'
|
62
|
-
require 'liquid/strainer_factory'
|
63
62
|
require 'liquid/strainer_template'
|
63
|
+
require 'liquid/strainer_factory'
|
64
64
|
require 'liquid/expression'
|
65
65
|
require 'liquid/context'
|
66
66
|
require 'liquid/parser_switching'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class FilterKwargTest < Minitest::Test
|
6
|
+
module KwargFilter
|
7
|
+
def html_tag(_tag, attributes)
|
8
|
+
attributes
|
9
|
+
.map { |key, value| "#{key}='#{value}'" }
|
10
|
+
.join(' ')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
include Liquid
|
15
|
+
|
16
|
+
def test_can_parse_data_kwargs
|
17
|
+
with_global_filter(KwargFilter) do
|
18
|
+
assert_equal(
|
19
|
+
"data-src='src' data-widths='100, 200'",
|
20
|
+
Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -145,6 +145,40 @@ class StandardFiltersTest < Minitest::Test
|
|
145
145
|
assert_equal('<strong>Hulk</strong>', @filters.escape_once('<strong>Hulk</strong>'))
|
146
146
|
end
|
147
147
|
|
148
|
+
def test_base64_encode
|
149
|
+
assert_equal('b25lIHR3byB0aHJlZQ==', @filters.base64_encode('one two three'))
|
150
|
+
assert_equal('', @filters.base64_encode(nil))
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_base64_decode
|
154
|
+
assert_equal('one two three', @filters.base64_decode('b25lIHR3byB0aHJlZQ=='))
|
155
|
+
|
156
|
+
exception = assert_raises(Liquid::ArgumentError) do
|
157
|
+
@filters.base64_decode("invalidbase64")
|
158
|
+
end
|
159
|
+
|
160
|
+
assert_equal('Liquid error: invalid base64 provided to base64_decode', exception.message)
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_base64_url_safe_encode
|
164
|
+
assert_equal(
|
165
|
+
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8',
|
166
|
+
@filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|')
|
167
|
+
)
|
168
|
+
assert_equal('', @filters.base64_url_safe_encode(nil))
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_base64_url_safe_decode
|
172
|
+
assert_equal(
|
173
|
+
'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|',
|
174
|
+
@filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8')
|
175
|
+
)
|
176
|
+
exception = assert_raises(Liquid::ArgumentError) do
|
177
|
+
@filters.base64_url_safe_decode("invalidbase64")
|
178
|
+
end
|
179
|
+
assert_equal('Liquid error: invalid base64 provided to base64_url_safe_decode', exception.message)
|
180
|
+
end
|
181
|
+
|
148
182
|
def test_url_encode
|
149
183
|
assert_equal('foo%2B1%40example.com', @filters.url_encode('foo+1@example.com'))
|
150
184
|
assert_equal('1', @filters.url_encode(1))
|
@@ -171,10 +205,17 @@ class StandardFiltersTest < Minitest::Test
|
|
171
205
|
assert_equal('one two three', @filters.truncatewords('one two three'))
|
172
206
|
assert_equal(
|
173
207
|
'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...',
|
174
|
-
|
208
|
+
@filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
175
209
|
)
|
176
210
|
assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
|
177
211
|
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
|
212
|
+
assert_equal('one two three...', @filters.truncatewords("one two\tthree\nfour", 3))
|
213
|
+
assert_equal('one two...', @filters.truncatewords("one two three four", 2))
|
214
|
+
assert_equal('one...', @filters.truncatewords("one two three four", 0))
|
215
|
+
exception = assert_raises(Liquid::ArgumentError) do
|
216
|
+
@filters.truncatewords("one two three four", 1 << 31)
|
217
|
+
end
|
218
|
+
assert_equal("Liquid error: integer #{1 << 31} too big for truncatewords", exception.message)
|
178
219
|
end
|
179
220
|
|
180
221
|
def test_strip_html
|
@@ -218,8 +259,8 @@ class StandardFiltersTest < Minitest::Test
|
|
218
259
|
{ "price" => 1, "handle" => "gamma" },
|
219
260
|
{ "price" => 2, "handle" => "epsilon" },
|
220
261
|
{ "price" => 4, "handle" => "alpha" },
|
221
|
-
{ "handle" => "delta" },
|
222
262
|
{ "handle" => "beta" },
|
263
|
+
{ "handle" => "delta" },
|
223
264
|
]
|
224
265
|
assert_equal(expectation, @filters.sort(input, "price"))
|
225
266
|
end
|
@@ -498,19 +539,31 @@ class StandardFiltersTest < Minitest::Test
|
|
498
539
|
end
|
499
540
|
|
500
541
|
def test_replace
|
501
|
-
assert_equal('
|
542
|
+
assert_equal('b b b b', @filters.replace('a a a a', 'a', 'b'))
|
502
543
|
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2))
|
503
|
-
assert_equal('
|
544
|
+
assert_equal('1 1 1 1', @filters.replace('1 1 1 1', 2, 3))
|
545
|
+
assert_template_result('2 2 2 2', "{{ '1 1 1 1' | replace: '1', 2 }}")
|
546
|
+
|
547
|
+
assert_equal('b a a a', @filters.replace_first('a a a a', 'a', 'b'))
|
504
548
|
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))
|
549
|
+
assert_equal('1 1 1 1', @filters.replace_first('1 1 1 1', 2, 3))
|
505
550
|
assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}")
|
551
|
+
|
552
|
+
assert_equal('a a a b', @filters.replace_last('a a a a', 'a', 'b'))
|
553
|
+
assert_equal('1 1 1 2', @filters.replace_last('1 1 1 1', 1, 2))
|
554
|
+
assert_equal('1 1 1 1', @filters.replace_last('1 1 1 1', 2, 3))
|
555
|
+
assert_template_result('1 1 1 2', "{{ '1 1 1 1' | replace_last: '1', 2 }}")
|
506
556
|
end
|
507
557
|
|
508
558
|
def test_remove
|
509
559
|
assert_equal(' ', @filters.remove("a a a a", 'a'))
|
510
|
-
|
511
|
-
|
512
|
-
assert_equal('
|
513
|
-
assert_template_result('
|
560
|
+
assert_template_result(' ', "{{ '1 1 1 1' | remove: 1 }}")
|
561
|
+
|
562
|
+
assert_equal('b a a', @filters.remove_first("a b a a", 'a '))
|
563
|
+
assert_template_result(' 1 1 1', "{{ '1 1 1 1' | remove_first: 1 }}")
|
564
|
+
|
565
|
+
assert_equal('a a b', @filters.remove_last("a a b a", ' a'))
|
566
|
+
assert_template_result('1 1 1 ', "{{ '1 1 1 1' | remove_last: 1 }}")
|
514
567
|
end
|
515
568
|
|
516
569
|
def test_pipes_in_string_arguments
|
@@ -539,6 +592,7 @@ class StandardFiltersTest < Minitest::Test
|
|
539
592
|
|
540
593
|
def test_newlines_to_br
|
541
594
|
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc")
|
595
|
+
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\r\nb\nc")
|
542
596
|
end
|
543
597
|
|
544
598
|
def test_plus
|
@@ -686,6 +740,8 @@ class StandardFiltersTest < Minitest::Test
|
|
686
740
|
assert_equal("bar", @filters.default([], "bar"))
|
687
741
|
assert_equal("bar", @filters.default({}, "bar"))
|
688
742
|
assert_template_result('bar', "{{ false | default: 'bar' }}")
|
743
|
+
assert_template_result('bar', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(false))
|
744
|
+
assert_template_result('Yay', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(true))
|
689
745
|
end
|
690
746
|
|
691
747
|
def test_default_handle_false
|
@@ -696,6 +752,8 @@ class StandardFiltersTest < Minitest::Test
|
|
696
752
|
assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
|
697
753
|
assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
|
698
754
|
assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
|
755
|
+
assert_template_result('Nay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(false))
|
756
|
+
assert_template_result('Yay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(true))
|
699
757
|
end
|
700
758
|
|
701
759
|
def test_cannot_access_private_methods
|
@@ -724,6 +782,18 @@ class StandardFiltersTest < Minitest::Test
|
|
724
782
|
assert_equal(expectation, @filters.where(input, "ok"))
|
725
783
|
end
|
726
784
|
|
785
|
+
def test_where_string_keys
|
786
|
+
input = [
|
787
|
+
"alpha", "beta", "gamma", "delta"
|
788
|
+
]
|
789
|
+
|
790
|
+
expectation = [
|
791
|
+
"beta",
|
792
|
+
]
|
793
|
+
|
794
|
+
assert_equal(expectation, @filters.where(input, "be"))
|
795
|
+
end
|
796
|
+
|
727
797
|
def test_where_no_key_set
|
728
798
|
input = [
|
729
799
|
{ "handle" => "alpha", "ok" => true },
|
@@ -794,19 +864,14 @@ class StandardFiltersTest < Minitest::Test
|
|
794
864
|
{ 1 => "bar" },
|
795
865
|
["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
|
796
866
|
]
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
@filters.send(method, *inputs)
|
806
|
-
rescue Liquid::ArgumentError, Liquid::ZeroDivisionError
|
807
|
-
nil
|
808
|
-
end
|
809
|
-
end
|
867
|
+
StandardFilters.public_instance_methods(false).each do |method|
|
868
|
+
arg_count = @filters.method(method).arity
|
869
|
+
arg_count *= -1 if arg_count < 0
|
870
|
+
|
871
|
+
test_types.repeated_permutation(arg_count) do |args|
|
872
|
+
@filters.send(method, *args)
|
873
|
+
rescue Liquid::Error
|
874
|
+
nil
|
810
875
|
end
|
811
876
|
end
|
812
877
|
end
|
@@ -96,12 +96,12 @@ class IncludeTagTest < Minitest::Test
|
|
96
96
|
|
97
97
|
def test_include_tag_with_alias
|
98
98
|
assert_template_result("Product: Draft 151cm ",
|
99
|
-
|
99
|
+
"{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
100
100
|
end
|
101
101
|
|
102
102
|
def test_include_tag_for_alias
|
103
103
|
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
104
|
-
|
104
|
+
"{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
105
105
|
end
|
106
106
|
|
107
107
|
def test_include_tag_with_default_name
|
@@ -151,7 +151,7 @@ class RenderTagTest < Minitest::Test
|
|
151
151
|
)
|
152
152
|
|
153
153
|
assert_template_result("Product: Draft 151cm ",
|
154
|
-
|
154
|
+
"{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
155
155
|
end
|
156
156
|
|
157
157
|
def test_render_tag_with_alias
|
@@ -161,7 +161,7 @@ class RenderTagTest < Minitest::Test
|
|
161
161
|
)
|
162
162
|
|
163
163
|
assert_template_result("Product: Draft 151cm ",
|
164
|
-
|
164
|
+
"{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
165
165
|
end
|
166
166
|
|
167
167
|
def test_render_tag_for_alias
|
@@ -171,7 +171,7 @@ class RenderTagTest < Minitest::Test
|
|
171
171
|
)
|
172
172
|
|
173
173
|
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
174
|
-
|
174
|
+
"{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
175
175
|
end
|
176
176
|
|
177
177
|
def test_render_tag_for
|
@@ -181,7 +181,7 @@ class RenderTagTest < Minitest::Test
|
|
181
181
|
)
|
182
182
|
|
183
183
|
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
184
|
-
|
184
|
+
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
185
185
|
end
|
186
186
|
|
187
187
|
def test_render_tag_forloop
|
@@ -190,7 +190,7 @@ class RenderTagTest < Minitest::Test
|
|
190
190
|
)
|
191
191
|
|
192
192
|
assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
|
193
|
-
|
193
|
+
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
194
194
|
end
|
195
195
|
|
196
196
|
def test_render_tag_for_drop
|
@@ -199,7 +199,7 @@ class RenderTagTest < Minitest::Test
|
|
199
199
|
)
|
200
200
|
|
201
201
|
assert_template_result("123",
|
202
|
-
|
202
|
+
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
|
203
203
|
end
|
204
204
|
|
205
205
|
def test_render_tag_with_drop
|
@@ -208,6 +208,6 @@ class RenderTagTest < Minitest::Test
|
|
208
208
|
)
|
209
209
|
|
210
210
|
assert_template_result("TestEnumerable",
|
211
|
-
|
211
|
+
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
|
212
212
|
end
|
213
213
|
end
|
@@ -323,4 +323,18 @@ class TemplateTest < Minitest::Test
|
|
323
323
|
result = t.render('x' => 1, 'y' => 5)
|
324
324
|
assert_equal('12345', result)
|
325
325
|
end
|
326
|
+
|
327
|
+
def test_source_string_subclass
|
328
|
+
string_subclass = Class.new(String) do
|
329
|
+
# E.g. ActiveSupport::SafeBuffer does this, so don't just rely on to_s to return a String
|
330
|
+
def to_s
|
331
|
+
self
|
332
|
+
end
|
333
|
+
end
|
334
|
+
source = string_subclass.new("{% assign x = 2 -%} x= {{- x }}")
|
335
|
+
assert_instance_of(string_subclass, source)
|
336
|
+
output = Template.parse(source).render!
|
337
|
+
assert_equal("x=2", output)
|
338
|
+
assert_instance_of(String, output)
|
339
|
+
end
|
326
340
|
end
|
@@ -15,6 +15,33 @@ class VariableTest < Minitest::Test
|
|
15
15
|
assert_template_result('foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new)
|
16
16
|
end
|
17
17
|
|
18
|
+
def test_variable_lookup_calls_to_liquid_value
|
19
|
+
assert_template_result('1', '{{ foo }}', 'foo' => IntegerDrop.new('1'))
|
20
|
+
assert_template_result('2', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3])
|
21
|
+
assert_template_result('one', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' })
|
22
|
+
assert_template_result('Yay', '{{ foo }}', 'foo' => BooleanDrop.new(true))
|
23
|
+
assert_template_result('YAY', '{{ foo | upcase }}', 'foo' => BooleanDrop.new(true))
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_if_tag_calls_to_liquid_value
|
27
|
+
assert_template_result('one', '{% if foo == 1 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
|
28
|
+
assert_template_result('one', '{% if 0 < foo %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
|
29
|
+
assert_template_result('one', '{% if foo > 0 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
|
30
|
+
assert_template_result('true', '{% if foo == true %}true{% endif %}', 'foo' => BooleanDrop.new(true))
|
31
|
+
assert_template_result('true', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(true))
|
32
|
+
|
33
|
+
assert_template_result('', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(false))
|
34
|
+
assert_template_result('', '{% if foo == true %}True{% endif %}', 'foo' => BooleanDrop.new(false))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_unless_tag_calls_to_liquid_value
|
38
|
+
assert_template_result('', '{% unless foo %}true{% endunless %}', 'foo' => BooleanDrop.new(true))
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_case_tag_calls_to_liquid_value
|
42
|
+
assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', 'foo' => IntegerDrop.new('1'))
|
43
|
+
end
|
44
|
+
|
18
45
|
def test_simple_with_whitespaces
|
19
46
|
template = Template.parse(%( {{ test }} ))
|
20
47
|
assert_equal(' worked ', template.render!('test' => 'worked'))
|
@@ -104,4 +131,8 @@ class VariableTest < Minitest::Test
|
|
104
131
|
def test_dynamic_find_var
|
105
132
|
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
|
106
133
|
end
|
134
|
+
|
135
|
+
def test_raw_value_variable
|
136
|
+
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
|
137
|
+
end
|
107
138
|
end
|
data/test/test_helper.rb
CHANGED
@@ -72,21 +72,21 @@ module Minitest
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def with_global_filter(*globals)
|
75
|
-
|
76
|
-
Liquid::StrainerFactory.
|
77
|
-
|
78
|
-
Liquid::StrainerFactory.add_global_filter(global)
|
79
|
-
end
|
80
|
-
|
81
|
-
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
75
|
+
original_global_cache = Liquid::StrainerFactory::GlobalCache
|
76
|
+
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
|
77
|
+
Liquid::StrainerFactory.const_set(:GlobalCache, Class.new(Liquid::StrainerTemplate))
|
82
78
|
|
83
79
|
globals.each do |global|
|
84
80
|
Liquid::Template.register_filter(global)
|
85
81
|
end
|
86
|
-
yield
|
87
|
-
ensure
|
88
82
|
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
89
|
-
|
83
|
+
begin
|
84
|
+
yield
|
85
|
+
ensure
|
86
|
+
Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
|
87
|
+
Liquid::StrainerFactory.const_set(:GlobalCache, original_global_cache)
|
88
|
+
Liquid::StrainerFactory.send(:strainer_class_cache).clear
|
89
|
+
end
|
90
90
|
end
|
91
91
|
|
92
92
|
def with_error_mode(mode)
|
@@ -119,6 +119,44 @@ class ThingWithToLiquid
|
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
|
+
class IntegerDrop < Liquid::Drop
|
123
|
+
def initialize(value)
|
124
|
+
super()
|
125
|
+
@value = value.to_i
|
126
|
+
end
|
127
|
+
|
128
|
+
def ==(other)
|
129
|
+
@value == other
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_s
|
133
|
+
@value.to_s
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_liquid_value
|
137
|
+
@value
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class BooleanDrop < Liquid::Drop
|
142
|
+
def initialize(value)
|
143
|
+
super()
|
144
|
+
@value = value
|
145
|
+
end
|
146
|
+
|
147
|
+
def ==(other)
|
148
|
+
@value == other
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_liquid_value
|
152
|
+
@value
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_s
|
156
|
+
@value ? "Yay" : "Nay"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
122
160
|
class ErrorDrop < Liquid::Drop
|
123
161
|
def standard_error
|
124
162
|
raise Liquid::StandardError, 'standard error'
|
@@ -26,6 +26,13 @@ class ParseTreeVisitorTest < Minitest::Test
|
|
26
26
|
)
|
27
27
|
end
|
28
28
|
|
29
|
+
def test_echo
|
30
|
+
assert_equal(
|
31
|
+
["test"],
|
32
|
+
visit(%({% echo test %}))
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
29
36
|
def test_if_condition
|
30
37
|
assert_equal(
|
31
38
|
["test"],
|
@@ -152,6 +159,13 @@ class ParseTreeVisitorTest < Minitest::Test
|
|
152
159
|
)
|
153
160
|
end
|
154
161
|
|
162
|
+
def test_for_range
|
163
|
+
assert_equal(
|
164
|
+
["test"],
|
165
|
+
visit(%({% for x in (1..test) %}{% endfor %}))
|
166
|
+
)
|
167
|
+
end
|
168
|
+
|
155
169
|
def test_tablerow_in
|
156
170
|
assert_equal(
|
157
171
|
["test"],
|
@@ -52,7 +52,8 @@ class StrainerFactoryUnitTest < Minitest::Test
|
|
52
52
|
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
|
53
53
|
exception.message
|
54
54
|
)
|
55
|
-
|
55
|
+
source = AccessScopeFilters.instance_method(:public_filter).source_location
|
56
|
+
assert_equal(source.map(&:to_s), exception.backtrace[0].split(':')[0..1])
|
56
57
|
end
|
57
58
|
|
58
59
|
def test_strainer_only_invokes_public_filter_methods
|
@@ -57,8 +57,8 @@ class StrainerTemplateUnitTest < Minitest::Test
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
|
60
|
-
strainer = Context.new.strainer
|
61
60
|
with_global_filter do
|
61
|
+
strainer = Context.new.strainer
|
62
62
|
strainer.class.add_filter(PublicMethodOverrideFilter)
|
63
63
|
assert(strainer.class.send(:filter_methods).include?('public_filter'))
|
64
64
|
end
|
@@ -32,21 +32,26 @@ class TokenizerTest < Minitest::Test
|
|
32
32
|
|
33
33
|
private
|
34
34
|
|
35
|
+
def new_tokenizer(source, parse_context: Liquid::ParseContext.new, start_line_number: nil)
|
36
|
+
parse_context.new_tokenizer(source, start_line_number: start_line_number)
|
37
|
+
end
|
38
|
+
|
35
39
|
def tokenize(source)
|
36
|
-
tokenizer =
|
40
|
+
tokenizer = new_tokenizer(source)
|
37
41
|
tokens = []
|
38
|
-
|
42
|
+
# shift is private in Liquid::C::Tokenizer, since it is only for unit testing
|
43
|
+
while (t = tokenizer.send(:shift))
|
39
44
|
tokens << t
|
40
45
|
end
|
41
46
|
tokens
|
42
47
|
end
|
43
48
|
|
44
49
|
def tokenize_line_numbers(source)
|
45
|
-
tokenizer =
|
50
|
+
tokenizer = new_tokenizer(source, start_line_number: 1)
|
46
51
|
line_numbers = []
|
47
52
|
loop do
|
48
53
|
line_number = tokenizer.line_number
|
49
|
-
if tokenizer.shift
|
54
|
+
if tokenizer.send(:shift)
|
50
55
|
line_numbers << line_number
|
51
56
|
else
|
52
57
|
break
|
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.2.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: 2022-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -120,6 +120,7 @@ files:
|
|
120
120
|
- test/integration/drop_test.rb
|
121
121
|
- test/integration/error_handling_test.rb
|
122
122
|
- test/integration/expression_test.rb
|
123
|
+
- test/integration/filter_kwarg_test.rb
|
123
124
|
- test/integration/filter_test.rb
|
124
125
|
- test/integration/hash_ordering_test.rb
|
125
126
|
- test/integration/output_test.rb
|
@@ -187,65 +188,66 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
188
|
- !ruby/object:Gem::Version
|
188
189
|
version: 1.3.7
|
189
190
|
requirements: []
|
190
|
-
rubygems_version: 3.
|
191
|
+
rubygems_version: 3.2.20
|
191
192
|
signing_key:
|
192
193
|
specification_version: 4
|
193
194
|
summary: A secure, non-evaling end user template engine with aesthetic markup.
|
194
195
|
test_files:
|
195
|
-
- test/
|
196
|
-
- test/unit/condition_unit_test.rb
|
197
|
-
- test/unit/template_factory_unit_test.rb
|
198
|
-
- test/unit/tag_unit_test.rb
|
199
|
-
- test/unit/tokenizer_unit_test.rb
|
200
|
-
- test/unit/regexp_unit_test.rb
|
201
|
-
- test/unit/file_system_unit_test.rb
|
202
|
-
- test/unit/block_unit_test.rb
|
203
|
-
- test/unit/strainer_factory_unit_test.rb
|
204
|
-
- test/unit/i18n_unit_test.rb
|
205
|
-
- test/unit/variable_unit_test.rb
|
206
|
-
- test/unit/static_registers_unit_test.rb
|
207
|
-
- test/unit/strainer_template_unit_test.rb
|
208
|
-
- test/unit/template_unit_test.rb
|
209
|
-
- test/unit/partial_cache_unit_test.rb
|
210
|
-
- test/unit/parse_tree_visitor_test.rb
|
211
|
-
- test/unit/tags/if_tag_unit_test.rb
|
212
|
-
- test/unit/tags/case_tag_unit_test.rb
|
213
|
-
- test/unit/tags/for_tag_unit_test.rb
|
214
|
-
- test/unit/parser_unit_test.rb
|
215
|
-
- test/test_helper.rb
|
216
|
-
- test/integration/expression_test.rb
|
196
|
+
- test/integration/tag/disableable_test.rb
|
217
197
|
- test/integration/parsing_quirks_test.rb
|
218
|
-
- test/integration/security_test.rb
|
219
|
-
- test/integration/template_test.rb
|
220
|
-
- test/integration/filter_test.rb
|
221
|
-
- test/integration/drop_test.rb
|
222
|
-
- test/integration/blank_test.rb
|
223
|
-
- test/integration/capture_test.rb
|
224
198
|
- test/integration/context_test.rb
|
225
|
-
- test/integration/
|
226
|
-
- test/integration/
|
227
|
-
- test/integration/tag_test.rb
|
228
|
-
- test/integration/tag/disableable_test.rb
|
229
|
-
- test/integration/standard_filter_test.rb
|
230
|
-
- test/integration/output_test.rb
|
231
|
-
- test/integration/assign_test.rb
|
232
|
-
- test/integration/profiler_test.rb
|
199
|
+
- test/integration/filter_kwarg_test.rb
|
200
|
+
- test/integration/capture_test.rb
|
233
201
|
- test/integration/trim_mode_test.rb
|
234
|
-
- test/integration/
|
235
|
-
- test/integration/tags/echo_test.rb
|
202
|
+
- test/integration/output_test.rb
|
236
203
|
- test/integration/tags/raw_tag_test.rb
|
237
|
-
- test/integration/tags/
|
238
|
-
- test/integration/tags/
|
239
|
-
- test/integration/tags/
|
240
|
-
- test/integration/tags/render_tag_test.rb
|
204
|
+
- test/integration/tags/continue_tag_test.rb
|
205
|
+
- test/integration/tags/increment_tag_test.rb
|
206
|
+
- test/integration/tags/if_else_tag_test.rb
|
241
207
|
- test/integration/tags/table_row_test.rb
|
208
|
+
- test/integration/tags/include_tag_test.rb
|
242
209
|
- test/integration/tags/break_tag_test.rb
|
243
|
-
- test/integration/tags/if_else_tag_test.rb
|
244
210
|
- test/integration/tags/unless_else_tag_test.rb
|
245
|
-
- test/integration/tags/
|
246
|
-
- test/integration/tags/
|
211
|
+
- test/integration/tags/standard_tag_test.rb
|
212
|
+
- test/integration/tags/for_tag_test.rb
|
213
|
+
- test/integration/tags/statements_test.rb
|
247
214
|
- test/integration/tags/liquid_tag_test.rb
|
248
|
-
- test/integration/tags/
|
215
|
+
- test/integration/tags/render_tag_test.rb
|
216
|
+
- test/integration/tags/echo_test.rb
|
217
|
+
- test/integration/drop_test.rb
|
218
|
+
- test/integration/error_handling_test.rb
|
219
|
+
- test/integration/template_test.rb
|
220
|
+
- test/integration/expression_test.rb
|
221
|
+
- test/integration/standard_filter_test.rb
|
222
|
+
- test/integration/tag_test.rb
|
249
223
|
- test/integration/hash_ordering_test.rb
|
224
|
+
- test/integration/security_test.rb
|
225
|
+
- test/integration/blank_test.rb
|
226
|
+
- test/integration/filter_test.rb
|
227
|
+
- test/integration/document_test.rb
|
228
|
+
- test/integration/block_test.rb
|
229
|
+
- test/integration/profiler_test.rb
|
250
230
|
- test/integration/variable_test.rb
|
231
|
+
- test/integration/assign_test.rb
|
232
|
+
- test/unit/template_unit_test.rb
|
233
|
+
- test/unit/tag_unit_test.rb
|
234
|
+
- test/unit/condition_unit_test.rb
|
235
|
+
- test/unit/strainer_template_unit_test.rb
|
236
|
+
- test/unit/lexer_unit_test.rb
|
237
|
+
- test/unit/partial_cache_unit_test.rb
|
238
|
+
- test/unit/template_factory_unit_test.rb
|
239
|
+
- test/unit/tags/if_tag_unit_test.rb
|
240
|
+
- test/unit/tags/case_tag_unit_test.rb
|
241
|
+
- test/unit/tags/for_tag_unit_test.rb
|
242
|
+
- test/unit/static_registers_unit_test.rb
|
243
|
+
- test/unit/regexp_unit_test.rb
|
244
|
+
- test/unit/i18n_unit_test.rb
|
245
|
+
- test/unit/parse_tree_visitor_test.rb
|
246
|
+
- test/unit/variable_unit_test.rb
|
247
|
+
- test/unit/tokenizer_unit_test.rb
|
248
|
+
- test/unit/file_system_unit_test.rb
|
249
|
+
- test/unit/block_unit_test.rb
|
250
|
+
- test/unit/strainer_factory_unit_test.rb
|
251
|
+
- test/unit/parser_unit_test.rb
|
252
|
+
- test/test_helper.rb
|
251
253
|
- test/fixtures/en_locale.yml
|