liquid 4.0.3 → 5.4.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 +89 -0
- data/README.md +10 -4
- data/lib/liquid/block.rb +31 -14
- data/lib/liquid/block_body.rb +169 -57
- data/lib/liquid/condition.rb +48 -21
- data/lib/liquid/context.rb +111 -52
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +4 -2
- data/lib/liquid/errors.rb +20 -18
- data/lib/liquid/expression.rb +28 -32
- data/lib/liquid/extensions.rb +2 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +54 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +30 -23
- data/lib/liquid/locales/en.yml +8 -5
- data/lib/liquid/parse_context.rb +20 -4
- data/lib/liquid/parse_tree_visitor.rb +2 -2
- data/lib/liquid/parser.rb +30 -18
- data/lib/liquid/parser_switching.rb +17 -3
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/range_lookup.rb +13 -3
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +551 -114
- data/lib/liquid/strainer_factory.rb +41 -0
- data/lib/liquid/strainer_template.rb +62 -0
- data/lib/liquid/tablerowloop_drop.rb +64 -5
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tag.rb +28 -6
- data/lib/liquid/tags/assign.rb +36 -18
- data/lib/liquid/tags/break.rb +16 -3
- data/lib/liquid/tags/capture.rb +24 -18
- data/lib/liquid/tags/case.rb +61 -27
- data/lib/liquid/tags/comment.rb +18 -3
- data/lib/liquid/tags/continue.rb +16 -12
- data/lib/liquid/tags/cycle.rb +37 -25
- data/lib/liquid/tags/decrement.rb +22 -20
- data/lib/liquid/tags/echo.rb +41 -0
- data/lib/liquid/tags/for.rb +90 -87
- data/lib/liquid/tags/if.rb +50 -32
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +49 -60
- data/lib/liquid/tags/increment.rb +23 -17
- data/lib/liquid/tags/inline_comment.rb +43 -0
- data/lib/liquid/tags/raw.rb +25 -11
- data/lib/liquid/tags/render.rb +109 -0
- data/lib/liquid/tags/table_row.rb +45 -19
- data/lib/liquid/tags/unless.rb +38 -19
- data/lib/liquid/template.rb +52 -72
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +18 -10
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +13 -3
- data/lib/liquid/variable.rb +49 -44
- data/lib/liquid/variable_lookup.rb +18 -10
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +18 -6
- metadata +20 -108
- data/lib/liquid/strainer.rb +0 -66
- data/lib/liquid/truffle.rb +0 -5
- data/test/fixtures/en_locale.yml +0 -9
- data/test/integration/assign_test.rb +0 -48
- data/test/integration/blank_test.rb +0 -106
- data/test/integration/block_test.rb +0 -12
- data/test/integration/capture_test.rb +0 -50
- data/test/integration/context_test.rb +0 -32
- data/test/integration/document_test.rb +0 -19
- data/test/integration/drop_test.rb +0 -273
- data/test/integration/error_handling_test.rb +0 -260
- data/test/integration/filter_test.rb +0 -178
- data/test/integration/hash_ordering_test.rb +0 -23
- data/test/integration/output_test.rb +0 -123
- data/test/integration/parse_tree_visitor_test.rb +0 -247
- data/test/integration/parsing_quirks_test.rb +0 -122
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -80
- data/test/integration/standard_filter_test.rb +0 -776
- data/test/integration/tags/break_tag_test.rb +0 -15
- data/test/integration/tags/continue_tag_test.rb +0 -15
- data/test/integration/tags/for_tag_test.rb +0 -410
- data/test/integration/tags/if_else_tag_test.rb +0 -188
- data/test/integration/tags/include_tag_test.rb +0 -253
- data/test/integration/tags/increment_tag_test.rb +0 -23
- data/test/integration/tags/raw_tag_test.rb +0 -31
- data/test/integration/tags/standard_tag_test.rb +0 -296
- data/test/integration/tags/statements_test.rb +0 -111
- data/test/integration/tags/table_row_test.rb +0 -64
- data/test/integration/tags/unless_else_tag_test.rb +0 -26
- data/test/integration/template_test.rb +0 -332
- data/test/integration/trim_mode_test.rb +0 -529
- data/test/integration/variable_test.rb +0 -96
- data/test/test_helper.rb +0 -116
- data/test/truffle/truffle_test.rb +0 -9
- data/test/unit/block_unit_test.rb +0 -58
- data/test/unit/condition_unit_test.rb +0 -166
- data/test/unit/context_unit_test.rb +0 -489
- data/test/unit/file_system_unit_test.rb +0 -35
- data/test/unit/i18n_unit_test.rb +0 -37
- data/test/unit/lexer_unit_test.rb +0 -51
- data/test/unit/parser_unit_test.rb +0 -82
- data/test/unit/regexp_unit_test.rb +0 -44
- data/test/unit/strainer_unit_test.rb +0 -164
- data/test/unit/tag_unit_test.rb +0 -21
- data/test/unit/tags/case_tag_unit_test.rb +0 -10
- data/test/unit/tags/for_tag_unit_test.rb +0 -13
- data/test/unit/tags/if_tag_unit_test.rb +0 -8
- data/test/unit/template_unit_test.rb +0 -78
- data/test/unit/tokenizer_unit_test.rb +0 -55
- data/test/unit/variable_unit_test.rb +0 -162
@@ -1,56 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cgi'
|
4
|
+
require 'base64'
|
2
5
|
require 'bigdecimal'
|
3
6
|
|
4
7
|
module Liquid
|
5
8
|
module StandardFilters
|
9
|
+
MAX_INT = (1 << 31) - 1
|
6
10
|
HTML_ESCAPE = {
|
7
|
-
'&'
|
8
|
-
'>'
|
9
|
-
'<'
|
10
|
-
'"'
|
11
|
-
"'"
|
11
|
+
'&' => '&',
|
12
|
+
'>' => '>',
|
13
|
+
'<' => '<',
|
14
|
+
'"' => '"',
|
15
|
+
"'" => ''',
|
12
16
|
}.freeze
|
13
17
|
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
14
|
-
STRIP_HTML_BLOCKS
|
15
|
-
|
18
|
+
STRIP_HTML_BLOCKS = Regexp.union(
|
19
|
+
%r{<script.*?</script>}m,
|
16
20
|
/<!--.*?-->/m,
|
17
|
-
|
21
|
+
%r{<style.*?</style>}m
|
18
22
|
)
|
19
23
|
STRIP_HTML_TAGS = /<.*?>/m
|
20
24
|
|
21
|
-
#
|
25
|
+
# @liquid_public_docs
|
26
|
+
# @liquid_type filter
|
27
|
+
# @liquid_category array
|
28
|
+
# @liquid_summary
|
29
|
+
# Returns the size of a string or array.
|
30
|
+
# @liquid_description
|
31
|
+
# The size of a string is the number of characters that the string includes. The size of an array is the number of items
|
32
|
+
# in the array.
|
33
|
+
# @liquid_syntax variable | size
|
34
|
+
# @liquid_return [number]
|
22
35
|
def size(input)
|
23
36
|
input.respond_to?(:size) ? input.size : 0
|
24
37
|
end
|
25
38
|
|
26
|
-
#
|
39
|
+
# @liquid_public_docs
|
40
|
+
# @liquid_type filter
|
41
|
+
# @liquid_category string
|
42
|
+
# @liquid_summary
|
43
|
+
# Converts a string to all lowercase characters.
|
44
|
+
# @liquid_syntax string | downcase
|
45
|
+
# @liquid_return [string]
|
27
46
|
def downcase(input)
|
28
47
|
input.to_s.downcase
|
29
48
|
end
|
30
49
|
|
31
|
-
#
|
50
|
+
# @liquid_public_docs
|
51
|
+
# @liquid_type filter
|
52
|
+
# @liquid_category string
|
53
|
+
# @liquid_summary
|
54
|
+
# Converts a string to all uppercase characters.
|
55
|
+
# @liquid_syntax string | upcase
|
56
|
+
# @liquid_return [string]
|
32
57
|
def upcase(input)
|
33
58
|
input.to_s.upcase
|
34
59
|
end
|
35
60
|
|
36
|
-
#
|
61
|
+
# @liquid_public_docs
|
62
|
+
# @liquid_type filter
|
63
|
+
# @liquid_category string
|
64
|
+
# @liquid_summary
|
65
|
+
# Capitalizes the first word in a string.
|
66
|
+
# @liquid_syntax string | capitalize
|
67
|
+
# @liquid_return [string]
|
37
68
|
def capitalize(input)
|
38
69
|
input.to_s.capitalize
|
39
70
|
end
|
40
71
|
|
72
|
+
# @liquid_public_docs
|
73
|
+
# @liquid_type filter
|
74
|
+
# @liquid_category string
|
75
|
+
# @liquid_summary
|
76
|
+
# Escapes a string.
|
77
|
+
# @liquid_syntax string | escape
|
78
|
+
# @liquid_return [string]
|
41
79
|
def escape(input)
|
42
|
-
CGI.escapeHTML(input.to_s)
|
80
|
+
CGI.escapeHTML(input.to_s) unless input.nil?
|
43
81
|
end
|
44
82
|
alias_method :h, :escape
|
45
83
|
|
84
|
+
# @liquid_public_docs
|
85
|
+
# @liquid_type filter
|
86
|
+
# @liquid_category string
|
87
|
+
# @liquid_summary
|
88
|
+
# Escapes a string without changing characters that have already been escaped.
|
89
|
+
# @liquid_syntax string | escape_once
|
90
|
+
# @liquid_return [string]
|
46
91
|
def escape_once(input)
|
47
92
|
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
48
93
|
end
|
49
94
|
|
95
|
+
# @liquid_public_docs
|
96
|
+
# @liquid_type filter
|
97
|
+
# @liquid_category string
|
98
|
+
# @liquid_summary
|
99
|
+
# Converts any URL-unsafe characters in a string to the
|
100
|
+
# [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) equivalent.
|
101
|
+
# @liquid_description
|
102
|
+
# > Note:
|
103
|
+
# > Spaces are converted to a `+` character, instead of a percent-encoded character.
|
104
|
+
# @liquid_syntax string | url_encode
|
105
|
+
# @liquid_return [string]
|
50
106
|
def url_encode(input)
|
51
107
|
CGI.escape(input.to_s) unless input.nil?
|
52
108
|
end
|
53
109
|
|
110
|
+
# @liquid_public_docs
|
111
|
+
# @liquid_type filter
|
112
|
+
# @liquid_category string
|
113
|
+
# @liquid_summary
|
114
|
+
# Decodes any [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) characters
|
115
|
+
# in a string.
|
116
|
+
# @liquid_syntax string | url_decode
|
117
|
+
# @liquid_return [string]
|
54
118
|
def url_decode(input)
|
55
119
|
return if input.nil?
|
56
120
|
|
@@ -60,6 +124,64 @@ module Liquid
|
|
60
124
|
result
|
61
125
|
end
|
62
126
|
|
127
|
+
# @liquid_public_docs
|
128
|
+
# @liquid_type filter
|
129
|
+
# @liquid_category string
|
130
|
+
# @liquid_summary
|
131
|
+
# Encodes a string to [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
132
|
+
# @liquid_syntax string | base64_encode
|
133
|
+
# @liquid_return [string]
|
134
|
+
def base64_encode(input)
|
135
|
+
Base64.strict_encode64(input.to_s)
|
136
|
+
end
|
137
|
+
|
138
|
+
# @liquid_public_docs
|
139
|
+
# @liquid_type filter
|
140
|
+
# @liquid_category string
|
141
|
+
# @liquid_summary
|
142
|
+
# Decodes a string in [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
143
|
+
# @liquid_syntax string | base64_decode
|
144
|
+
# @liquid_return [string]
|
145
|
+
def base64_decode(input)
|
146
|
+
Base64.strict_decode64(input.to_s)
|
147
|
+
rescue ::ArgumentError
|
148
|
+
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
|
149
|
+
end
|
150
|
+
|
151
|
+
# @liquid_public_docs
|
152
|
+
# @liquid_type filter
|
153
|
+
# @liquid_category string
|
154
|
+
# @liquid_summary
|
155
|
+
# Encodes a string to URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
156
|
+
# @liquid_syntax string | base64_url_safe_encode
|
157
|
+
# @liquid_return [string]
|
158
|
+
def base64_url_safe_encode(input)
|
159
|
+
Base64.urlsafe_encode64(input.to_s)
|
160
|
+
end
|
161
|
+
|
162
|
+
# @liquid_public_docs
|
163
|
+
# @liquid_type filter
|
164
|
+
# @liquid_category string
|
165
|
+
# @liquid_summary
|
166
|
+
# Decodes a string in URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
167
|
+
# @liquid_syntax string | base64_url_safe_decode
|
168
|
+
# @liquid_return [string]
|
169
|
+
def base64_url_safe_decode(input)
|
170
|
+
Base64.urlsafe_decode64(input.to_s)
|
171
|
+
rescue ::ArgumentError
|
172
|
+
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
|
173
|
+
end
|
174
|
+
|
175
|
+
# @liquid_public_docs
|
176
|
+
# @liquid_type filter
|
177
|
+
# @liquid_category string
|
178
|
+
# @liquid_summary
|
179
|
+
# Returns a substring or series of array items, starting at a given 0-based index.
|
180
|
+
# @liquid_description
|
181
|
+
# By default, the substring has a length of one character, and the array series has one array item. However, you can
|
182
|
+
# provide a second parameter to specify the number of characters or array items.
|
183
|
+
# @liquid_syntax string | slice
|
184
|
+
# @liquid_return [string]
|
63
185
|
def slice(input, offset, length = nil)
|
64
186
|
offset = Utils.to_integer(offset)
|
65
187
|
length = length ? Utils.to_integer(length) : 1
|
@@ -71,68 +193,151 @@ module Liquid
|
|
71
193
|
end
|
72
194
|
end
|
73
195
|
|
74
|
-
#
|
75
|
-
|
196
|
+
# @liquid_public_docs
|
197
|
+
# @liquid_type filter
|
198
|
+
# @liquid_category string
|
199
|
+
# @liquid_summary
|
200
|
+
# Truncates a string down to a given number of characters.
|
201
|
+
# @liquid_description
|
202
|
+
# If the specified number of characters is less than the length of the string, then an ellipsis (`...`) is appended to
|
203
|
+
# the truncated string. The ellipsis is included in the character count of the truncated string.
|
204
|
+
# @liquid_syntax string | truncate: number
|
205
|
+
# @liquid_return [string]
|
206
|
+
def truncate(input, length = 50, truncate_string = "...")
|
76
207
|
return if input.nil?
|
77
208
|
input_str = input.to_s
|
78
|
-
length
|
209
|
+
length = Utils.to_integer(length)
|
210
|
+
|
79
211
|
truncate_string_str = truncate_string.to_s
|
212
|
+
|
80
213
|
l = length - truncate_string_str.length
|
81
214
|
l = 0 if l < 0
|
82
|
-
|
215
|
+
|
216
|
+
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
|
83
217
|
end
|
84
218
|
|
85
|
-
|
219
|
+
# @liquid_public_docs
|
220
|
+
# @liquid_type filter
|
221
|
+
# @liquid_category string
|
222
|
+
# @liquid_summary
|
223
|
+
# Truncates a string down to a given number of words.
|
224
|
+
# @liquid_description
|
225
|
+
# If the specified number of words is less than the number of words in the string, then an ellipsis (`...`) is appended to
|
226
|
+
# the truncated string.
|
227
|
+
#
|
228
|
+
# > Caution:
|
229
|
+
# > HTML tags are treated as words, so you should strip any HTML from truncated content. If you don't strip HTML, then
|
230
|
+
# > closing HTML tags can be removed, which can result in unexpected behavior.
|
231
|
+
# @liquid_syntax string | truncatewords: number
|
232
|
+
# @liquid_return [string]
|
233
|
+
def truncatewords(input, words = 15, truncate_string = "...")
|
86
234
|
return if input.nil?
|
87
|
-
|
235
|
+
input = input.to_s
|
88
236
|
words = Utils.to_integer(words)
|
89
|
-
|
90
|
-
|
91
|
-
wordlist
|
237
|
+
words = 1 if words <= 0
|
238
|
+
|
239
|
+
wordlist = begin
|
240
|
+
input.split(" ", words + 1)
|
241
|
+
rescue RangeError
|
242
|
+
raise if words + 1 < MAX_INT
|
243
|
+
# e.g. integer #{words} too big to convert to `int'
|
244
|
+
raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
|
245
|
+
end
|
246
|
+
return input if wordlist.length <= words
|
247
|
+
|
248
|
+
wordlist.pop
|
249
|
+
wordlist.join(" ").concat(truncate_string.to_s)
|
92
250
|
end
|
93
251
|
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
252
|
+
# @liquid_public_docs
|
253
|
+
# @liquid_type filter
|
254
|
+
# @liquid_category string
|
255
|
+
# @liquid_summary
|
256
|
+
# Splits a string into an array of substrings based on a given separator.
|
257
|
+
# @liquid_syntax string | split: string
|
258
|
+
# @liquid_return [array[string]]
|
99
259
|
def split(input, pattern)
|
100
260
|
input.to_s.split(pattern.to_s)
|
101
261
|
end
|
102
262
|
|
263
|
+
# @liquid_public_docs
|
264
|
+
# @liquid_type filter
|
265
|
+
# @liquid_category string
|
266
|
+
# @liquid_summary
|
267
|
+
# Strips all whitespace from the left and right of a string.
|
268
|
+
# @liquid_syntax string | strip
|
269
|
+
# @liquid_return [string]
|
103
270
|
def strip(input)
|
104
271
|
input.to_s.strip
|
105
272
|
end
|
106
273
|
|
274
|
+
# @liquid_public_docs
|
275
|
+
# @liquid_type filter
|
276
|
+
# @liquid_category string
|
277
|
+
# @liquid_summary
|
278
|
+
# Strips all whitespace from the left of a string.
|
279
|
+
# @liquid_syntax string | lstrip
|
280
|
+
# @liquid_return [string]
|
107
281
|
def lstrip(input)
|
108
282
|
input.to_s.lstrip
|
109
283
|
end
|
110
284
|
|
285
|
+
# @liquid_public_docs
|
286
|
+
# @liquid_type filter
|
287
|
+
# @liquid_category string
|
288
|
+
# @liquid_summary
|
289
|
+
# Strips all whitespace from the right of a string.
|
290
|
+
# @liquid_syntax string | rstrip
|
291
|
+
# @liquid_return [string]
|
111
292
|
def rstrip(input)
|
112
293
|
input.to_s.rstrip
|
113
294
|
end
|
114
295
|
|
296
|
+
# @liquid_public_docs
|
297
|
+
# @liquid_type filter
|
298
|
+
# @liquid_category string
|
299
|
+
# @liquid_summary
|
300
|
+
# Strips all HTML tags from a string.
|
301
|
+
# @liquid_syntax string | strip_html
|
302
|
+
# @liquid_return [string]
|
115
303
|
def strip_html(input)
|
116
|
-
empty
|
304
|
+
empty = ''
|
117
305
|
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
118
306
|
result.gsub!(STRIP_HTML_TAGS, empty)
|
119
307
|
result
|
120
308
|
end
|
121
309
|
|
122
|
-
#
|
310
|
+
# @liquid_public_docs
|
311
|
+
# @liquid_type filter
|
312
|
+
# @liquid_category string
|
313
|
+
# @liquid_summary
|
314
|
+
# Strips all newline characters (line breaks) from a string.
|
315
|
+
# @liquid_syntax string | strip_newlines
|
316
|
+
# @liquid_return [string]
|
123
317
|
def strip_newlines(input)
|
124
|
-
input.to_s.gsub(/\r?\n/, ''
|
125
|
-
end
|
126
|
-
|
127
|
-
#
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
#
|
133
|
-
#
|
318
|
+
input.to_s.gsub(/\r?\n/, '')
|
319
|
+
end
|
320
|
+
|
321
|
+
# @liquid_public_docs
|
322
|
+
# @liquid_type filter
|
323
|
+
# @liquid_category array
|
324
|
+
# @liquid_summary
|
325
|
+
# Combines all of the items in an array into a single string, separated by a space.
|
326
|
+
# @liquid_syntax array | join
|
327
|
+
# @liquid_return [string]
|
328
|
+
def join(input, glue = ' ')
|
329
|
+
InputIterator.new(input, context).join(glue)
|
330
|
+
end
|
331
|
+
|
332
|
+
# @liquid_public_docs
|
333
|
+
# @liquid_type filter
|
334
|
+
# @liquid_category array
|
335
|
+
# @liquid_summary
|
336
|
+
# Sorts the items in an array in case-sensitive alphabetical, or numerical, order.
|
337
|
+
# @liquid_syntax array | sort
|
338
|
+
# @liquid_return [array[untyped]]
|
134
339
|
def sort(input, property = nil)
|
135
|
-
ary = InputIterator.new(input)
|
340
|
+
ary = InputIterator.new(input, context)
|
136
341
|
|
137
342
|
return [] if ary.empty?
|
138
343
|
|
@@ -149,10 +354,19 @@ module Liquid
|
|
149
354
|
end
|
150
355
|
end
|
151
356
|
|
152
|
-
#
|
153
|
-
#
|
357
|
+
# @liquid_public_docs
|
358
|
+
# @liquid_type filter
|
359
|
+
# @liquid_category array
|
360
|
+
# @liquid_summary
|
361
|
+
# Sorts the items in an array in case-insensitive alphabetical order.
|
362
|
+
# @liquid_description
|
363
|
+
# > Caution:
|
364
|
+
# > You shouldn't use the `sort_natural` filter to sort numerical values. When comparing items an array, each item is converted to a
|
365
|
+
# > string, so sorting on numerical values can lead to unexpected results.
|
366
|
+
# @liquid_syntax array | sort_natural
|
367
|
+
# @liquid_return [array[untyped]]
|
154
368
|
def sort_natural(input, property = nil)
|
155
|
-
ary = InputIterator.new(input)
|
369
|
+
ary = InputIterator.new(input, context)
|
156
370
|
|
157
371
|
return [] if ary.empty?
|
158
372
|
|
@@ -169,58 +383,91 @@ module Liquid
|
|
169
383
|
end
|
170
384
|
end
|
171
385
|
|
172
|
-
#
|
173
|
-
#
|
386
|
+
# @liquid_public_docs
|
387
|
+
# @liquid_type filter
|
388
|
+
# @liquid_category array
|
389
|
+
# @liquid_summary
|
390
|
+
# Filters an array to include only items with a specific property value.
|
391
|
+
# @liquid_description
|
392
|
+
# This requires you to provide both the property name and the associated value.
|
393
|
+
# @liquid_syntax array | where: string, string
|
394
|
+
# @liquid_return [array[untyped]]
|
174
395
|
def where(input, property, target_value = nil)
|
175
|
-
ary = InputIterator.new(input)
|
396
|
+
ary = InputIterator.new(input, context)
|
176
397
|
|
177
398
|
if ary.empty?
|
178
399
|
[]
|
179
|
-
elsif
|
180
|
-
|
181
|
-
|
400
|
+
elsif target_value.nil?
|
401
|
+
ary.select do |item|
|
402
|
+
item[property]
|
182
403
|
rescue TypeError
|
183
404
|
raise_property_error(property)
|
405
|
+
rescue NoMethodError
|
406
|
+
return nil unless item.respond_to?(:[])
|
407
|
+
raise
|
184
408
|
end
|
185
|
-
|
186
|
-
|
187
|
-
|
409
|
+
else
|
410
|
+
ary.select do |item|
|
411
|
+
item[property] == target_value
|
188
412
|
rescue TypeError
|
189
413
|
raise_property_error(property)
|
414
|
+
rescue NoMethodError
|
415
|
+
return nil unless item.respond_to?(:[])
|
416
|
+
raise
|
190
417
|
end
|
191
418
|
end
|
192
419
|
end
|
193
420
|
|
194
|
-
#
|
195
|
-
#
|
421
|
+
# @liquid_public_docs
|
422
|
+
# @liquid_type filter
|
423
|
+
# @liquid_category array
|
424
|
+
# @liquid_summary
|
425
|
+
# Removes any duplicate items in an array.
|
426
|
+
# @liquid_syntax array | uniq
|
427
|
+
# @liquid_return [array[untyped]]
|
196
428
|
def uniq(input, property = nil)
|
197
|
-
ary = InputIterator.new(input)
|
429
|
+
ary = InputIterator.new(input, context)
|
198
430
|
|
199
431
|
if property.nil?
|
200
432
|
ary.uniq
|
201
433
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
202
434
|
[]
|
203
|
-
|
204
|
-
|
205
|
-
|
435
|
+
else
|
436
|
+
ary.uniq do |item|
|
437
|
+
item[property]
|
206
438
|
rescue TypeError
|
207
439
|
raise_property_error(property)
|
440
|
+
rescue NoMethodError
|
441
|
+
return nil unless item.respond_to?(:[])
|
442
|
+
raise
|
208
443
|
end
|
209
444
|
end
|
210
445
|
end
|
211
446
|
|
212
|
-
#
|
447
|
+
# @liquid_public_docs
|
448
|
+
# @liquid_type filter
|
449
|
+
# @liquid_category array
|
450
|
+
# @liquid_summary
|
451
|
+
# Reverses the order of the items in an array.
|
452
|
+
# @liquid_syntax array | reverse
|
453
|
+
# @liquid_return [array[untyped]]
|
213
454
|
def reverse(input)
|
214
|
-
ary = InputIterator.new(input)
|
455
|
+
ary = InputIterator.new(input, context)
|
215
456
|
ary.reverse
|
216
457
|
end
|
217
458
|
|
218
|
-
#
|
459
|
+
# @liquid_public_docs
|
460
|
+
# @liquid_type filter
|
461
|
+
# @liquid_category array
|
462
|
+
# @liquid_summary
|
463
|
+
# Creates an array of values from a specific property of the items in an array.
|
464
|
+
# @liquid_syntax array | map: string
|
465
|
+
# @liquid_return [array[untyped]]
|
219
466
|
def map(input, property)
|
220
|
-
InputIterator.new(input).map do |e|
|
467
|
+
InputIterator.new(input, context).map do |e|
|
221
468
|
e = e.call if e.is_a?(Proc)
|
222
469
|
|
223
|
-
if property == "to_liquid"
|
470
|
+
if property == "to_liquid"
|
224
471
|
e
|
225
472
|
elsif e.respond_to?(:[])
|
226
473
|
r = e[property]
|
@@ -231,64 +478,157 @@ module Liquid
|
|
231
478
|
raise_property_error(property)
|
232
479
|
end
|
233
480
|
|
234
|
-
#
|
235
|
-
#
|
481
|
+
# @liquid_public_docs
|
482
|
+
# @liquid_type filter
|
483
|
+
# @liquid_category array
|
484
|
+
# @liquid_summary
|
485
|
+
# Removes any `nil` items from an array.
|
486
|
+
# @liquid_syntax array | compact
|
487
|
+
# @liquid_return [array[untyped]]
|
236
488
|
def compact(input, property = nil)
|
237
|
-
ary = InputIterator.new(input)
|
489
|
+
ary = InputIterator.new(input, context)
|
238
490
|
|
239
491
|
if property.nil?
|
240
492
|
ary.compact
|
241
493
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
242
494
|
[]
|
243
|
-
|
244
|
-
|
245
|
-
|
495
|
+
else
|
496
|
+
ary.reject do |item|
|
497
|
+
item[property].nil?
|
246
498
|
rescue TypeError
|
247
499
|
raise_property_error(property)
|
500
|
+
rescue NoMethodError
|
501
|
+
return nil unless item.respond_to?(:[])
|
502
|
+
raise
|
248
503
|
end
|
249
504
|
end
|
250
505
|
end
|
251
506
|
|
252
|
-
#
|
253
|
-
|
507
|
+
# @liquid_public_docs
|
508
|
+
# @liquid_type filter
|
509
|
+
# @liquid_category string
|
510
|
+
# @liquid_summary
|
511
|
+
# Replaces any instance of a substring inside a string with a given string.
|
512
|
+
# @liquid_syntax string | replace: string, string
|
513
|
+
# @liquid_return [string]
|
514
|
+
def replace(input, string, replacement = '')
|
254
515
|
input.to_s.gsub(string.to_s, replacement.to_s)
|
255
516
|
end
|
256
517
|
|
257
|
-
#
|
258
|
-
|
518
|
+
# @liquid_public_docs
|
519
|
+
# @liquid_type filter
|
520
|
+
# @liquid_category string
|
521
|
+
# @liquid_summary
|
522
|
+
# Replaces the first instance of a substring inside a string with a given string.
|
523
|
+
# @liquid_syntax string | replace_first: string, string
|
524
|
+
# @liquid_return [string]
|
525
|
+
def replace_first(input, string, replacement = '')
|
259
526
|
input.to_s.sub(string.to_s, replacement.to_s)
|
260
527
|
end
|
261
528
|
|
262
|
-
#
|
263
|
-
|
264
|
-
|
529
|
+
# @liquid_public_docs
|
530
|
+
# @liquid_type filter
|
531
|
+
# @liquid_category string
|
532
|
+
# @liquid_summary
|
533
|
+
# Replaces the last instance of a substring inside a string with a given string.
|
534
|
+
# @liquid_syntax string | replace_last: string, string
|
535
|
+
# @liquid_return [string]
|
536
|
+
def replace_last(input, string, replacement)
|
537
|
+
input = input.to_s
|
538
|
+
string = string.to_s
|
539
|
+
replacement = replacement.to_s
|
540
|
+
|
541
|
+
start_index = input.rindex(string)
|
542
|
+
|
543
|
+
return input unless start_index
|
544
|
+
|
545
|
+
output = input.dup
|
546
|
+
output[start_index, string.length] = replacement
|
547
|
+
output
|
265
548
|
end
|
266
549
|
|
267
|
-
#
|
268
|
-
|
269
|
-
|
550
|
+
# @liquid_public_docs
|
551
|
+
# @liquid_type filter
|
552
|
+
# @liquid_category string
|
553
|
+
# @liquid_summary
|
554
|
+
# Removes any instance of a substring inside a string.
|
555
|
+
# @liquid_syntax string | remove: string
|
556
|
+
# @liquid_return [string]
|
557
|
+
def remove(input, string)
|
558
|
+
replace(input, string, '')
|
270
559
|
end
|
271
560
|
|
272
|
-
#
|
561
|
+
# @liquid_public_docs
|
562
|
+
# @liquid_type filter
|
563
|
+
# @liquid_category string
|
564
|
+
# @liquid_summary
|
565
|
+
# Removes the first instance of a substring inside a string.
|
566
|
+
# @liquid_syntax string | remove_first: string
|
567
|
+
# @liquid_return [string]
|
568
|
+
def remove_first(input, string)
|
569
|
+
replace_first(input, string, '')
|
570
|
+
end
|
571
|
+
|
572
|
+
# @liquid_public_docs
|
573
|
+
# @liquid_type filter
|
574
|
+
# @liquid_category string
|
575
|
+
# @liquid_summary
|
576
|
+
# Removes the last instance of a substring inside a string.
|
577
|
+
# @liquid_syntax string | remove_last: string
|
578
|
+
# @liquid_return [string]
|
579
|
+
def remove_last(input, string)
|
580
|
+
replace_last(input, string, '')
|
581
|
+
end
|
582
|
+
|
583
|
+
# @liquid_public_docs
|
584
|
+
# @liquid_type filter
|
585
|
+
# @liquid_category string
|
586
|
+
# @liquid_summary
|
587
|
+
# Adds a given string to the end of a string.
|
588
|
+
# @liquid_syntax string | append: string
|
589
|
+
# @liquid_return [string]
|
273
590
|
def append(input, string)
|
274
591
|
input.to_s + string.to_s
|
275
592
|
end
|
276
593
|
|
594
|
+
# @liquid_public_docs
|
595
|
+
# @liquid_type filter
|
596
|
+
# @liquid_category array
|
597
|
+
# @liquid_summary
|
598
|
+
# Concatenates (combines) two arrays.
|
599
|
+
# @liquid_description
|
600
|
+
# > Note:
|
601
|
+
# > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
|
602
|
+
# > [`uniq` filter](/api/liquid/filters#uniq).
|
603
|
+
# @liquid_syntax array | concat: array
|
604
|
+
# @liquid_return [array[untyped]]
|
277
605
|
def concat(input, array)
|
278
606
|
unless array.respond_to?(:to_ary)
|
279
|
-
raise ArgumentError
|
607
|
+
raise ArgumentError, "concat filter requires an array argument"
|
280
608
|
end
|
281
|
-
InputIterator.new(input).concat(array)
|
609
|
+
InputIterator.new(input, context).concat(array)
|
282
610
|
end
|
283
611
|
|
284
|
-
#
|
612
|
+
# @liquid_public_docs
|
613
|
+
# @liquid_type filter
|
614
|
+
# @liquid_category string
|
615
|
+
# @liquid_summary
|
616
|
+
# Adds a given string to the beginning of a string.
|
617
|
+
# @liquid_syntax string | prepend: string
|
618
|
+
# @liquid_return [string]
|
285
619
|
def prepend(input, string)
|
286
620
|
string.to_s + input.to_s
|
287
621
|
end
|
288
622
|
|
289
|
-
#
|
623
|
+
# @liquid_public_docs
|
624
|
+
# @liquid_type filter
|
625
|
+
# @liquid_category string
|
626
|
+
# @liquid_summary
|
627
|
+
# Converts newlines (`\n`) in a string to HTML line breaks (`<br>`).
|
628
|
+
# @liquid_syntax string | newline_to_br
|
629
|
+
# @liquid_return [string]
|
290
630
|
def newline_to_br(input)
|
291
|
-
input.to_s.gsub(/\n/, "<br />\n"
|
631
|
+
input.to_s.gsub(/\r?\n/, "<br />\n")
|
292
632
|
end
|
293
633
|
|
294
634
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
@@ -325,63 +665,111 @@ module Liquid
|
|
325
665
|
def date(input, format)
|
326
666
|
return input if format.to_s.empty?
|
327
667
|
|
328
|
-
return input unless date = Utils.to_date(input)
|
668
|
+
return input unless (date = Utils.to_date(input))
|
329
669
|
|
330
670
|
date.strftime(format.to_s)
|
331
671
|
end
|
332
672
|
|
333
|
-
#
|
334
|
-
#
|
335
|
-
#
|
336
|
-
#
|
337
|
-
#
|
673
|
+
# @liquid_public_docs
|
674
|
+
# @liquid_type filter
|
675
|
+
# @liquid_category array
|
676
|
+
# @liquid_summary
|
677
|
+
# Returns the first item in an array.
|
678
|
+
# @liquid_syntax array | first
|
679
|
+
# @liquid_return [untyped]
|
338
680
|
def first(array)
|
339
681
|
array.first if array.respond_to?(:first)
|
340
682
|
end
|
341
683
|
|
342
|
-
#
|
343
|
-
#
|
344
|
-
#
|
345
|
-
#
|
346
|
-
#
|
684
|
+
# @liquid_public_docs
|
685
|
+
# @liquid_type filter
|
686
|
+
# @liquid_category array
|
687
|
+
# @liquid_summary
|
688
|
+
# Returns the last item in an array.
|
689
|
+
# @liquid_syntax array | last
|
690
|
+
# @liquid_return [untyped]
|
347
691
|
def last(array)
|
348
692
|
array.last if array.respond_to?(:last)
|
349
693
|
end
|
350
694
|
|
351
|
-
#
|
695
|
+
# @liquid_public_docs
|
696
|
+
# @liquid_type filter
|
697
|
+
# @liquid_category math
|
698
|
+
# @liquid_summary
|
699
|
+
# Returns the absolute value of a number.
|
700
|
+
# @liquid_syntax number | abs
|
701
|
+
# @liquid_return [number]
|
352
702
|
def abs(input)
|
353
703
|
result = Utils.to_number(input).abs
|
354
704
|
result.is_a?(BigDecimal) ? result.to_f : result
|
355
705
|
end
|
356
706
|
|
357
|
-
#
|
707
|
+
# @liquid_public_docs
|
708
|
+
# @liquid_type filter
|
709
|
+
# @liquid_category math
|
710
|
+
# @liquid_summary
|
711
|
+
# Adds two numbers.
|
712
|
+
# @liquid_syntax number | plus: number
|
713
|
+
# @liquid_return [number]
|
358
714
|
def plus(input, operand)
|
359
715
|
apply_operation(input, operand, :+)
|
360
716
|
end
|
361
717
|
|
362
|
-
#
|
718
|
+
# @liquid_public_docs
|
719
|
+
# @liquid_type filter
|
720
|
+
# @liquid_category math
|
721
|
+
# @liquid_summary
|
722
|
+
# Subtracts a given number from another number.
|
723
|
+
# @liquid_syntax number | minus: number
|
724
|
+
# @liquid_return [number]
|
363
725
|
def minus(input, operand)
|
364
726
|
apply_operation(input, operand, :-)
|
365
727
|
end
|
366
728
|
|
367
|
-
#
|
729
|
+
# @liquid_public_docs
|
730
|
+
# @liquid_type filter
|
731
|
+
# @liquid_category math
|
732
|
+
# @liquid_summary
|
733
|
+
# Multiplies a number by a given number.
|
734
|
+
# @liquid_syntax number | times: number
|
735
|
+
# @liquid_return [number]
|
368
736
|
def times(input, operand)
|
369
737
|
apply_operation(input, operand, :*)
|
370
738
|
end
|
371
739
|
|
372
|
-
#
|
740
|
+
# @liquid_public_docs
|
741
|
+
# @liquid_type filter
|
742
|
+
# @liquid_category math
|
743
|
+
# @liquid_summary
|
744
|
+
# Divides a number by a given number.
|
745
|
+
# @liquid_syntax number | divided_by: number
|
746
|
+
# @liquid_return [number]
|
373
747
|
def divided_by(input, operand)
|
374
748
|
apply_operation(input, operand, :/)
|
375
749
|
rescue ::ZeroDivisionError => e
|
376
750
|
raise Liquid::ZeroDivisionError, e.message
|
377
751
|
end
|
378
752
|
|
753
|
+
# @liquid_public_docs
|
754
|
+
# @liquid_type filter
|
755
|
+
# @liquid_category math
|
756
|
+
# @liquid_summary
|
757
|
+
# Returns the remainder of dividing a number by a given number.
|
758
|
+
# @liquid_syntax number | modulo: number
|
759
|
+
# @liquid_return [number]
|
379
760
|
def modulo(input, operand)
|
380
761
|
apply_operation(input, operand, :%)
|
381
762
|
rescue ::ZeroDivisionError => e
|
382
763
|
raise Liquid::ZeroDivisionError, e.message
|
383
764
|
end
|
384
765
|
|
766
|
+
# @liquid_public_docs
|
767
|
+
# @liquid_type filter
|
768
|
+
# @liquid_category math
|
769
|
+
# @liquid_summary
|
770
|
+
# Rounds a number to the nearest integer.
|
771
|
+
# @liquid_syntax number | round
|
772
|
+
# @liquid_return [number]
|
385
773
|
def round(input, n = 0)
|
386
774
|
result = Utils.to_number(input).round(Utils.to_number(n))
|
387
775
|
result = result.to_f if result.is_a?(BigDecimal)
|
@@ -391,18 +779,39 @@ module Liquid
|
|
391
779
|
raise Liquid::FloatDomainError, e.message
|
392
780
|
end
|
393
781
|
|
782
|
+
# @liquid_public_docs
|
783
|
+
# @liquid_type filter
|
784
|
+
# @liquid_category math
|
785
|
+
# @liquid_summary
|
786
|
+
# Rounds a number up to the nearest integer.
|
787
|
+
# @liquid_syntax number | ceil
|
788
|
+
# @liquid_return [number]
|
394
789
|
def ceil(input)
|
395
790
|
Utils.to_number(input).ceil.to_i
|
396
791
|
rescue ::FloatDomainError => e
|
397
792
|
raise Liquid::FloatDomainError, e.message
|
398
793
|
end
|
399
794
|
|
795
|
+
# @liquid_public_docs
|
796
|
+
# @liquid_type filter
|
797
|
+
# @liquid_category math
|
798
|
+
# @liquid_summary
|
799
|
+
# Rounds a number down to the nearest integer.
|
800
|
+
# @liquid_syntax number | floor
|
801
|
+
# @liquid_return [number]
|
400
802
|
def floor(input)
|
401
803
|
Utils.to_number(input).floor.to_i
|
402
804
|
rescue ::FloatDomainError => e
|
403
805
|
raise Liquid::FloatDomainError, e.message
|
404
806
|
end
|
405
807
|
|
808
|
+
# @liquid_public_docs
|
809
|
+
# @liquid_type filter
|
810
|
+
# @liquid_category math
|
811
|
+
# @liquid_summary
|
812
|
+
# Limits a number to a minimum value.
|
813
|
+
# @liquid_syntax number | at_least
|
814
|
+
# @liquid_return [number]
|
406
815
|
def at_least(input, n)
|
407
816
|
min_value = Utils.to_number(n)
|
408
817
|
|
@@ -411,6 +820,13 @@ module Liquid
|
|
411
820
|
result.is_a?(BigDecimal) ? result.to_f : result
|
412
821
|
end
|
413
822
|
|
823
|
+
# @liquid_public_docs
|
824
|
+
# @liquid_type filter
|
825
|
+
# @liquid_category math
|
826
|
+
# @liquid_summary
|
827
|
+
# Limits a number to a maximum value.
|
828
|
+
# @liquid_syntax number | at_most
|
829
|
+
# @liquid_return [number]
|
414
830
|
def at_most(input, n)
|
415
831
|
max_value = Utils.to_number(n)
|
416
832
|
|
@@ -419,18 +835,30 @@ module Liquid
|
|
419
835
|
result.is_a?(BigDecimal) ? result.to_f : result
|
420
836
|
end
|
421
837
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
838
|
+
# @liquid_public_docs
|
839
|
+
# @liquid_type filter
|
840
|
+
# @liquid_category default
|
841
|
+
# @liquid_summary
|
842
|
+
# Sets a default value for any variable whose value is one of the following:
|
843
|
+
#
|
844
|
+
# - [`empty`](/api/liquid/basics#empty)
|
845
|
+
# - [`false`](/api/liquid/basics#truthy-and-falsy)
|
846
|
+
# - [`nil`](/api/liquid/basics#nil)
|
847
|
+
# @liquid_syntax variable | default: variable
|
848
|
+
# @liquid_return [untyped]
|
849
|
+
# @liquid_optional_param allow_false [boolean] Whether to use false values instead of the default.
|
850
|
+
def default(input, default_value = '', options = {})
|
851
|
+
options = {} unless options.is_a?(Hash)
|
852
|
+
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
|
853
|
+
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
428
854
|
end
|
429
855
|
|
430
856
|
private
|
431
857
|
|
858
|
+
attr_reader :context
|
859
|
+
|
432
860
|
def raise_property_error(property)
|
433
|
-
raise Liquid::ArgumentError
|
861
|
+
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
|
434
862
|
end
|
435
863
|
|
436
864
|
def apply_operation(input, operand, operation)
|
@@ -439,10 +867,16 @@ module Liquid
|
|
439
867
|
end
|
440
868
|
|
441
869
|
def nil_safe_compare(a, b)
|
442
|
-
|
443
|
-
|
870
|
+
result = a <=> b
|
871
|
+
|
872
|
+
if result
|
873
|
+
result
|
874
|
+
elsif a.nil?
|
875
|
+
1
|
876
|
+
elsif b.nil?
|
877
|
+
-1
|
444
878
|
else
|
445
|
-
|
879
|
+
raise Liquid::ArgumentError, "cannot sort values of incompatible types"
|
446
880
|
end
|
447
881
|
end
|
448
882
|
|
@@ -457,8 +891,9 @@ module Liquid
|
|
457
891
|
class InputIterator
|
458
892
|
include Enumerable
|
459
893
|
|
460
|
-
def initialize(input)
|
461
|
-
@
|
894
|
+
def initialize(input, context)
|
895
|
+
@context = context
|
896
|
+
@input = if input.is_a?(Array)
|
462
897
|
input.flatten
|
463
898
|
elsif input.is_a?(Hash)
|
464
899
|
[input]
|
@@ -496,7 +931,9 @@ module Liquid
|
|
496
931
|
|
497
932
|
def each
|
498
933
|
@input.each do |e|
|
499
|
-
|
934
|
+
e = e.respond_to?(:to_liquid) ? e.to_liquid : e
|
935
|
+
e.context = @context if e.respond_to?(:context=)
|
936
|
+
yield(e)
|
500
937
|
end
|
501
938
|
end
|
502
939
|
end
|