liquid 5.3.0 → 5.5.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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +20 -1
  3. data/README.md +5 -5
  4. data/lib/liquid/block.rb +8 -4
  5. data/lib/liquid/block_body.rb +21 -6
  6. data/lib/liquid/condition.rb +9 -4
  7. data/lib/liquid/context.rb +13 -5
  8. data/lib/liquid/drop.rb +4 -0
  9. data/lib/liquid/errors.rb +16 -15
  10. data/lib/liquid/expression.rb +4 -1
  11. data/lib/liquid/forloop_drop.rb +45 -5
  12. data/lib/liquid/lexer.rb +2 -3
  13. data/lib/liquid/locales/en.yml +7 -5
  14. data/lib/liquid/partial_cache.rb +15 -6
  15. data/lib/liquid/range_lookup.rb +11 -1
  16. data/lib/liquid/{static_registers.rb → registers.rb} +13 -10
  17. data/lib/liquid/standardfilters.rb +480 -68
  18. data/lib/liquid/strainer_factory.rb +4 -0
  19. data/lib/liquid/strainer_template.rb +4 -0
  20. data/lib/liquid/tablerowloop_drop.rb +58 -1
  21. data/lib/liquid/tag/disabler.rb +0 -8
  22. data/lib/liquid/tag.rb +10 -3
  23. data/lib/liquid/tags/assign.rb +12 -8
  24. data/lib/liquid/tags/break.rb +8 -0
  25. data/lib/liquid/tags/capture.rb +13 -10
  26. data/lib/liquid/tags/case.rb +22 -1
  27. data/lib/liquid/tags/comment.rb +72 -0
  28. data/lib/liquid/tags/continue.rb +8 -9
  29. data/lib/liquid/tags/cycle.rb +12 -11
  30. data/lib/liquid/tags/decrement.rb +22 -20
  31. data/lib/liquid/tags/echo.rb +16 -9
  32. data/lib/liquid/tags/for.rb +25 -46
  33. data/lib/liquid/tags/if.rb +12 -10
  34. data/lib/liquid/tags/include.rb +21 -17
  35. data/lib/liquid/tags/increment.rb +22 -17
  36. data/lib/liquid/tags/inline_comment.rb +30 -0
  37. data/lib/liquid/tags/raw.rb +13 -2
  38. data/lib/liquid/tags/render.rb +37 -8
  39. data/lib/liquid/tags/table_row.rb +33 -3
  40. data/lib/liquid/tags/unless.rb +17 -6
  41. data/lib/liquid/template.rb +11 -4
  42. data/lib/liquid/tokenizer.rb +9 -3
  43. data/lib/liquid/variable.rb +4 -4
  44. data/lib/liquid/variable_lookup.rb +10 -7
  45. data/lib/liquid/version.rb +1 -1
  46. data/lib/liquid.rb +3 -3
  47. metadata +7 -123
  48. data/lib/liquid/register.rb +0 -6
  49. data/test/fixtures/en_locale.yml +0 -9
  50. data/test/integration/assign_test.rb +0 -117
  51. data/test/integration/blank_test.rb +0 -109
  52. data/test/integration/block_test.rb +0 -58
  53. data/test/integration/capture_test.rb +0 -58
  54. data/test/integration/context_test.rb +0 -634
  55. data/test/integration/document_test.rb +0 -21
  56. data/test/integration/drop_test.rb +0 -257
  57. data/test/integration/error_handling_test.rb +0 -272
  58. data/test/integration/expression_test.rb +0 -46
  59. data/test/integration/filter_kwarg_test.rb +0 -24
  60. data/test/integration/filter_test.rb +0 -189
  61. data/test/integration/hash_ordering_test.rb +0 -25
  62. data/test/integration/output_test.rb +0 -125
  63. data/test/integration/parsing_quirks_test.rb +0 -134
  64. data/test/integration/profiler_test.rb +0 -240
  65. data/test/integration/security_test.rb +0 -89
  66. data/test/integration/standard_filter_test.rb +0 -925
  67. data/test/integration/tag/disableable_test.rb +0 -59
  68. data/test/integration/tag_test.rb +0 -45
  69. data/test/integration/tags/break_tag_test.rb +0 -17
  70. data/test/integration/tags/continue_tag_test.rb +0 -17
  71. data/test/integration/tags/echo_test.rb +0 -13
  72. data/test/integration/tags/for_tag_test.rb +0 -466
  73. data/test/integration/tags/if_else_tag_test.rb +0 -190
  74. data/test/integration/tags/include_tag_test.rb +0 -269
  75. data/test/integration/tags/increment_tag_test.rb +0 -25
  76. data/test/integration/tags/liquid_tag_test.rb +0 -116
  77. data/test/integration/tags/raw_tag_test.rb +0 -34
  78. data/test/integration/tags/render_tag_test.rb +0 -213
  79. data/test/integration/tags/standard_tag_test.rb +0 -303
  80. data/test/integration/tags/statements_test.rb +0 -113
  81. data/test/integration/tags/table_row_test.rb +0 -66
  82. data/test/integration/tags/unless_else_tag_test.rb +0 -28
  83. data/test/integration/template_test.rb +0 -340
  84. data/test/integration/trim_mode_test.rb +0 -563
  85. data/test/integration/variable_test.rb +0 -138
  86. data/test/test_helper.rb +0 -207
  87. data/test/unit/block_unit_test.rb +0 -53
  88. data/test/unit/condition_unit_test.rb +0 -181
  89. data/test/unit/file_system_unit_test.rb +0 -37
  90. data/test/unit/i18n_unit_test.rb +0 -39
  91. data/test/unit/lexer_unit_test.rb +0 -53
  92. data/test/unit/parse_tree_visitor_test.rb +0 -261
  93. data/test/unit/parser_unit_test.rb +0 -84
  94. data/test/unit/partial_cache_unit_test.rb +0 -128
  95. data/test/unit/regexp_unit_test.rb +0 -46
  96. data/test/unit/static_registers_unit_test.rb +0 -156
  97. data/test/unit/strainer_factory_unit_test.rb +0 -101
  98. data/test/unit/strainer_template_unit_test.rb +0 -82
  99. data/test/unit/tag_unit_test.rb +0 -23
  100. data/test/unit/tags/case_tag_unit_test.rb +0 -12
  101. data/test/unit/tags/for_tag_unit_test.rb +0 -15
  102. data/test/unit/tags/if_tag_unit_test.rb +0 -10
  103. data/test/unit/template_factory_unit_test.rb +0 -12
  104. data/test/unit/template_unit_test.rb +0 -87
  105. data/test/unit/tokenizer_unit_test.rb +0 -62
  106. data/test/unit/variable_unit_test.rb +0 -164
@@ -6,7 +6,14 @@ require 'bigdecimal'
6
6
 
7
7
  module Liquid
8
8
  module StandardFilters
9
- MAX_INT = (1 << 31) - 1
9
+ MAX_I32 = (1 << 31) - 1
10
+ private_constant :MAX_I32
11
+
12
+ MIN_I64 = -(1 << 63)
13
+ MAX_I64 = (1 << 63) - 1
14
+ I64_RANGE = MIN_I64..MAX_I64
15
+ private_constant :MIN_I64, :MAX_I64, :I64_RANGE
16
+
10
17
  HTML_ESCAPE = {
11
18
  '&' => '&amp;',
12
19
  '>' => '&gt;',
@@ -18,43 +25,116 @@ module Liquid
18
25
  STRIP_HTML_BLOCKS = Regexp.union(
19
26
  %r{<script.*?</script>}m,
20
27
  /<!--.*?-->/m,
21
- %r{<style.*?</style>}m
28
+ %r{<style.*?</style>}m,
22
29
  )
23
30
  STRIP_HTML_TAGS = /<.*?>/m
24
31
 
25
- # Return the size of an array or of an string
32
+ class << self
33
+ def try_coerce_encoding(input, encoding:)
34
+ original_encoding = input.encoding
35
+ if input.encoding != encoding
36
+ input.force_encoding(encoding)
37
+ unless input.valid_encoding?
38
+ input.force_encoding(original_encoding)
39
+ end
40
+ end
41
+ input
42
+ end
43
+ end
44
+
45
+ # @liquid_public_docs
46
+ # @liquid_type filter
47
+ # @liquid_category array
48
+ # @liquid_summary
49
+ # Returns the size of a string or array.
50
+ # @liquid_description
51
+ # The size of a string is the number of characters that the string includes. The size of an array is the number of items
52
+ # in the array.
53
+ # @liquid_syntax variable | size
54
+ # @liquid_return [number]
26
55
  def size(input)
27
56
  input.respond_to?(:size) ? input.size : 0
28
57
  end
29
58
 
30
- # convert an input string to DOWNCASE
59
+ # @liquid_public_docs
60
+ # @liquid_type filter
61
+ # @liquid_category string
62
+ # @liquid_summary
63
+ # Converts a string to all lowercase characters.
64
+ # @liquid_syntax string | downcase
65
+ # @liquid_return [string]
31
66
  def downcase(input)
32
67
  input.to_s.downcase
33
68
  end
34
69
 
35
- # convert an input string to UPCASE
70
+ # @liquid_public_docs
71
+ # @liquid_type filter
72
+ # @liquid_category string
73
+ # @liquid_summary
74
+ # Converts a string to all uppercase characters.
75
+ # @liquid_syntax string | upcase
76
+ # @liquid_return [string]
36
77
  def upcase(input)
37
78
  input.to_s.upcase
38
79
  end
39
80
 
40
- # capitalize words in the input centence
81
+ # @liquid_public_docs
82
+ # @liquid_type filter
83
+ # @liquid_category string
84
+ # @liquid_summary
85
+ # Capitalizes the first word in a string and downcases the remaining characters.
86
+ # @liquid_syntax string | capitalize
87
+ # @liquid_return [string]
41
88
  def capitalize(input)
42
89
  input.to_s.capitalize
43
90
  end
44
91
 
92
+ # @liquid_public_docs
93
+ # @liquid_type filter
94
+ # @liquid_category string
95
+ # @liquid_summary
96
+ # 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.".
97
+ # @liquid_syntax string | escape
98
+ # @liquid_return [string]
45
99
  def escape(input)
46
100
  CGI.escapeHTML(input.to_s) unless input.nil?
47
101
  end
48
102
  alias_method :h, :escape
49
103
 
104
+ # @liquid_public_docs
105
+ # @liquid_type filter
106
+ # @liquid_category string
107
+ # @liquid_summary
108
+ # Escapes a string without changing characters that have already been escaped.
109
+ # @liquid_syntax string | escape_once
110
+ # @liquid_return [string]
50
111
  def escape_once(input)
51
112
  input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
52
113
  end
53
114
 
115
+ # @liquid_public_docs
116
+ # @liquid_type filter
117
+ # @liquid_category string
118
+ # @liquid_summary
119
+ # Converts any URL-unsafe characters in a string to the
120
+ # [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) equivalent.
121
+ # @liquid_description
122
+ # > Note:
123
+ # > Spaces are converted to a `+` character, instead of a percent-encoded character.
124
+ # @liquid_syntax string | url_encode
125
+ # @liquid_return [string]
54
126
  def url_encode(input)
55
127
  CGI.escape(input.to_s) unless input.nil?
56
128
  end
57
129
 
130
+ # @liquid_public_docs
131
+ # @liquid_type filter
132
+ # @liquid_category string
133
+ # @liquid_summary
134
+ # Decodes any [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) characters
135
+ # in a string.
136
+ # @liquid_syntax string | url_decode
137
+ # @liquid_return [string]
58
138
  def url_decode(input)
59
139
  return if input.nil?
60
140
 
@@ -64,38 +144,96 @@ module Liquid
64
144
  result
65
145
  end
66
146
 
147
+ # @liquid_public_docs
148
+ # @liquid_type filter
149
+ # @liquid_category string
150
+ # @liquid_summary
151
+ # Encodes a string to [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
152
+ # @liquid_syntax string | base64_encode
153
+ # @liquid_return [string]
67
154
  def base64_encode(input)
68
155
  Base64.strict_encode64(input.to_s)
69
156
  end
70
157
 
158
+ # @liquid_public_docs
159
+ # @liquid_type filter
160
+ # @liquid_category string
161
+ # @liquid_summary
162
+ # Decodes a string in [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
163
+ # @liquid_syntax string | base64_decode
164
+ # @liquid_return [string]
71
165
  def base64_decode(input)
72
- Base64.strict_decode64(input.to_s)
166
+ input = input.to_s
167
+ StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding)
73
168
  rescue ::ArgumentError
74
169
  raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
75
170
  end
76
171
 
172
+ # @liquid_public_docs
173
+ # @liquid_type filter
174
+ # @liquid_category string
175
+ # @liquid_summary
176
+ # Encodes a string to URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
177
+ # @liquid_syntax string | base64_url_safe_encode
178
+ # @liquid_return [string]
77
179
  def base64_url_safe_encode(input)
78
180
  Base64.urlsafe_encode64(input.to_s)
79
181
  end
80
182
 
183
+ # @liquid_public_docs
184
+ # @liquid_type filter
185
+ # @liquid_category string
186
+ # @liquid_summary
187
+ # Decodes a string in URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
188
+ # @liquid_syntax string | base64_url_safe_decode
189
+ # @liquid_return [string]
81
190
  def base64_url_safe_decode(input)
82
- Base64.urlsafe_decode64(input.to_s)
191
+ input = input.to_s
192
+ StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding)
83
193
  rescue ::ArgumentError
84
194
  raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
85
195
  end
86
196
 
197
+ # @liquid_public_docs
198
+ # @liquid_type filter
199
+ # @liquid_category string
200
+ # @liquid_summary
201
+ # Returns a substring or series of array items, starting at a given 0-based index.
202
+ # @liquid_description
203
+ # By default, the substring has a length of one character, and the array series has one array item. However, you can
204
+ # provide a second parameter to specify the number of characters or array items.
205
+ # @liquid_syntax string | slice
206
+ # @liquid_return [string]
87
207
  def slice(input, offset, length = nil)
88
208
  offset = Utils.to_integer(offset)
89
209
  length = length ? Utils.to_integer(length) : 1
90
210
 
91
- if input.is_a?(Array)
92
- input.slice(offset, length) || []
93
- else
94
- input.to_s.slice(offset, length) || ''
211
+ begin
212
+ if input.is_a?(Array)
213
+ input.slice(offset, length) || []
214
+ else
215
+ input.to_s.slice(offset, length) || ''
216
+ end
217
+ rescue RangeError
218
+ if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
219
+ raise # unexpected error
220
+ end
221
+ offset = offset.clamp(I64_RANGE)
222
+ length = length.clamp(I64_RANGE)
223
+ retry
95
224
  end
96
225
  end
97
226
 
98
- # Truncate a string down to x characters
227
+ # @liquid_public_docs
228
+ # @liquid_type filter
229
+ # @liquid_category string
230
+ # @liquid_summary
231
+ # Truncates a string down to a given number of characters.
232
+ # @liquid_description
233
+ # If the specified number of characters is less than the length of the string, then an ellipsis (`...`) is appended to
234
+ # the truncated string. The ellipsis is included in the character count of the truncated string.
235
+ # @liquid_syntax string | truncate: number
236
+ # @liquid_return [string]
99
237
  def truncate(input, length = 50, truncate_string = "...")
100
238
  return if input.nil?
101
239
  input_str = input.to_s
@@ -109,6 +247,20 @@ module Liquid
109
247
  input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
110
248
  end
111
249
 
250
+ # @liquid_public_docs
251
+ # @liquid_type filter
252
+ # @liquid_category string
253
+ # @liquid_summary
254
+ # Truncates a string down to a given number of words.
255
+ # @liquid_description
256
+ # If the specified number of words is less than the number of words in the string, then an ellipsis (`...`) is appended to
257
+ # the truncated string.
258
+ #
259
+ # > Caution:
260
+ # > HTML tags are treated as words, so you should strip any HTML from truncated content. If you don't strip HTML, then
261
+ # > closing HTML tags can be removed, which can result in unexpected behavior.
262
+ # @liquid_syntax string | truncatewords: number
263
+ # @liquid_return [string]
112
264
  def truncatewords(input, words = 15, truncate_string = "...")
113
265
  return if input.nil?
114
266
  input = input.to_s
@@ -118,9 +270,9 @@ module Liquid
118
270
  wordlist = begin
119
271
  input.split(" ", words + 1)
120
272
  rescue RangeError
121
- raise if words + 1 < MAX_INT
122
- # e.g. integer #{words} too big to convert to `int'
123
- raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
273
+ # integer too big for String#split, but we can semantically assume no truncation is needed
274
+ return input if words + 1 > MAX_I32
275
+ raise # unexpected error
124
276
  end
125
277
  return input if wordlist.length <= words
126
278
 
@@ -128,27 +280,57 @@ module Liquid
128
280
  wordlist.join(" ").concat(truncate_string.to_s)
129
281
  end
130
282
 
131
- # Split input string into an array of substrings separated by given pattern.
132
- #
133
- # Example:
134
- # <div class="summary">{{ post | split '//' | first }}</div>
135
- #
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]]
136
290
  def split(input, pattern)
137
291
  input.to_s.split(pattern.to_s)
138
292
  end
139
293
 
294
+ # @liquid_public_docs
295
+ # @liquid_type filter
296
+ # @liquid_category string
297
+ # @liquid_summary
298
+ # Strips all whitespace from the left and right of a string.
299
+ # @liquid_syntax string | strip
300
+ # @liquid_return [string]
140
301
  def strip(input)
141
302
  input.to_s.strip
142
303
  end
143
304
 
305
+ # @liquid_public_docs
306
+ # @liquid_type filter
307
+ # @liquid_category string
308
+ # @liquid_summary
309
+ # Strips all whitespace from the left of a string.
310
+ # @liquid_syntax string | lstrip
311
+ # @liquid_return [string]
144
312
  def lstrip(input)
145
313
  input.to_s.lstrip
146
314
  end
147
315
 
316
+ # @liquid_public_docs
317
+ # @liquid_type filter
318
+ # @liquid_category string
319
+ # @liquid_summary
320
+ # Strips all whitespace from the right of a string.
321
+ # @liquid_syntax string | rstrip
322
+ # @liquid_return [string]
148
323
  def rstrip(input)
149
324
  input.to_s.rstrip
150
325
  end
151
326
 
327
+ # @liquid_public_docs
328
+ # @liquid_type filter
329
+ # @liquid_category string
330
+ # @liquid_summary
331
+ # Strips all HTML tags from a string.
332
+ # @liquid_syntax string | strip_html
333
+ # @liquid_return [string]
152
334
  def strip_html(input)
153
335
  empty = ''
154
336
  result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
@@ -156,18 +338,35 @@ module Liquid
156
338
  result
157
339
  end
158
340
 
159
- # Remove all newlines from the string
341
+ # @liquid_public_docs
342
+ # @liquid_type filter
343
+ # @liquid_category string
344
+ # @liquid_summary
345
+ # Strips all newline characters (line breaks) from a string.
346
+ # @liquid_syntax string | strip_newlines
347
+ # @liquid_return [string]
160
348
  def strip_newlines(input)
161
349
  input.to_s.gsub(/\r?\n/, '')
162
350
  end
163
351
 
164
- # Join elements of the array with certain character between them
352
+ # @liquid_public_docs
353
+ # @liquid_type filter
354
+ # @liquid_category array
355
+ # @liquid_summary
356
+ # Combines all of the items in an array into a single string, separated by a space.
357
+ # @liquid_syntax array | join
358
+ # @liquid_return [string]
165
359
  def join(input, glue = ' ')
166
360
  InputIterator.new(input, context).join(glue)
167
361
  end
168
362
 
169
- # Sort elements of the array
170
- # provide optional property with which to sort an array of hashes or drops
363
+ # @liquid_public_docs
364
+ # @liquid_type filter
365
+ # @liquid_category array
366
+ # @liquid_summary
367
+ # Sorts the items in an array in case-sensitive alphabetical, or numerical, order.
368
+ # @liquid_syntax array | sort
369
+ # @liquid_return [array[untyped]]
171
370
  def sort(input, property = nil)
172
371
  ary = InputIterator.new(input, context)
173
372
 
@@ -186,8 +385,17 @@ module Liquid
186
385
  end
187
386
  end
188
387
 
189
- # Sort elements of an array ignoring case if strings
190
- # provide optional property with which to sort an array of hashes or drops
388
+ # @liquid_public_docs
389
+ # @liquid_type filter
390
+ # @liquid_category array
391
+ # @liquid_summary
392
+ # Sorts the items in an array in case-insensitive alphabetical order.
393
+ # @liquid_description
394
+ # > Caution:
395
+ # > You shouldn't use the `sort_natural` filter to sort numerical values. When comparing items an array, each item is converted to a
396
+ # > string, so sorting on numerical values can lead to unexpected results.
397
+ # @liquid_syntax array | sort_natural
398
+ # @liquid_return [array[untyped]]
191
399
  def sort_natural(input, property = nil)
192
400
  ary = InputIterator.new(input, context)
193
401
 
@@ -206,8 +414,15 @@ module Liquid
206
414
  end
207
415
  end
208
416
 
209
- # Filter the elements of an array to those with a certain property value.
210
- # By default the target is any truthy value.
417
+ # @liquid_public_docs
418
+ # @liquid_type filter
419
+ # @liquid_category array
420
+ # @liquid_summary
421
+ # Filters an array to include only items with a specific property value.
422
+ # @liquid_description
423
+ # This requires you to provide both the property name and the associated value.
424
+ # @liquid_syntax array | where: string, string
425
+ # @liquid_return [array[untyped]]
211
426
  def where(input, property, target_value = nil)
212
427
  ary = InputIterator.new(input, context)
213
428
 
@@ -234,8 +449,13 @@ module Liquid
234
449
  end
235
450
  end
236
451
 
237
- # Remove duplicate elements from an array
238
- # provide optional property with which to determine uniqueness
452
+ # @liquid_public_docs
453
+ # @liquid_type filter
454
+ # @liquid_category array
455
+ # @liquid_summary
456
+ # Removes any duplicate items in an array.
457
+ # @liquid_syntax array | uniq
458
+ # @liquid_return [array[untyped]]
239
459
  def uniq(input, property = nil)
240
460
  ary = InputIterator.new(input, context)
241
461
 
@@ -255,13 +475,25 @@ module Liquid
255
475
  end
256
476
  end
257
477
 
258
- # Reverse the elements of an array
478
+ # @liquid_public_docs
479
+ # @liquid_type filter
480
+ # @liquid_category array
481
+ # @liquid_summary
482
+ # Reverses the order of the items in an array.
483
+ # @liquid_syntax array | reverse
484
+ # @liquid_return [array[untyped]]
259
485
  def reverse(input)
260
486
  ary = InputIterator.new(input, context)
261
487
  ary.reverse
262
488
  end
263
489
 
264
- # map/collect on a given property
490
+ # @liquid_public_docs
491
+ # @liquid_type filter
492
+ # @liquid_category array
493
+ # @liquid_summary
494
+ # Creates an array of values from a specific property of the items in an array.
495
+ # @liquid_syntax array | map: string
496
+ # @liquid_return [array[untyped]]
265
497
  def map(input, property)
266
498
  InputIterator.new(input, context).map do |e|
267
499
  e = e.call if e.is_a?(Proc)
@@ -277,8 +509,13 @@ module Liquid
277
509
  raise_property_error(property)
278
510
  end
279
511
 
280
- # Remove nils within an array
281
- # provide optional property with which to check for nil
512
+ # @liquid_public_docs
513
+ # @liquid_type filter
514
+ # @liquid_category array
515
+ # @liquid_summary
516
+ # Removes any `nil` items from an array.
517
+ # @liquid_syntax array | compact
518
+ # @liquid_return [array[untyped]]
282
519
  def compact(input, property = nil)
283
520
  ary = InputIterator.new(input, context)
284
521
 
@@ -298,17 +535,35 @@ module Liquid
298
535
  end
299
536
  end
300
537
 
301
- # Replace occurrences of a string with another
538
+ # @liquid_public_docs
539
+ # @liquid_type filter
540
+ # @liquid_category string
541
+ # @liquid_summary
542
+ # Replaces any instance of a substring inside a string with a given string.
543
+ # @liquid_syntax string | replace: string, string
544
+ # @liquid_return [string]
302
545
  def replace(input, string, replacement = '')
303
546
  input.to_s.gsub(string.to_s, replacement.to_s)
304
547
  end
305
548
 
306
- # Replace the first occurrences of a string with another
549
+ # @liquid_public_docs
550
+ # @liquid_type filter
551
+ # @liquid_category string
552
+ # @liquid_summary
553
+ # Replaces the first instance of a substring inside a string with a given string.
554
+ # @liquid_syntax string | replace_first: string, string
555
+ # @liquid_return [string]
307
556
  def replace_first(input, string, replacement = '')
308
557
  input.to_s.sub(string.to_s, replacement.to_s)
309
558
  end
310
559
 
311
- # Replace the last occurrences of a string with another
560
+ # @liquid_public_docs
561
+ # @liquid_type filter
562
+ # @liquid_category string
563
+ # @liquid_summary
564
+ # Replaces the last instance of a substring inside a string with a given string.
565
+ # @liquid_syntax string | replace_last: string, string
566
+ # @liquid_return [string]
312
567
  def replace_last(input, string, replacement)
313
568
  input = input.to_s
314
569
  string = string.to_s
@@ -323,26 +578,61 @@ module Liquid
323
578
  output
324
579
  end
325
580
 
326
- # remove a substring
581
+ # @liquid_public_docs
582
+ # @liquid_type filter
583
+ # @liquid_category string
584
+ # @liquid_summary
585
+ # Removes any instance of a substring inside a string.
586
+ # @liquid_syntax string | remove: string
587
+ # @liquid_return [string]
327
588
  def remove(input, string)
328
589
  replace(input, string, '')
329
590
  end
330
591
 
331
- # remove the first occurrences of a substring
592
+ # @liquid_public_docs
593
+ # @liquid_type filter
594
+ # @liquid_category string
595
+ # @liquid_summary
596
+ # Removes the first instance of a substring inside a string.
597
+ # @liquid_syntax string | remove_first: string
598
+ # @liquid_return [string]
332
599
  def remove_first(input, string)
333
600
  replace_first(input, string, '')
334
601
  end
335
602
 
336
- # remove the last occurences of a substring
603
+ # @liquid_public_docs
604
+ # @liquid_type filter
605
+ # @liquid_category string
606
+ # @liquid_summary
607
+ # Removes the last instance of a substring inside a string.
608
+ # @liquid_syntax string | remove_last: string
609
+ # @liquid_return [string]
337
610
  def remove_last(input, string)
338
611
  replace_last(input, string, '')
339
612
  end
340
613
 
341
- # add one string to another
614
+ # @liquid_public_docs
615
+ # @liquid_type filter
616
+ # @liquid_category string
617
+ # @liquid_summary
618
+ # Adds a given string to the end of a string.
619
+ # @liquid_syntax string | append: string
620
+ # @liquid_return [string]
342
621
  def append(input, string)
343
622
  input.to_s + string.to_s
344
623
  end
345
624
 
625
+ # @liquid_public_docs
626
+ # @liquid_type filter
627
+ # @liquid_category array
628
+ # @liquid_summary
629
+ # Concatenates (combines) two arrays.
630
+ # @liquid_description
631
+ # > Note:
632
+ # > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
633
+ # > [`uniq` filter](/docs/api/liquid/filters/uniq).
634
+ # @liquid_syntax array | concat: array
635
+ # @liquid_return [array[untyped]]
346
636
  def concat(input, array)
347
637
  unless array.respond_to?(:to_ary)
348
638
  raise ArgumentError, "concat filter requires an array argument"
@@ -350,12 +640,24 @@ module Liquid
350
640
  InputIterator.new(input, context).concat(array)
351
641
  end
352
642
 
353
- # prepend a string to another
643
+ # @liquid_public_docs
644
+ # @liquid_type filter
645
+ # @liquid_category string
646
+ # @liquid_summary
647
+ # Adds a given string to the beginning of a string.
648
+ # @liquid_syntax string | prepend: string
649
+ # @liquid_return [string]
354
650
  def prepend(input, string)
355
651
  string.to_s + input.to_s
356
652
  end
357
653
 
358
- # Add <br /> tags in front of all newlines in input string
654
+ # @liquid_public_docs
655
+ # @liquid_type filter
656
+ # @liquid_category string
657
+ # @liquid_summary
658
+ # Converts newlines (`\n`) in a string to HTML line breaks (`<br>`).
659
+ # @liquid_syntax string | newline_to_br
660
+ # @liquid_return [string]
359
661
  def newline_to_br(input)
360
662
  input.to_s.gsub(/\r?\n/, "<br />\n")
361
663
  end
@@ -399,58 +701,106 @@ module Liquid
399
701
  date.strftime(format.to_s)
400
702
  end
401
703
 
402
- # Get the first element of the passed in array
403
- #
404
- # Example:
405
- # {{ product.images | first | to_img }}
406
- #
704
+ # @liquid_public_docs
705
+ # @liquid_type filter
706
+ # @liquid_category array
707
+ # @liquid_summary
708
+ # Returns the first item in an array.
709
+ # @liquid_syntax array | first
710
+ # @liquid_return [untyped]
407
711
  def first(array)
408
712
  array.first if array.respond_to?(:first)
409
713
  end
410
714
 
411
- # Get the last element of the passed in array
412
- #
413
- # Example:
414
- # {{ product.images | last | to_img }}
415
- #
715
+ # @liquid_public_docs
716
+ # @liquid_type filter
717
+ # @liquid_category array
718
+ # @liquid_summary
719
+ # Returns the last item in an array.
720
+ # @liquid_syntax array | last
721
+ # @liquid_return [untyped]
416
722
  def last(array)
417
723
  array.last if array.respond_to?(:last)
418
724
  end
419
725
 
420
- # absolute value
726
+ # @liquid_public_docs
727
+ # @liquid_type filter
728
+ # @liquid_category math
729
+ # @liquid_summary
730
+ # Returns the absolute value of a number.
731
+ # @liquid_syntax number | abs
732
+ # @liquid_return [number]
421
733
  def abs(input)
422
734
  result = Utils.to_number(input).abs
423
735
  result.is_a?(BigDecimal) ? result.to_f : result
424
736
  end
425
737
 
426
- # addition
738
+ # @liquid_public_docs
739
+ # @liquid_type filter
740
+ # @liquid_category math
741
+ # @liquid_summary
742
+ # Adds two numbers.
743
+ # @liquid_syntax number | plus: number
744
+ # @liquid_return [number]
427
745
  def plus(input, operand)
428
746
  apply_operation(input, operand, :+)
429
747
  end
430
748
 
431
- # subtraction
749
+ # @liquid_public_docs
750
+ # @liquid_type filter
751
+ # @liquid_category math
752
+ # @liquid_summary
753
+ # Subtracts a given number from another number.
754
+ # @liquid_syntax number | minus: number
755
+ # @liquid_return [number]
432
756
  def minus(input, operand)
433
757
  apply_operation(input, operand, :-)
434
758
  end
435
759
 
436
- # multiplication
760
+ # @liquid_public_docs
761
+ # @liquid_type filter
762
+ # @liquid_category math
763
+ # @liquid_summary
764
+ # Multiplies a number by a given number.
765
+ # @liquid_syntax number | times: number
766
+ # @liquid_return [number]
437
767
  def times(input, operand)
438
768
  apply_operation(input, operand, :*)
439
769
  end
440
770
 
441
- # division
771
+ # @liquid_public_docs
772
+ # @liquid_type filter
773
+ # @liquid_category math
774
+ # @liquid_summary
775
+ # 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.
776
+ # @liquid_syntax number | divided_by: number
777
+ # @liquid_return [number]
442
778
  def divided_by(input, operand)
443
779
  apply_operation(input, operand, :/)
444
780
  rescue ::ZeroDivisionError => e
445
781
  raise Liquid::ZeroDivisionError, e.message
446
782
  end
447
783
 
784
+ # @liquid_public_docs
785
+ # @liquid_type filter
786
+ # @liquid_category math
787
+ # @liquid_summary
788
+ # Returns the remainder of dividing a number by a given number.
789
+ # @liquid_syntax number | modulo: number
790
+ # @liquid_return [number]
448
791
  def modulo(input, operand)
449
792
  apply_operation(input, operand, :%)
450
793
  rescue ::ZeroDivisionError => e
451
794
  raise Liquid::ZeroDivisionError, e.message
452
795
  end
453
796
 
797
+ # @liquid_public_docs
798
+ # @liquid_type filter
799
+ # @liquid_category math
800
+ # @liquid_summary
801
+ # Rounds a number to the nearest integer.
802
+ # @liquid_syntax number | round
803
+ # @liquid_return [number]
454
804
  def round(input, n = 0)
455
805
  result = Utils.to_number(input).round(Utils.to_number(n))
456
806
  result = result.to_f if result.is_a?(BigDecimal)
@@ -460,18 +810,39 @@ module Liquid
460
810
  raise Liquid::FloatDomainError, e.message
461
811
  end
462
812
 
813
+ # @liquid_public_docs
814
+ # @liquid_type filter
815
+ # @liquid_category math
816
+ # @liquid_summary
817
+ # Rounds a number up to the nearest integer.
818
+ # @liquid_syntax number | ceil
819
+ # @liquid_return [number]
463
820
  def ceil(input)
464
821
  Utils.to_number(input).ceil.to_i
465
822
  rescue ::FloatDomainError => e
466
823
  raise Liquid::FloatDomainError, e.message
467
824
  end
468
825
 
826
+ # @liquid_public_docs
827
+ # @liquid_type filter
828
+ # @liquid_category math
829
+ # @liquid_summary
830
+ # Rounds a number down to the nearest integer.
831
+ # @liquid_syntax number | floor
832
+ # @liquid_return [number]
469
833
  def floor(input)
470
834
  Utils.to_number(input).floor.to_i
471
835
  rescue ::FloatDomainError => e
472
836
  raise Liquid::FloatDomainError, e.message
473
837
  end
474
838
 
839
+ # @liquid_public_docs
840
+ # @liquid_type filter
841
+ # @liquid_category math
842
+ # @liquid_summary
843
+ # Limits a number to a minimum value.
844
+ # @liquid_syntax number | at_least
845
+ # @liquid_return [number]
475
846
  def at_least(input, n)
476
847
  min_value = Utils.to_number(n)
477
848
 
@@ -480,6 +851,13 @@ module Liquid
480
851
  result.is_a?(BigDecimal) ? result.to_f : result
481
852
  end
482
853
 
854
+ # @liquid_public_docs
855
+ # @liquid_type filter
856
+ # @liquid_category math
857
+ # @liquid_summary
858
+ # Limits a number to a maximum value.
859
+ # @liquid_syntax number | at_most
860
+ # @liquid_return [number]
483
861
  def at_most(input, n)
484
862
  max_value = Utils.to_number(n)
485
863
 
@@ -488,22 +866,54 @@ module Liquid
488
866
  result.is_a?(BigDecimal) ? result.to_f : result
489
867
  end
490
868
 
491
- # Set a default value when the input is nil, false or empty
492
- #
493
- # Example:
494
- # {{ product.title | default: "No Title" }}
495
- #
496
- # Use `allow_false` when an input should only be tested against nil or empty and not false.
497
- #
498
- # Example:
499
- # {{ product.title | default: "No Title", allow_false: true }}
869
+ # @liquid_public_docs
870
+ # @liquid_type filter
871
+ # @liquid_category default
872
+ # @liquid_summary
873
+ # Sets a default value for any variable whose value is one of the following:
500
874
  #
875
+ # - [`empty`](/docs/api/liquid/basics#empty)
876
+ # - [`false`](/docs/api/liquid/basics#truthy-and-falsy)
877
+ # - [`nil`](/docs/api/liquid/basics#nil)
878
+ # @liquid_syntax variable | default: variable
879
+ # @liquid_return [untyped]
880
+ # @liquid_optional_param allow_false [boolean] Whether to use false values instead of the default.
501
881
  def default(input, default_value = '', options = {})
502
882
  options = {} unless options.is_a?(Hash)
503
883
  false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
504
884
  false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
505
885
  end
506
886
 
887
+ # @liquid_public_docs
888
+ # @liquid_type filter
889
+ # @liquid_category array
890
+ # @liquid_summary
891
+ # Returns the sum of all elements in an array.
892
+ # @liquid_syntax array | sum
893
+ # @liquid_return [number]
894
+ def sum(input, property = nil)
895
+ ary = InputIterator.new(input, context)
896
+ return 0 if ary.empty?
897
+
898
+ values_for_sum = ary.map do |item|
899
+ if property.nil?
900
+ item
901
+ elsif item.respond_to?(:[])
902
+ item[property]
903
+ else
904
+ 0
905
+ end
906
+ rescue TypeError
907
+ raise_property_error(property)
908
+ end
909
+
910
+ result = InputIterator.new(values_for_sum, context).sum do |item|
911
+ Utils.to_number(item)
912
+ end
913
+
914
+ result.is_a?(BigDecimal) ? result.to_f : result
915
+ end
916
+
507
917
  private
508
918
 
509
919
  attr_reader :context
@@ -534,6 +944,8 @@ module Liquid
534
944
  def nil_safe_casecmp(a, b)
535
945
  if !a.nil? && !b.nil?
536
946
  a.to_s.casecmp(b.to_s)
947
+ elsif a.nil? && b.nil?
948
+ 0
537
949
  else
538
950
  a.nil? ? 1 : -1
539
951
  end