liquid 5.6.1 → 5.8.7
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 +67 -2
- data/lib/liquid/context.rb +1 -4
- data/lib/liquid/expression.rb +9 -4
- data/lib/liquid/lexer.rb +6 -0
- data/lib/liquid/locales/en.yml +2 -0
- data/lib/liquid/standardfilters.rb +153 -61
- data/lib/liquid/tags/decrement.rb +1 -1
- data/lib/liquid/tags/doc.rb +81 -0
- data/lib/liquid/tags/include.rb +1 -1
- data/lib/liquid/tags/increment.rb +1 -1
- data/lib/liquid/tags/render.rb +1 -1
- data/lib/liquid/tags.rb +2 -0
- data/lib/liquid/tokenizer.rb +6 -0
- data/lib/liquid/utils.rb +96 -0
- data/lib/liquid/variable.rb +2 -2
- data/lib/liquid/variable_lookup.rb +1 -1
- data/lib/liquid/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3b75c321445b52cb025c14359defc6f1c5c38662f4cc49fdb25665578d7e0bd
|
4
|
+
data.tar.gz: c8b444e6a3848fbe278c8208feca605b788417aba56d2091e39cc6cf427f703b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44360766b328bd38396bf8a0fa0a37f6808f955804fb0208d415d01ef5370ad3504adb8fa204e1df16fb28917e96588a0d51bcb21a46607a0c350b1434f7ea35
|
7
|
+
data.tar.gz: 59de1d715d6a9dcff97600e160c0827bd83a6e4f36f92d546c8f6d7734103ffcbe0477d829a8a3c397db8bde5ca56fab13b981997e1222f734794c23303e29e4
|
data/History.md
CHANGED
@@ -1,11 +1,76 @@
|
|
1
1
|
# Liquid Change Log
|
2
2
|
|
3
|
-
## 5.
|
3
|
+
## 5.8.7
|
4
|
+
* Expose body content in the `Doc` tag [James Meng]
|
5
|
+
|
6
|
+
## 5.8.1
|
7
|
+
|
8
|
+
* Fix `{% doc %}` tag to be visitable [Guilherme Carreiro]
|
9
|
+
|
10
|
+
## 5.8.0
|
11
|
+
|
12
|
+
* Introduce the new `{% doc %}` tag [Guilherme Carreiro]
|
13
|
+
|
14
|
+
## 5.7.3
|
15
|
+
|
16
|
+
* Raise Liquid::SyntaxError when parsing invalidly encoded strings [Chris AtLee]
|
17
|
+
|
18
|
+
## 5.7.2 2025-01-31
|
19
|
+
|
20
|
+
* Fix array filters to not support nested properties [Guilherme Carreiro]
|
21
|
+
|
22
|
+
## 5.7.1 2025-01-24
|
23
|
+
|
24
|
+
* Fix the `find` and `find_index`filters to return `nil` when filtering empty arrays [Guilherme Carreiro]
|
25
|
+
* Fix the `has` filter to return `false` when filtering empty arrays [Guilherme Carreiro]
|
26
|
+
|
27
|
+
## 5.7.0 2025-01-16
|
28
|
+
|
29
|
+
### Features
|
30
|
+
|
31
|
+
* Add `find`, `find_index`, `has`, and `reject` filters to arrays [Guilherme Carreiro]
|
32
|
+
* Compatibility with Ruby 3.4 [Ian Ker-Seymer]
|
33
|
+
|
34
|
+
## 5.6.4 2025-01-14
|
4
35
|
|
5
36
|
### Fixes
|
37
|
+
* Add a default `string_scanner` to avoid errors with `Liquid::VariableLookup.parse("foo.bar")` [Ian Ker-Seymer]
|
6
38
|
|
7
|
-
|
39
|
+
## 5.6.3 2025-01-13
|
40
|
+
* Remove `lru_redux` dependency [Michael Go]
|
41
|
+
|
42
|
+
## 5.6.2 2025-01-13
|
43
|
+
|
44
|
+
### Fixes
|
45
|
+
* Preserve the old behavior of requiring floats to start with a digit [Michael Go]
|
46
|
+
|
47
|
+
## 5.6.1 2025-01-13
|
48
|
+
|
49
|
+
### Performance improvements
|
50
|
+
* Faster Expression parser / Tokenizer with StringScanner [Michael Go]
|
8
51
|
|
52
|
+
## 5.6.0 2024-12-19
|
53
|
+
|
54
|
+
### Architectural changes
|
55
|
+
* Added new `Environment` class to manage configuration and state that was previously stored in `Template` [Ian Ker-Seymer]
|
56
|
+
* Moved tag registration from `Template` to `Environment` [Ian Ker-Seymer]
|
57
|
+
* Removed `StrainerFactory` in favor of `Environment`-based strainer creation [Ian Ker-Seymer]
|
58
|
+
* Consolidated standard tags into a new `Tags` module with `STANDARD_TAGS` constant [Ian Ker-Seymer]
|
59
|
+
|
60
|
+
### Performance improvements
|
61
|
+
* Optimized `Lexer` with a new `Lexer2` implementation using jump tables for faster tokenization, requires Ruby 3.4 [Ian Ker-Seymer]
|
62
|
+
* Improved variable rendering with specialized handling for different types [Michael Go]
|
63
|
+
* Reduced array allocations by using frozen empty constants [Michael Go]
|
64
|
+
|
65
|
+
### API changes
|
66
|
+
* Deprecated several `Template` class methods in favor of `Environment` methods [Ian Ker-Seymer]
|
67
|
+
* Added deprecation warnings system [Ian Ker-Seymer]
|
68
|
+
* Changed how filters and tags are registered to use Environment [Ian Ker-Seymer]
|
69
|
+
|
70
|
+
### Fixes
|
71
|
+
* Fixed table row handling of break interrupts [Alex Coco]
|
72
|
+
* Improved variable output handling for arrays [Ian Ker-Seymer]
|
73
|
+
* Fix Tokenizer to handle null source value (#1873) [Bahar Pourazar]
|
9
74
|
|
10
75
|
## 5.5.0 2024-03-21
|
11
76
|
|
data/lib/liquid/context.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "lru_redux"
|
4
|
-
|
5
3
|
module Liquid
|
6
4
|
# Context keeps the variable stack and resolves variables, as well as keywords
|
7
5
|
#
|
@@ -41,7 +39,6 @@ module Liquid
|
|
41
39
|
@filters = []
|
42
40
|
@global_filter = nil
|
43
41
|
@disabled_tags = {}
|
44
|
-
@expression_cache = LruRedux::ThreadSafeCache.new(1000)
|
45
42
|
|
46
43
|
# Instead of constructing new StringScanner objects for each Expression parse,
|
47
44
|
# we recycle the same one.
|
@@ -183,7 +180,7 @@ module Liquid
|
|
183
180
|
# Example:
|
184
181
|
# products == empty #=> products.empty?
|
185
182
|
def [](expression)
|
186
|
-
evaluate(Expression.parse(expression, @string_scanner
|
183
|
+
evaluate(Expression.parse(expression, @string_scanner))
|
187
184
|
end
|
188
185
|
|
189
186
|
def key?(key)
|
data/lib/liquid/expression.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "lru_redux"
|
4
|
-
|
5
3
|
module Liquid
|
6
4
|
class Expression
|
7
5
|
LITERALS = {
|
@@ -79,10 +77,17 @@ module Liquid
|
|
79
77
|
end
|
80
78
|
|
81
79
|
ss.string = markup
|
82
|
-
# the first byte must be a digit
|
80
|
+
# the first byte must be a digit or a dash
|
83
81
|
byte = ss.scan_byte
|
84
82
|
|
85
|
-
return false if byte != DASH &&
|
83
|
+
return false if byte != DASH && (byte < ZERO || byte > NINE)
|
84
|
+
|
85
|
+
if byte == DASH
|
86
|
+
peek_byte = ss.peek_byte
|
87
|
+
|
88
|
+
# if it starts with a dash, the next byte must be a digit
|
89
|
+
return false if peek_byte.nil? || !(peek_byte >= ZERO && peek_byte <= NINE)
|
90
|
+
end
|
86
91
|
|
87
92
|
# The markup could be a float with multiple dots
|
88
93
|
first_dot_pos = nil
|
data/lib/liquid/lexer.rb
CHANGED
@@ -161,6 +161,12 @@ module Liquid
|
|
161
161
|
end
|
162
162
|
# rubocop:enable Metrics/BlockNesting
|
163
163
|
output << EOS
|
164
|
+
rescue ::ArgumentError => e
|
165
|
+
if e.message == "invalid byte sequence in #{ss.string.encoding}"
|
166
|
+
raise SyntaxError, "Invalid byte sequence in #{ss.string.encoding}"
|
167
|
+
else
|
168
|
+
raise
|
169
|
+
end
|
164
170
|
end
|
165
171
|
|
166
172
|
def raise_syntax_error(start_pos, ss)
|
data/lib/liquid/locales/en.yml
CHANGED
@@ -2,12 +2,14 @@
|
|
2
2
|
errors:
|
3
3
|
syntax:
|
4
4
|
tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{tag}"
|
5
|
+
block_tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: {% %{tag} %}{% end%{tag} %}"
|
5
6
|
assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
|
6
7
|
capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
|
7
8
|
case: "Syntax Error in 'case' - Valid syntax: case [condition]"
|
8
9
|
case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}"
|
9
10
|
case_invalid_else: "Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) "
|
10
11
|
cycle: "Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]"
|
12
|
+
doc_invalid_nested: "Syntax Error in 'doc' - Nested doc tags are not allowed"
|
11
13
|
for: "Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]"
|
12
14
|
for_invalid_in: "For loops require an 'in' clause"
|
13
15
|
for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'cgi'
|
4
4
|
require 'base64'
|
5
5
|
require 'bigdecimal'
|
6
|
-
|
7
6
|
module Liquid
|
8
7
|
module StandardFilters
|
9
8
|
MAX_I32 = (1 << 31) - 1
|
@@ -64,7 +63,7 @@ module Liquid
|
|
64
63
|
# @liquid_syntax string | downcase
|
65
64
|
# @liquid_return [string]
|
66
65
|
def downcase(input)
|
67
|
-
|
66
|
+
Utils.to_s(input).downcase
|
68
67
|
end
|
69
68
|
|
70
69
|
# @liquid_public_docs
|
@@ -75,7 +74,7 @@ module Liquid
|
|
75
74
|
# @liquid_syntax string | upcase
|
76
75
|
# @liquid_return [string]
|
77
76
|
def upcase(input)
|
78
|
-
|
77
|
+
Utils.to_s(input).upcase
|
79
78
|
end
|
80
79
|
|
81
80
|
# @liquid_public_docs
|
@@ -86,7 +85,7 @@ module Liquid
|
|
86
85
|
# @liquid_syntax string | capitalize
|
87
86
|
# @liquid_return [string]
|
88
87
|
def capitalize(input)
|
89
|
-
|
88
|
+
Utils.to_s(input).capitalize
|
90
89
|
end
|
91
90
|
|
92
91
|
# @liquid_public_docs
|
@@ -97,7 +96,7 @@ module Liquid
|
|
97
96
|
# @liquid_syntax string | escape
|
98
97
|
# @liquid_return [string]
|
99
98
|
def escape(input)
|
100
|
-
CGI.escapeHTML(
|
99
|
+
CGI.escapeHTML(Utils.to_s(input)) unless input.nil?
|
101
100
|
end
|
102
101
|
alias_method :h, :escape
|
103
102
|
|
@@ -109,7 +108,7 @@ module Liquid
|
|
109
108
|
# @liquid_syntax string | escape_once
|
110
109
|
# @liquid_return [string]
|
111
110
|
def escape_once(input)
|
112
|
-
|
111
|
+
Utils.to_s(input).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
113
112
|
end
|
114
113
|
|
115
114
|
# @liquid_public_docs
|
@@ -124,7 +123,7 @@ module Liquid
|
|
124
123
|
# @liquid_syntax string | url_encode
|
125
124
|
# @liquid_return [string]
|
126
125
|
def url_encode(input)
|
127
|
-
CGI.escape(
|
126
|
+
CGI.escape(Utils.to_s(input)) unless input.nil?
|
128
127
|
end
|
129
128
|
|
130
129
|
# @liquid_public_docs
|
@@ -138,7 +137,7 @@ module Liquid
|
|
138
137
|
def url_decode(input)
|
139
138
|
return if input.nil?
|
140
139
|
|
141
|
-
result = CGI.unescape(
|
140
|
+
result = CGI.unescape(Utils.to_s(input))
|
142
141
|
raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding?
|
143
142
|
|
144
143
|
result
|
@@ -152,7 +151,7 @@ module Liquid
|
|
152
151
|
# @liquid_syntax string | base64_encode
|
153
152
|
# @liquid_return [string]
|
154
153
|
def base64_encode(input)
|
155
|
-
Base64.strict_encode64(
|
154
|
+
Base64.strict_encode64(Utils.to_s(input))
|
156
155
|
end
|
157
156
|
|
158
157
|
# @liquid_public_docs
|
@@ -163,7 +162,7 @@ module Liquid
|
|
163
162
|
# @liquid_syntax string | base64_decode
|
164
163
|
# @liquid_return [string]
|
165
164
|
def base64_decode(input)
|
166
|
-
input =
|
165
|
+
input = Utils.to_s(input)
|
167
166
|
StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding)
|
168
167
|
rescue ::ArgumentError
|
169
168
|
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
|
@@ -177,7 +176,7 @@ module Liquid
|
|
177
176
|
# @liquid_syntax string | base64_url_safe_encode
|
178
177
|
# @liquid_return [string]
|
179
178
|
def base64_url_safe_encode(input)
|
180
|
-
Base64.urlsafe_encode64(
|
179
|
+
Base64.urlsafe_encode64(Utils.to_s(input))
|
181
180
|
end
|
182
181
|
|
183
182
|
# @liquid_public_docs
|
@@ -188,7 +187,7 @@ module Liquid
|
|
188
187
|
# @liquid_syntax string | base64_url_safe_decode
|
189
188
|
# @liquid_return [string]
|
190
189
|
def base64_url_safe_decode(input)
|
191
|
-
input =
|
190
|
+
input = Utils.to_s(input)
|
192
191
|
StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding)
|
193
192
|
rescue ::ArgumentError
|
194
193
|
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
|
@@ -212,7 +211,7 @@ module Liquid
|
|
212
211
|
if input.is_a?(Array)
|
213
212
|
input.slice(offset, length) || []
|
214
213
|
else
|
215
|
-
|
214
|
+
Utils.to_s(input).slice(offset, length) || ''
|
216
215
|
end
|
217
216
|
rescue RangeError
|
218
217
|
if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
|
@@ -236,10 +235,10 @@ module Liquid
|
|
236
235
|
# @liquid_return [string]
|
237
236
|
def truncate(input, length = 50, truncate_string = "...")
|
238
237
|
return if input.nil?
|
239
|
-
input_str =
|
238
|
+
input_str = Utils.to_s(input)
|
240
239
|
length = Utils.to_integer(length)
|
241
240
|
|
242
|
-
truncate_string_str =
|
241
|
+
truncate_string_str = Utils.to_s(truncate_string)
|
243
242
|
|
244
243
|
l = length - truncate_string_str.length
|
245
244
|
l = 0 if l < 0
|
@@ -263,7 +262,7 @@ module Liquid
|
|
263
262
|
# @liquid_return [string]
|
264
263
|
def truncatewords(input, words = 15, truncate_string = "...")
|
265
264
|
return if input.nil?
|
266
|
-
input =
|
265
|
+
input = Utils.to_s(input)
|
267
266
|
words = Utils.to_integer(words)
|
268
267
|
words = 1 if words <= 0
|
269
268
|
|
@@ -277,7 +276,8 @@ module Liquid
|
|
277
276
|
return input if wordlist.length <= words
|
278
277
|
|
279
278
|
wordlist.pop
|
280
|
-
|
279
|
+
truncate_string = Utils.to_s(truncate_string)
|
280
|
+
wordlist.join(" ").concat(truncate_string)
|
281
281
|
end
|
282
282
|
|
283
283
|
# @liquid_public_docs
|
@@ -288,7 +288,9 @@ module Liquid
|
|
288
288
|
# @liquid_syntax string | split: string
|
289
289
|
# @liquid_return [array[string]]
|
290
290
|
def split(input, pattern)
|
291
|
-
|
291
|
+
pattern = Utils.to_s(pattern)
|
292
|
+
input = Utils.to_s(input)
|
293
|
+
input.split(pattern)
|
292
294
|
end
|
293
295
|
|
294
296
|
# @liquid_public_docs
|
@@ -299,7 +301,8 @@ module Liquid
|
|
299
301
|
# @liquid_syntax string | strip
|
300
302
|
# @liquid_return [string]
|
301
303
|
def strip(input)
|
302
|
-
input.to_s
|
304
|
+
input = Utils.to_s(input)
|
305
|
+
input.strip
|
303
306
|
end
|
304
307
|
|
305
308
|
# @liquid_public_docs
|
@@ -310,7 +313,8 @@ module Liquid
|
|
310
313
|
# @liquid_syntax string | lstrip
|
311
314
|
# @liquid_return [string]
|
312
315
|
def lstrip(input)
|
313
|
-
input.to_s
|
316
|
+
input = Utils.to_s(input)
|
317
|
+
input.lstrip
|
314
318
|
end
|
315
319
|
|
316
320
|
# @liquid_public_docs
|
@@ -321,7 +325,8 @@ module Liquid
|
|
321
325
|
# @liquid_syntax string | rstrip
|
322
326
|
# @liquid_return [string]
|
323
327
|
def rstrip(input)
|
324
|
-
input.to_s
|
328
|
+
input = Utils.to_s(input)
|
329
|
+
input.rstrip
|
325
330
|
end
|
326
331
|
|
327
332
|
# @liquid_public_docs
|
@@ -332,8 +337,9 @@ module Liquid
|
|
332
337
|
# @liquid_syntax string | strip_html
|
333
338
|
# @liquid_return [string]
|
334
339
|
def strip_html(input)
|
340
|
+
input = Utils.to_s(input)
|
335
341
|
empty = ''
|
336
|
-
result = input.
|
342
|
+
result = input.gsub(STRIP_HTML_BLOCKS, empty)
|
337
343
|
result.gsub!(STRIP_HTML_TAGS, empty)
|
338
344
|
result
|
339
345
|
end
|
@@ -346,7 +352,8 @@ module Liquid
|
|
346
352
|
# @liquid_syntax string | strip_newlines
|
347
353
|
# @liquid_return [string]
|
348
354
|
def strip_newlines(input)
|
349
|
-
input.to_s
|
355
|
+
input = Utils.to_s(input)
|
356
|
+
input.gsub(/\r?\n/, '')
|
350
357
|
end
|
351
358
|
|
352
359
|
# @liquid_public_docs
|
@@ -357,6 +364,7 @@ module Liquid
|
|
357
364
|
# @liquid_syntax array | join
|
358
365
|
# @liquid_return [string]
|
359
366
|
def join(input, glue = ' ')
|
367
|
+
glue = Utils.to_s(glue)
|
360
368
|
InputIterator.new(input, context).join(glue)
|
361
369
|
end
|
362
370
|
|
@@ -424,29 +432,59 @@ module Liquid
|
|
424
432
|
# @liquid_syntax array | where: string, string
|
425
433
|
# @liquid_return [array[untyped]]
|
426
434
|
def where(input, property, target_value = nil)
|
427
|
-
ary
|
435
|
+
filter_array(input, property, target_value) { |ary, &block| ary.select(&block) }
|
436
|
+
end
|
428
437
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
438
|
+
# @liquid_public_docs
|
439
|
+
# @liquid_type filter
|
440
|
+
# @liquid_category array
|
441
|
+
# @liquid_summary
|
442
|
+
# Filters an array to exclude items with a specific property value.
|
443
|
+
# @liquid_description
|
444
|
+
# This requires you to provide both the property name and the associated value.
|
445
|
+
# @liquid_syntax array | reject: string, string
|
446
|
+
# @liquid_return [array[untyped]]
|
447
|
+
def reject(input, property, target_value = nil)
|
448
|
+
filter_array(input, property, target_value) { |ary, &block| ary.reject(&block) }
|
449
|
+
end
|
450
|
+
|
451
|
+
# @liquid_public_docs
|
452
|
+
# @liquid_type filter
|
453
|
+
# @liquid_category array
|
454
|
+
# @liquid_summary
|
455
|
+
# Tests if any item in an array has a specific property value.
|
456
|
+
# @liquid_description
|
457
|
+
# This requires you to provide both the property name and the associated value.
|
458
|
+
# @liquid_syntax array | has: string, string
|
459
|
+
# @liquid_return [boolean]
|
460
|
+
def has(input, property, target_value = nil)
|
461
|
+
filter_array(input, property, target_value, false) { |ary, &block| ary.any?(&block) }
|
462
|
+
end
|
463
|
+
|
464
|
+
# @liquid_public_docs
|
465
|
+
# @liquid_type filter
|
466
|
+
# @liquid_category array
|
467
|
+
# @liquid_summary
|
468
|
+
# Returns the first item in an array with a specific property value.
|
469
|
+
# @liquid_description
|
470
|
+
# This requires you to provide both the property name and the associated value.
|
471
|
+
# @liquid_syntax array | find: string, string
|
472
|
+
# @liquid_return [untyped]
|
473
|
+
def find(input, property, target_value = nil)
|
474
|
+
filter_array(input, property, target_value, nil) { |ary, &block| ary.find(&block) }
|
475
|
+
end
|
476
|
+
|
477
|
+
# @liquid_public_docs
|
478
|
+
# @liquid_type filter
|
479
|
+
# @liquid_category array
|
480
|
+
# @liquid_summary
|
481
|
+
# Returns the index of the first item in an array with a specific property value.
|
482
|
+
# @liquid_description
|
483
|
+
# This requires you to provide both the property name and the associated value.
|
484
|
+
# @liquid_syntax array | find_index: string, string
|
485
|
+
# @liquid_return [number]
|
486
|
+
def find_index(input, property, target_value = nil)
|
487
|
+
filter_array(input, property, target_value, nil) { |ary, &block| ary.find_index(&block) }
|
450
488
|
end
|
451
489
|
|
452
490
|
# @liquid_public_docs
|
@@ -543,7 +581,10 @@ module Liquid
|
|
543
581
|
# @liquid_syntax string | replace: string, string
|
544
582
|
# @liquid_return [string]
|
545
583
|
def replace(input, string, replacement = '')
|
546
|
-
|
584
|
+
string = Utils.to_s(string)
|
585
|
+
replacement = Utils.to_s(replacement)
|
586
|
+
input = Utils.to_s(input)
|
587
|
+
input.gsub(string, replacement)
|
547
588
|
end
|
548
589
|
|
549
590
|
# @liquid_public_docs
|
@@ -554,7 +595,10 @@ module Liquid
|
|
554
595
|
# @liquid_syntax string | replace_first: string, string
|
555
596
|
# @liquid_return [string]
|
556
597
|
def replace_first(input, string, replacement = '')
|
557
|
-
|
598
|
+
string = Utils.to_s(string)
|
599
|
+
replacement = Utils.to_s(replacement)
|
600
|
+
input = Utils.to_s(input)
|
601
|
+
input.sub(string, replacement)
|
558
602
|
end
|
559
603
|
|
560
604
|
# @liquid_public_docs
|
@@ -565,9 +609,9 @@ module Liquid
|
|
565
609
|
# @liquid_syntax string | replace_last: string, string
|
566
610
|
# @liquid_return [string]
|
567
611
|
def replace_last(input, string, replacement)
|
568
|
-
input =
|
569
|
-
string =
|
570
|
-
replacement =
|
612
|
+
input = Utils.to_s(input)
|
613
|
+
string = Utils.to_s(string)
|
614
|
+
replacement = Utils.to_s(replacement)
|
571
615
|
|
572
616
|
start_index = input.rindex(string)
|
573
617
|
|
@@ -619,7 +663,9 @@ module Liquid
|
|
619
663
|
# @liquid_syntax string | append: string
|
620
664
|
# @liquid_return [string]
|
621
665
|
def append(input, string)
|
622
|
-
input
|
666
|
+
input = Utils.to_s(input)
|
667
|
+
string = Utils.to_s(string)
|
668
|
+
input + string
|
623
669
|
end
|
624
670
|
|
625
671
|
# @liquid_public_docs
|
@@ -648,7 +694,9 @@ module Liquid
|
|
648
694
|
# @liquid_syntax string | prepend: string
|
649
695
|
# @liquid_return [string]
|
650
696
|
def prepend(input, string)
|
651
|
-
|
697
|
+
input = Utils.to_s(input)
|
698
|
+
string = Utils.to_s(string)
|
699
|
+
string + input
|
652
700
|
end
|
653
701
|
|
654
702
|
# @liquid_public_docs
|
@@ -659,10 +707,20 @@ module Liquid
|
|
659
707
|
# @liquid_syntax string | newline_to_br
|
660
708
|
# @liquid_return [string]
|
661
709
|
def newline_to_br(input)
|
662
|
-
input.to_s
|
710
|
+
input = Utils.to_s(input)
|
711
|
+
input.gsub(/\r?\n/, "<br />\n")
|
663
712
|
end
|
664
713
|
|
665
|
-
#
|
714
|
+
# @liquid_public_docs
|
715
|
+
# @liquid_type filter
|
716
|
+
# @liquid_category date
|
717
|
+
# @liquid_summary
|
718
|
+
# Formats a date according to a specified format string.
|
719
|
+
# @liquid_description
|
720
|
+
# This filter formats a date using various format specifiers. If the format string is empty,
|
721
|
+
# the original input is returned. If the input cannot be converted to a date, the original input is returned.
|
722
|
+
#
|
723
|
+
# The following format specifiers can be used:
|
666
724
|
#
|
667
725
|
# %a - The abbreviated weekday name (``Sun'')
|
668
726
|
# %A - The full weekday name (``Sunday'')
|
@@ -691,14 +749,15 @@ module Liquid
|
|
691
749
|
# %Y - Year with century
|
692
750
|
# %Z - Time zone name
|
693
751
|
# %% - Literal ``%'' character
|
694
|
-
#
|
695
|
-
#
|
752
|
+
# @liquid_syntax date | date: string
|
753
|
+
# @liquid_return [string]
|
696
754
|
def date(input, format)
|
697
|
-
|
755
|
+
str_format = Utils.to_s(format)
|
756
|
+
return input if str_format.empty?
|
698
757
|
|
699
758
|
return input unless (date = Utils.to_date(input))
|
700
759
|
|
701
|
-
date.strftime(
|
760
|
+
date.strftime(str_format)
|
702
761
|
end
|
703
762
|
|
704
763
|
# @liquid_public_docs
|
@@ -918,8 +977,27 @@ module Liquid
|
|
918
977
|
|
919
978
|
attr_reader :context
|
920
979
|
|
980
|
+
def filter_array(input, property, target_value, default_value = [], &block)
|
981
|
+
ary = InputIterator.new(input, context)
|
982
|
+
|
983
|
+
return default_value if ary.empty?
|
984
|
+
|
985
|
+
block.call(ary) do |item|
|
986
|
+
if target_value.nil?
|
987
|
+
item[property]
|
988
|
+
else
|
989
|
+
item[property] == target_value
|
990
|
+
end
|
991
|
+
rescue TypeError
|
992
|
+
raise_property_error(property)
|
993
|
+
rescue NoMethodError
|
994
|
+
return nil unless item.respond_to?(:[])
|
995
|
+
raise
|
996
|
+
end
|
997
|
+
end
|
998
|
+
|
921
999
|
def raise_property_error(property)
|
922
|
-
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
|
1000
|
+
raise Liquid::ArgumentError, "cannot select the property '#{Utils.to_s(property)}'"
|
923
1001
|
end
|
924
1002
|
|
925
1003
|
def apply_operation(input, operand, operation)
|
@@ -968,7 +1046,18 @@ module Liquid
|
|
968
1046
|
end
|
969
1047
|
|
970
1048
|
def join(glue)
|
971
|
-
|
1049
|
+
first = true
|
1050
|
+
output = +""
|
1051
|
+
each do |item|
|
1052
|
+
if first
|
1053
|
+
first = false
|
1054
|
+
else
|
1055
|
+
output << glue
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
output << Liquid::Utils.to_s(item)
|
1059
|
+
end
|
1060
|
+
output
|
972
1061
|
end
|
973
1062
|
|
974
1063
|
def concat(args)
|
@@ -980,7 +1069,10 @@ module Liquid
|
|
980
1069
|
end
|
981
1070
|
|
982
1071
|
def uniq(&block)
|
983
|
-
to_a.uniq
|
1072
|
+
to_a.uniq do |item|
|
1073
|
+
item = Utils.to_liquid_value(item)
|
1074
|
+
block ? yield(item) : item
|
1075
|
+
end
|
984
1076
|
end
|
985
1077
|
|
986
1078
|
def compact
|
@@ -10,7 +10,7 @@ module Liquid
|
|
10
10
|
# @liquid_description
|
11
11
|
# Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
|
12
12
|
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
13
|
-
# [snippets](/themes/architecture
|
13
|
+
# [snippets](/themes/architecture/snippets) included in the file.
|
14
14
|
#
|
15
15
|
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
|
16
16
|
# and [`capture`](/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](/docs/api/liquid/tags/increment) share
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
# @liquid_public_docs
|
5
|
+
# @liquid_type tag
|
6
|
+
# @liquid_category syntax
|
7
|
+
# @liquid_name doc
|
8
|
+
# @liquid_summary
|
9
|
+
# Documents template elements with annotations.
|
10
|
+
# @liquid_description
|
11
|
+
# The `doc` tag allows developers to include documentation within Liquid
|
12
|
+
# templates. Any content inside `doc` tags is not rendered or outputted.
|
13
|
+
# Liquid code inside will be parsed but not executed. This facilitates
|
14
|
+
# tooling support for features like code completion, linting, and inline
|
15
|
+
# documentation.
|
16
|
+
#
|
17
|
+
# For detailed documentation syntax and examples, see the
|
18
|
+
# [`LiquidDoc` reference](/docs/storefronts/themes/tools/liquid-doc).
|
19
|
+
#
|
20
|
+
# @liquid_syntax
|
21
|
+
# {% doc %}
|
22
|
+
# Renders a message.
|
23
|
+
#
|
24
|
+
# @param {string} foo - A string value.
|
25
|
+
# @param {string} [bar] - An optional string value.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# {% render 'message', foo: 'Hello', bar: 'World' %}
|
29
|
+
# {% enddoc %}
|
30
|
+
class Doc < Block
|
31
|
+
NO_UNEXPECTED_ARGS = /\A\s*\z/
|
32
|
+
|
33
|
+
def initialize(tag_name, markup, parse_context)
|
34
|
+
super
|
35
|
+
ensure_valid_markup(tag_name, markup, parse_context)
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse(tokens)
|
39
|
+
@body = +""
|
40
|
+
|
41
|
+
while (token = tokens.shift)
|
42
|
+
tag_name = token =~ BlockBody::FullTokenPossiblyInvalid && Regexp.last_match(2)
|
43
|
+
|
44
|
+
raise_nested_doc_error if tag_name == @tag_name
|
45
|
+
|
46
|
+
if tag_name == block_delimiter
|
47
|
+
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
48
|
+
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
|
49
|
+
return
|
50
|
+
end
|
51
|
+
@body << token unless token.empty?
|
52
|
+
end
|
53
|
+
|
54
|
+
raise_tag_never_closed(block_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def render_to_output_buffer(_context, output)
|
58
|
+
output
|
59
|
+
end
|
60
|
+
|
61
|
+
def blank?
|
62
|
+
@body.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
def nodelist
|
66
|
+
[@body]
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def ensure_valid_markup(tag_name, markup, parse_context)
|
72
|
+
unless NO_UNEXPECTED_ARGS.match?(markup)
|
73
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.block_tag_unexpected_args", tag: tag_name)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def raise_nested_doc_error
|
78
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.doc_invalid_nested")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/liquid/tags/include.rb
CHANGED
@@ -6,7 +6,7 @@ module Liquid
|
|
6
6
|
# @liquid_category theme
|
7
7
|
# @liquid_name include
|
8
8
|
# @liquid_summary
|
9
|
-
# Renders a [snippet](/themes/architecture
|
9
|
+
# Renders a [snippet](/themes/architecture/snippets).
|
10
10
|
# @liquid_description
|
11
11
|
# Inside the snippet, you can access and alter variables that are [created](/docs/api/liquid/tags/variable-tags) outside of the
|
12
12
|
# snippet.
|
@@ -10,7 +10,7 @@ module Liquid
|
|
10
10
|
# @liquid_description
|
11
11
|
# Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
|
12
12
|
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
13
|
-
# [snippets](/themes/architecture
|
13
|
+
# [snippets](/themes/architecture/snippets) included in the file.
|
14
14
|
#
|
15
15
|
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
|
16
16
|
# and [`capture`](/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](/docs/api/liquid/tags/decrement) share
|
data/lib/liquid/tags/render.rb
CHANGED
@@ -6,7 +6,7 @@ module Liquid
|
|
6
6
|
# @liquid_category theme
|
7
7
|
# @liquid_name render
|
8
8
|
# @liquid_summary
|
9
|
-
# Renders a [snippet](/themes/architecture
|
9
|
+
# Renders a [snippet](/themes/architecture/snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
|
10
10
|
# @liquid_description
|
11
11
|
# Inside snippets and app blocks, you can't directly access variables that are [created](/docs/api/liquid/tags/variable-tags) outside
|
12
12
|
# of the snippet or app block. However, you can [specify variables as parameters](/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet)
|
data/lib/liquid/tags.rb
CHANGED
@@ -19,6 +19,7 @@ require_relative "tags/comment"
|
|
19
19
|
require_relative "tags/raw"
|
20
20
|
require_relative "tags/render"
|
21
21
|
require_relative "tags/cycle"
|
22
|
+
require_relative "tags/doc"
|
22
23
|
|
23
24
|
module Liquid
|
24
25
|
module Tags
|
@@ -42,6 +43,7 @@ module Liquid
|
|
42
43
|
'if' => If,
|
43
44
|
'echo' => Echo,
|
44
45
|
'tablerow' => TableRow,
|
46
|
+
'doc' => Doc,
|
45
47
|
}.freeze
|
46
48
|
end
|
47
49
|
end
|
data/lib/liquid/tokenizer.rb
CHANGED
@@ -103,6 +103,12 @@ module Liquid
|
|
103
103
|
|
104
104
|
pos = @ss.pos -= 2
|
105
105
|
@source.byteslice(start, pos - start)
|
106
|
+
rescue ::ArgumentError => e
|
107
|
+
if e.message == "invalid byte sequence in #{@ss.string.encoding}"
|
108
|
+
raise SyntaxError, "Invalid byte sequence in #{@ss.string.encoding}"
|
109
|
+
else
|
110
|
+
raise
|
111
|
+
end
|
106
112
|
end
|
107
113
|
|
108
114
|
def next_variable_token
|
data/lib/liquid/utils.rb
CHANGED
@@ -89,5 +89,101 @@ module Liquid
|
|
89
89
|
# Otherwise return the object itself
|
90
90
|
obj
|
91
91
|
end
|
92
|
+
|
93
|
+
def self.to_s(obj, seen = {})
|
94
|
+
case obj
|
95
|
+
when Hash
|
96
|
+
# If the custom hash implementation overrides `#to_s`, use their
|
97
|
+
# custom implementation. Otherwise we use Liquid's default
|
98
|
+
# implementation.
|
99
|
+
if obj.class.instance_method(:to_s) == HASH_TO_S_METHOD
|
100
|
+
hash_inspect(obj, seen)
|
101
|
+
else
|
102
|
+
obj.to_s
|
103
|
+
end
|
104
|
+
when Array
|
105
|
+
array_inspect(obj, seen)
|
106
|
+
else
|
107
|
+
obj.to_s
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.inspect(obj, seen = {})
|
112
|
+
case obj
|
113
|
+
when Hash
|
114
|
+
# If the custom hash implementation overrides `#inspect`, use their
|
115
|
+
# custom implementation. Otherwise we use Liquid's default
|
116
|
+
# implementation.
|
117
|
+
if obj.class.instance_method(:inspect) == HASH_INSPECT_METHOD
|
118
|
+
hash_inspect(obj, seen)
|
119
|
+
else
|
120
|
+
obj.inspect
|
121
|
+
end
|
122
|
+
when Array
|
123
|
+
array_inspect(obj, seen)
|
124
|
+
else
|
125
|
+
obj.inspect
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.array_inspect(arr, seen = {})
|
130
|
+
if seen[arr.object_id]
|
131
|
+
return "[...]"
|
132
|
+
end
|
133
|
+
|
134
|
+
seen[arr.object_id] = true
|
135
|
+
str = +"["
|
136
|
+
cursor = 0
|
137
|
+
len = arr.length
|
138
|
+
|
139
|
+
while cursor < len
|
140
|
+
if cursor > 0
|
141
|
+
str << ", "
|
142
|
+
end
|
143
|
+
|
144
|
+
item_str = inspect(arr[cursor], seen)
|
145
|
+
str << item_str
|
146
|
+
cursor += 1
|
147
|
+
end
|
148
|
+
|
149
|
+
str << "]"
|
150
|
+
str
|
151
|
+
ensure
|
152
|
+
seen.delete(arr.object_id)
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.hash_inspect(hash, seen = {})
|
156
|
+
if seen[hash.object_id]
|
157
|
+
return "{...}"
|
158
|
+
end
|
159
|
+
seen[hash.object_id] = true
|
160
|
+
|
161
|
+
str = +"{"
|
162
|
+
first = true
|
163
|
+
hash.each do |key, value|
|
164
|
+
if first
|
165
|
+
first = false
|
166
|
+
else
|
167
|
+
str << ", "
|
168
|
+
end
|
169
|
+
|
170
|
+
key_str = inspect(key, seen)
|
171
|
+
str << key_str
|
172
|
+
str << "=>"
|
173
|
+
|
174
|
+
value_str = inspect(value, seen)
|
175
|
+
str << value_str
|
176
|
+
end
|
177
|
+
str << "}"
|
178
|
+
str
|
179
|
+
ensure
|
180
|
+
seen.delete(hash.object_id)
|
181
|
+
end
|
182
|
+
|
183
|
+
HASH_TO_S_METHOD = Hash.instance_method(:to_s)
|
184
|
+
private_constant :HASH_TO_S_METHOD
|
185
|
+
|
186
|
+
HASH_INSPECT_METHOD = Hash.instance_method(:inspect)
|
187
|
+
private_constant :HASH_INSPECT_METHOD
|
92
188
|
end
|
93
189
|
end
|
data/lib/liquid/variable.rb
CHANGED
data/lib/liquid/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: liquid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.8.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tobias Lütke
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: strscan
|
@@ -120,6 +120,7 @@ files:
|
|
120
120
|
- lib/liquid/tags/continue.rb
|
121
121
|
- lib/liquid/tags/cycle.rb
|
122
122
|
- lib/liquid/tags/decrement.rb
|
123
|
+
- lib/liquid/tags/doc.rb
|
123
124
|
- lib/liquid/tags/echo.rb
|
124
125
|
- lib/liquid/tags/for.rb
|
125
126
|
- lib/liquid/tags/if.rb
|
@@ -158,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
159
|
- !ruby/object:Gem::Version
|
159
160
|
version: 1.3.7
|
160
161
|
requirements: []
|
161
|
-
rubygems_version: 3.6.
|
162
|
+
rubygems_version: 3.6.9
|
162
163
|
specification_version: 4
|
163
164
|
summary: A secure, non-evaling end user template engine with aesthetic markup.
|
164
165
|
test_files: []
|