liquid 5.3.0 → 5.5.0

Sign up to get free protection for your applications and to get access to all the features.
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