liquid 5.6.4 → 5.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33f5ba5a5f6175dc7ddf6f0f618581fac11cf81b33ed508f174590f2a0911b4a
4
- data.tar.gz: 07e512179364e4c9be0649ada3495a5314e1c57d97e91b697cac098c175bd1c7
3
+ metadata.gz: 72a0f18697a90c81db846fc86027cbec36198105fb458d40989f88b1acd55687
4
+ data.tar.gz: ba8f6ecc9612f737f954006109b91759ea7cea41074de2ab38e73221d4e18be2
5
5
  SHA512:
6
- metadata.gz: 7f54ca814d38f03c384b147776cb645686c61a5962bf9e736220d60bd2b6c0a9e5e853643429349170a1920ce5b1373b3d0c6ff47118afc0568368823e6dc920
7
- data.tar.gz: 46ecbe3e271d8a7083d8d58f360d9476594d434561cbafec829cd1294ceb44c2a623096e084206ef220808974f7a804a92945b0a60290d414ca9651ceb74f20c
6
+ metadata.gz: ad608314f023c78123d3cf57a3c9b54229ec7dca712f1b3897218c27880e5f78d7188cdce775d15759960803a31c5e7d1adc81c9282ab784a6f6fa50efeb051e
7
+ data.tar.gz: 6988a403789ca9a3a3d3d48e5cba0639f81226b3cd8af477dcce1d8eeb9db3693e90b373ea391f5d458a723ec40d7c0c6d28f7e445d3d0562b219e9cbcfff1ee
data/History.md CHANGED
@@ -1,11 +1,53 @@
1
1
  # Liquid Change Log
2
2
 
3
- ## 5.6.0 (unreleased)
3
+ ## 5.8.0 (unreleased)
4
+
5
+ ## 5.7.0 2025-01-16
6
+
7
+ ### Features
8
+ * Add `find`, `find_index`, `has`, and `reject` filters to arrays
9
+ * Compatibility with Ruby 3.4
10
+
11
+ ## 5.6.4 2025-01-14
4
12
 
5
13
  ### Fixes
14
+ * Add a default `string_scanner` to avoid errors with `Liquid::VariableLookup.parse("foo.bar")` [Ian Ker-Seymer]
6
15
 
7
- * Fix Tokenizer to handle null source value (#1873) [Bahar Pourazar]
16
+ ## 5.6.3 2025-01-13
17
+ * Remove `lru_redux` dependency [Michael Go]
18
+
19
+ ## 5.6.2 2025-01-13
20
+
21
+ ### Fixes
22
+ * Preserve the old behavior of requiring floats to start with a digit [Michael Go]
8
23
 
24
+ ## 5.6.1 2025-01-13
25
+
26
+ ### Performance improvements
27
+ * Faster Expression parser / Tokenizer with StringScanner [Michael Go]
28
+
29
+ ## 5.6.0 2024-12-19
30
+
31
+ ### Architectural changes
32
+ * Added new `Environment` class to manage configuration and state that was previously stored in `Template` [Ian Ker-Seymer]
33
+ * Moved tag registration from `Template` to `Environment` [Ian Ker-Seymer]
34
+ * Removed `StrainerFactory` in favor of `Environment`-based strainer creation [Ian Ker-Seymer]
35
+ * Consolidated standard tags into a new `Tags` module with `STANDARD_TAGS` constant [Ian Ker-Seymer]
36
+
37
+ ### Performance improvements
38
+ * Optimized `Lexer` with a new `Lexer2` implementation using jump tables for faster tokenization, requires Ruby 3.4 [Ian Ker-Seymer]
39
+ * Improved variable rendering with specialized handling for different types [Michael Go]
40
+ * Reduced array allocations by using frozen empty constants [Michael Go]
41
+
42
+ ### API changes
43
+ * Deprecated several `Template` class methods in favor of `Environment` methods [Ian Ker-Seymer]
44
+ * Added deprecation warnings system [Ian Ker-Seymer]
45
+ * Changed how filters and tags are registered to use Environment [Ian Ker-Seymer]
46
+
47
+ ### Fixes
48
+ * Fixed table row handling of break interrupts [Alex Coco]
49
+ * Improved variable output handling for arrays [Ian Ker-Seymer]
50
+ * Fix Tokenizer to handle null source value (#1873) [Bahar Pourazar]
9
51
 
10
52
  ## 5.5.0 2024-03-21
11
53
 
@@ -64,7 +64,7 @@ module Liquid
64
64
  # @liquid_syntax string | downcase
65
65
  # @liquid_return [string]
66
66
  def downcase(input)
67
- input.to_s.downcase
67
+ Utils.to_s(input).downcase
68
68
  end
69
69
 
70
70
  # @liquid_public_docs
@@ -75,7 +75,7 @@ module Liquid
75
75
  # @liquid_syntax string | upcase
76
76
  # @liquid_return [string]
77
77
  def upcase(input)
78
- input.to_s.upcase
78
+ Utils.to_s(input).upcase
79
79
  end
80
80
 
81
81
  # @liquid_public_docs
@@ -86,7 +86,7 @@ module Liquid
86
86
  # @liquid_syntax string | capitalize
87
87
  # @liquid_return [string]
88
88
  def capitalize(input)
89
- input.to_s.capitalize
89
+ Utils.to_s(input).capitalize
90
90
  end
91
91
 
92
92
  # @liquid_public_docs
@@ -97,7 +97,7 @@ module Liquid
97
97
  # @liquid_syntax string | escape
98
98
  # @liquid_return [string]
99
99
  def escape(input)
100
- CGI.escapeHTML(input.to_s) unless input.nil?
100
+ CGI.escapeHTML(Utils.to_s(input)) unless input.nil?
101
101
  end
102
102
  alias_method :h, :escape
103
103
 
@@ -109,7 +109,7 @@ module Liquid
109
109
  # @liquid_syntax string | escape_once
110
110
  # @liquid_return [string]
111
111
  def escape_once(input)
112
- input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
112
+ Utils.to_s(input).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
113
113
  end
114
114
 
115
115
  # @liquid_public_docs
@@ -124,7 +124,7 @@ module Liquid
124
124
  # @liquid_syntax string | url_encode
125
125
  # @liquid_return [string]
126
126
  def url_encode(input)
127
- CGI.escape(input.to_s) unless input.nil?
127
+ CGI.escape(Utils.to_s(input)) unless input.nil?
128
128
  end
129
129
 
130
130
  # @liquid_public_docs
@@ -138,7 +138,7 @@ module Liquid
138
138
  def url_decode(input)
139
139
  return if input.nil?
140
140
 
141
- result = CGI.unescape(input.to_s)
141
+ result = CGI.unescape(Utils.to_s(input))
142
142
  raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding?
143
143
 
144
144
  result
@@ -152,7 +152,7 @@ module Liquid
152
152
  # @liquid_syntax string | base64_encode
153
153
  # @liquid_return [string]
154
154
  def base64_encode(input)
155
- Base64.strict_encode64(input.to_s)
155
+ Base64.strict_encode64(Utils.to_s(input))
156
156
  end
157
157
 
158
158
  # @liquid_public_docs
@@ -163,7 +163,7 @@ module Liquid
163
163
  # @liquid_syntax string | base64_decode
164
164
  # @liquid_return [string]
165
165
  def base64_decode(input)
166
- input = input.to_s
166
+ input = Utils.to_s(input)
167
167
  StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding)
168
168
  rescue ::ArgumentError
169
169
  raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
@@ -177,7 +177,7 @@ module Liquid
177
177
  # @liquid_syntax string | base64_url_safe_encode
178
178
  # @liquid_return [string]
179
179
  def base64_url_safe_encode(input)
180
- Base64.urlsafe_encode64(input.to_s)
180
+ Base64.urlsafe_encode64(Utils.to_s(input))
181
181
  end
182
182
 
183
183
  # @liquid_public_docs
@@ -188,7 +188,7 @@ module Liquid
188
188
  # @liquid_syntax string | base64_url_safe_decode
189
189
  # @liquid_return [string]
190
190
  def base64_url_safe_decode(input)
191
- input = input.to_s
191
+ input = Utils.to_s(input)
192
192
  StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding)
193
193
  rescue ::ArgumentError
194
194
  raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
@@ -212,7 +212,7 @@ module Liquid
212
212
  if input.is_a?(Array)
213
213
  input.slice(offset, length) || []
214
214
  else
215
- input.to_s.slice(offset, length) || ''
215
+ Utils.to_s(input).slice(offset, length) || ''
216
216
  end
217
217
  rescue RangeError
218
218
  if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
@@ -236,10 +236,10 @@ module Liquid
236
236
  # @liquid_return [string]
237
237
  def truncate(input, length = 50, truncate_string = "...")
238
238
  return if input.nil?
239
- input_str = input.to_s
239
+ input_str = Utils.to_s(input)
240
240
  length = Utils.to_integer(length)
241
241
 
242
- truncate_string_str = truncate_string.to_s
242
+ truncate_string_str = Utils.to_s(truncate_string)
243
243
 
244
244
  l = length - truncate_string_str.length
245
245
  l = 0 if l < 0
@@ -263,7 +263,7 @@ module Liquid
263
263
  # @liquid_return [string]
264
264
  def truncatewords(input, words = 15, truncate_string = "...")
265
265
  return if input.nil?
266
- input = input.to_s
266
+ input = Utils.to_s(input)
267
267
  words = Utils.to_integer(words)
268
268
  words = 1 if words <= 0
269
269
 
@@ -277,7 +277,8 @@ module Liquid
277
277
  return input if wordlist.length <= words
278
278
 
279
279
  wordlist.pop
280
- wordlist.join(" ").concat(truncate_string.to_s)
280
+ truncate_string = Utils.to_s(truncate_string)
281
+ wordlist.join(" ").concat(truncate_string)
281
282
  end
282
283
 
283
284
  # @liquid_public_docs
@@ -288,7 +289,9 @@ module Liquid
288
289
  # @liquid_syntax string | split: string
289
290
  # @liquid_return [array[string]]
290
291
  def split(input, pattern)
291
- input.to_s.split(pattern.to_s)
292
+ pattern = Utils.to_s(pattern)
293
+ input = Utils.to_s(input)
294
+ input.split(pattern)
292
295
  end
293
296
 
294
297
  # @liquid_public_docs
@@ -299,7 +302,8 @@ module Liquid
299
302
  # @liquid_syntax string | strip
300
303
  # @liquid_return [string]
301
304
  def strip(input)
302
- input.to_s.strip
305
+ input = Utils.to_s(input)
306
+ input.strip
303
307
  end
304
308
 
305
309
  # @liquid_public_docs
@@ -310,7 +314,8 @@ module Liquid
310
314
  # @liquid_syntax string | lstrip
311
315
  # @liquid_return [string]
312
316
  def lstrip(input)
313
- input.to_s.lstrip
317
+ input = Utils.to_s(input)
318
+ input.lstrip
314
319
  end
315
320
 
316
321
  # @liquid_public_docs
@@ -321,7 +326,8 @@ module Liquid
321
326
  # @liquid_syntax string | rstrip
322
327
  # @liquid_return [string]
323
328
  def rstrip(input)
324
- input.to_s.rstrip
329
+ input = Utils.to_s(input)
330
+ input.rstrip
325
331
  end
326
332
 
327
333
  # @liquid_public_docs
@@ -332,8 +338,9 @@ module Liquid
332
338
  # @liquid_syntax string | strip_html
333
339
  # @liquid_return [string]
334
340
  def strip_html(input)
341
+ input = Utils.to_s(input)
335
342
  empty = ''
336
- result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
343
+ result = input.gsub(STRIP_HTML_BLOCKS, empty)
337
344
  result.gsub!(STRIP_HTML_TAGS, empty)
338
345
  result
339
346
  end
@@ -346,7 +353,8 @@ module Liquid
346
353
  # @liquid_syntax string | strip_newlines
347
354
  # @liquid_return [string]
348
355
  def strip_newlines(input)
349
- input.to_s.gsub(/\r?\n/, '')
356
+ input = Utils.to_s(input)
357
+ input.gsub(/\r?\n/, '')
350
358
  end
351
359
 
352
360
  # @liquid_public_docs
@@ -357,6 +365,7 @@ module Liquid
357
365
  # @liquid_syntax array | join
358
366
  # @liquid_return [string]
359
367
  def join(input, glue = ' ')
368
+ glue = Utils.to_s(glue)
360
369
  InputIterator.new(input, context).join(glue)
361
370
  end
362
371
 
@@ -378,7 +387,7 @@ module Liquid
378
387
  end
379
388
  elsif ary.all? { |el| el.respond_to?(:[]) }
380
389
  begin
381
- ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
390
+ ary.sort { |a, b| nil_safe_compare(fetch_property(a, property), fetch_property(b, property)) }
382
391
  rescue TypeError
383
392
  raise_property_error(property)
384
393
  end
@@ -407,7 +416,7 @@ module Liquid
407
416
  end
408
417
  elsif ary.all? { |el| el.respond_to?(:[]) }
409
418
  begin
410
- ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
419
+ ary.sort { |a, b| nil_safe_casecmp(fetch_property(a, property), fetch_property(b, property)) }
411
420
  rescue TypeError
412
421
  raise_property_error(property)
413
422
  end
@@ -424,29 +433,59 @@ module Liquid
424
433
  # @liquid_syntax array | where: string, string
425
434
  # @liquid_return [array[untyped]]
426
435
  def where(input, property, target_value = nil)
427
- ary = InputIterator.new(input, context)
436
+ filter_array(input, property, target_value) { |ary, &block| ary.select(&block) }
437
+ end
428
438
 
429
- if ary.empty?
430
- []
431
- elsif target_value.nil?
432
- ary.select do |item|
433
- item[property]
434
- rescue TypeError
435
- raise_property_error(property)
436
- rescue NoMethodError
437
- return nil unless item.respond_to?(:[])
438
- raise
439
- end
440
- else
441
- ary.select do |item|
442
- item[property] == target_value
443
- rescue TypeError
444
- raise_property_error(property)
445
- rescue NoMethodError
446
- return nil unless item.respond_to?(:[])
447
- raise
448
- end
449
- end
439
+ # @liquid_public_docs
440
+ # @liquid_type filter
441
+ # @liquid_category array
442
+ # @liquid_summary
443
+ # Filters an array to exclude items with a specific property value.
444
+ # @liquid_description
445
+ # This requires you to provide both the property name and the associated value.
446
+ # @liquid_syntax array | reject: string, string
447
+ # @liquid_return [array[untyped]]
448
+ def reject(input, property, target_value = nil)
449
+ filter_array(input, property, target_value) { |ary, &block| ary.reject(&block) }
450
+ end
451
+
452
+ # @liquid_public_docs
453
+ # @liquid_type filter
454
+ # @liquid_category array
455
+ # @liquid_summary
456
+ # Tests if any item in an array has a specific property value.
457
+ # @liquid_description
458
+ # This requires you to provide both the property name and the associated value.
459
+ # @liquid_syntax array | some: string, string
460
+ # @liquid_return [boolean]
461
+ def has(input, property, target_value = nil)
462
+ filter_array(input, property, target_value) { |ary, &block| ary.any?(&block) }
463
+ end
464
+
465
+ # @liquid_public_docs
466
+ # @liquid_type filter
467
+ # @liquid_category array
468
+ # @liquid_summary
469
+ # Returns the first item in an array with a specific property value.
470
+ # @liquid_description
471
+ # This requires you to provide both the property name and the associated value.
472
+ # @liquid_syntax array | find: string, string
473
+ # @liquid_return [untyped]
474
+ def find(input, property, target_value = nil)
475
+ filter_array(input, property, target_value) { |ary, &block| ary.find(&block) }
476
+ end
477
+
478
+ # @liquid_public_docs
479
+ # @liquid_type filter
480
+ # @liquid_category array
481
+ # @liquid_summary
482
+ # Returns the index of the first item in an array with a specific property value.
483
+ # @liquid_description
484
+ # This requires you to provide both the property name and the associated value.
485
+ # @liquid_syntax array | find_index: string, string
486
+ # @liquid_return [number]
487
+ def find_index(input, property, target_value = nil)
488
+ filter_array(input, property, target_value) { |ary, &block| ary.find_index(&block) }
450
489
  end
451
490
 
452
491
  # @liquid_public_docs
@@ -465,7 +504,7 @@ module Liquid
465
504
  []
466
505
  else
467
506
  ary.uniq do |item|
468
- item[property]
507
+ fetch_property(item, property)
469
508
  rescue TypeError
470
509
  raise_property_error(property)
471
510
  rescue NoMethodError
@@ -501,7 +540,7 @@ module Liquid
501
540
  if property == "to_liquid"
502
541
  e
503
542
  elsif e.respond_to?(:[])
504
- r = e[property]
543
+ r = fetch_property(e, property)
505
544
  r.is_a?(Proc) ? r.call : r
506
545
  end
507
546
  end
@@ -525,7 +564,7 @@ module Liquid
525
564
  []
526
565
  else
527
566
  ary.reject do |item|
528
- item[property].nil?
567
+ fetch_property(item, property).nil?
529
568
  rescue TypeError
530
569
  raise_property_error(property)
531
570
  rescue NoMethodError
@@ -543,7 +582,10 @@ module Liquid
543
582
  # @liquid_syntax string | replace: string, string
544
583
  # @liquid_return [string]
545
584
  def replace(input, string, replacement = '')
546
- input.to_s.gsub(string.to_s, replacement.to_s)
585
+ string = Utils.to_s(string)
586
+ replacement = Utils.to_s(replacement)
587
+ input = Utils.to_s(input)
588
+ input.gsub(string, replacement)
547
589
  end
548
590
 
549
591
  # @liquid_public_docs
@@ -554,7 +596,10 @@ module Liquid
554
596
  # @liquid_syntax string | replace_first: string, string
555
597
  # @liquid_return [string]
556
598
  def replace_first(input, string, replacement = '')
557
- input.to_s.sub(string.to_s, replacement.to_s)
599
+ string = Utils.to_s(string)
600
+ replacement = Utils.to_s(replacement)
601
+ input = Utils.to_s(input)
602
+ input.sub(string, replacement)
558
603
  end
559
604
 
560
605
  # @liquid_public_docs
@@ -565,9 +610,9 @@ module Liquid
565
610
  # @liquid_syntax string | replace_last: string, string
566
611
  # @liquid_return [string]
567
612
  def replace_last(input, string, replacement)
568
- input = input.to_s
569
- string = string.to_s
570
- replacement = replacement.to_s
613
+ input = Utils.to_s(input)
614
+ string = Utils.to_s(string)
615
+ replacement = Utils.to_s(replacement)
571
616
 
572
617
  start_index = input.rindex(string)
573
618
 
@@ -619,7 +664,9 @@ module Liquid
619
664
  # @liquid_syntax string | append: string
620
665
  # @liquid_return [string]
621
666
  def append(input, string)
622
- input.to_s + string.to_s
667
+ input = Utils.to_s(input)
668
+ string = Utils.to_s(string)
669
+ input + string
623
670
  end
624
671
 
625
672
  # @liquid_public_docs
@@ -648,7 +695,9 @@ module Liquid
648
695
  # @liquid_syntax string | prepend: string
649
696
  # @liquid_return [string]
650
697
  def prepend(input, string)
651
- string.to_s + input.to_s
698
+ input = Utils.to_s(input)
699
+ string = Utils.to_s(string)
700
+ string + input
652
701
  end
653
702
 
654
703
  # @liquid_public_docs
@@ -659,7 +708,8 @@ module Liquid
659
708
  # @liquid_syntax string | newline_to_br
660
709
  # @liquid_return [string]
661
710
  def newline_to_br(input)
662
- input.to_s.gsub(/\r?\n/, "<br />\n")
711
+ input = Utils.to_s(input)
712
+ input.gsub(/\r?\n/, "<br />\n")
663
713
  end
664
714
 
665
715
  # Reformat a date using Ruby's core Time#strftime( string ) -> string
@@ -694,11 +744,12 @@ module Liquid
694
744
  #
695
745
  # See also: http://www.ruby-doc.org/core/Time.html#method-i-strftime
696
746
  def date(input, format)
697
- return input if format.to_s.empty?
747
+ str_format = Utils.to_s(format)
748
+ return input if str_format.empty?
698
749
 
699
750
  return input unless (date = Utils.to_date(input))
700
751
 
701
- date.strftime(format.to_s)
752
+ date.strftime(str_format)
702
753
  end
703
754
 
704
755
  # @liquid_public_docs
@@ -899,7 +950,7 @@ module Liquid
899
950
  if property.nil?
900
951
  item
901
952
  elsif item.respond_to?(:[])
902
- item[property]
953
+ fetch_property(item, property)
903
954
  else
904
955
  0
905
956
  end
@@ -918,6 +969,50 @@ module Liquid
918
969
 
919
970
  attr_reader :context
920
971
 
972
+ def filter_array(input, property, target_value, &block)
973
+ ary = InputIterator.new(input, context)
974
+
975
+ return [] if ary.empty?
976
+
977
+ block.call(ary) do |item|
978
+ if target_value.nil?
979
+ fetch_property(item, property)
980
+ else
981
+ fetch_property(item, property) == target_value
982
+ end
983
+ rescue TypeError
984
+ raise_property_error(property)
985
+ rescue NoMethodError
986
+ return nil unless item.respond_to?(:[])
987
+ raise
988
+ end
989
+ end
990
+
991
+ def fetch_property(drop, property_or_keys)
992
+ ##
993
+ # This keeps backward compatibility by supporting properties containing
994
+ # dots. This is valid in Liquid syntax and used in some runtimes, such as
995
+ # Shopify with metafields.
996
+ #
997
+ # Using this approach, properties like 'price.value' can be accessed in
998
+ # both of the following examples:
999
+ #
1000
+ # ```
1001
+ # [
1002
+ # { 'name' => 'Item 1', 'price.price' => 40000 },
1003
+ # { 'name' => 'Item 2', 'price' => { 'value' => 39900 } }
1004
+ # ]
1005
+ # ```
1006
+ value = drop[property_or_keys]
1007
+
1008
+ return value if !value.nil? || !property_or_keys.is_a?(String)
1009
+
1010
+ keys = property_or_keys.split('.')
1011
+ keys.reduce(drop) do |drop, key|
1012
+ drop.respond_to?(:[]) ? drop[key] : drop
1013
+ end
1014
+ end
1015
+
921
1016
  def raise_property_error(property)
922
1017
  raise Liquid::ArgumentError, "cannot select the property '#{property}'"
923
1018
  end
@@ -968,7 +1063,18 @@ module Liquid
968
1063
  end
969
1064
 
970
1065
  def join(glue)
971
- to_a.join(glue.to_s)
1066
+ first = true
1067
+ output = +""
1068
+ each do |item|
1069
+ if first
1070
+ first = false
1071
+ else
1072
+ output << glue
1073
+ end
1074
+
1075
+ output << Liquid::Utils.to_s(item)
1076
+ end
1077
+ output
972
1078
  end
973
1079
 
974
1080
  def concat(args)
data/lib/liquid/utils.rb CHANGED
@@ -89,5 +89,101 @@ module Liquid
89
89
  # Otherwise return the object itself
90
90
  obj
91
91
  end
92
+
93
+ def self.to_s(obj, seen = {})
94
+ case obj
95
+ when Hash
96
+ # If the custom hash implementation overrides `#to_s`, use their
97
+ # custom implementation. Otherwise we use Liquid's default
98
+ # implementation.
99
+ if obj.class.instance_method(:to_s) == HASH_TO_S_METHOD
100
+ hash_inspect(obj, seen)
101
+ else
102
+ obj.to_s
103
+ end
104
+ when Array
105
+ array_inspect(obj, seen)
106
+ else
107
+ obj.to_s
108
+ end
109
+ end
110
+
111
+ def self.inspect(obj, seen = {})
112
+ case obj
113
+ when Hash
114
+ # If the custom hash implementation overrides `#inspect`, use their
115
+ # custom implementation. Otherwise we use Liquid's default
116
+ # implementation.
117
+ if obj.class.instance_method(:inspect) == HASH_INSPECT_METHOD
118
+ hash_inspect(obj, seen)
119
+ else
120
+ obj.inspect
121
+ end
122
+ when Array
123
+ array_inspect(obj, seen)
124
+ else
125
+ obj.inspect
126
+ end
127
+ end
128
+
129
+ def self.array_inspect(arr, seen = {})
130
+ if seen[arr.object_id]
131
+ return "[...]"
132
+ end
133
+
134
+ seen[arr.object_id] = true
135
+ str = +"["
136
+ cursor = 0
137
+ len = arr.length
138
+
139
+ while cursor < len
140
+ if cursor > 0
141
+ str << ", "
142
+ end
143
+
144
+ item_str = inspect(arr[cursor], seen)
145
+ str << item_str
146
+ cursor += 1
147
+ end
148
+
149
+ str << "]"
150
+ str
151
+ ensure
152
+ seen.delete(arr.object_id)
153
+ end
154
+
155
+ def self.hash_inspect(hash, seen = {})
156
+ if seen[hash.object_id]
157
+ return "{...}"
158
+ end
159
+ seen[hash.object_id] = true
160
+
161
+ str = +"{"
162
+ first = true
163
+ hash.each do |key, value|
164
+ if first
165
+ first = false
166
+ else
167
+ str << ", "
168
+ end
169
+
170
+ key_str = inspect(key, seen)
171
+ str << key_str
172
+ str << "=>"
173
+
174
+ value_str = inspect(value, seen)
175
+ str << value_str
176
+ end
177
+ str << "}"
178
+ str
179
+ ensure
180
+ seen.delete(hash.object_id)
181
+ end
182
+
183
+ HASH_TO_S_METHOD = Hash.instance_method(:to_s)
184
+ private_constant :HASH_TO_S_METHOD
185
+
186
+ HASH_INSPECT_METHOD = Hash.instance_method(:inspect)
187
+ private_constant :HASH_INSPECT_METHOD
92
188
  end
93
189
  end
@@ -107,8 +107,8 @@ module Liquid
107
107
  obj.each do |o|
108
108
  render_obj_to_output(o, output)
109
109
  end
110
- when
111
- output << obj.to_s
110
+ else
111
+ output << Liquid::Utils.to_s(obj)
112
112
  end
113
113
  end
114
114
 
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Liquid
5
- VERSION = "5.6.4"
5
+ VERSION = "5.7.0"
6
6
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.4
4
+ version: 5.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Lütke
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-01-14 00:00:00.000000000 Z
10
+ date: 2025-01-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: strscan