activesupport 7.0.8 → 7.1.0.beta1

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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +736 -293
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/active_support/actionable_error.rb +3 -1
  6. data/lib/active_support/array_inquirer.rb +2 -0
  7. data/lib/active_support/backtrace_cleaner.rb +25 -5
  8. data/lib/active_support/benchmarkable.rb +1 -0
  9. data/lib/active_support/builder.rb +1 -1
  10. data/lib/active_support/cache/coder.rb +153 -0
  11. data/lib/active_support/cache/entry.rb +128 -0
  12. data/lib/active_support/cache/file_store.rb +36 -9
  13. data/lib/active_support/cache/mem_cache_store.rb +84 -68
  14. data/lib/active_support/cache/memory_store.rb +76 -24
  15. data/lib/active_support/cache/null_store.rb +6 -0
  16. data/lib/active_support/cache/redis_cache_store.rb +126 -131
  17. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  18. data/lib/active_support/cache/strategy/local_cache.rb +20 -8
  19. data/lib/active_support/cache.rb +304 -246
  20. data/lib/active_support/callbacks.rb +38 -18
  21. data/lib/active_support/concern.rb +4 -2
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
  23. data/lib/active_support/concurrency/null_lock.rb +13 -0
  24. data/lib/active_support/configurable.rb +10 -0
  25. data/lib/active_support/core_ext/array/conversions.rb +2 -1
  26. data/lib/active_support/core_ext/array.rb +0 -1
  27. data/lib/active_support/core_ext/class/subclasses.rb +13 -10
  28. data/lib/active_support/core_ext/date/conversions.rb +1 -0
  29. data/lib/active_support/core_ext/date.rb +0 -1
  30. data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
  31. data/lib/active_support/core_ext/date_time/conversions.rb +6 -2
  32. data/lib/active_support/core_ext/date_time.rb +0 -1
  33. data/lib/active_support/core_ext/digest/uuid.rb +1 -10
  34. data/lib/active_support/core_ext/enumerable.rb +3 -75
  35. data/lib/active_support/core_ext/erb/util.rb +196 -0
  36. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  37. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  38. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
  39. data/lib/active_support/core_ext/module/delegation.rb +40 -11
  40. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  41. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  42. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  43. data/lib/active_support/core_ext/numeric/conversions.rb +2 -0
  44. data/lib/active_support/core_ext/numeric.rb +0 -1
  45. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  46. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  47. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  48. data/lib/active_support/core_ext/object/json.rb +10 -2
  49. data/lib/active_support/core_ext/object/with.rb +44 -0
  50. data/lib/active_support/core_ext/object/with_options.rb +3 -3
  51. data/lib/active_support/core_ext/object.rb +1 -0
  52. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  53. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  54. data/lib/active_support/core_ext/pathname.rb +1 -0
  55. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  56. data/lib/active_support/core_ext/range/{overlaps.rb → overlap.rb} +5 -3
  57. data/lib/active_support/core_ext/range.rb +1 -2
  58. data/lib/active_support/core_ext/securerandom.rb +24 -12
  59. data/lib/active_support/core_ext/string/filters.rb +20 -14
  60. data/lib/active_support/core_ext/string/inflections.rb +16 -5
  61. data/lib/active_support/core_ext/string/output_safety.rb +38 -174
  62. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  63. data/lib/active_support/core_ext/time/calculations.rb +18 -2
  64. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  65. data/lib/active_support/core_ext/time/zones.rb +4 -4
  66. data/lib/active_support/core_ext/time.rb +0 -1
  67. data/lib/active_support/current_attributes.rb +15 -6
  68. data/lib/active_support/dependencies/autoload.rb +17 -12
  69. data/lib/active_support/deprecation/behaviors.rb +53 -32
  70. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  71. data/lib/active_support/deprecation/deprecators.rb +104 -0
  72. data/lib/active_support/deprecation/disallowed.rb +3 -5
  73. data/lib/active_support/deprecation/instance_delegator.rb +31 -4
  74. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  75. data/lib/active_support/deprecation/proxy_wrappers.rb +37 -22
  76. data/lib/active_support/deprecation/reporting.rb +35 -21
  77. data/lib/active_support/deprecation.rb +32 -5
  78. data/lib/active_support/deprecator.rb +7 -0
  79. data/lib/active_support/descendants_tracker.rb +104 -132
  80. data/lib/active_support/duration/iso8601_serializer.rb +0 -2
  81. data/lib/active_support/duration.rb +2 -1
  82. data/lib/active_support/encrypted_configuration.rb +30 -9
  83. data/lib/active_support/encrypted_file.rb +8 -3
  84. data/lib/active_support/environment_inquirer.rb +22 -2
  85. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  86. data/lib/active_support/error_reporter.rb +121 -35
  87. data/lib/active_support/execution_wrapper.rb +4 -4
  88. data/lib/active_support/file_update_checker.rb +4 -2
  89. data/lib/active_support/fork_tracker.rb +10 -2
  90. data/lib/active_support/gem_version.rb +4 -4
  91. data/lib/active_support/gzip.rb +2 -0
  92. data/lib/active_support/hash_with_indifferent_access.rb +35 -17
  93. data/lib/active_support/i18n.rb +1 -1
  94. data/lib/active_support/i18n_railtie.rb +20 -13
  95. data/lib/active_support/inflector/inflections.rb +2 -0
  96. data/lib/active_support/inflector/methods.rb +22 -10
  97. data/lib/active_support/inflector/transliterate.rb +3 -1
  98. data/lib/active_support/isolated_execution_state.rb +26 -22
  99. data/lib/active_support/json/decoding.rb +2 -1
  100. data/lib/active_support/json/encoding.rb +25 -43
  101. data/lib/active_support/key_generator.rb +9 -1
  102. data/lib/active_support/lazy_load_hooks.rb +6 -4
  103. data/lib/active_support/locale/en.yml +2 -0
  104. data/lib/active_support/log_subscriber.rb +78 -33
  105. data/lib/active_support/logger.rb +1 -1
  106. data/lib/active_support/logger_thread_safe_level.rb +9 -21
  107. data/lib/active_support/message_encryptor.rb +197 -53
  108. data/lib/active_support/message_encryptors.rb +140 -0
  109. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  110. data/lib/active_support/message_pack/extensions.rb +292 -0
  111. data/lib/active_support/message_pack/serializer.rb +63 -0
  112. data/lib/active_support/message_pack.rb +50 -0
  113. data/lib/active_support/message_verifier.rb +212 -93
  114. data/lib/active_support/message_verifiers.rb +134 -0
  115. data/lib/active_support/messages/codec.rb +65 -0
  116. data/lib/active_support/messages/metadata.rb +111 -45
  117. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  118. data/lib/active_support/messages/rotator.rb +34 -32
  119. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  120. data/lib/active_support/multibyte/chars.rb +2 -0
  121. data/lib/active_support/multibyte/unicode.rb +9 -37
  122. data/lib/active_support/notifications/fanout.rb +239 -81
  123. data/lib/active_support/notifications/instrumenter.rb +71 -14
  124. data/lib/active_support/notifications.rb +1 -1
  125. data/lib/active_support/number_helper/number_converter.rb +2 -2
  126. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  127. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  128. data/lib/active_support/ordered_hash.rb +3 -3
  129. data/lib/active_support/ordered_options.rb +14 -0
  130. data/lib/active_support/parameter_filter.rb +84 -69
  131. data/lib/active_support/proxy_object.rb +2 -0
  132. data/lib/active_support/railtie.rb +33 -21
  133. data/lib/active_support/reloader.rb +12 -4
  134. data/lib/active_support/rescuable.rb +2 -0
  135. data/lib/active_support/secure_compare_rotator.rb +16 -9
  136. data/lib/active_support/string_inquirer.rb +3 -1
  137. data/lib/active_support/subscriber.rb +9 -27
  138. data/lib/active_support/syntax_error_proxy.rb +49 -0
  139. data/lib/active_support/tagged_logging.rb +60 -24
  140. data/lib/active_support/test_case.rb +153 -6
  141. data/lib/active_support/testing/assertions.rb +25 -9
  142. data/lib/active_support/testing/autorun.rb +0 -2
  143. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  144. data/lib/active_support/testing/deprecation.rb +25 -25
  145. data/lib/active_support/testing/error_reporter_assertions.rb +108 -0
  146. data/lib/active_support/testing/isolation.rb +1 -1
  147. data/lib/active_support/testing/method_call_assertions.rb +21 -8
  148. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  149. data/lib/active_support/testing/stream.rb +1 -1
  150. data/lib/active_support/testing/strict_warnings.rb +38 -0
  151. data/lib/active_support/testing/time_helpers.rb +32 -14
  152. data/lib/active_support/time_with_zone.rb +4 -14
  153. data/lib/active_support/values/time_zone.rb +9 -7
  154. data/lib/active_support/version.rb +1 -1
  155. data/lib/active_support/xml_mini/jdom.rb +3 -10
  156. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  157. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  158. data/lib/active_support/xml_mini/rexml.rb +1 -1
  159. data/lib/active_support/xml_mini.rb +2 -2
  160. data/lib/active_support.rb +13 -3
  161. metadata +102 -17
  162. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  163. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
  164. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
  165. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  166. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
  167. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
  168. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
  169. data/lib/active_support/core_ext/uri.rb +0 -5
  170. data/lib/active_support/per_thread_registry.rb +0 -65
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Object
4
+ # Set and restore public attributes around a block.
5
+ #
6
+ # client.timeout # => 5
7
+ # client.with(timeout: 1) do
8
+ # client.timeout # => 1
9
+ # end
10
+ # client.timeout # => 5
11
+ #
12
+ # This method is a shorthand for the common begin/ensure pattern:
13
+ #
14
+ # old_value = object.attribute
15
+ # begin
16
+ # object.attribute = new_value
17
+ # # do things
18
+ # ensure
19
+ # object.attribute = old_value
20
+ # end
21
+ #
22
+ # It can be used on any object as long as both the reader and writer methods
23
+ # are public.
24
+ def with(**attributes)
25
+ old_values = {}
26
+ begin
27
+ attributes.each do |key, value|
28
+ old_values[key] = public_send(key)
29
+ public_send("#{key}=", value)
30
+ end
31
+ yield
32
+ ensure
33
+ old_values.each do |key, old_value|
34
+ public_send("#{key}=", old_value)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # #with isn't usable on immediates, so we might as well undefine the
41
+ # method in common immediate classes to avoid potential confusion.
42
+ [NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass|
43
+ klass.undef_method(:with)
44
+ end
@@ -5,9 +5,9 @@ require "active_support/option_merger"
5
5
  class Object
6
6
  # An elegant way to factor duplication out of options passed to a series of
7
7
  # method calls. Each method called in the block, with the block variable as
8
- # the receiver, will have its options merged with the default +options+ hash
9
- # provided. Each method called on the block variable must take an options
10
- # hash as its final argument.
8
+ # the receiver, will have its options merged with the default +options+
9
+ # <tt>Hash</tt> or <tt>Hash</tt>-like object provided. Each method called on
10
+ # the block variable must take an options hash as its final argument.
11
11
  #
12
12
  # Without <tt>with_options</tt>, this code contains duplication:
13
13
  #
@@ -13,4 +13,5 @@ require "active_support/core_ext/object/instance_variables"
13
13
  require "active_support/core_ext/object/json"
14
14
  require "active_support/core_ext/object/to_param"
15
15
  require "active_support/core_ext/object/to_query"
16
+ require "active_support/core_ext/object/with"
16
17
  require "active_support/core_ext/object/with_options"
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ class Pathname
6
+ # An Pathname is blank if it's empty:
7
+ #
8
+ # Pathname.new("").blank? # => true
9
+ # Pathname.new(" ").blank? # => false
10
+ # Pathname.new("test").blank? # => false
11
+ #
12
+ # @return [true, false]
13
+ def blank?
14
+ to_s.empty?
15
+ end
16
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
4
+
3
5
  class Pathname
4
6
  # Returns the receiver if the named file exists otherwise returns +nil+.
5
7
  # <tt>pathname.existence</tt> is equivalent to
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/pathname/blank"
3
4
  require "active_support/core_ext/pathname/existence"
@@ -1,13 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
+ # = \Range With Format
4
5
  module RangeWithFormat
5
6
  RANGE_FORMATS = {
6
7
  db: -> (start, stop) do
7
- case start
8
- when String then "BETWEEN '#{start}' AND '#{stop}'"
9
- else
10
- "BETWEEN '#{start.to_fs(:db)}' AND '#{stop.to_fs(:db)}'"
8
+ if start && stop
9
+ case start
10
+ when String then "BETWEEN '#{start}' AND '#{stop}'"
11
+ else
12
+ "BETWEEN '#{start.to_fs(:db)}' AND '#{stop.to_fs(:db)}'"
13
+ end
14
+ elsif start
15
+ case start
16
+ when String then ">= '#{start}'"
17
+ else
18
+ ">= '#{start.to_fs(:db)}'"
19
+ end
20
+ elsif stop
21
+ case stop
22
+ when String then "<= '#{stop}'"
23
+ else
24
+ "<= '#{stop.to_fs(:db)}'"
25
+ end
11
26
  end
12
27
  end
13
28
  }
@@ -19,9 +34,15 @@ module ActiveSupport
19
34
  # range = (1..100) # => 1..100
20
35
  #
21
36
  # range.to_s # => "1..100"
22
- # range.to_fs(:db) # => "BETWEEN '1' AND '100'"
37
+ # range.to_fs(:db) # => "BETWEEN '1' AND '100'"
23
38
  #
24
- # == Adding your own range formats to to_s
39
+ # range = (1..) # => 1..
40
+ # range.to_fs(:db) # => ">= '1'"
41
+ #
42
+ # range = (..100) # => ..100
43
+ # range.to_fs(:db) # => "<= '100'"
44
+ #
45
+ # == Adding your own range formats to to_fs
25
46
  # You can add your own formats to the Range::RANGE_FORMATS hash.
26
47
  # Use the format name as the hash key and a Proc instance.
27
48
  #
@@ -29,7 +50,7 @@ module ActiveSupport
29
50
  # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_fs(:db)} and #{stop.to_fs(:db)}" }
30
51
  def to_fs(format = :default)
31
52
  if formatter = RANGE_FORMATS[format]
32
- formatter.call(first, last)
53
+ formatter.call(self.begin, self.end)
33
54
  else
34
55
  to_s
35
56
  end
@@ -2,9 +2,11 @@
2
2
 
3
3
  class Range
4
4
  # Compare two ranges and see if they overlap each other
5
- # (1..5).overlaps?(4..6) # => true
6
- # (1..5).overlaps?(7..9) # => false
7
- def overlaps?(other)
5
+ # (1..5).overlap?(4..6) # => true
6
+ # (1..5).overlap?(7..9) # => false
7
+ def overlap?(other)
8
8
  other.begin == self.begin || cover?(other.begin) || other.cover?(self.begin)
9
9
  end
10
+
11
+ alias :overlaps? :overlap?
10
12
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/range/conversions"
4
- require "active_support/core_ext/range/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
5
4
  require "active_support/core_ext/range/compare_range"
6
- require "active_support/core_ext/range/overlaps"
5
+ require "active_support/core_ext/range/overlap"
7
6
  require "active_support/core_ext/range/each"
@@ -16,12 +16,18 @@ module SecureRandom
16
16
  #
17
17
  # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
18
18
  # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
19
- def self.base58(n = 16)
20
- SecureRandom.random_bytes(n).unpack("C*").map do |byte|
21
- idx = byte % 64
22
- idx = SecureRandom.random_number(58) if idx >= 58
23
- BASE58_ALPHABET[idx]
24
- end.join
19
+ if RUBY_VERSION >= "3.3"
20
+ def self.base58(n = 16)
21
+ SecureRandom.alphanumeric(n, chars: BASE58_ALPHABET)
22
+ end
23
+ else
24
+ def self.base58(n = 16)
25
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
26
+ idx = byte % 64
27
+ idx = SecureRandom.random_number(58) if idx >= 58
28
+ BASE58_ALPHABET[idx]
29
+ end.join
30
+ end
25
31
  end
26
32
 
27
33
  # SecureRandom.base36 generates a random base36 string in lowercase.
@@ -35,11 +41,17 @@ module SecureRandom
35
41
  #
36
42
  # p SecureRandom.base36 # => "4kugl2pdqmscqtje"
37
43
  # p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
38
- def self.base36(n = 16)
39
- SecureRandom.random_bytes(n).unpack("C*").map do |byte|
40
- idx = byte % 64
41
- idx = SecureRandom.random_number(36) if idx >= 36
42
- BASE36_ALPHABET[idx]
43
- end.join
44
+ if RUBY_VERSION >= "3.3"
45
+ def self.base36(n = 16)
46
+ SecureRandom.alphanumeric(n, chars: BASE36_ALPHABET)
47
+ end
48
+ else
49
+ def self.base36(n = 16)
50
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
51
+ idx = byte % 64
52
+ idx = SecureRandom.random_number(36) if idx >= 36
53
+ BASE36_ALPHABET[idx]
54
+ end.join
55
+ end
44
56
  end
45
57
  end
@@ -45,7 +45,7 @@ class String
45
45
  self
46
46
  end
47
47
 
48
- # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
48
+ # Truncates a given +text+ to length <tt>truncate_to</tt> if +text+ is longer than <tt>truncate_to</tt>:
49
49
  #
50
50
  # 'Once upon a time in a world far far away'.truncate(27)
51
51
  # # => "Once upon a time in a wo..."
@@ -58,16 +58,20 @@ class String
58
58
  # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
59
59
  # # => "Once upon a time in a..."
60
60
  #
61
- # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
62
- # for a total length not exceeding <tt>length</tt>:
61
+ # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...").
62
+ # The total length will not exceed <tt>truncate_to</tt> unless both +text+ and <tt>:omission</tt>
63
+ # are longer than <tt>truncate_to</tt>:
63
64
  #
64
65
  # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
65
66
  # # => "And they f... (continued)"
66
- def truncate(truncate_at, options = {})
67
- return dup unless length > truncate_at
67
+ #
68
+ # 'And they found that many people were sleeping better.'.truncate(4, omission: '... (continued)')
69
+ # # => "... (continued)"
70
+ def truncate(truncate_to, options = {})
71
+ return dup unless length > truncate_to
68
72
 
69
73
  omission = options[:omission] || "..."
70
- length_with_room_for_omission = truncate_at - omission.length
74
+ length_with_room_for_omission = truncate_to - omission.length
71
75
  stop = \
72
76
  if options[:separator]
73
77
  rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
@@ -78,7 +82,7 @@ class String
78
82
  +"#{self[0, stop]}#{omission}"
79
83
  end
80
84
 
81
- # Truncates +text+ to at most <tt>bytesize</tt> bytes in length without
85
+ # Truncates +text+ to at most <tt>truncate_to</tt> bytes in length without
82
86
  # breaking string encoding by splitting multibyte characters or breaking
83
87
  # grapheme clusters ("perceptual characters") by truncating at combining
84
88
  # characters.
@@ -91,20 +95,22 @@ class String
91
95
  # => "🔪🔪🔪🔪…"
92
96
  #
93
97
  # The truncated text ends with the <tt>:omission</tt> string, defaulting
94
- # to "…", for a total length not exceeding <tt>bytesize</tt>.
95
- def truncate_bytes(truncate_at, omission: "…")
98
+ # to "…", for a total length not exceeding <tt>truncate_to</tt>.
99
+ #
100
+ # Raises +ArgumentError+ when the bytesize of <tt>:omission</tt> exceeds <tt>truncate_to</tt>.
101
+ def truncate_bytes(truncate_to, omission: "…")
96
102
  omission ||= ""
97
103
 
98
104
  case
99
- when bytesize <= truncate_at
105
+ when bytesize <= truncate_to
100
106
  dup
101
- when omission.bytesize > truncate_at
102
- raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes"
103
- when omission.bytesize == truncate_at
107
+ when omission.bytesize > truncate_to
108
+ raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_to} bytes"
109
+ when omission.bytesize == truncate_to
104
110
  omission.dup
105
111
  else
106
112
  self.class.new.tap do |cut|
107
- cut_at = truncate_at - omission.bytesize
113
+ cut_at = truncate_to - omission.bytesize
108
114
 
109
115
  each_grapheme_cluster do |grapheme|
110
116
  if cut.bytesize + grapheme.bytesize <= cut_at
@@ -112,7 +112,7 @@ class String
112
112
 
113
113
  # Capitalizes all the words and replaces some characters in the string to create
114
114
  # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
115
- # used in the Rails internals.
115
+ # used in the \Rails internals.
116
116
  #
117
117
  # The trailing '_id','Id'.. can be kept and capitalized by setting the
118
118
  # optional parameter +keep_id_suffix+ to true.
@@ -216,7 +216,7 @@ class String
216
216
  ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale)
217
217
  end
218
218
 
219
- # Creates the name of a table like Rails does for models to table names. This method
219
+ # Creates the name of a table like \Rails does for models to table names. This method
220
220
  # uses the +pluralize+ method on the last word in the string.
221
221
  #
222
222
  # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
@@ -228,7 +228,7 @@ class String
228
228
  ActiveSupport::Inflector.tableize(self)
229
229
  end
230
230
 
231
- # Creates a class name from a plural table name like Rails does for table names to models.
231
+ # Creates a class name from a plural table name like \Rails does for table names to models.
232
232
  # Note that this returns a string and not a class. (To convert to an actual class
233
233
  # follow +classify+ with +constantize+.)
234
234
  #
@@ -240,7 +240,7 @@ class String
240
240
  ActiveSupport::Inflector.classify(self)
241
241
  end
242
242
 
243
- # Capitalizes the first word, turns underscores into spaces, and (by default)strips a
243
+ # Capitalizes the first word, turns underscores into spaces, and (by default) strips a
244
244
  # trailing '_id' if present.
245
245
  # Like +titleize+, this is meant for creating pretty output.
246
246
  #
@@ -263,7 +263,7 @@ class String
263
263
  ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix)
264
264
  end
265
265
 
266
- # Converts just the first character to uppercase.
266
+ # Converts the first character to uppercase.
267
267
  #
268
268
  # 'what a Lovely Day'.upcase_first # => "What a Lovely Day"
269
269
  # 'w'.upcase_first # => "W"
@@ -274,6 +274,17 @@ class String
274
274
  ActiveSupport::Inflector.upcase_first(self)
275
275
  end
276
276
 
277
+ # Converts the first character to lowercase.
278
+ #
279
+ # 'If they enjoyed The Matrix'.downcase_first # => "if they enjoyed The Matrix"
280
+ # 'I'.downcase_first # => "i"
281
+ # ''.downcase_first # => ""
282
+ #
283
+ # See ActiveSupport::Inflector.downcase_first.
284
+ def downcase_first
285
+ ActiveSupport::Inflector.downcase_first(self)
286
+ end
287
+
277
288
  # Creates a foreign key name from a class name.
278
289
  # +separate_class_name_and_id_with_underscore+ sets whether
279
290
  # the method should put '_' between the name and 'id'.
@@ -1,151 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
- require "active_support/core_ext/module/redefine_method"
3
+ require "active_support/core_ext/erb/util"
5
4
  require "active_support/multibyte/unicode"
6
5
 
7
- class ERB
8
- module Util
9
- HTML_ESCAPE = { "&" => "&amp;", ">" => "&gt;", "<" => "&lt;", '"' => "&quot;", "'" => "&#39;" }
10
- JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
11
- HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
12
- JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
13
-
14
- # Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name
15
- TAG_NAME_START_REGEXP_SET = "@:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \
16
- "\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \
17
- "\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}"
18
- TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}]/
19
- TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}]/
20
- TAG_NAME_REPLACEMENT_CHAR = "_"
21
-
22
- # A utility method for escaping HTML tag characters.
23
- # This method is also aliased as <tt>h</tt>.
24
- #
25
- # puts html_escape('is a > 0 & a < 10?')
26
- # # => is a &gt; 0 &amp; a &lt; 10?
27
- def html_escape(s)
28
- unwrapped_html_escape(s).html_safe
29
- end
30
-
31
- silence_redefinition_of_method :h
32
- alias h html_escape
33
-
34
- module_function :h
35
-
36
- singleton_class.silence_redefinition_of_method :html_escape
37
- module_function :html_escape
38
-
39
- # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
40
- # This method is not for public consumption! Seriously!
41
- def unwrapped_html_escape(s) # :nodoc:
42
- s = s.to_s
43
- if s.html_safe?
44
- s
45
- else
46
- CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
47
- end
48
- end
49
- module_function :unwrapped_html_escape
50
-
51
- # A utility method for escaping HTML without affecting existing escaped entities.
52
- #
53
- # html_escape_once('1 < 2 &amp; 3')
54
- # # => "1 &lt; 2 &amp; 3"
55
- #
56
- # html_escape_once('&lt;&lt; Accept & Checkout')
57
- # # => "&lt;&lt; Accept &amp; Checkout"
58
- def html_escape_once(s)
59
- result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
60
- s.html_safe? ? result.html_safe : result
61
- end
62
-
63
- module_function :html_escape_once
64
-
65
- # A utility method for escaping HTML entities in JSON strings. Specifically, the
66
- # &, > and < characters are replaced with their equivalent unicode escaped form -
67
- # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
68
- # escaped as they are treated as newline characters in some JavaScript engines.
69
- # These sequences have identical meaning as the original characters inside the
70
- # context of a JSON string, so assuming the input is a valid and well-formed
71
- # JSON value, the output will have equivalent meaning when parsed:
72
- #
73
- # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
74
- # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
75
- #
76
- # json_escape(json)
77
- # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
78
- #
79
- # JSON.parse(json) == JSON.parse(json_escape(json))
80
- # # => true
81
- #
82
- # The intended use case for this method is to escape JSON strings before including
83
- # them inside a script tag to avoid XSS vulnerability:
84
- #
85
- # <script>
86
- # var currentUser = <%= raw json_escape(current_user.to_json) %>;
87
- # </script>
88
- #
89
- # It is necessary to +raw+ the result of +json_escape+, so that quotation marks
90
- # don't get converted to <tt>&quot;</tt> entities. +json_escape+ doesn't
91
- # automatically flag the result as HTML safe, since the raw value is unsafe to
92
- # use inside HTML attributes.
93
- #
94
- # If your JSON is being used downstream for insertion into the DOM, be aware of
95
- # whether or not it is being inserted via <tt>html()</tt>. Most jQuery plugins do this.
96
- # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
97
- # content returned by your JSON.
98
- #
99
- # If you need to output JSON elsewhere in your HTML, you can just do something
100
- # like this, as any unsafe characters (including quotation marks) will be
101
- # automatically escaped for you:
102
- #
103
- # <div data-user-info="<%= current_user.to_json %>">...</div>
104
- #
105
- # WARNING: this helper only works with valid JSON. Using this on non-JSON values
106
- # will open up serious XSS vulnerabilities. For example, if you replace the
107
- # +current_user.to_json+ in the example above with user input instead, the browser
108
- # will happily <tt>eval()</tt> that string as JavaScript.
109
- #
110
- # The escaping performed in this method is identical to those performed in the
111
- # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
112
- # set to true. Because this transformation is idempotent, this helper can be
113
- # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
114
- #
115
- # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
116
- # is enabled, or if you are unsure where your JSON string originated from, it
117
- # is recommended that you always apply this helper (other libraries, such as the
118
- # JSON gem, do not provide this kind of protection by default; also some gems
119
- # might override +to_json+ to bypass Active Support's encoder).
120
- def json_escape(s)
121
- result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
122
- s.html_safe? ? result.html_safe : result
123
- end
124
-
125
- module_function :json_escape
126
-
127
- # A utility method for escaping XML names of tags and names of attributes.
128
- #
129
- # xml_name_escape('1 < 2 & 3')
130
- # # => "1___2___3"
131
- #
132
- # It follows the requirements of the specification: https://www.w3.org/TR/REC-xml/#NT-Name
133
- def xml_name_escape(name)
134
- name = name.to_s
135
- return "" if name.blank?
136
-
137
- starting_char = name[0].gsub(TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
138
-
139
- return starting_char if name.size == 1
140
-
141
- following_chars = name[1..-1].gsub(TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
142
-
143
- starting_char + following_chars
144
- end
145
- module_function :xml_name_escape
146
- end
147
- end
148
-
149
6
  class Object
150
7
  def html_safe?
151
8
  false
@@ -162,7 +19,7 @@ module ActiveSupport # :nodoc:
162
19
  class SafeBuffer < String
163
20
  UNSAFE_STRING_METHODS = %w(
164
21
  capitalize chomp chop delete delete_prefix delete_suffix
165
- downcase lstrip next reverse rstrip scrub slice squeeze strip
22
+ downcase lstrip next reverse rstrip scrub squeeze strip
166
23
  succ swapcase tr tr_s unicode_normalize upcase
167
24
  )
168
25
 
@@ -174,7 +31,7 @@ module ActiveSupport # :nodoc:
174
31
  # Raised when ActiveSupport::SafeBuffer#safe_concat is called on unsafe buffers.
175
32
  class SafeConcatError < StandardError
176
33
  def initialize
177
- super "Could not concatenate to the buffer because it is not html safe."
34
+ super "Could not concatenate to the buffer because it is not HTML safe."
178
35
  end
179
36
  end
180
37
 
@@ -184,13 +41,26 @@ module ActiveSupport # :nodoc:
184
41
 
185
42
  return unless new_string
186
43
 
187
- new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
188
- new_safe_buffer.instance_variable_set :@html_safe, true
189
- new_safe_buffer
44
+ string_into_safe_buffer(new_string, true)
190
45
  else
191
46
  to_str[*args]
192
47
  end
193
48
  end
49
+ alias_method :slice, :[]
50
+
51
+ def slice!(*args)
52
+ new_string = super
53
+
54
+ return new_string if !html_safe? || new_string.nil?
55
+
56
+ string_into_safe_buffer(new_string, true)
57
+ end
58
+
59
+ def chr
60
+ return super unless html_safe?
61
+
62
+ string_into_safe_buffer(super, true)
63
+ end
194
64
 
195
65
  def safe_concat(value)
196
66
  raise SafeConcatError unless html_safe?
@@ -207,7 +77,10 @@ module ActiveSupport # :nodoc:
207
77
  @html_safe = other.html_safe?
208
78
  end
209
79
 
210
- def clone_empty
80
+ def clone_empty # :nodoc:
81
+ ActiveSupport.deprecator.warn <<~EOM
82
+ ActiveSupport::SafeBuffer#clone_empty is deprecated and will be removed in Rails 7.2.
83
+ EOM
211
84
  self[0, 0]
212
85
  end
213
86
 
@@ -235,11 +108,11 @@ module ActiveSupport # :nodoc:
235
108
  super(implicit_html_escape_interpolated_argument(value))
236
109
  end
237
110
 
238
- def []=(*args)
239
- if args.length == 3
240
- super(args[0], args[1], implicit_html_escape_interpolated_argument(args[2]))
111
+ def []=(arg1, arg2, arg3 = nil)
112
+ if arg3
113
+ super(arg1, arg2, implicit_html_escape_interpolated_argument(arg3))
241
114
  else
242
- super(args[0], implicit_html_escape_interpolated_argument(args[1]))
115
+ super(arg1, implicit_html_escape_interpolated_argument(arg2))
243
116
  end
244
117
  end
245
118
 
@@ -247,7 +120,7 @@ module ActiveSupport # :nodoc:
247
120
  dup.concat(other)
248
121
  end
249
122
 
250
- def *(*)
123
+ def *(_)
251
124
  new_string = super
252
125
  new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
253
126
  new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
@@ -265,9 +138,9 @@ module ActiveSupport # :nodoc:
265
138
  self.class.new(super(escaped_args))
266
139
  end
267
140
 
268
- def html_safe?
269
- defined?(@html_safe) && @html_safe
270
- end
141
+ attr_reader :html_safe
142
+ alias_method :html_safe?, :html_safe
143
+ remove_method :html_safe
271
144
 
272
145
  def to_s
273
146
  self
@@ -332,22 +205,7 @@ module ActiveSupport # :nodoc:
332
205
  if !html_safe? || arg.html_safe?
333
206
  arg
334
207
  else
335
- arg_string = begin
336
- arg.to_str
337
- rescue NoMethodError => error
338
- if error.name == :to_str
339
- str = arg.to_s
340
- ActiveSupport::Deprecation.warn <<~MSG.squish
341
- Implicit conversion of #{arg.class} into String by ActiveSupport::SafeBuffer
342
- is deprecated and will be removed in Rails 7.1.
343
- You must explicitly cast it to a String.
344
- MSG
345
- str
346
- else
347
- raise
348
- end
349
- end
350
- CGI.escapeHTML(arg_string)
208
+ CGI.escapeHTML(arg.to_str)
351
209
  end
352
210
  end
353
211
 
@@ -356,6 +214,12 @@ module ActiveSupport # :nodoc:
356
214
  rescue ArgumentError
357
215
  # Can't create binding from C level Proc
358
216
  end
217
+
218
+ def string_into_safe_buffer(new_string, is_html_safe)
219
+ new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
220
+ new_safe_buffer.instance_variable_set :@html_safe, is_html_safe
221
+ new_safe_buffer
222
+ end
359
223
  end
360
224
  end
361
225
 
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Thread::Backtrace::Location # :nodoc:
4
+ if defined?(ErrorHighlight) && Gem::Version.new(ErrorHighlight::VERSION) >= Gem::Version.new("0.4.0")
5
+ def spot(ex)
6
+ ErrorHighlight.spot(ex, backtrace_location: self)
7
+ end
8
+ else
9
+ def spot(ex)
10
+ end
11
+ end
12
+ end