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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +736 -293
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- data/lib/active_support/actionable_error.rb +3 -1
- data/lib/active_support/array_inquirer.rb +2 -0
- data/lib/active_support/backtrace_cleaner.rb +25 -5
- data/lib/active_support/benchmarkable.rb +1 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +128 -0
- data/lib/active_support/cache/file_store.rb +36 -9
- data/lib/active_support/cache/mem_cache_store.rb +84 -68
- data/lib/active_support/cache/memory_store.rb +76 -24
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +126 -131
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +20 -8
- data/lib/active_support/cache.rb +304 -246
- data/lib/active_support/callbacks.rb +38 -18
- data/lib/active_support/concern.rb +4 -2
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/configurable.rb +10 -0
- data/lib/active_support/core_ext/array/conversions.rb +2 -1
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/class/subclasses.rb +13 -10
- data/lib/active_support/core_ext/date/conversions.rb +1 -0
- data/lib/active_support/core_ext/date.rb +0 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +6 -2
- data/lib/active_support/core_ext/date_time.rb +0 -1
- data/lib/active_support/core_ext/digest/uuid.rb +1 -10
- data/lib/active_support/core_ext/enumerable.rb +3 -75
- data/lib/active_support/core_ext/erb/util.rb +196 -0
- data/lib/active_support/core_ext/hash/conversions.rb +1 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
- data/lib/active_support/core_ext/module/delegation.rb +40 -11
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +0 -1
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +2 -0
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
- data/lib/active_support/core_ext/object/json.rb +10 -2
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +3 -3
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +16 -0
- data/lib/active_support/core_ext/pathname/existence.rb +2 -0
- data/lib/active_support/core_ext/pathname.rb +1 -0
- data/lib/active_support/core_ext/range/conversions.rb +28 -7
- data/lib/active_support/core_ext/range/{overlaps.rb → overlap.rb} +5 -3
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/securerandom.rb +24 -12
- data/lib/active_support/core_ext/string/filters.rb +20 -14
- data/lib/active_support/core_ext/string/inflections.rb +16 -5
- data/lib/active_support/core_ext/string/output_safety.rb +38 -174
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +18 -2
- data/lib/active_support/core_ext/time/conversions.rb +2 -2
- data/lib/active_support/core_ext/time/zones.rb +4 -4
- data/lib/active_support/core_ext/time.rb +0 -1
- data/lib/active_support/current_attributes.rb +15 -6
- data/lib/active_support/dependencies/autoload.rb +17 -12
- data/lib/active_support/deprecation/behaviors.rb +53 -32
- data/lib/active_support/deprecation/constant_accessor.rb +5 -4
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +3 -5
- data/lib/active_support/deprecation/instance_delegator.rb +31 -4
- data/lib/active_support/deprecation/method_wrappers.rb +6 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +37 -22
- data/lib/active_support/deprecation/reporting.rb +35 -21
- data/lib/active_support/deprecation.rb +32 -5
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +104 -132
- data/lib/active_support/duration/iso8601_serializer.rb +0 -2
- data/lib/active_support/duration.rb +2 -1
- data/lib/active_support/encrypted_configuration.rb +30 -9
- data/lib/active_support/encrypted_file.rb +8 -3
- data/lib/active_support/environment_inquirer.rb +22 -2
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +121 -35
- data/lib/active_support/execution_wrapper.rb +4 -4
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +10 -2
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +35 -17
- data/lib/active_support/i18n.rb +1 -1
- data/lib/active_support/i18n_railtie.rb +20 -13
- data/lib/active_support/inflector/inflections.rb +2 -0
- data/lib/active_support/inflector/methods.rb +22 -10
- data/lib/active_support/inflector/transliterate.rb +3 -1
- data/lib/active_support/isolated_execution_state.rb +26 -22
- data/lib/active_support/json/decoding.rb +2 -1
- data/lib/active_support/json/encoding.rb +25 -43
- data/lib/active_support/key_generator.rb +9 -1
- data/lib/active_support/lazy_load_hooks.rb +6 -4
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber.rb +78 -33
- data/lib/active_support/logger.rb +1 -1
- data/lib/active_support/logger_thread_safe_level.rb +9 -21
- data/lib/active_support/message_encryptor.rb +197 -53
- data/lib/active_support/message_encryptors.rb +140 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +292 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +212 -93
- data/lib/active_support/message_verifiers.rb +134 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +111 -45
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +34 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +2 -0
- data/lib/active_support/multibyte/unicode.rb +9 -37
- data/lib/active_support/notifications/fanout.rb +239 -81
- data/lib/active_support/notifications/instrumenter.rb +71 -14
- data/lib/active_support/notifications.rb +1 -1
- data/lib/active_support/number_helper/number_converter.rb +2 -2
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
- data/lib/active_support/ordered_hash.rb +3 -3
- data/lib/active_support/ordered_options.rb +14 -0
- data/lib/active_support/parameter_filter.rb +84 -69
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/railtie.rb +33 -21
- data/lib/active_support/reloader.rb +12 -4
- data/lib/active_support/rescuable.rb +2 -0
- data/lib/active_support/secure_compare_rotator.rb +16 -9
- data/lib/active_support/string_inquirer.rb +3 -1
- data/lib/active_support/subscriber.rb +9 -27
- data/lib/active_support/syntax_error_proxy.rb +49 -0
- data/lib/active_support/tagged_logging.rb +60 -24
- data/lib/active_support/test_case.rb +153 -6
- data/lib/active_support/testing/assertions.rb +25 -9
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +32 -0
- data/lib/active_support/testing/deprecation.rb +25 -25
- data/lib/active_support/testing/error_reporter_assertions.rb +108 -0
- data/lib/active_support/testing/isolation.rb +1 -1
- data/lib/active_support/testing/method_call_assertions.rb +21 -8
- data/lib/active_support/testing/parallelize_executor.rb +8 -3
- data/lib/active_support/testing/stream.rb +1 -1
- data/lib/active_support/testing/strict_warnings.rb +38 -0
- data/lib/active_support/testing/time_helpers.rb +32 -14
- data/lib/active_support/time_with_zone.rb +4 -14
- data/lib/active_support/values/time_zone.rb +9 -7
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +3 -10
- data/lib/active_support/xml_mini/nokogiri.rb +1 -1
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
- data/lib/active_support/xml_mini/rexml.rb +1 -1
- data/lib/active_support/xml_mini.rb +2 -2
- data/lib/active_support.rb +13 -3
- metadata +102 -17
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
- data/lib/active_support/core_ext/uri.rb +0 -5
- 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+
|
|
9
|
-
# provided. Each method called on
|
|
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,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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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)
|
|
37
|
+
# range.to_fs(:db) # => "BETWEEN '1' AND '100'"
|
|
23
38
|
#
|
|
24
|
-
#
|
|
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(
|
|
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).
|
|
6
|
-
# (1..5).
|
|
7
|
-
def
|
|
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/
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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+
|
|
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
|
-
#
|
|
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
|
-
|
|
67
|
-
|
|
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 =
|
|
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>
|
|
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>
|
|
95
|
-
|
|
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 <=
|
|
105
|
+
when bytesize <= truncate_to
|
|
100
106
|
dup
|
|
101
|
-
when omission.bytesize >
|
|
102
|
-
raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{
|
|
103
|
-
when omission.bytesize ==
|
|
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 =
|
|
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
|
|
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 = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" }
|
|
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 > 0 & a < 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 & 3')
|
|
54
|
-
# # => "1 < 2 & 3"
|
|
55
|
-
#
|
|
56
|
-
# html_escape_once('<< Accept & Checkout')
|
|
57
|
-
# # => "<< Accept & 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>"</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
|
|
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
|
|
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
|
-
|
|
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 []=(
|
|
239
|
-
if
|
|
240
|
-
super(
|
|
111
|
+
def []=(arg1, arg2, arg3 = nil)
|
|
112
|
+
if arg3
|
|
113
|
+
super(arg1, arg2, implicit_html_escape_interpolated_argument(arg3))
|
|
241
114
|
else
|
|
242
|
-
super(
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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
|