liquid 4.0.0 → 5.10.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 +5 -5
- data/History.md +235 -2
- data/README.md +58 -8
- data/lib/liquid/block.rb +51 -20
- data/lib/liquid/block_body.rb +216 -82
- data/lib/liquid/condition.rb +83 -32
- data/lib/liquid/const.rb +8 -0
- data/lib/liquid/context.rb +130 -59
- data/lib/liquid/deprecations.rb +22 -0
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +8 -2
- data/lib/liquid/environment.rb +159 -0
- data/lib/liquid/errors.rb +23 -20
- data/lib/liquid/expression.rb +114 -31
- data/lib/liquid/extensions.rb +8 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +51 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +165 -39
- data/lib/liquid/locales/en.yml +16 -6
- data/lib/liquid/parse_context.rb +62 -7
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +31 -19
- data/lib/liquid/parser_switching.rb +42 -3
- data/lib/liquid/partial_cache.rb +33 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/range_lookup.rb +26 -6
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/snippet_drop.rb +22 -0
- data/lib/liquid/standardfilters.rb +813 -137
- 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 +13 -0
- data/lib/liquid/tag.rb +42 -6
- data/lib/liquid/tags/assign.rb +46 -18
- data/lib/liquid/tags/break.rb +15 -4
- data/lib/liquid/tags/capture.rb +26 -18
- data/lib/liquid/tags/case.rb +108 -32
- data/lib/liquid/tags/comment.rb +76 -4
- data/lib/liquid/tags/continue.rb +15 -13
- data/lib/liquid/tags/cycle.rb +117 -34
- data/lib/liquid/tags/decrement.rb +30 -23
- data/lib/liquid/tags/doc.rb +81 -0
- data/lib/liquid/tags/echo.rb +39 -0
- data/lib/liquid/tags/for.rb +109 -96
- data/lib/liquid/tags/if.rb +72 -41
- data/lib/liquid/tags/ifchanged.rb +10 -11
- data/lib/liquid/tags/include.rb +89 -63
- data/lib/liquid/tags/increment.rb +31 -20
- data/lib/liquid/tags/inline_comment.rb +28 -0
- data/lib/liquid/tags/raw.rb +25 -13
- data/lib/liquid/tags/render.rb +151 -0
- data/lib/liquid/tags/snippet.rb +45 -0
- data/lib/liquid/tags/table_row.rb +104 -21
- data/lib/liquid/tags/unless.rb +37 -20
- data/lib/liquid/tags.rb +51 -0
- data/lib/liquid/template.rb +90 -106
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +143 -13
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +114 -5
- data/lib/liquid/variable.rb +119 -45
- data/lib/liquid/variable_lookup.rb +35 -13
- data/lib/liquid/version.rb +3 -1
- data/lib/liquid.rb +31 -18
- metadata +56 -107
- data/lib/liquid/strainer.rb +0 -66
- 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/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/parsing_quirks_test.rb +0 -118
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -66
- data/test/integration/standard_filter_test.rb +0 -535
- 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 -238
- 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 -323
- data/test/integration/trim_mode_test.rb +0 -525
- data/test/integration/variable_test.rb +0 -92
- data/test/test_helper.rb +0 -117
- data/test/unit/block_unit_test.rb +0 -58
- data/test/unit/condition_unit_test.rb +0 -158
- data/test/unit/context_unit_test.rb +0 -483
- 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 -148
- 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,247 +1,726 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'cgi'
|
|
4
|
+
require 'base64'
|
|
2
5
|
require 'bigdecimal'
|
|
3
|
-
|
|
4
6
|
module Liquid
|
|
5
7
|
module StandardFilters
|
|
8
|
+
MAX_I32 = (1 << 31) - 1
|
|
9
|
+
private_constant :MAX_I32
|
|
10
|
+
|
|
11
|
+
MIN_I64 = -(1 << 63)
|
|
12
|
+
MAX_I64 = (1 << 63) - 1
|
|
13
|
+
I64_RANGE = MIN_I64..MAX_I64
|
|
14
|
+
private_constant :MIN_I64, :MAX_I64, :I64_RANGE
|
|
15
|
+
|
|
6
16
|
HTML_ESCAPE = {
|
|
7
|
-
'&'
|
|
8
|
-
'>'
|
|
9
|
-
'<'
|
|
10
|
-
'"'
|
|
11
|
-
"'"
|
|
12
|
-
}
|
|
17
|
+
'&' => '&',
|
|
18
|
+
'>' => '>',
|
|
19
|
+
'<' => '<',
|
|
20
|
+
'"' => '"',
|
|
21
|
+
"'" => ''',
|
|
22
|
+
}.freeze
|
|
13
23
|
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
|
24
|
+
STRIP_HTML_BLOCKS = Regexp.union(
|
|
25
|
+
%r{<script.*?</script>}m,
|
|
26
|
+
/<!--.*?-->/m,
|
|
27
|
+
%r{<style.*?</style>}m,
|
|
28
|
+
)
|
|
29
|
+
STRIP_HTML_TAGS = /<.*?>/m
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
def try_coerce_encoding(input, encoding:)
|
|
33
|
+
original_encoding = input.encoding
|
|
34
|
+
if input.encoding != encoding
|
|
35
|
+
input.force_encoding(encoding)
|
|
36
|
+
unless input.valid_encoding?
|
|
37
|
+
input.force_encoding(original_encoding)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
input
|
|
41
|
+
end
|
|
42
|
+
end
|
|
14
43
|
|
|
15
|
-
#
|
|
44
|
+
# @liquid_public_docs
|
|
45
|
+
# @liquid_type filter
|
|
46
|
+
# @liquid_category array
|
|
47
|
+
# @liquid_summary
|
|
48
|
+
# Returns the size of a string or array.
|
|
49
|
+
# @liquid_description
|
|
50
|
+
# The size of a string is the number of characters that the string includes. The size of an array is the number of items
|
|
51
|
+
# in the array.
|
|
52
|
+
# @liquid_syntax variable | size
|
|
53
|
+
# @liquid_return [number]
|
|
16
54
|
def size(input)
|
|
17
55
|
input.respond_to?(:size) ? input.size : 0
|
|
18
56
|
end
|
|
19
57
|
|
|
20
|
-
#
|
|
58
|
+
# @liquid_public_docs
|
|
59
|
+
# @liquid_type filter
|
|
60
|
+
# @liquid_category string
|
|
61
|
+
# @liquid_summary
|
|
62
|
+
# Converts a string to all lowercase characters.
|
|
63
|
+
# @liquid_syntax string | downcase
|
|
64
|
+
# @liquid_return [string]
|
|
21
65
|
def downcase(input)
|
|
22
|
-
|
|
66
|
+
Utils.to_s(input).downcase
|
|
23
67
|
end
|
|
24
68
|
|
|
25
|
-
#
|
|
69
|
+
# @liquid_public_docs
|
|
70
|
+
# @liquid_type filter
|
|
71
|
+
# @liquid_category string
|
|
72
|
+
# @liquid_summary
|
|
73
|
+
# Converts a string to all uppercase characters.
|
|
74
|
+
# @liquid_syntax string | upcase
|
|
75
|
+
# @liquid_return [string]
|
|
26
76
|
def upcase(input)
|
|
27
|
-
|
|
77
|
+
Utils.to_s(input).upcase
|
|
28
78
|
end
|
|
29
79
|
|
|
30
|
-
#
|
|
80
|
+
# @liquid_public_docs
|
|
81
|
+
# @liquid_type filter
|
|
82
|
+
# @liquid_category string
|
|
83
|
+
# @liquid_summary
|
|
84
|
+
# Capitalizes the first word in a string and downcases the remaining characters.
|
|
85
|
+
# @liquid_syntax string | capitalize
|
|
86
|
+
# @liquid_return [string]
|
|
31
87
|
def capitalize(input)
|
|
32
|
-
|
|
88
|
+
Utils.to_s(input).capitalize
|
|
33
89
|
end
|
|
34
90
|
|
|
91
|
+
# @liquid_public_docs
|
|
92
|
+
# @liquid_type filter
|
|
93
|
+
# @liquid_category string
|
|
94
|
+
# @liquid_summary
|
|
95
|
+
# Escapes special characters in HTML, such as `<>`, `'`, and `&`, and converts characters into escape sequences. The filter doesn't effect characters within the string that don’t have a corresponding escape sequence.".
|
|
96
|
+
# @liquid_syntax string | escape
|
|
97
|
+
# @liquid_return [string]
|
|
35
98
|
def escape(input)
|
|
36
|
-
CGI.escapeHTML(input)
|
|
99
|
+
CGI.escapeHTML(Utils.to_s(input)) unless input.nil?
|
|
37
100
|
end
|
|
38
101
|
alias_method :h, :escape
|
|
39
102
|
|
|
103
|
+
# @liquid_public_docs
|
|
104
|
+
# @liquid_type filter
|
|
105
|
+
# @liquid_category string
|
|
106
|
+
# @liquid_summary
|
|
107
|
+
# Escapes a string without changing characters that have already been escaped.
|
|
108
|
+
# @liquid_syntax string | escape_once
|
|
109
|
+
# @liquid_return [string]
|
|
40
110
|
def escape_once(input)
|
|
41
|
-
|
|
111
|
+
Utils.to_s(input).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
|
42
112
|
end
|
|
43
113
|
|
|
114
|
+
# @liquid_public_docs
|
|
115
|
+
# @liquid_type filter
|
|
116
|
+
# @liquid_category string
|
|
117
|
+
# @liquid_summary
|
|
118
|
+
# Converts any URL-unsafe characters in a string to the
|
|
119
|
+
# [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) equivalent.
|
|
120
|
+
# @liquid_description
|
|
121
|
+
# > Note:
|
|
122
|
+
# > Spaces are converted to a `+` character, instead of a percent-encoded character.
|
|
123
|
+
# @liquid_syntax string | url_encode
|
|
124
|
+
# @liquid_return [string]
|
|
44
125
|
def url_encode(input)
|
|
45
|
-
CGI.escape(input) unless input.nil?
|
|
126
|
+
CGI.escape(Utils.to_s(input)) unless input.nil?
|
|
46
127
|
end
|
|
47
128
|
|
|
129
|
+
# @liquid_public_docs
|
|
130
|
+
# @liquid_type filter
|
|
131
|
+
# @liquid_category string
|
|
132
|
+
# @liquid_summary
|
|
133
|
+
# Decodes any [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) characters
|
|
134
|
+
# in a string.
|
|
135
|
+
# @liquid_syntax string | url_decode
|
|
136
|
+
# @liquid_return [string]
|
|
48
137
|
def url_decode(input)
|
|
49
|
-
|
|
138
|
+
return if input.nil?
|
|
139
|
+
|
|
140
|
+
result = CGI.unescape(Utils.to_s(input))
|
|
141
|
+
raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding?
|
|
142
|
+
|
|
143
|
+
result
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @liquid_public_docs
|
|
147
|
+
# @liquid_type filter
|
|
148
|
+
# @liquid_category string
|
|
149
|
+
# @liquid_summary
|
|
150
|
+
# Encodes a string to [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
|
151
|
+
# @liquid_syntax string | base64_encode
|
|
152
|
+
# @liquid_return [string]
|
|
153
|
+
def base64_encode(input)
|
|
154
|
+
Base64.strict_encode64(Utils.to_s(input))
|
|
50
155
|
end
|
|
51
156
|
|
|
157
|
+
# @liquid_public_docs
|
|
158
|
+
# @liquid_type filter
|
|
159
|
+
# @liquid_category string
|
|
160
|
+
# @liquid_summary
|
|
161
|
+
# Decodes a string in [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
|
162
|
+
# @liquid_syntax string | base64_decode
|
|
163
|
+
# @liquid_return [string]
|
|
164
|
+
def base64_decode(input)
|
|
165
|
+
input = Utils.to_s(input)
|
|
166
|
+
StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding)
|
|
167
|
+
rescue ::ArgumentError
|
|
168
|
+
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @liquid_public_docs
|
|
172
|
+
# @liquid_type filter
|
|
173
|
+
# @liquid_category string
|
|
174
|
+
# @liquid_summary
|
|
175
|
+
# Encodes a string to URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
|
176
|
+
# @liquid_syntax string | base64_url_safe_encode
|
|
177
|
+
# @liquid_return [string]
|
|
178
|
+
def base64_url_safe_encode(input)
|
|
179
|
+
Base64.urlsafe_encode64(Utils.to_s(input))
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @liquid_public_docs
|
|
183
|
+
# @liquid_type filter
|
|
184
|
+
# @liquid_category string
|
|
185
|
+
# @liquid_summary
|
|
186
|
+
# Decodes a string in URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
|
|
187
|
+
# @liquid_syntax string | base64_url_safe_decode
|
|
188
|
+
# @liquid_return [string]
|
|
189
|
+
def base64_url_safe_decode(input)
|
|
190
|
+
input = Utils.to_s(input)
|
|
191
|
+
StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding)
|
|
192
|
+
rescue ::ArgumentError
|
|
193
|
+
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# @liquid_public_docs
|
|
197
|
+
# @liquid_type filter
|
|
198
|
+
# @liquid_category string
|
|
199
|
+
# @liquid_summary
|
|
200
|
+
# Returns a substring or series of array items, starting at a given 0-based index.
|
|
201
|
+
# @liquid_description
|
|
202
|
+
# By default, the substring has a length of one character, and the array series has one array item. However, you can
|
|
203
|
+
# provide a second parameter to specify the number of characters or array items.
|
|
204
|
+
# @liquid_syntax string | slice
|
|
205
|
+
# @liquid_return [string]
|
|
52
206
|
def slice(input, offset, length = nil)
|
|
53
207
|
offset = Utils.to_integer(offset)
|
|
54
208
|
length = length ? Utils.to_integer(length) : 1
|
|
55
209
|
|
|
56
|
-
|
|
57
|
-
input.
|
|
58
|
-
|
|
59
|
-
|
|
210
|
+
begin
|
|
211
|
+
if input.is_a?(Array)
|
|
212
|
+
input.slice(offset, length) || []
|
|
213
|
+
else
|
|
214
|
+
Utils.to_s(input).slice(offset, length) || ''
|
|
215
|
+
end
|
|
216
|
+
rescue RangeError
|
|
217
|
+
if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
|
|
218
|
+
raise # unexpected error
|
|
219
|
+
end
|
|
220
|
+
offset = offset.clamp(I64_RANGE)
|
|
221
|
+
length = length.clamp(I64_RANGE)
|
|
222
|
+
retry
|
|
60
223
|
end
|
|
61
224
|
end
|
|
62
225
|
|
|
63
|
-
#
|
|
64
|
-
|
|
226
|
+
# @liquid_public_docs
|
|
227
|
+
# @liquid_type filter
|
|
228
|
+
# @liquid_category string
|
|
229
|
+
# @liquid_summary
|
|
230
|
+
# Truncates a string down to a given number of characters.
|
|
231
|
+
# @liquid_description
|
|
232
|
+
# If the specified number of characters is less than the length of the string, then an ellipsis (`...`) is appended to
|
|
233
|
+
# the truncated string. The ellipsis is included in the character count of the truncated string.
|
|
234
|
+
# @liquid_syntax string | truncate: number
|
|
235
|
+
# @liquid_return [string]
|
|
236
|
+
def truncate(input, length = 50, truncate_string = "...")
|
|
65
237
|
return if input.nil?
|
|
66
|
-
input_str =
|
|
67
|
-
length
|
|
68
|
-
|
|
238
|
+
input_str = Utils.to_s(input)
|
|
239
|
+
length = Utils.to_integer(length)
|
|
240
|
+
|
|
241
|
+
truncate_string_str = Utils.to_s(truncate_string)
|
|
242
|
+
|
|
69
243
|
l = length - truncate_string_str.length
|
|
70
244
|
l = 0 if l < 0
|
|
71
|
-
|
|
245
|
+
|
|
246
|
+
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
|
|
72
247
|
end
|
|
73
248
|
|
|
74
|
-
|
|
249
|
+
# @liquid_public_docs
|
|
250
|
+
# @liquid_type filter
|
|
251
|
+
# @liquid_category string
|
|
252
|
+
# @liquid_summary
|
|
253
|
+
# Truncates a string down to a given number of words.
|
|
254
|
+
# @liquid_description
|
|
255
|
+
# If the specified number of words is less than the number of words in the string, then an ellipsis (`...`) is appended to
|
|
256
|
+
# the truncated string.
|
|
257
|
+
#
|
|
258
|
+
# > Caution:
|
|
259
|
+
# > HTML tags are treated as words, so you should strip any HTML from truncated content. If you don't strip HTML, then
|
|
260
|
+
# > closing HTML tags can be removed, which can result in unexpected behavior.
|
|
261
|
+
# @liquid_syntax string | truncatewords: number
|
|
262
|
+
# @liquid_return [string]
|
|
263
|
+
def truncatewords(input, words = 15, truncate_string = "...")
|
|
75
264
|
return if input.nil?
|
|
76
|
-
|
|
265
|
+
input = Utils.to_s(input)
|
|
77
266
|
words = Utils.to_integer(words)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
wordlist
|
|
267
|
+
words = 1 if words <= 0
|
|
268
|
+
|
|
269
|
+
wordlist = begin
|
|
270
|
+
input.split(" ", words + 1)
|
|
271
|
+
rescue RangeError
|
|
272
|
+
# integer too big for String#split, but we can semantically assume no truncation is needed
|
|
273
|
+
return input if words + 1 > MAX_I32
|
|
274
|
+
raise # unexpected error
|
|
275
|
+
end
|
|
276
|
+
return input if wordlist.length <= words
|
|
277
|
+
|
|
278
|
+
wordlist.pop
|
|
279
|
+
truncate_string = Utils.to_s(truncate_string)
|
|
280
|
+
wordlist.join(" ").concat(truncate_string)
|
|
81
281
|
end
|
|
82
282
|
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
283
|
+
# @liquid_public_docs
|
|
284
|
+
# @liquid_type filter
|
|
285
|
+
# @liquid_category string
|
|
286
|
+
# @liquid_summary
|
|
287
|
+
# Splits a string into an array of substrings based on a given separator.
|
|
288
|
+
# @liquid_syntax string | split: string
|
|
289
|
+
# @liquid_return [array[string]]
|
|
88
290
|
def split(input, pattern)
|
|
89
|
-
|
|
291
|
+
pattern = Utils.to_s(pattern)
|
|
292
|
+
input = Utils.to_s(input)
|
|
293
|
+
input.split(pattern)
|
|
90
294
|
end
|
|
91
295
|
|
|
296
|
+
# @liquid_public_docs
|
|
297
|
+
# @liquid_type filter
|
|
298
|
+
# @liquid_category string
|
|
299
|
+
# @liquid_summary
|
|
300
|
+
# Strips all whitespace from the left and right of a string.
|
|
301
|
+
# @liquid_syntax string | strip
|
|
302
|
+
# @liquid_return [string]
|
|
92
303
|
def strip(input)
|
|
93
|
-
input.to_s
|
|
304
|
+
input = Utils.to_s(input)
|
|
305
|
+
input.strip
|
|
94
306
|
end
|
|
95
307
|
|
|
308
|
+
# @liquid_public_docs
|
|
309
|
+
# @liquid_type filter
|
|
310
|
+
# @liquid_category string
|
|
311
|
+
# @liquid_summary
|
|
312
|
+
# Strips all whitespace from the left of a string.
|
|
313
|
+
# @liquid_syntax string | lstrip
|
|
314
|
+
# @liquid_return [string]
|
|
96
315
|
def lstrip(input)
|
|
97
|
-
input.to_s
|
|
316
|
+
input = Utils.to_s(input)
|
|
317
|
+
input.lstrip
|
|
98
318
|
end
|
|
99
319
|
|
|
320
|
+
# @liquid_public_docs
|
|
321
|
+
# @liquid_type filter
|
|
322
|
+
# @liquid_category string
|
|
323
|
+
# @liquid_summary
|
|
324
|
+
# Strips all whitespace from the right of a string.
|
|
325
|
+
# @liquid_syntax string | rstrip
|
|
326
|
+
# @liquid_return [string]
|
|
100
327
|
def rstrip(input)
|
|
101
|
-
input.to_s
|
|
328
|
+
input = Utils.to_s(input)
|
|
329
|
+
input.rstrip
|
|
102
330
|
end
|
|
103
331
|
|
|
332
|
+
# @liquid_public_docs
|
|
333
|
+
# @liquid_type filter
|
|
334
|
+
# @liquid_category string
|
|
335
|
+
# @liquid_summary
|
|
336
|
+
# Strips all HTML tags from a string.
|
|
337
|
+
# @liquid_syntax string | strip_html
|
|
338
|
+
# @liquid_return [string]
|
|
104
339
|
def strip_html(input)
|
|
105
|
-
|
|
106
|
-
|
|
340
|
+
input = Utils.to_s(input)
|
|
341
|
+
empty = ''
|
|
342
|
+
result = input.gsub(STRIP_HTML_BLOCKS, empty)
|
|
343
|
+
result.gsub!(STRIP_HTML_TAGS, empty)
|
|
344
|
+
result
|
|
107
345
|
end
|
|
108
346
|
|
|
109
|
-
#
|
|
347
|
+
# @liquid_public_docs
|
|
348
|
+
# @liquid_type filter
|
|
349
|
+
# @liquid_category string
|
|
350
|
+
# @liquid_summary
|
|
351
|
+
# Strips all newline characters (line breaks) from a string.
|
|
352
|
+
# @liquid_syntax string | strip_newlines
|
|
353
|
+
# @liquid_return [string]
|
|
110
354
|
def strip_newlines(input)
|
|
111
|
-
input.to_s
|
|
355
|
+
input = Utils.to_s(input)
|
|
356
|
+
input.gsub(/\r?\n/, '')
|
|
112
357
|
end
|
|
113
358
|
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
359
|
+
# @liquid_public_docs
|
|
360
|
+
# @liquid_type filter
|
|
361
|
+
# @liquid_category array
|
|
362
|
+
# @liquid_summary
|
|
363
|
+
# Combines all of the items in an array into a single string, separated by a space.
|
|
364
|
+
# @liquid_syntax array | join
|
|
365
|
+
# @liquid_return [string]
|
|
366
|
+
def join(input, glue = ' ')
|
|
367
|
+
glue = Utils.to_s(glue)
|
|
368
|
+
InputIterator.new(input, context).join(glue)
|
|
117
369
|
end
|
|
118
370
|
|
|
119
|
-
#
|
|
120
|
-
#
|
|
371
|
+
# @liquid_public_docs
|
|
372
|
+
# @liquid_type filter
|
|
373
|
+
# @liquid_category array
|
|
374
|
+
# @liquid_summary
|
|
375
|
+
# Sorts the items in an array in case-sensitive alphabetical, or numerical, order.
|
|
376
|
+
# @liquid_syntax array | sort
|
|
377
|
+
# @liquid_return [array[untyped]]
|
|
121
378
|
def sort(input, property = nil)
|
|
122
|
-
ary = InputIterator.new(input)
|
|
379
|
+
ary = InputIterator.new(input, context)
|
|
380
|
+
|
|
381
|
+
return [] if ary.empty?
|
|
382
|
+
|
|
123
383
|
if property.nil?
|
|
124
|
-
ary.sort
|
|
125
|
-
elsif ary.empty? # The next two cases assume a non-empty array.
|
|
126
|
-
[]
|
|
127
|
-
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
|
128
384
|
ary.sort do |a, b|
|
|
129
|
-
a
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
385
|
+
nil_safe_compare(a, b)
|
|
386
|
+
end
|
|
387
|
+
elsif ary.all? { |el| el.respond_to?(:[]) }
|
|
388
|
+
begin
|
|
389
|
+
ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
|
|
390
|
+
rescue TypeError
|
|
391
|
+
raise_property_error(property)
|
|
136
392
|
end
|
|
137
393
|
end
|
|
138
394
|
end
|
|
139
395
|
|
|
140
|
-
#
|
|
141
|
-
#
|
|
396
|
+
# @liquid_public_docs
|
|
397
|
+
# @liquid_type filter
|
|
398
|
+
# @liquid_category array
|
|
399
|
+
# @liquid_summary
|
|
400
|
+
# Sorts the items in an array in case-insensitive alphabetical order.
|
|
401
|
+
# @liquid_description
|
|
402
|
+
# > Caution:
|
|
403
|
+
# > You shouldn't use the `sort_natural` filter to sort numerical values. When comparing items an array, each item is converted to a
|
|
404
|
+
# > string, so sorting on numerical values can lead to unexpected results.
|
|
405
|
+
# @liquid_syntax array | sort_natural
|
|
406
|
+
# @liquid_return [array[untyped]]
|
|
142
407
|
def sort_natural(input, property = nil)
|
|
143
|
-
ary = InputIterator.new(input)
|
|
408
|
+
ary = InputIterator.new(input, context)
|
|
409
|
+
|
|
410
|
+
return [] if ary.empty?
|
|
144
411
|
|
|
145
412
|
if property.nil?
|
|
146
|
-
ary.sort
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
elsif ary.
|
|
150
|
-
|
|
413
|
+
ary.sort do |a, b|
|
|
414
|
+
nil_safe_casecmp(a, b)
|
|
415
|
+
end
|
|
416
|
+
elsif ary.all? { |el| el.respond_to?(:[]) }
|
|
417
|
+
begin
|
|
418
|
+
ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
|
|
419
|
+
rescue TypeError
|
|
420
|
+
raise_property_error(property)
|
|
421
|
+
end
|
|
151
422
|
end
|
|
152
423
|
end
|
|
153
424
|
|
|
154
|
-
#
|
|
155
|
-
#
|
|
425
|
+
# @liquid_public_docs
|
|
426
|
+
# @liquid_type filter
|
|
427
|
+
# @liquid_category array
|
|
428
|
+
# @liquid_summary
|
|
429
|
+
# Filters an array to include only items with a specific property value.
|
|
430
|
+
# @liquid_description
|
|
431
|
+
# This requires you to provide both the property name and the associated value.
|
|
432
|
+
# @liquid_syntax array | where: string, string
|
|
433
|
+
# @liquid_return [array[untyped]]
|
|
434
|
+
def where(input, property, target_value = nil)
|
|
435
|
+
filter_array(input, property, target_value) { |ary, &block| ary.select(&block) }
|
|
436
|
+
end
|
|
437
|
+
|
|
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) }
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# @liquid_public_docs
|
|
491
|
+
# @liquid_type filter
|
|
492
|
+
# @liquid_category array
|
|
493
|
+
# @liquid_summary
|
|
494
|
+
# Removes any duplicate items in an array.
|
|
495
|
+
# @liquid_syntax array | uniq
|
|
496
|
+
# @liquid_return [array[untyped]]
|
|
156
497
|
def uniq(input, property = nil)
|
|
157
|
-
ary = InputIterator.new(input)
|
|
498
|
+
ary = InputIterator.new(input, context)
|
|
158
499
|
|
|
159
500
|
if property.nil?
|
|
160
501
|
ary.uniq
|
|
161
502
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
|
162
503
|
[]
|
|
163
|
-
|
|
164
|
-
ary.uniq
|
|
504
|
+
else
|
|
505
|
+
ary.uniq do |item|
|
|
506
|
+
item[property]
|
|
507
|
+
rescue TypeError
|
|
508
|
+
raise_property_error(property)
|
|
509
|
+
rescue NoMethodError
|
|
510
|
+
return nil unless item.respond_to?(:[])
|
|
511
|
+
raise
|
|
512
|
+
end
|
|
165
513
|
end
|
|
166
514
|
end
|
|
167
515
|
|
|
168
|
-
#
|
|
516
|
+
# @liquid_public_docs
|
|
517
|
+
# @liquid_type filter
|
|
518
|
+
# @liquid_category array
|
|
519
|
+
# @liquid_summary
|
|
520
|
+
# Reverses the order of the items in an array.
|
|
521
|
+
# @liquid_syntax array | reverse
|
|
522
|
+
# @liquid_return [array[untyped]]
|
|
169
523
|
def reverse(input)
|
|
170
|
-
ary = InputIterator.new(input)
|
|
524
|
+
ary = InputIterator.new(input, context)
|
|
171
525
|
ary.reverse
|
|
172
526
|
end
|
|
173
527
|
|
|
174
|
-
#
|
|
528
|
+
# @liquid_public_docs
|
|
529
|
+
# @liquid_type filter
|
|
530
|
+
# @liquid_category array
|
|
531
|
+
# @liquid_summary
|
|
532
|
+
# Creates an array of values from a specific property of the items in an array.
|
|
533
|
+
# @liquid_syntax array | map: string
|
|
534
|
+
# @liquid_return [array[untyped]]
|
|
175
535
|
def map(input, property)
|
|
176
|
-
InputIterator.new(input).map do |e|
|
|
536
|
+
InputIterator.new(input, context).map do |e|
|
|
177
537
|
e = e.call if e.is_a?(Proc)
|
|
178
538
|
|
|
179
|
-
if property == "to_liquid"
|
|
539
|
+
if property == "to_liquid"
|
|
180
540
|
e
|
|
181
541
|
elsif e.respond_to?(:[])
|
|
182
542
|
r = e[property]
|
|
183
543
|
r.is_a?(Proc) ? r.call : r
|
|
184
544
|
end
|
|
185
545
|
end
|
|
546
|
+
rescue TypeError
|
|
547
|
+
raise_property_error(property)
|
|
186
548
|
end
|
|
187
549
|
|
|
188
|
-
#
|
|
189
|
-
#
|
|
550
|
+
# @liquid_public_docs
|
|
551
|
+
# @liquid_type filter
|
|
552
|
+
# @liquid_category array
|
|
553
|
+
# @liquid_summary
|
|
554
|
+
# Removes any `nil` items from an array.
|
|
555
|
+
# @liquid_syntax array | compact
|
|
556
|
+
# @liquid_return [array[untyped]]
|
|
190
557
|
def compact(input, property = nil)
|
|
191
|
-
ary = InputIterator.new(input)
|
|
558
|
+
ary = InputIterator.new(input, context)
|
|
192
559
|
|
|
193
560
|
if property.nil?
|
|
194
561
|
ary.compact
|
|
195
562
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
|
196
563
|
[]
|
|
197
|
-
|
|
198
|
-
ary.reject
|
|
564
|
+
else
|
|
565
|
+
ary.reject do |item|
|
|
566
|
+
item[property].nil?
|
|
567
|
+
rescue TypeError
|
|
568
|
+
raise_property_error(property)
|
|
569
|
+
rescue NoMethodError
|
|
570
|
+
return nil unless item.respond_to?(:[])
|
|
571
|
+
raise
|
|
572
|
+
end
|
|
199
573
|
end
|
|
200
574
|
end
|
|
201
575
|
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
576
|
+
# @liquid_public_docs
|
|
577
|
+
# @liquid_type filter
|
|
578
|
+
# @liquid_category string
|
|
579
|
+
# @liquid_summary
|
|
580
|
+
# Replaces any instance of a substring inside a string with a given string.
|
|
581
|
+
# @liquid_syntax string | replace: string, string
|
|
582
|
+
# @liquid_return [string]
|
|
583
|
+
def replace(input, string, replacement = '')
|
|
584
|
+
string = Utils.to_s(string)
|
|
585
|
+
replacement = Utils.to_s(replacement)
|
|
586
|
+
input = Utils.to_s(input)
|
|
587
|
+
input.gsub(string, replacement)
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# @liquid_public_docs
|
|
591
|
+
# @liquid_type filter
|
|
592
|
+
# @liquid_category string
|
|
593
|
+
# @liquid_summary
|
|
594
|
+
# Replaces the first instance of a substring inside a string with a given string.
|
|
595
|
+
# @liquid_syntax string | replace_first: string, string
|
|
596
|
+
# @liquid_return [string]
|
|
597
|
+
def replace_first(input, string, replacement = '')
|
|
598
|
+
string = Utils.to_s(string)
|
|
599
|
+
replacement = Utils.to_s(replacement)
|
|
600
|
+
input = Utils.to_s(input)
|
|
601
|
+
input.sub(string, replacement)
|
|
205
602
|
end
|
|
206
603
|
|
|
207
|
-
#
|
|
208
|
-
|
|
209
|
-
|
|
604
|
+
# @liquid_public_docs
|
|
605
|
+
# @liquid_type filter
|
|
606
|
+
# @liquid_category string
|
|
607
|
+
# @liquid_summary
|
|
608
|
+
# Replaces the last instance of a substring inside a string with a given string.
|
|
609
|
+
# @liquid_syntax string | replace_last: string, string
|
|
610
|
+
# @liquid_return [string]
|
|
611
|
+
def replace_last(input, string, replacement)
|
|
612
|
+
input = Utils.to_s(input)
|
|
613
|
+
string = Utils.to_s(string)
|
|
614
|
+
replacement = Utils.to_s(replacement)
|
|
615
|
+
|
|
616
|
+
start_index = input.rindex(string)
|
|
617
|
+
|
|
618
|
+
return input unless start_index
|
|
619
|
+
|
|
620
|
+
output = input.dup
|
|
621
|
+
output[start_index, string.length] = replacement
|
|
622
|
+
output
|
|
210
623
|
end
|
|
211
624
|
|
|
212
|
-
#
|
|
625
|
+
# @liquid_public_docs
|
|
626
|
+
# @liquid_type filter
|
|
627
|
+
# @liquid_category string
|
|
628
|
+
# @liquid_summary
|
|
629
|
+
# Removes any instance of a substring inside a string.
|
|
630
|
+
# @liquid_syntax string | remove: string
|
|
631
|
+
# @liquid_return [string]
|
|
213
632
|
def remove(input, string)
|
|
214
|
-
input
|
|
633
|
+
replace(input, string, '')
|
|
215
634
|
end
|
|
216
635
|
|
|
217
|
-
#
|
|
636
|
+
# @liquid_public_docs
|
|
637
|
+
# @liquid_type filter
|
|
638
|
+
# @liquid_category string
|
|
639
|
+
# @liquid_summary
|
|
640
|
+
# Removes the first instance of a substring inside a string.
|
|
641
|
+
# @liquid_syntax string | remove_first: string
|
|
642
|
+
# @liquid_return [string]
|
|
218
643
|
def remove_first(input, string)
|
|
219
|
-
input
|
|
644
|
+
replace_first(input, string, '')
|
|
220
645
|
end
|
|
221
646
|
|
|
222
|
-
#
|
|
647
|
+
# @liquid_public_docs
|
|
648
|
+
# @liquid_type filter
|
|
649
|
+
# @liquid_category string
|
|
650
|
+
# @liquid_summary
|
|
651
|
+
# Removes the last instance of a substring inside a string.
|
|
652
|
+
# @liquid_syntax string | remove_last: string
|
|
653
|
+
# @liquid_return [string]
|
|
654
|
+
def remove_last(input, string)
|
|
655
|
+
replace_last(input, string, '')
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# @liquid_public_docs
|
|
659
|
+
# @liquid_type filter
|
|
660
|
+
# @liquid_category string
|
|
661
|
+
# @liquid_summary
|
|
662
|
+
# Adds a given string to the end of a string.
|
|
663
|
+
# @liquid_syntax string | append: string
|
|
664
|
+
# @liquid_return [string]
|
|
223
665
|
def append(input, string)
|
|
224
|
-
input
|
|
666
|
+
input = Utils.to_s(input)
|
|
667
|
+
string = Utils.to_s(string)
|
|
668
|
+
input + string
|
|
225
669
|
end
|
|
226
670
|
|
|
671
|
+
# @liquid_public_docs
|
|
672
|
+
# @liquid_type filter
|
|
673
|
+
# @liquid_category array
|
|
674
|
+
# @liquid_summary
|
|
675
|
+
# Concatenates (combines) two arrays.
|
|
676
|
+
# @liquid_description
|
|
677
|
+
# > Note:
|
|
678
|
+
# > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
|
|
679
|
+
# > [`uniq` filter](/docs/api/liquid/filters/uniq).
|
|
680
|
+
# @liquid_syntax array | concat: array
|
|
681
|
+
# @liquid_return [array[untyped]]
|
|
227
682
|
def concat(input, array)
|
|
228
683
|
unless array.respond_to?(:to_ary)
|
|
229
|
-
raise ArgumentError
|
|
684
|
+
raise ArgumentError, "concat filter requires an array argument"
|
|
230
685
|
end
|
|
231
|
-
InputIterator.new(input).concat(array)
|
|
686
|
+
InputIterator.new(input, context).concat(array)
|
|
232
687
|
end
|
|
233
688
|
|
|
234
|
-
#
|
|
689
|
+
# @liquid_public_docs
|
|
690
|
+
# @liquid_type filter
|
|
691
|
+
# @liquid_category string
|
|
692
|
+
# @liquid_summary
|
|
693
|
+
# Adds a given string to the beginning of a string.
|
|
694
|
+
# @liquid_syntax string | prepend: string
|
|
695
|
+
# @liquid_return [string]
|
|
235
696
|
def prepend(input, string)
|
|
236
|
-
|
|
697
|
+
input = Utils.to_s(input)
|
|
698
|
+
string = Utils.to_s(string)
|
|
699
|
+
string + input
|
|
237
700
|
end
|
|
238
701
|
|
|
239
|
-
#
|
|
702
|
+
# @liquid_public_docs
|
|
703
|
+
# @liquid_type filter
|
|
704
|
+
# @liquid_category string
|
|
705
|
+
# @liquid_summary
|
|
706
|
+
# Converts newlines (`\n`) in a string to HTML line breaks (`<br>`).
|
|
707
|
+
# @liquid_syntax string | newline_to_br
|
|
708
|
+
# @liquid_return [string]
|
|
240
709
|
def newline_to_br(input)
|
|
241
|
-
input.to_s
|
|
710
|
+
input = Utils.to_s(input)
|
|
711
|
+
input.gsub(/\r?\n/, "<br />\n")
|
|
242
712
|
end
|
|
243
713
|
|
|
244
|
-
#
|
|
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:
|
|
245
724
|
#
|
|
246
725
|
# %a - The abbreviated weekday name (``Sun'')
|
|
247
726
|
# %A - The full weekday name (``Sunday'')
|
|
@@ -270,68 +749,117 @@ module Liquid
|
|
|
270
749
|
# %Y - Year with century
|
|
271
750
|
# %Z - Time zone name
|
|
272
751
|
# %% - Literal ``%'' character
|
|
273
|
-
#
|
|
274
|
-
#
|
|
752
|
+
# @liquid_syntax date | date: string
|
|
753
|
+
# @liquid_return [string]
|
|
275
754
|
def date(input, format)
|
|
276
|
-
|
|
755
|
+
str_format = Utils.to_s(format)
|
|
756
|
+
return input if str_format.empty?
|
|
277
757
|
|
|
278
|
-
return input unless date = Utils.to_date(input)
|
|
758
|
+
return input unless (date = Utils.to_date(input))
|
|
279
759
|
|
|
280
|
-
date.strftime(
|
|
760
|
+
date.strftime(str_format)
|
|
281
761
|
end
|
|
282
762
|
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
763
|
+
# @liquid_public_docs
|
|
764
|
+
# @liquid_type filter
|
|
765
|
+
# @liquid_category array
|
|
766
|
+
# @liquid_summary
|
|
767
|
+
# Returns the first item in an array.
|
|
768
|
+
# @liquid_syntax array | first
|
|
769
|
+
# @liquid_return [untyped]
|
|
288
770
|
def first(array)
|
|
289
771
|
array.first if array.respond_to?(:first)
|
|
290
772
|
end
|
|
291
773
|
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
#
|
|
774
|
+
# @liquid_public_docs
|
|
775
|
+
# @liquid_type filter
|
|
776
|
+
# @liquid_category array
|
|
777
|
+
# @liquid_summary
|
|
778
|
+
# Returns the last item in an array.
|
|
779
|
+
# @liquid_syntax array | last
|
|
780
|
+
# @liquid_return [untyped]
|
|
297
781
|
def last(array)
|
|
298
782
|
array.last if array.respond_to?(:last)
|
|
299
783
|
end
|
|
300
784
|
|
|
301
|
-
#
|
|
785
|
+
# @liquid_public_docs
|
|
786
|
+
# @liquid_type filter
|
|
787
|
+
# @liquid_category math
|
|
788
|
+
# @liquid_summary
|
|
789
|
+
# Returns the absolute value of a number.
|
|
790
|
+
# @liquid_syntax number | abs
|
|
791
|
+
# @liquid_return [number]
|
|
302
792
|
def abs(input)
|
|
303
793
|
result = Utils.to_number(input).abs
|
|
304
794
|
result.is_a?(BigDecimal) ? result.to_f : result
|
|
305
795
|
end
|
|
306
796
|
|
|
307
|
-
#
|
|
797
|
+
# @liquid_public_docs
|
|
798
|
+
# @liquid_type filter
|
|
799
|
+
# @liquid_category math
|
|
800
|
+
# @liquid_summary
|
|
801
|
+
# Adds two numbers.
|
|
802
|
+
# @liquid_syntax number | plus: number
|
|
803
|
+
# @liquid_return [number]
|
|
308
804
|
def plus(input, operand)
|
|
309
805
|
apply_operation(input, operand, :+)
|
|
310
806
|
end
|
|
311
807
|
|
|
312
|
-
#
|
|
808
|
+
# @liquid_public_docs
|
|
809
|
+
# @liquid_type filter
|
|
810
|
+
# @liquid_category math
|
|
811
|
+
# @liquid_summary
|
|
812
|
+
# Subtracts a given number from another number.
|
|
813
|
+
# @liquid_syntax number | minus: number
|
|
814
|
+
# @liquid_return [number]
|
|
313
815
|
def minus(input, operand)
|
|
314
816
|
apply_operation(input, operand, :-)
|
|
315
817
|
end
|
|
316
818
|
|
|
317
|
-
#
|
|
819
|
+
# @liquid_public_docs
|
|
820
|
+
# @liquid_type filter
|
|
821
|
+
# @liquid_category math
|
|
822
|
+
# @liquid_summary
|
|
823
|
+
# Multiplies a number by a given number.
|
|
824
|
+
# @liquid_syntax number | times: number
|
|
825
|
+
# @liquid_return [number]
|
|
318
826
|
def times(input, operand)
|
|
319
827
|
apply_operation(input, operand, :*)
|
|
320
828
|
end
|
|
321
829
|
|
|
322
|
-
#
|
|
830
|
+
# @liquid_public_docs
|
|
831
|
+
# @liquid_type filter
|
|
832
|
+
# @liquid_category math
|
|
833
|
+
# @liquid_summary
|
|
834
|
+
# Divides a number by a given number. The `divided_by` filter produces a result of the same type as the divisor. This means if you divide by an integer, the result will be an integer, and if you divide by a float, the result will be a float.
|
|
835
|
+
# @liquid_syntax number | divided_by: number
|
|
836
|
+
# @liquid_return [number]
|
|
323
837
|
def divided_by(input, operand)
|
|
324
838
|
apply_operation(input, operand, :/)
|
|
325
839
|
rescue ::ZeroDivisionError => e
|
|
326
840
|
raise Liquid::ZeroDivisionError, e.message
|
|
327
841
|
end
|
|
328
842
|
|
|
843
|
+
# @liquid_public_docs
|
|
844
|
+
# @liquid_type filter
|
|
845
|
+
# @liquid_category math
|
|
846
|
+
# @liquid_summary
|
|
847
|
+
# Returns the remainder of dividing a number by a given number.
|
|
848
|
+
# @liquid_syntax number | modulo: number
|
|
849
|
+
# @liquid_return [number]
|
|
329
850
|
def modulo(input, operand)
|
|
330
851
|
apply_operation(input, operand, :%)
|
|
331
852
|
rescue ::ZeroDivisionError => e
|
|
332
853
|
raise Liquid::ZeroDivisionError, e.message
|
|
333
854
|
end
|
|
334
855
|
|
|
856
|
+
# @liquid_public_docs
|
|
857
|
+
# @liquid_type filter
|
|
858
|
+
# @liquid_category math
|
|
859
|
+
# @liquid_summary
|
|
860
|
+
# Rounds a number to the nearest integer.
|
|
861
|
+
# @liquid_syntax number | round
|
|
862
|
+
# @liquid_return [number]
|
|
335
863
|
def round(input, n = 0)
|
|
336
864
|
result = Utils.to_number(input).round(Utils.to_number(n))
|
|
337
865
|
result = result.to_f if result.is_a?(BigDecimal)
|
|
@@ -341,38 +869,172 @@ module Liquid
|
|
|
341
869
|
raise Liquid::FloatDomainError, e.message
|
|
342
870
|
end
|
|
343
871
|
|
|
872
|
+
# @liquid_public_docs
|
|
873
|
+
# @liquid_type filter
|
|
874
|
+
# @liquid_category math
|
|
875
|
+
# @liquid_summary
|
|
876
|
+
# Rounds a number up to the nearest integer.
|
|
877
|
+
# @liquid_syntax number | ceil
|
|
878
|
+
# @liquid_return [number]
|
|
344
879
|
def ceil(input)
|
|
345
880
|
Utils.to_number(input).ceil.to_i
|
|
346
881
|
rescue ::FloatDomainError => e
|
|
347
882
|
raise Liquid::FloatDomainError, e.message
|
|
348
883
|
end
|
|
349
884
|
|
|
885
|
+
# @liquid_public_docs
|
|
886
|
+
# @liquid_type filter
|
|
887
|
+
# @liquid_category math
|
|
888
|
+
# @liquid_summary
|
|
889
|
+
# Rounds a number down to the nearest integer.
|
|
890
|
+
# @liquid_syntax number | floor
|
|
891
|
+
# @liquid_return [number]
|
|
350
892
|
def floor(input)
|
|
351
893
|
Utils.to_number(input).floor.to_i
|
|
352
894
|
rescue ::FloatDomainError => e
|
|
353
895
|
raise Liquid::FloatDomainError, e.message
|
|
354
896
|
end
|
|
355
897
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
898
|
+
# @liquid_public_docs
|
|
899
|
+
# @liquid_type filter
|
|
900
|
+
# @liquid_category math
|
|
901
|
+
# @liquid_summary
|
|
902
|
+
# Limits a number to a minimum value.
|
|
903
|
+
# @liquid_syntax number | at_least
|
|
904
|
+
# @liquid_return [number]
|
|
905
|
+
def at_least(input, n)
|
|
906
|
+
min_value = Utils.to_number(n)
|
|
907
|
+
|
|
908
|
+
result = Utils.to_number(input)
|
|
909
|
+
result = min_value if min_value > result
|
|
910
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# @liquid_public_docs
|
|
914
|
+
# @liquid_type filter
|
|
915
|
+
# @liquid_category math
|
|
916
|
+
# @liquid_summary
|
|
917
|
+
# Limits a number to a maximum value.
|
|
918
|
+
# @liquid_syntax number | at_most
|
|
919
|
+
# @liquid_return [number]
|
|
920
|
+
def at_most(input, n)
|
|
921
|
+
max_value = Utils.to_number(n)
|
|
922
|
+
|
|
923
|
+
result = Utils.to_number(input)
|
|
924
|
+
result = max_value if max_value < result
|
|
925
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
# @liquid_public_docs
|
|
929
|
+
# @liquid_type filter
|
|
930
|
+
# @liquid_category default
|
|
931
|
+
# @liquid_summary
|
|
932
|
+
# Sets a default value for any variable whose value is one of the following:
|
|
933
|
+
#
|
|
934
|
+
# - [`empty`](/docs/api/liquid/basics#empty)
|
|
935
|
+
# - [`false`](/docs/api/liquid/basics#truthy-and-falsy)
|
|
936
|
+
# - [`nil`](/docs/api/liquid/basics#nil)
|
|
937
|
+
# @liquid_syntax variable | default: variable
|
|
938
|
+
# @liquid_return [untyped]
|
|
939
|
+
# @liquid_optional_param allow_false: [boolean] Whether to use false values instead of the default.
|
|
940
|
+
def default(input, default_value = '', options = {})
|
|
941
|
+
options = {} unless options.is_a?(Hash)
|
|
942
|
+
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
|
|
943
|
+
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
# @liquid_public_docs
|
|
947
|
+
# @liquid_type filter
|
|
948
|
+
# @liquid_category array
|
|
949
|
+
# @liquid_summary
|
|
950
|
+
# Returns the sum of all elements in an array.
|
|
951
|
+
# @liquid_syntax array | sum
|
|
952
|
+
# @liquid_return [number]
|
|
953
|
+
def sum(input, property = nil)
|
|
954
|
+
ary = InputIterator.new(input, context)
|
|
955
|
+
return 0 if ary.empty?
|
|
956
|
+
|
|
957
|
+
values_for_sum = ary.map do |item|
|
|
958
|
+
if property.nil?
|
|
959
|
+
item
|
|
960
|
+
elsif item.respond_to?(:[])
|
|
961
|
+
item[property]
|
|
962
|
+
else
|
|
963
|
+
0
|
|
964
|
+
end
|
|
965
|
+
rescue TypeError
|
|
966
|
+
raise_property_error(property)
|
|
361
967
|
end
|
|
968
|
+
|
|
969
|
+
result = InputIterator.new(values_for_sum, context).sum do |item|
|
|
970
|
+
Utils.to_number(item)
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
|
362
974
|
end
|
|
363
975
|
|
|
364
976
|
private
|
|
365
977
|
|
|
978
|
+
attr_reader :context
|
|
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
|
+
|
|
999
|
+
def raise_property_error(property)
|
|
1000
|
+
raise Liquid::ArgumentError, "cannot select the property '#{Utils.to_s(property)}'"
|
|
1001
|
+
end
|
|
1002
|
+
|
|
366
1003
|
def apply_operation(input, operand, operation)
|
|
367
1004
|
result = Utils.to_number(input).send(operation, Utils.to_number(operand))
|
|
368
1005
|
result.is_a?(BigDecimal) ? result.to_f : result
|
|
369
1006
|
end
|
|
370
1007
|
|
|
1008
|
+
def nil_safe_compare(a, b)
|
|
1009
|
+
result = a <=> b
|
|
1010
|
+
|
|
1011
|
+
if result
|
|
1012
|
+
result
|
|
1013
|
+
elsif a.nil?
|
|
1014
|
+
1
|
|
1015
|
+
elsif b.nil?
|
|
1016
|
+
-1
|
|
1017
|
+
else
|
|
1018
|
+
raise Liquid::ArgumentError, "cannot sort values of incompatible types"
|
|
1019
|
+
end
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
def nil_safe_casecmp(a, b)
|
|
1023
|
+
if !a.nil? && !b.nil?
|
|
1024
|
+
a.to_s.casecmp(b.to_s)
|
|
1025
|
+
elsif a.nil? && b.nil?
|
|
1026
|
+
0
|
|
1027
|
+
else
|
|
1028
|
+
a.nil? ? 1 : -1
|
|
1029
|
+
end
|
|
1030
|
+
end
|
|
1031
|
+
|
|
371
1032
|
class InputIterator
|
|
372
1033
|
include Enumerable
|
|
373
1034
|
|
|
374
|
-
def initialize(input)
|
|
375
|
-
@
|
|
1035
|
+
def initialize(input, context)
|
|
1036
|
+
@context = context
|
|
1037
|
+
@input = if input.is_a?(Array)
|
|
376
1038
|
input.flatten
|
|
377
1039
|
elsif input.is_a?(Hash)
|
|
378
1040
|
[input]
|
|
@@ -384,7 +1046,18 @@ module Liquid
|
|
|
384
1046
|
end
|
|
385
1047
|
|
|
386
1048
|
def join(glue)
|
|
387
|
-
|
|
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
|
|
388
1061
|
end
|
|
389
1062
|
|
|
390
1063
|
def concat(args)
|
|
@@ -396,7 +1069,10 @@ module Liquid
|
|
|
396
1069
|
end
|
|
397
1070
|
|
|
398
1071
|
def uniq(&block)
|
|
399
|
-
to_a.uniq
|
|
1072
|
+
to_a.uniq do |item|
|
|
1073
|
+
item = Utils.to_liquid_value(item)
|
|
1074
|
+
block ? yield(item) : item
|
|
1075
|
+
end
|
|
400
1076
|
end
|
|
401
1077
|
|
|
402
1078
|
def compact
|
|
@@ -410,11 +1086,11 @@ module Liquid
|
|
|
410
1086
|
|
|
411
1087
|
def each
|
|
412
1088
|
@input.each do |e|
|
|
413
|
-
|
|
1089
|
+
e = e.respond_to?(:to_liquid) ? e.to_liquid : e
|
|
1090
|
+
e.context = @context if e.respond_to?(:context=)
|
|
1091
|
+
yield(e)
|
|
414
1092
|
end
|
|
415
1093
|
end
|
|
416
1094
|
end
|
|
417
1095
|
end
|
|
418
|
-
|
|
419
|
-
Template.register_filter(StandardFilters)
|
|
420
1096
|
end
|