activesupport 1.2.4 → 8.1.2
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 +7 -0
- data/CHANGELOG.md +505 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +40 -0
- data/lib/active_support/actionable_error.rb +50 -0
- data/lib/active_support/all.rb +5 -0
- data/lib/active_support/array_inquirer.rb +50 -0
- data/lib/active_support/backtrace_cleaner.rb +234 -0
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +53 -0
- data/lib/active_support/broadcast_logger.rb +238 -0
- data/lib/active_support/builder.rb +8 -0
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +244 -0
- data/lib/active_support/cache/mem_cache_store.rb +288 -0
- data/lib/active_support/cache/memory_store.rb +264 -0
- data/lib/active_support/cache/null_store.rb +62 -0
- data/lib/active_support/cache/redis_cache_store.rb +498 -0
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +246 -0
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
- data/lib/active_support/cache.rb +1170 -0
- data/lib/active_support/callbacks.rb +960 -0
- data/lib/active_support/class_attribute.rb +33 -0
- data/lib/active_support/code_generator.rb +79 -0
- data/lib/active_support/concern.rb +217 -0
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/concurrency/share_lock.rb +225 -0
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +193 -0
- data/lib/active_support/configuration_file.rb +60 -0
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array/access.rb +100 -0
- data/lib/active_support/core_ext/array/conversions.rb +209 -26
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/extract_options.rb +31 -0
- data/lib/active_support/core_ext/array/grouping.rb +109 -0
- data/lib/active_support/core_ext/array/inquiry.rb +19 -0
- data/lib/active_support/core_ext/array/wrap.rb +48 -0
- data/lib/active_support/core_ext/array.rb +8 -4
- data/lib/active_support/core_ext/benchmark.rb +6 -0
- data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
- data/lib/active_support/core_ext/big_decimal.rb +3 -0
- data/lib/active_support/core_ext/class/attribute.rb +137 -0
- data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/class/subclasses.rb +24 -0
- data/lib/active_support/core_ext/class.rb +4 -0
- data/lib/active_support/core_ext/date/acts_like.rb +10 -0
- data/lib/active_support/core_ext/date/blank.rb +18 -0
- data/lib/active_support/core_ext/date/calculations.rb +161 -0
- data/lib/active_support/core_ext/date/conversions.rb +95 -28
- data/lib/active_support/core_ext/date/zones.rb +8 -0
- data/lib/active_support/core_ext/date.rb +6 -5
- data/lib/active_support/core_ext/date_and_time/calculations.rb +374 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +23 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
- data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
- data/lib/active_support/core_ext/date_time/blank.rb +18 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +215 -0
- data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +108 -0
- data/lib/active_support/core_ext/date_time.rb +7 -0
- data/lib/active_support/core_ext/digest/uuid.rb +76 -0
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +277 -7
- data/lib/active_support/core_ext/erb/util.rb +201 -0
- data/lib/active_support/core_ext/file/atomic.rb +72 -0
- data/lib/active_support/core_ext/file.rb +3 -0
- data/lib/active_support/core_ext/hash/conversions.rb +262 -0
- data/lib/active_support/core_ext/hash/deep_merge.rb +43 -0
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +12 -0
- data/lib/active_support/core_ext/hash/indifferent_access.rb +19 -55
- data/lib/active_support/core_ext/hash/keys.rb +134 -44
- data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -22
- data/lib/active_support/core_ext/hash/slice.rb +27 -0
- data/lib/active_support/core_ext/hash.rb +9 -8
- data/lib/active_support/core_ext/integer/inflections.rb +29 -13
- data/lib/active_support/core_ext/integer/multiple.rb +12 -0
- data/lib/active_support/core_ext/integer/time.rb +22 -0
- data/lib/active_support/core_ext/integer.rb +4 -6
- data/lib/active_support/core_ext/kernel/concern.rb +14 -0
- data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
- data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
- data/lib/active_support/core_ext/kernel.rb +4 -78
- data/lib/active_support/core_ext/load_error.rb +6 -35
- data/lib/active_support/core_ext/module/aliasing.rb +31 -0
- data/lib/active_support/core_ext/module/anonymous.rb +30 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +48 -0
- data/lib/active_support/core_ext/module/attribute_accessors.rb +214 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +175 -0
- data/lib/active_support/core_ext/module/concerning.rb +140 -0
- data/lib/active_support/core_ext/module/delegation.rb +225 -0
- data/lib/active_support/core_ext/module/deprecation.rb +25 -0
- data/lib/active_support/core_ext/module/introspection.rb +65 -0
- data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
- data/lib/active_support/core_ext/module/remove_method.rb +17 -0
- data/lib/active_support/core_ext/module.rb +13 -0
- data/lib/active_support/core_ext/name_error.rb +59 -0
- data/lib/active_support/core_ext/numeric/bytes.rb +73 -42
- data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
- data/lib/active_support/core_ext/numeric/time.rb +64 -57
- data/lib/active_support/core_ext/numeric.rb +4 -6
- data/lib/active_support/core_ext/object/acts_like.rb +45 -0
- data/lib/active_support/core_ext/object/blank.rb +199 -0
- data/lib/active_support/core_ext/object/conversions.rb +6 -0
- data/lib/active_support/core_ext/object/deep_dup.rb +71 -0
- data/lib/active_support/core_ext/object/duplicable.rb +69 -0
- data/lib/active_support/core_ext/object/inclusion.rb +37 -0
- data/lib/active_support/core_ext/object/instance_variables.rb +32 -0
- data/lib/active_support/core_ext/object/json.rb +267 -0
- data/lib/active_support/core_ext/object/to_param.rb +3 -0
- data/lib/active_support/core_ext/object/to_query.rb +93 -0
- data/lib/active_support/core_ext/object/try.rb +158 -0
- data/lib/active_support/core_ext/object/with.rb +46 -0
- data/lib/active_support/core_ext/object/with_options.rb +101 -0
- data/lib/active_support/core_ext/object.rb +17 -0
- data/lib/active_support/core_ext/pathname/blank.rb +20 -0
- data/lib/active_support/core_ext/pathname/existence.rb +23 -0
- data/lib/active_support/core_ext/pathname.rb +4 -0
- data/lib/active_support/core_ext/range/compare_range.rb +57 -0
- data/lib/active_support/core_ext/range/conversions.rb +58 -17
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +5 -4
- data/lib/active_support/core_ext/regexp.rb +14 -0
- data/lib/active_support/core_ext/securerandom.rb +57 -0
- data/lib/active_support/core_ext/string/access.rb +93 -56
- data/lib/active_support/core_ext/string/behavior.rb +8 -0
- data/lib/active_support/core_ext/string/conversions.rb +57 -16
- data/lib/active_support/core_ext/string/exclude.rb +13 -0
- data/lib/active_support/core_ext/string/filters.rb +151 -0
- data/lib/active_support/core_ext/string/indent.rb +45 -0
- data/lib/active_support/core_ext/string/inflections.rb +297 -54
- data/lib/active_support/core_ext/string/inquiry.rb +16 -0
- data/lib/active_support/core_ext/string/multibyte.rb +67 -0
- data/lib/active_support/core_ext/string/output_safety.rb +235 -0
- data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -18
- data/lib/active_support/core_ext/string/strip.rb +27 -0
- data/lib/active_support/core_ext/string/zones.rb +16 -0
- data/lib/active_support/core_ext/string.rb +14 -10
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/thread/backtrace/location.rb +7 -0
- data/lib/active_support/core_ext/time/acts_like.rb +10 -0
- data/lib/active_support/core_ext/time/calculations.rb +358 -153
- data/lib/active_support/core_ext/time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/time/conversions.rb +69 -30
- data/lib/active_support/core_ext/time/zones.rb +97 -0
- data/lib/active_support/core_ext/time.rb +6 -6
- data/lib/active_support/core_ext.rb +5 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +243 -0
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/delegation.rb +183 -0
- data/lib/active_support/dependencies/autoload.rb +72 -0
- data/lib/active_support/dependencies/interlock.rb +55 -0
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +84 -222
- data/lib/active_support/deprecation/behaviors.rb +148 -0
- data/lib/active_support/deprecation/constant_accessor.rb +74 -0
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +54 -0
- data/lib/active_support/deprecation/method_wrappers.rb +68 -0
- data/lib/active_support/deprecation/proxy_wrappers.rb +189 -0
- data/lib/active_support/deprecation/reporting.rb +162 -0
- data/lib/active_support/deprecation.rb +81 -0
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +112 -0
- data/lib/active_support/digest.rb +22 -0
- data/lib/active_support/duration/iso8601_parser.rb +123 -0
- data/lib/active_support/duration/iso8601_serializer.rb +64 -0
- data/lib/active_support/duration.rb +524 -0
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/encrypted_configuration.rb +126 -0
- data/lib/active_support/encrypted_file.rb +133 -0
- data/lib/active_support/environment_inquirer.rb +40 -0
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +318 -0
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +592 -0
- data/lib/active_support/evented_file_update_checker.rb +185 -0
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +110 -0
- data/lib/active_support/execution_wrapper.rb +150 -0
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/executor.rb +8 -0
- data/lib/active_support/file_update_checker.rb +166 -0
- data/lib/active_support/fork_tracker.rb +43 -0
- data/lib/active_support/gem_version.rb +17 -0
- data/lib/active_support/gzip.rb +41 -0
- data/lib/active_support/hash_with_indifferent_access.rb +464 -0
- data/lib/active_support/html_safe_translation.rb +56 -0
- data/lib/active_support/i18n.rb +17 -0
- data/lib/active_support/i18n_railtie.rb +140 -0
- data/lib/active_support/inflections.rb +68 -49
- data/lib/active_support/inflector/inflections.rb +290 -0
- data/lib/active_support/inflector/methods.rb +387 -0
- data/lib/active_support/inflector/transliterate.rb +147 -0
- data/lib/active_support/inflector.rb +7 -164
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +78 -0
- data/lib/active_support/json/encoding.rb +256 -0
- data/lib/active_support/json.rb +4 -0
- data/lib/active_support/key_generator.rb +66 -0
- data/lib/active_support/lazy_load_hooks.rb +107 -0
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/locale/en.yml +141 -0
- data/lib/active_support/log_subscriber/test_helper.rb +106 -0
- data/lib/active_support/log_subscriber.rb +188 -0
- data/lib/active_support/logger.rb +55 -0
- data/lib/active_support/logger_silence.rb +21 -0
- data/lib/active_support/logger_thread_safe_level.rb +50 -0
- data/lib/active_support/message_encryptor.rb +374 -0
- data/lib/active_support/message_encryptors.rb +193 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +310 -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 +377 -0
- data/lib/active_support/message_verifiers.rb +189 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +146 -0
- data/lib/active_support/messages/rotation_configuration.rb +23 -0
- data/lib/active_support/messages/rotation_coordinator.rb +102 -0
- data/lib/active_support/messages/rotator.rb +69 -0
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +188 -0
- data/lib/active_support/multibyte/unicode.rb +42 -0
- data/lib/active_support/multibyte.rb +27 -0
- data/lib/active_support/notifications/fanout.rb +467 -0
- data/lib/active_support/notifications/instrumenter.rb +240 -0
- data/lib/active_support/notifications.rb +281 -0
- data/lib/active_support/number_helper/number_converter.rb +190 -0
- data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
- data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
- data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
- data/lib/active_support/number_helper/rounding_helper.rb +46 -0
- data/lib/active_support/number_helper.rb +479 -0
- data/lib/active_support/option_merger.rb +38 -0
- data/lib/active_support/ordered_hash.rb +50 -0
- data/lib/active_support/ordered_options.rb +141 -25
- data/lib/active_support/parameter_filter.rb +157 -0
- data/lib/active_support/rails.rb +26 -0
- data/lib/active_support/railtie.rb +180 -0
- data/lib/active_support/reloader.rb +138 -0
- data/lib/active_support/rescuable.rb +176 -0
- data/lib/active_support/secure_compare_rotator.rb +58 -0
- data/lib/active_support/security_utils.rb +38 -0
- data/lib/active_support/string_inquirer.rb +35 -0
- data/lib/active_support/structured_event_subscriber.rb +99 -0
- data/lib/active_support/subscriber.rb +141 -0
- data/lib/active_support/syntax_error_proxy.rb +67 -0
- data/lib/active_support/tagged_logging.rb +157 -0
- data/lib/active_support/test_case.rb +365 -0
- data/lib/active_support/testing/assertions.rb +369 -0
- data/lib/active_support/testing/autorun.rb +10 -0
- data/lib/active_support/testing/constant_lookup.rb +51 -0
- data/lib/active_support/testing/constant_stubbing.rb +54 -0
- data/lib/active_support/testing/declarative.rb +28 -0
- data/lib/active_support/testing/deprecation.rb +82 -0
- data/lib/active_support/testing/error_reporter_assertions.rb +124 -0
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/file_fixtures.rb +38 -0
- data/lib/active_support/testing/isolation.rb +121 -0
- data/lib/active_support/testing/method_call_assertions.rb +69 -0
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +98 -0
- data/lib/active_support/testing/parallelization/worker.rb +107 -0
- data/lib/active_support/testing/parallelization.rb +79 -0
- data/lib/active_support/testing/parallelize_executor.rb +81 -0
- data/lib/active_support/testing/setup_and_teardown.rb +57 -0
- data/lib/active_support/testing/stream.rb +41 -0
- data/lib/active_support/testing/tagged_logging.rb +27 -0
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +273 -0
- data/lib/active_support/time.rb +20 -0
- data/lib/active_support/time_with_zone.rb +613 -0
- data/lib/active_support/values/time_zone.rb +599 -158
- data/lib/active_support/version.rb +7 -6
- data/lib/active_support/xml_mini/jdom.rb +175 -0
- data/lib/active_support/xml_mini/libxml.rb +80 -0
- data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
- data/lib/active_support/xml_mini/nokogiri.rb +83 -0
- data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
- data/lib/active_support/xml_mini/rexml.rb +137 -0
- data/lib/active_support/xml_mini.rb +212 -0
- data/lib/active_support.rb +122 -10
- metadata +524 -93
- data/CHANGELOG +0 -283
- data/lib/active_support/binding_of_caller.rb +0 -84
- data/lib/active_support/breakpoint.rb +0 -523
- data/lib/active_support/class_attribute_accessors.rb +0 -57
- data/lib/active_support/class_inheritable_attributes.rb +0 -117
- data/lib/active_support/clean_logger.rb +0 -36
- data/lib/active_support/core_ext/blank.rb +0 -38
- data/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb +0 -14
- data/lib/active_support/core_ext/cgi.rb +0 -5
- data/lib/active_support/core_ext/exception.rb +0 -29
- data/lib/active_support/core_ext/integer/even_odd.rb +0 -24
- data/lib/active_support/core_ext/object_and_class.rb +0 -44
- data/lib/active_support/module_attribute_accessors.rb +0 -57
- data/lib/active_support/whiny_nil.rb +0 -38
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/array/conversions"
|
|
4
|
+
require "active_support/core_ext/module/delegation"
|
|
5
|
+
require "active_support/core_ext/object/acts_like"
|
|
6
|
+
|
|
7
|
+
module ActiveSupport
|
|
8
|
+
# = Active Support \Duration
|
|
9
|
+
#
|
|
10
|
+
# Provides accurate date and time measurements using Date#advance and
|
|
11
|
+
# Time#advance, respectively. It mainly supports the methods on Numeric.
|
|
12
|
+
#
|
|
13
|
+
# 1.month.ago # equivalent to Time.now.advance(months: -1)
|
|
14
|
+
class Duration
|
|
15
|
+
class Scalar < Numeric # :nodoc:
|
|
16
|
+
attr_reader :value
|
|
17
|
+
delegate :to_i, :to_f, :to_s, to: :@value
|
|
18
|
+
|
|
19
|
+
def initialize(value)
|
|
20
|
+
@value = value
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def coerce(other)
|
|
24
|
+
[Scalar.new(other), self]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def -@
|
|
28
|
+
Scalar.new(-value)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def <=>(other)
|
|
32
|
+
if Scalar === other || Duration === other
|
|
33
|
+
value <=> other.value
|
|
34
|
+
elsif Numeric === other
|
|
35
|
+
value <=> other
|
|
36
|
+
else
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def +(other)
|
|
42
|
+
if Duration === other
|
|
43
|
+
seconds = value + other._parts.fetch(:seconds, 0)
|
|
44
|
+
new_parts = other._parts.merge(seconds: seconds)
|
|
45
|
+
new_value = value + other.value
|
|
46
|
+
|
|
47
|
+
Duration.new(new_value, new_parts, other.variable?)
|
|
48
|
+
else
|
|
49
|
+
calculate(:+, other)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def -(other)
|
|
54
|
+
if Duration === other
|
|
55
|
+
seconds = value - other._parts.fetch(:seconds, 0)
|
|
56
|
+
new_parts = other._parts.transform_values(&:-@)
|
|
57
|
+
new_parts = new_parts.merge(seconds: seconds)
|
|
58
|
+
new_value = value - other.value
|
|
59
|
+
|
|
60
|
+
Duration.new(new_value, new_parts, other.variable?)
|
|
61
|
+
else
|
|
62
|
+
calculate(:-, other)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def *(other)
|
|
67
|
+
if Duration === other
|
|
68
|
+
new_parts = other._parts.transform_values { |other_value| value * other_value }
|
|
69
|
+
new_value = value * other.value
|
|
70
|
+
|
|
71
|
+
Duration.new(new_value, new_parts, other.variable?)
|
|
72
|
+
else
|
|
73
|
+
calculate(:*, other)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def /(other)
|
|
78
|
+
if Duration === other
|
|
79
|
+
value / other.value
|
|
80
|
+
else
|
|
81
|
+
calculate(:/, other)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def %(other)
|
|
86
|
+
if Duration === other
|
|
87
|
+
Duration.build(value % other.value)
|
|
88
|
+
else
|
|
89
|
+
calculate(:%, other)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def variable? # :nodoc:
|
|
94
|
+
false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
def calculate(op, other)
|
|
99
|
+
if Scalar === other
|
|
100
|
+
Scalar.new(value.public_send(op, other.value))
|
|
101
|
+
elsif Numeric === other
|
|
102
|
+
Scalar.new(value.public_send(op, other))
|
|
103
|
+
else
|
|
104
|
+
raise_type_error(other)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def raise_type_error(other)
|
|
109
|
+
raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
SECONDS_PER_MINUTE = 60
|
|
114
|
+
SECONDS_PER_HOUR = 3600
|
|
115
|
+
SECONDS_PER_DAY = 86400
|
|
116
|
+
SECONDS_PER_WEEK = 604800
|
|
117
|
+
SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year
|
|
118
|
+
SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days)
|
|
119
|
+
|
|
120
|
+
PARTS_IN_SECONDS = {
|
|
121
|
+
seconds: 1,
|
|
122
|
+
minutes: SECONDS_PER_MINUTE,
|
|
123
|
+
hours: SECONDS_PER_HOUR,
|
|
124
|
+
days: SECONDS_PER_DAY,
|
|
125
|
+
weeks: SECONDS_PER_WEEK,
|
|
126
|
+
months: SECONDS_PER_MONTH,
|
|
127
|
+
years: SECONDS_PER_YEAR
|
|
128
|
+
}.freeze
|
|
129
|
+
|
|
130
|
+
PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
|
|
131
|
+
VARIABLE_PARTS = [:years, :months, :weeks, :days].freeze
|
|
132
|
+
|
|
133
|
+
attr_reader :value
|
|
134
|
+
|
|
135
|
+
autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
|
|
136
|
+
autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
|
|
137
|
+
|
|
138
|
+
class << self
|
|
139
|
+
# Creates a new Duration from string formatted according to ISO 8601 Duration.
|
|
140
|
+
#
|
|
141
|
+
# See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
|
|
142
|
+
# This method allows negative parts to be present in pattern.
|
|
143
|
+
# If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
|
|
144
|
+
def parse(iso8601duration)
|
|
145
|
+
parts = ISO8601Parser.new(iso8601duration).parse!
|
|
146
|
+
new(calculate_total_seconds(parts), parts)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def ===(other) # :nodoc:
|
|
150
|
+
other.is_a?(Duration)
|
|
151
|
+
rescue ::NoMethodError
|
|
152
|
+
false
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def seconds(value) # :nodoc:
|
|
156
|
+
new(value, { seconds: value }, false)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def minutes(value) # :nodoc:
|
|
160
|
+
new(value * SECONDS_PER_MINUTE, { minutes: value }, false)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def hours(value) # :nodoc:
|
|
164
|
+
new(value * SECONDS_PER_HOUR, { hours: value }, false)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def days(value) # :nodoc:
|
|
168
|
+
new(value * SECONDS_PER_DAY, { days: value }, true)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def weeks(value) # :nodoc:
|
|
172
|
+
new(value * SECONDS_PER_WEEK, { weeks: value }, true)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def months(value) # :nodoc:
|
|
176
|
+
new(value * SECONDS_PER_MONTH, { months: value }, true)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def years(value) # :nodoc:
|
|
180
|
+
new(value * SECONDS_PER_YEAR, { years: value }, true)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Creates a new Duration from a seconds value that is converted
|
|
184
|
+
# to the individual parts:
|
|
185
|
+
#
|
|
186
|
+
# ActiveSupport::Duration.build(31556952).parts # => {:years=>1}
|
|
187
|
+
# ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
|
|
188
|
+
#
|
|
189
|
+
def build(value)
|
|
190
|
+
unless value.is_a?(::Numeric)
|
|
191
|
+
raise TypeError, "can't build an #{self.name} from a #{value.class.name}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
parts = {}
|
|
195
|
+
remainder_sign = value <=> 0
|
|
196
|
+
remainder = value.round(9).abs
|
|
197
|
+
variable = false
|
|
198
|
+
|
|
199
|
+
PARTS.each do |part|
|
|
200
|
+
unless part == :seconds
|
|
201
|
+
part_in_seconds = PARTS_IN_SECONDS[part]
|
|
202
|
+
parts[part] = remainder.div(part_in_seconds) * remainder_sign
|
|
203
|
+
remainder %= part_in_seconds
|
|
204
|
+
|
|
205
|
+
unless parts[part].zero?
|
|
206
|
+
variable ||= VARIABLE_PARTS.include?(part)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end unless value == 0
|
|
210
|
+
|
|
211
|
+
parts[:seconds] = remainder * remainder_sign
|
|
212
|
+
|
|
213
|
+
new(value, parts, variable)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
private
|
|
217
|
+
def calculate_total_seconds(parts)
|
|
218
|
+
parts.inject(0) do |total, (part, value)|
|
|
219
|
+
total + value * PARTS_IN_SECONDS[part]
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
Delegation.generate(self, [:to_f, :positive?, :negative?, :zero?, :abs], to: :@value, as: Integer, nilable: false)
|
|
225
|
+
|
|
226
|
+
def initialize(value, parts, variable = nil) # :nodoc:
|
|
227
|
+
@value, @parts = value, parts
|
|
228
|
+
@parts.reject! { |k, v| v.zero? } unless value == 0
|
|
229
|
+
@parts.freeze
|
|
230
|
+
@variable = variable
|
|
231
|
+
|
|
232
|
+
if @variable.nil?
|
|
233
|
+
@variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) }
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Returns a copy of the parts hash that defines the duration.
|
|
238
|
+
#
|
|
239
|
+
# 5.minutes.parts # => {:minutes=>5}
|
|
240
|
+
# 3.years.parts # => {:years=>3}
|
|
241
|
+
def parts
|
|
242
|
+
@parts.dup
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def coerce(other) # :nodoc:
|
|
246
|
+
case other
|
|
247
|
+
when Scalar
|
|
248
|
+
[other, self]
|
|
249
|
+
when Duration
|
|
250
|
+
[Scalar.new(other.value), self]
|
|
251
|
+
else
|
|
252
|
+
[Scalar.new(other), self]
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Compares one Duration with another or a Numeric to this Duration.
|
|
257
|
+
# Numeric values are treated as seconds.
|
|
258
|
+
def <=>(other)
|
|
259
|
+
if Duration === other
|
|
260
|
+
value <=> other.value
|
|
261
|
+
elsif Numeric === other
|
|
262
|
+
value <=> other
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Adds another Duration or a Numeric to this Duration. Numeric values
|
|
267
|
+
# are treated as seconds.
|
|
268
|
+
def +(other)
|
|
269
|
+
if Duration === other
|
|
270
|
+
parts = @parts.merge(other._parts) do |_key, value, other_value|
|
|
271
|
+
value + other_value
|
|
272
|
+
end
|
|
273
|
+
Duration.new(value + other.value, parts, @variable || other.variable?)
|
|
274
|
+
else
|
|
275
|
+
seconds = @parts.fetch(:seconds, 0) + other
|
|
276
|
+
Duration.new(value + other, @parts.merge(seconds: seconds), @variable)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Subtracts another Duration or a Numeric from this Duration. Numeric
|
|
281
|
+
# values are treated as seconds.
|
|
282
|
+
def -(other)
|
|
283
|
+
self + (-other)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Multiplies this Duration by a Numeric and returns a new Duration.
|
|
287
|
+
def *(other)
|
|
288
|
+
if Scalar === other || Duration === other
|
|
289
|
+
Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?)
|
|
290
|
+
elsif Numeric === other
|
|
291
|
+
Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable)
|
|
292
|
+
else
|
|
293
|
+
raise_type_error(other)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Divides this Duration by a Numeric and returns a new Duration.
|
|
298
|
+
def /(other)
|
|
299
|
+
if Scalar === other
|
|
300
|
+
Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable)
|
|
301
|
+
elsif Duration === other
|
|
302
|
+
value / other.value
|
|
303
|
+
elsif Numeric === other
|
|
304
|
+
Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable)
|
|
305
|
+
else
|
|
306
|
+
raise_type_error(other)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Returns the modulo of this Duration by another Duration or Numeric.
|
|
311
|
+
# Numeric values are treated as seconds.
|
|
312
|
+
def %(other)
|
|
313
|
+
if Duration === other || Scalar === other
|
|
314
|
+
Duration.build(value % other.value)
|
|
315
|
+
elsif Numeric === other
|
|
316
|
+
Duration.build(value % other)
|
|
317
|
+
else
|
|
318
|
+
raise_type_error(other)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def -@ # :nodoc:
|
|
323
|
+
Duration.new(-value, @parts.transform_values(&:-@), @variable)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def +@ # :nodoc:
|
|
327
|
+
self
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def is_a?(klass) # :nodoc:
|
|
331
|
+
Duration == klass || value.is_a?(klass)
|
|
332
|
+
end
|
|
333
|
+
alias :kind_of? :is_a?
|
|
334
|
+
|
|
335
|
+
def instance_of?(klass) # :nodoc:
|
|
336
|
+
Duration == klass || value.instance_of?(klass)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Returns +true+ if +other+ is also a Duration instance with the
|
|
340
|
+
# same +value+, or if <tt>other == value</tt>.
|
|
341
|
+
def ==(other)
|
|
342
|
+
if Duration === other
|
|
343
|
+
other.value == value
|
|
344
|
+
else
|
|
345
|
+
other == value
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Returns the amount of seconds a duration covers as a string.
|
|
350
|
+
# For more information check to_i method.
|
|
351
|
+
#
|
|
352
|
+
# 1.day.to_s # => "86400"
|
|
353
|
+
def to_s
|
|
354
|
+
@value.to_s
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Returns the number of seconds that this Duration represents.
|
|
358
|
+
#
|
|
359
|
+
# 1.minute.to_i # => 60
|
|
360
|
+
# 1.hour.to_i # => 3600
|
|
361
|
+
# 1.day.to_i # => 86400
|
|
362
|
+
#
|
|
363
|
+
# Note that this conversion makes some assumptions about the
|
|
364
|
+
# duration of some periods, e.g. months are always 1/12 of year
|
|
365
|
+
# and years are 365.2425 days:
|
|
366
|
+
#
|
|
367
|
+
# # equivalent to (1.year / 12).to_i
|
|
368
|
+
# 1.month.to_i # => 2629746
|
|
369
|
+
#
|
|
370
|
+
# # equivalent to 365.2425.days.to_i
|
|
371
|
+
# 1.year.to_i # => 31556952
|
|
372
|
+
#
|
|
373
|
+
# In such cases, Ruby's core
|
|
374
|
+
# Date[https://docs.ruby-lang.org/en/master/Date.html] and
|
|
375
|
+
# Time[https://docs.ruby-lang.org/en/master/Time.html] should be used for precision
|
|
376
|
+
# date and time arithmetic.
|
|
377
|
+
def to_i
|
|
378
|
+
@value.to_i
|
|
379
|
+
end
|
|
380
|
+
alias :in_seconds :to_i
|
|
381
|
+
|
|
382
|
+
# Returns the amount of minutes a duration covers as a float
|
|
383
|
+
#
|
|
384
|
+
# 1.day.in_minutes # => 1440.0
|
|
385
|
+
def in_minutes
|
|
386
|
+
in_seconds / SECONDS_PER_MINUTE.to_f
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Returns the amount of hours a duration covers as a float
|
|
390
|
+
#
|
|
391
|
+
# 1.day.in_hours # => 24.0
|
|
392
|
+
def in_hours
|
|
393
|
+
in_seconds / SECONDS_PER_HOUR.to_f
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Returns the amount of days a duration covers as a float
|
|
397
|
+
#
|
|
398
|
+
# 12.hours.in_days # => 0.5
|
|
399
|
+
def in_days
|
|
400
|
+
in_seconds / SECONDS_PER_DAY.to_f
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Returns the amount of weeks a duration covers as a float
|
|
404
|
+
#
|
|
405
|
+
# 2.months.in_weeks # => 8.696
|
|
406
|
+
def in_weeks
|
|
407
|
+
in_seconds / SECONDS_PER_WEEK.to_f
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Returns the amount of months a duration covers as a float
|
|
411
|
+
#
|
|
412
|
+
# 9.weeks.in_months # => 2.07
|
|
413
|
+
def in_months
|
|
414
|
+
in_seconds / SECONDS_PER_MONTH.to_f
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Returns the amount of years a duration covers as a float
|
|
418
|
+
#
|
|
419
|
+
# 30.days.in_years # => 0.082
|
|
420
|
+
def in_years
|
|
421
|
+
in_seconds / SECONDS_PER_YEAR.to_f
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Returns +true+ if +other+ is also a Duration instance, which has the
|
|
425
|
+
# same parts as this one.
|
|
426
|
+
def eql?(other)
|
|
427
|
+
Duration === other && other.value.eql?(value)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def hash
|
|
431
|
+
@value.hash
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Calculates a new Time or Date that is as far in the future
|
|
435
|
+
# as this Duration represents.
|
|
436
|
+
def since(time = ::Time.current)
|
|
437
|
+
sum(1, time)
|
|
438
|
+
end
|
|
439
|
+
alias :from_now :since
|
|
440
|
+
alias :after :since
|
|
441
|
+
|
|
442
|
+
# Calculates a new Time or Date that is as far in the past
|
|
443
|
+
# as this Duration represents.
|
|
444
|
+
def ago(time = ::Time.current)
|
|
445
|
+
sum(-1, time)
|
|
446
|
+
end
|
|
447
|
+
alias :until :ago
|
|
448
|
+
alias :before :ago
|
|
449
|
+
|
|
450
|
+
def inspect # :nodoc:
|
|
451
|
+
return "#{value} seconds" if @parts.empty?
|
|
452
|
+
|
|
453
|
+
@parts.
|
|
454
|
+
sort_by { |unit, _ | PARTS.index(unit) }.
|
|
455
|
+
map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
|
|
456
|
+
to_sentence(locale: false)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def as_json(options = nil) # :nodoc:
|
|
460
|
+
to_i
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def init_with(coder) # :nodoc:
|
|
464
|
+
initialize(coder["value"], coder["parts"])
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def encode_with(coder) # :nodoc:
|
|
468
|
+
coder.map = { "value" => @value, "parts" => @parts }
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# Build ISO 8601 Duration string for this duration.
|
|
472
|
+
# The +precision+ parameter can be used to limit seconds' precision of duration.
|
|
473
|
+
def iso8601(precision: nil)
|
|
474
|
+
ISO8601Serializer.new(self, precision: precision).serialize
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def variable? # :nodoc:
|
|
478
|
+
@variable
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def _parts # :nodoc:
|
|
482
|
+
@parts
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
private
|
|
486
|
+
def sum(sign, time = ::Time.current)
|
|
487
|
+
unless time.acts_like?(:time) || time.acts_like?(:date)
|
|
488
|
+
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
if @parts.empty?
|
|
492
|
+
time.since(sign * value)
|
|
493
|
+
else
|
|
494
|
+
@parts.each do |type, number|
|
|
495
|
+
t = time
|
|
496
|
+
time =
|
|
497
|
+
if type == :seconds
|
|
498
|
+
t.since(sign * number)
|
|
499
|
+
elsif type == :minutes
|
|
500
|
+
t.since(sign * number * 60)
|
|
501
|
+
elsif type == :hours
|
|
502
|
+
t.since(sign * number * 3600)
|
|
503
|
+
else
|
|
504
|
+
t.advance(type => sign * number)
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
time
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def respond_to_missing?(method, _)
|
|
513
|
+
value.respond_to?(method)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def method_missing(...)
|
|
517
|
+
value.public_send(...)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def raise_type_error(other)
|
|
521
|
+
raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
module ActiveSupport
|
|
6
|
+
class Editor
|
|
7
|
+
@editors = {}
|
|
8
|
+
@current = false
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# Registers a URL pattern for opening file in a given editor.
|
|
12
|
+
# This allows Rails to generate clickable links to control known editors.
|
|
13
|
+
#
|
|
14
|
+
# Example:
|
|
15
|
+
#
|
|
16
|
+
# ActiveSupport::Editor.register("myeditor", "myeditor://%s:%d")
|
|
17
|
+
def register(name, url_pattern, aliases: [])
|
|
18
|
+
editor = new(url_pattern)
|
|
19
|
+
@editors[name] = editor
|
|
20
|
+
aliases.each do |a|
|
|
21
|
+
@editors[a] = editor
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns the current editor pattern if it is known.
|
|
26
|
+
# First check for the `RAILS_EDITOR` environment variable, and if it's
|
|
27
|
+
# missing, check for the `EDITOR` environment variable.
|
|
28
|
+
def current
|
|
29
|
+
if @current == false
|
|
30
|
+
@current = if editor_name = ENV["RAILS_EDITOR"] || ENV["EDITOR"]
|
|
31
|
+
@editors[editor_name]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
@current
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# :nodoc:
|
|
38
|
+
|
|
39
|
+
def find(name)
|
|
40
|
+
@editors[name]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def reset
|
|
44
|
+
@current = false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(url_pattern)
|
|
49
|
+
@url_pattern = url_pattern
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def url_for(path, line)
|
|
53
|
+
sprintf(@url_pattern, path, line)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
register "atom", "atom://core/open/file?filename=%s&line=%d"
|
|
57
|
+
register "cursor", "cursor://file/%s:%f"
|
|
58
|
+
register "emacs", "emacs://open?url=file://%s&line=%d", aliases: ["emacsclient"]
|
|
59
|
+
register "idea", "idea://open?file=%s&line=%d"
|
|
60
|
+
register "macvim", "mvim://open?url=file://%s&line=%d", aliases: ["mvim"]
|
|
61
|
+
register "nova", "nova://open?path=%s&line=%d"
|
|
62
|
+
register "rubymine", "x-mine://open?file=%s&line=%d"
|
|
63
|
+
register "sublime", "subl://open?url=file://%s&line=%d", aliases: ["subl"]
|
|
64
|
+
register "textmate", "txmt://open?url=file://%s&line=%d", aliases: ["mate"]
|
|
65
|
+
register "vscode", "vscode://file/%s:%d", aliases: ["code"]
|
|
66
|
+
register "vscodium", "vscodium://file/%s:%d", aliases: ["codium"]
|
|
67
|
+
register "windsurf", "windsurf://file/%s:%d"
|
|
68
|
+
register "zed", "zed://file/%s:%d"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "active_support/encrypted_file"
|
|
5
|
+
require "active_support/ordered_options"
|
|
6
|
+
require "active_support/core_ext/object/inclusion"
|
|
7
|
+
require "active_support/core_ext/hash/keys"
|
|
8
|
+
require "active_support/core_ext/module/delegation"
|
|
9
|
+
|
|
10
|
+
module ActiveSupport
|
|
11
|
+
# = Encrypted Configuration
|
|
12
|
+
#
|
|
13
|
+
# Provides convenience methods on top of EncryptedFile to access values stored
|
|
14
|
+
# as encrypted YAML.
|
|
15
|
+
#
|
|
16
|
+
# Values can be accessed via +Hash+ methods, such as +fetch+ and +dig+, or via
|
|
17
|
+
# dynamic accessor methods, similar to OrderedOptions.
|
|
18
|
+
#
|
|
19
|
+
# my_config = ActiveSupport::EncryptedConfiguration.new(...)
|
|
20
|
+
# my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456"
|
|
21
|
+
#
|
|
22
|
+
# my_config[:some_secret]
|
|
23
|
+
# # => 123
|
|
24
|
+
# my_config.some_secret
|
|
25
|
+
# # => 123
|
|
26
|
+
# my_config.dig(:some_namespace, :another_secret)
|
|
27
|
+
# # => 456
|
|
28
|
+
# my_config.some_namespace.another_secret
|
|
29
|
+
# # => 456
|
|
30
|
+
# my_config.fetch(:foo)
|
|
31
|
+
# # => KeyError
|
|
32
|
+
# my_config.foo!
|
|
33
|
+
# # => KeyError
|
|
34
|
+
#
|
|
35
|
+
class EncryptedConfiguration < EncryptedFile
|
|
36
|
+
class InvalidContentError < RuntimeError
|
|
37
|
+
def initialize(content_path)
|
|
38
|
+
super "Invalid YAML in '#{content_path}'."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def message
|
|
42
|
+
cause.is_a?(Psych::SyntaxError) ? "#{super}\n\n #{cause.message}" : super
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class InvalidKeyError < RuntimeError
|
|
47
|
+
def initialize(content_path, key)
|
|
48
|
+
super "Key '#{key}' is invalid, it must respond to '#to_sym' from configuration in '#{content_path}'."
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
delegate_missing_to :options
|
|
53
|
+
|
|
54
|
+
def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
|
|
55
|
+
super content_path: config_path, key_path: key_path,
|
|
56
|
+
env_key: env_key, raise_if_missing_key: raise_if_missing_key
|
|
57
|
+
@config = nil
|
|
58
|
+
@options = nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Reads the file and returns the decrypted content. See EncryptedFile#read.
|
|
62
|
+
def read
|
|
63
|
+
super
|
|
64
|
+
rescue ActiveSupport::EncryptedFile::MissingContentError
|
|
65
|
+
# Allow a config to be started without a file present
|
|
66
|
+
""
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate! # :nodoc:
|
|
70
|
+
deserialize(read).each_key do |key|
|
|
71
|
+
key.to_sym
|
|
72
|
+
rescue NoMethodError
|
|
73
|
+
raise InvalidKeyError.new(content_path, key)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Returns the decrypted content as a Hash with symbolized keys.
|
|
78
|
+
#
|
|
79
|
+
# my_config = ActiveSupport::EncryptedConfiguration.new(...)
|
|
80
|
+
# my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456"
|
|
81
|
+
#
|
|
82
|
+
# my_config.config
|
|
83
|
+
# # => { some_secret: 123, some_namespace: { another_secret: 789 } }
|
|
84
|
+
#
|
|
85
|
+
def config
|
|
86
|
+
@config ||= deep_symbolize_keys(deserialize(read))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def inspect # :nodoc:
|
|
90
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
def deep_symbolize_keys(hash)
|
|
95
|
+
hash.deep_transform_keys do |key|
|
|
96
|
+
key.to_sym
|
|
97
|
+
rescue NoMethodError
|
|
98
|
+
raise InvalidKeyError.new(content_path, key)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def deep_transform(hash)
|
|
103
|
+
return hash unless hash.is_a?(Hash)
|
|
104
|
+
|
|
105
|
+
h = ActiveSupport::OrderedOptions.new
|
|
106
|
+
hash.each do |k, v|
|
|
107
|
+
h[k] = deep_transform(v)
|
|
108
|
+
end
|
|
109
|
+
h
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def options
|
|
113
|
+
@options ||= deep_transform(config)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def deserialize(content)
|
|
117
|
+
config = YAML.respond_to?(:unsafe_load) ?
|
|
118
|
+
YAML.unsafe_load(content, filename: content_path) :
|
|
119
|
+
YAML.load(content, filename: content_path)
|
|
120
|
+
|
|
121
|
+
config.presence || {}
|
|
122
|
+
rescue Psych::SyntaxError
|
|
123
|
+
raise InvalidContentError.new(content_path)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|