liquid 5.0.0 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +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
|