activesupport 6.0.6.1 → 6.1.7.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +433 -464
- data/MIT-LICENSE +1 -1
- data/lib/active_support/array_inquirer.rb +4 -2
- data/lib/active_support/backtrace_cleaner.rb +3 -3
- data/lib/active_support/benchmarkable.rb +1 -1
- data/lib/active_support/cache/file_store.rb +3 -3
- data/lib/active_support/cache/mem_cache_store.rb +28 -18
- data/lib/active_support/cache/memory_store.rb +46 -26
- data/lib/active_support/cache/redis_cache_store.rb +25 -25
- data/lib/active_support/cache/strategy/local_cache.rb +20 -5
- data/lib/active_support/cache.rb +87 -40
- data/lib/active_support/callbacks.rb +65 -56
- data/lib/active_support/concern.rb +46 -2
- data/lib/active_support/configurable.rb +3 -3
- data/lib/active_support/configuration_file.rb +51 -0
- data/lib/active_support/core_ext/benchmark.rb +2 -2
- data/lib/active_support/core_ext/class/attribute.rb +34 -44
- data/lib/active_support/core_ext/class/subclasses.rb +17 -38
- data/lib/active_support/core_ext/date/conversions.rb +2 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/digest/uuid.rb +1 -0
- data/lib/active_support/core_ext/enumerable.rb +76 -4
- data/lib/active_support/core_ext/hash/conversions.rb +2 -2
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
- data/lib/active_support/core_ext/hash/keys.rb +1 -1
- data/lib/active_support/core_ext/hash/slice.rb +3 -2
- data/lib/active_support/core_ext/load_error.rb +1 -1
- data/lib/active_support/core_ext/marshal.rb +2 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
- data/lib/active_support/core_ext/module/concerning.rb +8 -2
- data/lib/active_support/core_ext/module/delegation.rb +38 -28
- data/lib/active_support/core_ext/module/introspection.rb +1 -25
- data/lib/active_support/core_ext/name_error.rb +29 -2
- data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
- data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
- data/lib/active_support/core_ext/object/json.rb +12 -1
- data/lib/active_support/core_ext/object/try.rb +2 -2
- data/lib/active_support/core_ext/range/compare_range.rb +9 -3
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
- data/lib/active_support/core_ext/regexp.rb +8 -1
- data/lib/active_support/core_ext/string/access.rb +5 -24
- data/lib/active_support/core_ext/string/conversions.rb +1 -0
- data/lib/active_support/core_ext/string/inflections.rb +38 -4
- data/lib/active_support/core_ext/string/inquiry.rb +1 -0
- data/lib/active_support/core_ext/string/multibyte.rb +2 -2
- data/lib/active_support/core_ext/string/output_safety.rb +7 -4
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/time/calculations.rb +19 -0
- data/lib/active_support/core_ext/time/conversions.rb +2 -0
- data/lib/active_support/core_ext/uri.rb +5 -1
- data/lib/active_support/core_ext.rb +1 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +9 -2
- data/lib/active_support/dependencies/zeitwerk_integration.rb +4 -1
- data/lib/active_support/dependencies.rb +37 -18
- data/lib/active_support/deprecation/behaviors.rb +15 -2
- data/lib/active_support/deprecation/disallowed.rb +56 -0
- data/lib/active_support/deprecation/instance_delegator.rb +0 -1
- data/lib/active_support/deprecation/method_wrappers.rb +3 -2
- data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
- data/lib/active_support/deprecation/reporting.rb +50 -7
- data/lib/active_support/deprecation.rb +6 -1
- data/lib/active_support/descendants_tracker.rb +6 -2
- data/lib/active_support/digest.rb +2 -0
- data/lib/active_support/duration/iso8601_serializer.rb +15 -9
- data/lib/active_support/duration.rb +75 -25
- data/lib/active_support/encrypted_file.rb +19 -2
- data/lib/active_support/environment_inquirer.rb +20 -0
- data/lib/active_support/evented_file_update_checker.rb +69 -133
- data/lib/active_support/fork_tracker.rb +64 -0
- data/lib/active_support/gem_version.rb +3 -3
- data/lib/active_support/hash_with_indifferent_access.rb +48 -24
- data/lib/active_support/i18n_railtie.rb +14 -19
- data/lib/active_support/inflector/inflections.rb +1 -2
- data/lib/active_support/inflector/methods.rb +36 -33
- data/lib/active_support/inflector/transliterate.rb +4 -4
- data/lib/active_support/json/decoding.rb +4 -4
- data/lib/active_support/json/encoding.rb +5 -1
- data/lib/active_support/key_generator.rb +1 -1
- data/lib/active_support/locale/en.yml +7 -3
- data/lib/active_support/log_subscriber.rb +8 -0
- data/lib/active_support/logger.rb +1 -1
- data/lib/active_support/logger_silence.rb +2 -26
- data/lib/active_support/logger_thread_safe_level.rb +34 -12
- data/lib/active_support/message_encryptor.rb +4 -7
- data/lib/active_support/message_verifier.rb +5 -5
- data/lib/active_support/messages/rotation_configuration.rb +2 -1
- data/lib/active_support/messages/rotator.rb +6 -5
- data/lib/active_support/multibyte/chars.rb +4 -42
- data/lib/active_support/multibyte/unicode.rb +9 -83
- data/lib/active_support/notifications/fanout.rb +23 -8
- data/lib/active_support/notifications/instrumenter.rb +6 -15
- data/lib/active_support/notifications.rb +32 -5
- data/lib/active_support/number_helper/number_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
- data/lib/active_support/number_helper/rounding_helper.rb +12 -28
- data/lib/active_support/number_helper.rb +29 -14
- data/lib/active_support/option_merger.rb +2 -1
- data/lib/active_support/ordered_options.rb +8 -2
- data/lib/active_support/parameter_filter.rb +16 -11
- data/lib/active_support/per_thread_registry.rb +2 -1
- data/lib/active_support/rails.rb +1 -4
- data/lib/active_support/railtie.rb +23 -1
- data/lib/active_support/rescuable.rb +4 -4
- data/lib/active_support/secure_compare_rotator.rb +51 -0
- data/lib/active_support/security_utils.rb +19 -12
- data/lib/active_support/string_inquirer.rb +4 -2
- data/lib/active_support/subscriber.rb +12 -7
- data/lib/active_support/tagged_logging.rb +30 -5
- data/lib/active_support/testing/assertions.rb +18 -11
- data/lib/active_support/testing/parallelization/server.rb +78 -0
- data/lib/active_support/testing/parallelization/worker.rb +100 -0
- data/lib/active_support/testing/parallelization.rb +12 -95
- data/lib/active_support/testing/time_helpers.rb +40 -3
- data/lib/active_support/time_with_zone.rb +67 -43
- data/lib/active_support/values/time_zone.rb +22 -10
- data/lib/active_support/xml_mini/rexml.rb +8 -1
- data/lib/active_support.rb +13 -1
- metadata +33 -35
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
- data/lib/active_support/core_ext/hash/compact.rb +0 -5
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
- data/lib/active_support/core_ext/module/reachable.rb +0 -6
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
- data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -13,6 +13,7 @@ module ActiveSupport
|
|
13
13
|
descendants = @@direct_descendants[klass]
|
14
14
|
descendants ? descendants.to_a : []
|
15
15
|
end
|
16
|
+
alias_method :subclasses, :direct_descendants
|
16
17
|
|
17
18
|
def descendants(klass)
|
18
19
|
arr = []
|
@@ -59,6 +60,7 @@ module ActiveSupport
|
|
59
60
|
def direct_descendants
|
60
61
|
DescendantsTracker.direct_descendants(self)
|
61
62
|
end
|
63
|
+
alias_method :subclasses, :direct_descendants
|
62
64
|
|
63
65
|
def descendants
|
64
66
|
DescendantsTracker.descendants(self)
|
@@ -77,15 +79,17 @@ module ActiveSupport
|
|
77
79
|
end
|
78
80
|
|
79
81
|
def <<(klass)
|
80
|
-
cleanup!
|
81
82
|
@refs << WeakRef.new(klass)
|
82
83
|
end
|
83
84
|
|
84
85
|
def each
|
85
|
-
@refs.
|
86
|
+
@refs.reject! do |ref|
|
86
87
|
yield ref.__getobj__
|
88
|
+
false
|
87
89
|
rescue WeakRef::RefError
|
90
|
+
true
|
88
91
|
end
|
92
|
+
self
|
89
93
|
end
|
90
94
|
|
91
95
|
def refs_size
|
@@ -6,6 +6,8 @@ module ActiveSupport
|
|
6
6
|
class Duration
|
7
7
|
# Serializes duration to string according to ISO 8601 Duration format.
|
8
8
|
class ISO8601Serializer # :nodoc:
|
9
|
+
DATE_COMPONENTS = %i(years months days)
|
10
|
+
|
9
11
|
def initialize(duration, precision: nil)
|
10
12
|
@duration = duration
|
11
13
|
@precision = precision
|
@@ -13,14 +15,14 @@ module ActiveSupport
|
|
13
15
|
|
14
16
|
# Builds and returns output string.
|
15
17
|
def serialize
|
16
|
-
parts
|
18
|
+
parts = normalize
|
17
19
|
return "PT0S" if parts.empty?
|
18
20
|
|
19
21
|
output = +"P"
|
20
22
|
output << "#{parts[:years]}Y" if parts.key?(:years)
|
21
23
|
output << "#{parts[:months]}M" if parts.key?(:months)
|
22
|
-
output << "#{parts[:weeks]}W" if parts.key?(:weeks)
|
23
24
|
output << "#{parts[:days]}D" if parts.key?(:days)
|
25
|
+
output << "#{parts[:weeks]}W" if parts.key?(:weeks)
|
24
26
|
time = +""
|
25
27
|
time << "#{parts[:hours]}H" if parts.key?(:hours)
|
26
28
|
time << "#{parts[:minutes]}M" if parts.key?(:minutes)
|
@@ -28,7 +30,7 @@ module ActiveSupport
|
|
28
30
|
time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
|
29
31
|
end
|
30
32
|
output << "T#{time}" unless time.empty?
|
31
|
-
|
33
|
+
output
|
32
34
|
end
|
33
35
|
|
34
36
|
private
|
@@ -40,13 +42,17 @@ module ActiveSupport
|
|
40
42
|
parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
|
41
43
|
p[k] += v unless v.zero?
|
42
44
|
end
|
43
|
-
|
44
|
-
|
45
|
-
if parts
|
46
|
-
|
47
|
-
parts.transform_values!(&:-@)
|
45
|
+
|
46
|
+
# Convert weeks to days and remove weeks if mixed with date parts
|
47
|
+
if week_mixed_with_date?(parts)
|
48
|
+
parts[:days] += parts.delete(:weeks) * SECONDS_PER_WEEK / SECONDS_PER_DAY
|
48
49
|
end
|
49
|
-
|
50
|
+
|
51
|
+
parts
|
52
|
+
end
|
53
|
+
|
54
|
+
def week_mixed_with_date?(parts)
|
55
|
+
parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
|
50
56
|
end
|
51
57
|
end
|
52
58
|
end
|
@@ -39,7 +39,7 @@ module ActiveSupport
|
|
39
39
|
|
40
40
|
def +(other)
|
41
41
|
if Duration === other
|
42
|
-
seconds = value + other.parts
|
42
|
+
seconds = value + other.parts.fetch(:seconds, 0)
|
43
43
|
new_parts = other.parts.merge(seconds: seconds)
|
44
44
|
new_value = value + other.value
|
45
45
|
|
@@ -51,8 +51,8 @@ module ActiveSupport
|
|
51
51
|
|
52
52
|
def -(other)
|
53
53
|
if Duration === other
|
54
|
-
seconds = value - other.parts
|
55
|
-
new_parts = other.parts.
|
54
|
+
seconds = value - other.parts.fetch(:seconds, 0)
|
55
|
+
new_parts = other.parts.transform_values(&:-@)
|
56
56
|
new_parts = new_parts.merge(seconds: seconds)
|
57
57
|
new_value = value - other.value
|
58
58
|
|
@@ -64,7 +64,7 @@ module ActiveSupport
|
|
64
64
|
|
65
65
|
def *(other)
|
66
66
|
if Duration === other
|
67
|
-
new_parts = other.parts.
|
67
|
+
new_parts = other.parts.transform_values { |other_value| value * other_value }
|
68
68
|
new_value = value * other.value
|
69
69
|
|
70
70
|
Duration.new(new_value, new_parts)
|
@@ -147,31 +147,31 @@ module ActiveSupport
|
|
147
147
|
end
|
148
148
|
|
149
149
|
def seconds(value) #:nodoc:
|
150
|
-
new(value,
|
150
|
+
new(value, seconds: value)
|
151
151
|
end
|
152
152
|
|
153
153
|
def minutes(value) #:nodoc:
|
154
|
-
new(value * SECONDS_PER_MINUTE,
|
154
|
+
new(value * SECONDS_PER_MINUTE, minutes: value)
|
155
155
|
end
|
156
156
|
|
157
157
|
def hours(value) #:nodoc:
|
158
|
-
new(value * SECONDS_PER_HOUR,
|
158
|
+
new(value * SECONDS_PER_HOUR, hours: value)
|
159
159
|
end
|
160
160
|
|
161
161
|
def days(value) #:nodoc:
|
162
|
-
new(value * SECONDS_PER_DAY,
|
162
|
+
new(value * SECONDS_PER_DAY, days: value)
|
163
163
|
end
|
164
164
|
|
165
165
|
def weeks(value) #:nodoc:
|
166
|
-
new(value * SECONDS_PER_WEEK,
|
166
|
+
new(value * SECONDS_PER_WEEK, weeks: value)
|
167
167
|
end
|
168
168
|
|
169
169
|
def months(value) #:nodoc:
|
170
|
-
new(value * SECONDS_PER_MONTH,
|
170
|
+
new(value * SECONDS_PER_MONTH, months: value)
|
171
171
|
end
|
172
172
|
|
173
173
|
def years(value) #:nodoc:
|
174
|
-
new(value * SECONDS_PER_YEAR,
|
174
|
+
new(value * SECONDS_PER_YEAR, years: value)
|
175
175
|
end
|
176
176
|
|
177
177
|
# Creates a new Duration from a seconds value that is converted
|
@@ -181,18 +181,23 @@ module ActiveSupport
|
|
181
181
|
# ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
|
182
182
|
#
|
183
183
|
def build(value)
|
184
|
+
unless value.is_a?(::Numeric)
|
185
|
+
raise TypeError, "can't build an #{self.name} from a #{value.class.name}"
|
186
|
+
end
|
187
|
+
|
184
188
|
parts = {}
|
185
|
-
|
189
|
+
remainder_sign = value <=> 0
|
190
|
+
remainder = value.round(9).abs
|
186
191
|
|
187
192
|
PARTS.each do |part|
|
188
193
|
unless part == :seconds
|
189
194
|
part_in_seconds = PARTS_IN_SECONDS[part]
|
190
|
-
parts[part] = remainder.div(part_in_seconds)
|
195
|
+
parts[part] = remainder.div(part_in_seconds) * remainder_sign
|
191
196
|
remainder %= part_in_seconds
|
192
197
|
end
|
193
198
|
end unless value == 0
|
194
199
|
|
195
|
-
parts[:seconds] = remainder
|
200
|
+
parts[:seconds] = remainder * remainder_sign
|
196
201
|
|
197
202
|
new(value, parts)
|
198
203
|
end
|
@@ -206,8 +211,7 @@ module ActiveSupport
|
|
206
211
|
end
|
207
212
|
|
208
213
|
def initialize(value, parts) #:nodoc:
|
209
|
-
@value, @parts = value, parts
|
210
|
-
@parts.default = 0
|
214
|
+
@value, @parts = value, parts
|
211
215
|
@parts.reject! { |k, v| v.zero? } unless value == 0
|
212
216
|
end
|
213
217
|
|
@@ -236,13 +240,12 @@ module ActiveSupport
|
|
236
240
|
# are treated as seconds.
|
237
241
|
def +(other)
|
238
242
|
if Duration === other
|
239
|
-
parts = @parts.
|
240
|
-
|
241
|
-
parts[key] += value
|
243
|
+
parts = @parts.merge(other.parts) do |_key, value, other_value|
|
244
|
+
value + other_value
|
242
245
|
end
|
243
246
|
Duration.new(value + other.value, parts)
|
244
247
|
else
|
245
|
-
seconds = @parts
|
248
|
+
seconds = @parts.fetch(:seconds, 0) + other
|
246
249
|
Duration.new(value + other, @parts.merge(seconds: seconds))
|
247
250
|
end
|
248
251
|
end
|
@@ -256,9 +259,9 @@ module ActiveSupport
|
|
256
259
|
# Multiplies this Duration by a Numeric and returns a new Duration.
|
257
260
|
def *(other)
|
258
261
|
if Scalar === other || Duration === other
|
259
|
-
Duration.new(value * other.value, parts.
|
262
|
+
Duration.new(value * other.value, parts.transform_values { |number| number * other.value })
|
260
263
|
elsif Numeric === other
|
261
|
-
Duration.new(value * other, parts.
|
264
|
+
Duration.new(value * other, parts.transform_values { |number| number * other })
|
262
265
|
else
|
263
266
|
raise_type_error(other)
|
264
267
|
end
|
@@ -267,11 +270,11 @@ module ActiveSupport
|
|
267
270
|
# Divides this Duration by a Numeric and returns a new Duration.
|
268
271
|
def /(other)
|
269
272
|
if Scalar === other
|
270
|
-
Duration.new(value / other.value, parts.
|
273
|
+
Duration.new(value / other.value, parts.transform_values { |number| number / other.value })
|
271
274
|
elsif Duration === other
|
272
275
|
value / other.value
|
273
276
|
elsif Numeric === other
|
274
|
-
Duration.new(value / other, parts.
|
277
|
+
Duration.new(value / other, parts.transform_values { |number| number / other })
|
275
278
|
else
|
276
279
|
raise_type_error(other)
|
277
280
|
end
|
@@ -290,7 +293,11 @@ module ActiveSupport
|
|
290
293
|
end
|
291
294
|
|
292
295
|
def -@ #:nodoc:
|
293
|
-
Duration.new(-value, parts.
|
296
|
+
Duration.new(-value, parts.transform_values(&:-@))
|
297
|
+
end
|
298
|
+
|
299
|
+
def +@ #:nodoc:
|
300
|
+
self
|
294
301
|
end
|
295
302
|
|
296
303
|
def is_a?(klass) #:nodoc:
|
@@ -343,6 +350,49 @@ module ActiveSupport
|
|
343
350
|
def to_i
|
344
351
|
@value.to_i
|
345
352
|
end
|
353
|
+
alias :in_seconds :to_i
|
354
|
+
|
355
|
+
# Returns the amount of minutes a duration covers as a float
|
356
|
+
#
|
357
|
+
# 1.day.in_minutes # => 1440.0
|
358
|
+
def in_minutes
|
359
|
+
in_seconds / SECONDS_PER_MINUTE.to_f
|
360
|
+
end
|
361
|
+
|
362
|
+
# Returns the amount of hours a duration covers as a float
|
363
|
+
#
|
364
|
+
# 1.day.in_hours # => 24.0
|
365
|
+
def in_hours
|
366
|
+
in_seconds / SECONDS_PER_HOUR.to_f
|
367
|
+
end
|
368
|
+
|
369
|
+
# Returns the amount of days a duration covers as a float
|
370
|
+
#
|
371
|
+
# 12.hours.in_days # => 0.5
|
372
|
+
def in_days
|
373
|
+
in_seconds / SECONDS_PER_DAY.to_f
|
374
|
+
end
|
375
|
+
|
376
|
+
# Returns the amount of weeks a duration covers as a float
|
377
|
+
#
|
378
|
+
# 2.months.in_weeks # => 8.696
|
379
|
+
def in_weeks
|
380
|
+
in_seconds / SECONDS_PER_WEEK.to_f
|
381
|
+
end
|
382
|
+
|
383
|
+
# Returns the amount of months a duration covers as a float
|
384
|
+
#
|
385
|
+
# 9.weeks.in_months # => 2.07
|
386
|
+
def in_months
|
387
|
+
in_seconds / SECONDS_PER_MONTH.to_f
|
388
|
+
end
|
389
|
+
|
390
|
+
# Returns the amount of years a duration covers as a float
|
391
|
+
#
|
392
|
+
# 30.days.in_years # => 0.082
|
393
|
+
def in_years
|
394
|
+
in_seconds / SECONDS_PER_YEAR.to_f
|
395
|
+
end
|
346
396
|
|
347
397
|
# Returns +true+ if +other+ is also a Duration instance, which has the
|
348
398
|
# same parts as this one.
|
@@ -20,17 +20,28 @@ module ActiveSupport
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
class InvalidKeyLengthError < RuntimeError
|
24
|
+
def initialize
|
25
|
+
super "Encryption key must be exactly #{EncryptedFile.expected_key_length} characters."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
23
29
|
CIPHER = "aes-128-gcm"
|
24
30
|
|
25
31
|
def self.generate_key
|
26
32
|
SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
|
27
33
|
end
|
28
34
|
|
35
|
+
def self.expected_key_length # :nodoc:
|
36
|
+
@expected_key_length ||= generate_key.length
|
37
|
+
end
|
38
|
+
|
29
39
|
|
30
40
|
attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
|
31
41
|
|
32
42
|
def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
|
33
|
-
@content_path
|
43
|
+
@content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path }
|
44
|
+
@key_path = Pathname.new(key_path)
|
34
45
|
@env_key, @raise_if_missing_key = env_key, raise_if_missing_key
|
35
46
|
end
|
36
47
|
|
@@ -73,6 +84,7 @@ module ActiveSupport
|
|
73
84
|
|
74
85
|
|
75
86
|
def encrypt(contents)
|
87
|
+
check_key_length
|
76
88
|
encryptor.encrypt_and_sign contents
|
77
89
|
end
|
78
90
|
|
@@ -90,11 +102,16 @@ module ActiveSupport
|
|
90
102
|
end
|
91
103
|
|
92
104
|
def read_key_file
|
93
|
-
|
105
|
+
return @key_file_contents if defined?(@key_file_contents)
|
106
|
+
@key_file_contents = (key_path.binread.strip if key_path.exist?)
|
94
107
|
end
|
95
108
|
|
96
109
|
def handle_missing_key
|
97
110
|
raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key
|
98
111
|
end
|
112
|
+
|
113
|
+
def check_key_length
|
114
|
+
raise InvalidKeyLengthError if key&.length != self.class.expected_key_length
|
115
|
+
end
|
99
116
|
end
|
100
117
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/string_inquirer"
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
class EnvironmentInquirer < StringInquirer #:nodoc:
|
7
|
+
DEFAULT_ENVIRONMENTS = ["development", "test", "production"]
|
8
|
+
def initialize(env)
|
9
|
+
super(env)
|
10
|
+
|
11
|
+
DEFAULT_ENVIRONMENTS.each do |default|
|
12
|
+
instance_variable_set :"@#{default}", env == default
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
DEFAULT_ENVIRONMENTS.each do |env|
|
17
|
+
class_eval "def #{env}?; @#{env}; end"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require "set"
|
4
4
|
require "pathname"
|
5
5
|
require "concurrent/atomic/atomic_boolean"
|
6
|
+
require "listen"
|
7
|
+
require "active_support/fork_tracker"
|
6
8
|
|
7
9
|
module ActiveSupport
|
8
10
|
# Allows you to "listen" to changes in a file system.
|
@@ -38,62 +40,22 @@ module ActiveSupport
|
|
38
40
|
raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker"
|
39
41
|
end
|
40
42
|
|
41
|
-
@
|
42
|
-
@
|
43
|
-
|
44
|
-
@dirs = {}
|
45
|
-
dirs.each do |dir, exts|
|
46
|
-
@dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
|
47
|
-
end
|
48
|
-
|
49
|
-
@block = block
|
50
|
-
@updated = Concurrent::AtomicBoolean.new(false)
|
51
|
-
@lcsp = @ph.longest_common_subpath(@dirs.keys)
|
52
|
-
@pid = Process.pid
|
53
|
-
@boot_mutex = Mutex.new
|
54
|
-
|
55
|
-
dtw = directories_to_watch
|
56
|
-
@dtw, @missing = dtw.partition(&:exist?)
|
57
|
-
|
58
|
-
if @dtw.any?
|
59
|
-
# Loading listen triggers warnings. These are originated by a legit
|
60
|
-
# usage of attr_* macros for private attributes, but adds a lot of noise
|
61
|
-
# to our test suite. Thus, we lazy load it and disable warnings locally.
|
62
|
-
silence_warnings do
|
63
|
-
require "listen"
|
64
|
-
rescue LoadError => e
|
65
|
-
raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
|
66
|
-
end
|
67
|
-
end
|
68
|
-
boot!
|
43
|
+
@block = block
|
44
|
+
@core = Core.new(files, dirs)
|
45
|
+
ObjectSpace.define_finalizer(self, @core.finalizer)
|
69
46
|
end
|
70
47
|
|
71
48
|
def updated?
|
72
|
-
@
|
73
|
-
|
74
|
-
|
75
|
-
@pid = Process.pid
|
76
|
-
@updated.make_true
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
if @missing.any?(&:exist?)
|
81
|
-
@boot_mutex.synchronize do
|
82
|
-
appeared, @missing = @missing.partition(&:exist?)
|
83
|
-
shutdown!
|
84
|
-
|
85
|
-
@dtw += appeared
|
86
|
-
boot!
|
87
|
-
|
88
|
-
@updated.make_true
|
89
|
-
end
|
49
|
+
if @core.restart?
|
50
|
+
@core.thread_safely(&:restart)
|
51
|
+
@core.updated.make_true
|
90
52
|
end
|
91
53
|
|
92
|
-
@updated.true?
|
54
|
+
@core.updated.true?
|
93
55
|
end
|
94
56
|
|
95
57
|
def execute
|
96
|
-
@updated.make_false
|
58
|
+
@core.updated.make_false
|
97
59
|
@block.call
|
98
60
|
end
|
99
61
|
|
@@ -105,17 +67,59 @@ module ActiveSupport
|
|
105
67
|
end
|
106
68
|
end
|
107
69
|
|
108
|
-
|
109
|
-
|
110
|
-
|
70
|
+
class Core
|
71
|
+
attr_reader :updated
|
72
|
+
|
73
|
+
def initialize(files, dirs)
|
74
|
+
@files = files.map { |file| Pathname(file).expand_path }.to_set
|
75
|
+
|
76
|
+
@dirs = dirs.each_with_object({}) do |(dir, exts), hash|
|
77
|
+
hash[Pathname(dir).expand_path] = Array(exts).map { |ext| ext.to_s.sub(/\A\.?/, ".") }.to_set
|
78
|
+
end
|
79
|
+
|
80
|
+
@common_path = common_path(@dirs.keys)
|
81
|
+
|
82
|
+
@dtw = directories_to_watch
|
83
|
+
@missing = []
|
84
|
+
|
85
|
+
@updated = Concurrent::AtomicBoolean.new(false)
|
86
|
+
@mutex = Mutex.new
|
87
|
+
|
88
|
+
start
|
89
|
+
@after_fork = ActiveSupport::ForkTracker.after_fork { start }
|
90
|
+
end
|
91
|
+
|
92
|
+
def finalizer
|
93
|
+
proc do
|
94
|
+
stop
|
95
|
+
ActiveSupport::ForkTracker.unregister(@after_fork)
|
96
|
+
end
|
97
|
+
end
|
111
98
|
|
112
|
-
|
113
|
-
|
99
|
+
def thread_safely
|
100
|
+
@mutex.synchronize do
|
101
|
+
yield self
|
114
102
|
end
|
115
103
|
end
|
116
104
|
|
117
|
-
def
|
118
|
-
|
105
|
+
def start
|
106
|
+
normalize_dirs!
|
107
|
+
@dtw, @missing = [*@dtw, *@missing].partition(&:exist?)
|
108
|
+
@listener = @dtw.any? ? Listen.to(*@dtw, &method(:changed)) : nil
|
109
|
+
@listener&.start
|
110
|
+
end
|
111
|
+
|
112
|
+
def stop
|
113
|
+
@listener&.stop
|
114
|
+
end
|
115
|
+
|
116
|
+
def restart
|
117
|
+
stop
|
118
|
+
start
|
119
|
+
end
|
120
|
+
|
121
|
+
def restart?
|
122
|
+
@missing.any?(&:exist?)
|
119
123
|
end
|
120
124
|
|
121
125
|
def normalize_dirs!
|
@@ -125,27 +129,27 @@ module ActiveSupport
|
|
125
129
|
end
|
126
130
|
|
127
131
|
def changed(modified, added, removed)
|
128
|
-
unless updated?
|
132
|
+
unless @updated.true?
|
129
133
|
@updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
|
130
134
|
end
|
131
135
|
end
|
132
136
|
|
133
137
|
def watching?(file)
|
134
|
-
file =
|
138
|
+
file = Pathname(file)
|
135
139
|
|
136
140
|
if @files.member?(file)
|
137
141
|
true
|
138
142
|
elsif file.directory?
|
139
143
|
false
|
140
144
|
else
|
141
|
-
ext =
|
145
|
+
ext = file.extname
|
142
146
|
|
143
147
|
file.dirname.ascend do |dir|
|
144
148
|
matching = @dirs[dir]
|
145
149
|
|
146
150
|
if matching && (matching.empty? || matching.include?(ext))
|
147
151
|
break true
|
148
|
-
elsif dir == @
|
152
|
+
elsif dir == @common_path || dir.root?
|
149
153
|
break false
|
150
154
|
end
|
151
155
|
end
|
@@ -153,82 +157,14 @@ module ActiveSupport
|
|
153
157
|
end
|
154
158
|
|
155
159
|
def directories_to_watch
|
156
|
-
dtw = @files.map(&:dirname)
|
157
|
-
dtw.
|
158
|
-
dtw.
|
159
|
-
|
160
|
-
normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
|
161
|
-
dtw = dtw.reject do |path|
|
162
|
-
normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) }
|
163
|
-
end
|
164
|
-
|
165
|
-
@ph.filter_out_descendants(dtw)
|
160
|
+
dtw = @dirs.keys | @files.map(&:dirname)
|
161
|
+
accounted_for = dtw.to_set + Gem.path.map { |path| Pathname(path) }
|
162
|
+
dtw.reject { |dir| dir.ascend.drop(1).any? { |parent| accounted_for.include?(parent) } }
|
166
163
|
end
|
167
164
|
|
168
|
-
|
169
|
-
|
170
|
-
Pathname.new(path).expand_path
|
171
|
-
end
|
172
|
-
|
173
|
-
def normalize_extension(ext)
|
174
|
-
ext.to_s.sub(/\A\./, "")
|
175
|
-
end
|
176
|
-
|
177
|
-
# Given a collection of Pathname objects returns the longest subpath
|
178
|
-
# common to all of them, or +nil+ if there is none.
|
179
|
-
def longest_common_subpath(paths)
|
180
|
-
return if paths.empty?
|
181
|
-
|
182
|
-
lcsp = Pathname.new(paths[0])
|
183
|
-
|
184
|
-
paths[1..-1].each do |path|
|
185
|
-
until ascendant_of?(lcsp, path)
|
186
|
-
if lcsp.root?
|
187
|
-
# If we get here a root directory is not an ascendant of path.
|
188
|
-
# This may happen if there are paths in different drives on
|
189
|
-
# Windows.
|
190
|
-
return
|
191
|
-
else
|
192
|
-
lcsp = lcsp.parent
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
lcsp
|
198
|
-
end
|
199
|
-
|
200
|
-
# Returns the deepest existing ascendant, which could be the argument itself.
|
201
|
-
def existing_parent(dir)
|
202
|
-
dir.ascend do |ascendant|
|
203
|
-
break ascendant if ascendant.directory?
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
# Filters out directories which are descendants of others in the collection (stable).
|
208
|
-
def filter_out_descendants(dirs)
|
209
|
-
return dirs if dirs.length < 2
|
210
|
-
|
211
|
-
dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
|
212
|
-
descendants = []
|
213
|
-
|
214
|
-
until dirs_sorted_by_nparts.empty?
|
215
|
-
dir = dirs_sorted_by_nparts.shift
|
216
|
-
|
217
|
-
dirs_sorted_by_nparts.reject! do |possible_descendant|
|
218
|
-
ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
# Array#- preserves order.
|
223
|
-
dirs - descendants
|
224
|
-
end
|
225
|
-
|
226
|
-
private
|
227
|
-
def ascendant_of?(base, other)
|
228
|
-
base != other && other.ascend do |ascendant|
|
229
|
-
break true if base == ascendant
|
230
|
-
end
|
231
|
-
end
|
165
|
+
def common_path(paths)
|
166
|
+
paths.map { |path| path.ascend.to_a }.reduce(&:&)&.first
|
232
167
|
end
|
168
|
+
end
|
233
169
|
end
|
234
170
|
end
|