activesupport 4.2.0 → 5.2.0
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 +5 -5
- data/CHANGELOG.md +366 -232
- data/MIT-LICENSE +2 -2
- data/README.rdoc +4 -5
- data/lib/active_support.rb +17 -7
- data/lib/active_support/all.rb +5 -3
- data/lib/active_support/array_inquirer.rb +48 -0
- data/lib/active_support/backtrace_cleaner.rb +7 -5
- data/lib/active_support/benchmarkable.rb +6 -4
- data/lib/active_support/builder.rb +3 -1
- data/lib/active_support/cache.rb +271 -177
- data/lib/active_support/cache/file_store.rb +41 -35
- data/lib/active_support/cache/mem_cache_store.rb +97 -88
- data/lib/active_support/cache/memory_store.rb +27 -30
- data/lib/active_support/cache/null_store.rb +7 -8
- data/lib/active_support/cache/redis_cache_store.rb +454 -0
- data/lib/active_support/cache/strategy/local_cache.rb +67 -34
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
- data/lib/active_support/callbacks.rb +654 -560
- data/lib/active_support/concern.rb +5 -3
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
- data/lib/active_support/concurrency/share_lock.rb +227 -0
- data/lib/active_support/configurable.rb +8 -5
- data/lib/active_support/core_ext.rb +3 -1
- data/lib/active_support/core_ext/array.rb +9 -6
- data/lib/active_support/core_ext/array/access.rb +29 -1
- data/lib/active_support/core_ext/array/conversions.rb +22 -18
- data/lib/active_support/core_ext/array/extract_options.rb +2 -0
- data/lib/active_support/core_ext/array/grouping.rb +11 -18
- data/lib/active_support/core_ext/array/inquiry.rb +19 -0
- data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -3
- data/lib/active_support/core_ext/array/wrap.rb +7 -4
- data/lib/active_support/core_ext/benchmark.rb +3 -1
- data/lib/active_support/core_ext/big_decimal.rb +3 -1
- data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
- data/lib/active_support/core_ext/class.rb +4 -3
- data/lib/active_support/core_ext/class/attribute.rb +41 -22
- data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
- data/lib/active_support/core_ext/class/subclasses.rb +20 -8
- data/lib/active_support/core_ext/date.rb +6 -4
- data/lib/active_support/core_ext/date/acts_like.rb +3 -1
- data/lib/active_support/core_ext/date/blank.rb +14 -0
- data/lib/active_support/core_ext/date/calculations.rb +11 -9
- data/lib/active_support/core_ext/date/conversions.rb +31 -23
- data/lib/active_support/core_ext/date/zones.rb +4 -2
- data/lib/active_support/core_ext/date_and_time/calculations.rb +179 -56
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +12 -12
- data/lib/active_support/core_ext/date_time.rb +7 -4
- data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
- data/lib/active_support/core_ext/date_time/blank.rb +14 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +58 -20
- data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +16 -12
- data/lib/active_support/core_ext/digest/uuid.rb +7 -5
- data/lib/active_support/core_ext/enumerable.rb +107 -28
- data/lib/active_support/core_ext/file.rb +3 -1
- data/lib/active_support/core_ext/file/atomic.rb +38 -31
- data/lib/active_support/core_ext/hash.rb +11 -9
- data/lib/active_support/core_ext/hash/compact.rb +24 -15
- data/lib/active_support/core_ext/hash/conversions.rb +63 -43
- data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
- data/lib/active_support/core_ext/hash/except.rb +11 -8
- data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
- data/lib/active_support/core_ext/hash/keys.rb +33 -27
- data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
- data/lib/active_support/core_ext/hash/slice.rb +8 -8
- data/lib/active_support/core_ext/hash/transform_values.rb +16 -7
- data/lib/active_support/core_ext/integer.rb +5 -3
- data/lib/active_support/core_ext/integer/inflections.rb +3 -1
- data/lib/active_support/core_ext/integer/multiple.rb +2 -0
- data/lib/active_support/core_ext/integer/time.rb +11 -33
- data/lib/active_support/core_ext/kernel.rb +6 -5
- data/lib/active_support/core_ext/kernel/agnostics.rb +2 -0
- data/lib/active_support/core_ext/kernel/concern.rb +5 -1
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -83
- data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
- data/lib/active_support/core_ext/load_error.rb +3 -22
- data/lib/active_support/core_ext/marshal.rb +13 -10
- data/lib/active_support/core_ext/module.rb +14 -11
- data/lib/active_support/core_ext/module/aliasing.rb +6 -44
- data/lib/active_support/core_ext/module/anonymous.rb +12 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +8 -9
- data/lib/active_support/core_ext/module/attribute_accessors.rb +43 -40
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +150 -0
- data/lib/active_support/core_ext/module/concerning.rb +11 -12
- data/lib/active_support/core_ext/module/delegation.rb +121 -39
- data/lib/active_support/core_ext/module/deprecation.rb +4 -2
- data/lib/active_support/core_ext/module/introspection.rb +9 -9
- data/lib/active_support/core_ext/module/reachable.rb +5 -2
- data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
- data/lib/active_support/core_ext/module/remove_method.rb +8 -3
- data/lib/active_support/core_ext/name_error.rb +22 -2
- data/lib/active_support/core_ext/numeric.rb +6 -3
- data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +79 -74
- data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
- data/lib/active_support/core_ext/numeric/time.rb +35 -38
- data/lib/active_support/core_ext/object.rb +14 -13
- data/lib/active_support/core_ext/object/acts_like.rb +12 -1
- data/lib/active_support/core_ext/object/blank.rb +29 -4
- data/lib/active_support/core_ext/object/conversions.rb +6 -4
- data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
- data/lib/active_support/core_ext/object/duplicable.rb +98 -45
- data/lib/active_support/core_ext/object/inclusion.rb +5 -3
- data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
- data/lib/active_support/core_ext/object/json.rb +49 -19
- data/lib/active_support/core_ext/object/to_param.rb +3 -1
- data/lib/active_support/core_ext/object/to_query.rb +6 -4
- data/lib/active_support/core_ext/object/try.rb +70 -22
- data/lib/active_support/core_ext/object/with_options.rb +16 -3
- data/lib/active_support/core_ext/range.rb +7 -4
- data/lib/active_support/core_ext/range/conversions.rb +27 -7
- data/lib/active_support/core_ext/range/each.rb +19 -17
- data/lib/active_support/core_ext/range/include_range.rb +21 -19
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
- data/lib/active_support/core_ext/range/overlaps.rb +2 -0
- data/lib/active_support/core_ext/regexp.rb +6 -0
- data/lib/active_support/core_ext/securerandom.rb +25 -0
- data/lib/active_support/core_ext/string.rb +15 -13
- data/lib/active_support/core_ext/string/access.rb +9 -7
- data/lib/active_support/core_ext/string/behavior.rb +3 -1
- data/lib/active_support/core_ext/string/conversions.rb +8 -5
- data/lib/active_support/core_ext/string/exclude.rb +2 -0
- data/lib/active_support/core_ext/string/filters.rb +10 -8
- data/lib/active_support/core_ext/string/indent.rb +6 -4
- data/lib/active_support/core_ext/string/inflections.rb +61 -24
- data/lib/active_support/core_ext/string/inquiry.rb +3 -1
- data/lib/active_support/core_ext/string/multibyte.rb +15 -7
- data/lib/active_support/core_ext/string/output_safety.rb +35 -35
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
- data/lib/active_support/core_ext/string/strip.rb +4 -5
- data/lib/active_support/core_ext/string/zones.rb +4 -2
- data/lib/active_support/core_ext/time.rb +7 -5
- data/lib/active_support/core_ext/time/acts_like.rb +3 -1
- data/lib/active_support/core_ext/time/calculations.rb +101 -51
- data/lib/active_support/core_ext/time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/time/conversions.rb +20 -13
- data/lib/active_support/core_ext/time/zones.rb +41 -7
- data/lib/active_support/core_ext/uri.rb +5 -4
- data/lib/active_support/current_attributes.rb +195 -0
- data/lib/active_support/dependencies.rb +143 -160
- data/lib/active_support/dependencies/autoload.rb +2 -0
- data/lib/active_support/dependencies/interlock.rb +57 -0
- data/lib/active_support/deprecation.rb +12 -9
- data/lib/active_support/deprecation/behaviors.rb +41 -12
- data/lib/active_support/deprecation/constant_accessor.rb +52 -0
- data/lib/active_support/deprecation/instance_delegator.rb +17 -2
- data/lib/active_support/deprecation/method_wrappers.rb +54 -21
- data/lib/active_support/deprecation/proxy_wrappers.rb +56 -28
- data/lib/active_support/deprecation/reporting.rb +32 -12
- data/lib/active_support/descendants_tracker.rb +2 -0
- data/lib/active_support/digest.rb +20 -0
- data/lib/active_support/duration.rb +326 -30
- data/lib/active_support/duration/iso8601_parser.rb +125 -0
- data/lib/active_support/duration/iso8601_serializer.rb +55 -0
- data/lib/active_support/encrypted_configuration.rb +49 -0
- data/lib/active_support/encrypted_file.rb +99 -0
- data/lib/active_support/evented_file_update_checker.rb +205 -0
- data/lib/active_support/execution_wrapper.rb +128 -0
- data/lib/active_support/executor.rb +8 -0
- data/lib/active_support/file_update_checker.rb +63 -37
- data/lib/active_support/gem_version.rb +4 -2
- data/lib/active_support/gzip.rb +7 -5
- data/lib/active_support/hash_with_indifferent_access.rb +130 -30
- data/lib/active_support/i18n.rb +8 -6
- data/lib/active_support/i18n_railtie.rb +34 -14
- data/lib/active_support/inflections.rb +13 -11
- data/lib/active_support/inflector.rb +7 -5
- data/lib/active_support/inflector/inflections.rb +61 -12
- data/lib/active_support/inflector/methods.rb +161 -136
- data/lib/active_support/inflector/transliterate.rb +48 -27
- data/lib/active_support/json.rb +4 -2
- data/lib/active_support/json/decoding.rb +16 -13
- data/lib/active_support/json/encoding.rb +15 -57
- data/lib/active_support/key_generator.rb +25 -25
- data/lib/active_support/lazy_load_hooks.rb +50 -20
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber.rb +13 -10
- data/lib/active_support/log_subscriber/test_helper.rb +14 -12
- data/lib/active_support/logger.rb +54 -3
- data/lib/active_support/logger_silence.rb +12 -7
- data/lib/active_support/logger_thread_safe_level.rb +33 -0
- data/lib/active_support/message_encryptor.rb +173 -51
- data/lib/active_support/message_verifier.rb +150 -17
- data/lib/active_support/messages/metadata.rb +71 -0
- data/lib/active_support/messages/rotation_configuration.rb +22 -0
- data/lib/active_support/messages/rotator.rb +56 -0
- data/lib/active_support/multibyte.rb +4 -2
- data/lib/active_support/multibyte/chars.rb +37 -24
- data/lib/active_support/multibyte/unicode.rb +100 -96
- data/lib/active_support/notifications.rb +11 -7
- data/lib/active_support/notifications/fanout.rb +10 -8
- data/lib/active_support/notifications/instrumenter.rb +27 -7
- data/lib/active_support/number_helper.rb +94 -68
- data/lib/active_support/number_helper/number_converter.rb +13 -11
- data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +9 -3
- data/lib/active_support/number_helper/number_to_human_converter.rb +11 -9
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +9 -8
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +13 -4
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +23 -56
- data/lib/active_support/number_helper/rounding_helper.rb +66 -0
- data/lib/active_support/option_merger.rb +3 -1
- data/lib/active_support/ordered_hash.rb +6 -4
- data/lib/active_support/ordered_options.rb +22 -4
- data/lib/active_support/per_thread_registry.rb +13 -6
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +16 -8
- data/lib/active_support/railtie.rb +43 -9
- data/lib/active_support/reloader.rb +131 -0
- data/lib/active_support/rescuable.rb +108 -53
- data/lib/active_support/security_utils.rb +17 -6
- data/lib/active_support/string_inquirer.rb +11 -3
- data/lib/active_support/subscriber.rb +15 -14
- data/lib/active_support/tagged_logging.rb +14 -11
- data/lib/active_support/test_case.rb +18 -46
- data/lib/active_support/testing/assertions.rb +137 -20
- data/lib/active_support/testing/autorun.rb +4 -2
- data/lib/active_support/testing/constant_lookup.rb +2 -1
- data/lib/active_support/testing/declarative.rb +3 -1
- data/lib/active_support/testing/deprecation.rb +14 -10
- data/lib/active_support/testing/file_fixtures.rb +36 -0
- data/lib/active_support/testing/isolation.rb +34 -25
- data/lib/active_support/testing/method_call_assertions.rb +43 -0
- data/lib/active_support/testing/setup_and_teardown.rb +12 -3
- data/lib/active_support/testing/stream.rb +44 -0
- data/lib/active_support/testing/tagged_logging.rb +3 -1
- data/lib/active_support/testing/time_helpers.rb +96 -27
- data/lib/active_support/time.rb +14 -12
- data/lib/active_support/time_with_zone.rb +195 -53
- data/lib/active_support/values/time_zone.rb +200 -61
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/version.rb +3 -1
- data/lib/active_support/xml_mini.rb +69 -51
- data/lib/active_support/xml_mini/jdom.rb +116 -113
- data/lib/active_support/xml_mini/libxml.rb +17 -16
- data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
- data/lib/active_support/xml_mini/nokogiri.rb +15 -15
- data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
- data/lib/active_support/xml_mini/rexml.rb +17 -16
- metadata +55 -43
- data/lib/active_support/concurrency/latch.rb +0 -27
- data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -14
- data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
- data/lib/active_support/core_ext/date_time/zones.rb +0 -6
- data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
- data/lib/active_support/core_ext/module/method_transplanting.rb +0 -11
- data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
- data/lib/active_support/core_ext/object/itself.rb +0 -15
- data/lib/active_support/core_ext/struct.rb +0 -6
- data/lib/active_support/core_ext/thread.rb +0 -86
- data/lib/active_support/core_ext/time/marshal.rb +0 -30
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "strscan"
|
4
|
+
require "active_support/core_ext/regexp"
|
5
|
+
|
6
|
+
module ActiveSupport
|
7
|
+
class Duration
|
8
|
+
# Parses a string formatted according to ISO 8601 Duration into the hash.
|
9
|
+
#
|
10
|
+
# See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
|
11
|
+
#
|
12
|
+
# This parser allows negative parts to be present in pattern.
|
13
|
+
class ISO8601Parser # :nodoc:
|
14
|
+
class ParsingError < ::ArgumentError; end
|
15
|
+
|
16
|
+
PERIOD_OR_COMMA = /\.|,/
|
17
|
+
PERIOD = ".".freeze
|
18
|
+
COMMA = ",".freeze
|
19
|
+
|
20
|
+
SIGN_MARKER = /\A\-|\+|/
|
21
|
+
DATE_MARKER = /P/
|
22
|
+
TIME_MARKER = /T/
|
23
|
+
DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
|
24
|
+
TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/
|
25
|
+
|
26
|
+
DATE_TO_PART = { "Y" => :years, "M" => :months, "W" => :weeks, "D" => :days }
|
27
|
+
TIME_TO_PART = { "H" => :hours, "M" => :minutes, "S" => :seconds }
|
28
|
+
|
29
|
+
DATE_COMPONENTS = [:years, :months, :days]
|
30
|
+
TIME_COMPONENTS = [:hours, :minutes, :seconds]
|
31
|
+
|
32
|
+
attr_reader :parts, :scanner
|
33
|
+
attr_accessor :mode, :sign
|
34
|
+
|
35
|
+
def initialize(string)
|
36
|
+
@scanner = StringScanner.new(string)
|
37
|
+
@parts = {}
|
38
|
+
@mode = :start
|
39
|
+
@sign = 1
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse!
|
43
|
+
while !finished?
|
44
|
+
case mode
|
45
|
+
when :start
|
46
|
+
if scan(SIGN_MARKER)
|
47
|
+
self.sign = (scanner.matched == "-") ? -1 : 1
|
48
|
+
self.mode = :sign
|
49
|
+
else
|
50
|
+
raise_parsing_error
|
51
|
+
end
|
52
|
+
|
53
|
+
when :sign
|
54
|
+
if scan(DATE_MARKER)
|
55
|
+
self.mode = :date
|
56
|
+
else
|
57
|
+
raise_parsing_error
|
58
|
+
end
|
59
|
+
|
60
|
+
when :date
|
61
|
+
if scan(TIME_MARKER)
|
62
|
+
self.mode = :time
|
63
|
+
elsif scan(DATE_COMPONENT)
|
64
|
+
parts[DATE_TO_PART[scanner[2]]] = number * sign
|
65
|
+
else
|
66
|
+
raise_parsing_error
|
67
|
+
end
|
68
|
+
|
69
|
+
when :time
|
70
|
+
if scan(TIME_COMPONENT)
|
71
|
+
parts[TIME_TO_PART[scanner[2]]] = number * sign
|
72
|
+
else
|
73
|
+
raise_parsing_error
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
validate!
|
80
|
+
parts
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def finished?
|
86
|
+
scanner.eos?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Parses number which can be a float with either comma or period.
|
90
|
+
def number
|
91
|
+
PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
|
92
|
+
end
|
93
|
+
|
94
|
+
def scan(pattern)
|
95
|
+
scanner.scan(pattern)
|
96
|
+
end
|
97
|
+
|
98
|
+
def raise_parsing_error(reason = nil)
|
99
|
+
raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
|
100
|
+
end
|
101
|
+
|
102
|
+
# Checks for various semantic errors as stated in ISO 8601 standard.
|
103
|
+
def validate!
|
104
|
+
raise_parsing_error("is empty duration") if parts.empty?
|
105
|
+
|
106
|
+
# Mixing any of Y, M, D with W is invalid.
|
107
|
+
if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
|
108
|
+
raise_parsing_error("mixing weeks with other date parts not allowed")
|
109
|
+
end
|
110
|
+
|
111
|
+
# Specifying an empty T part is invalid.
|
112
|
+
if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
|
113
|
+
raise_parsing_error("time part marker is present but time part is empty")
|
114
|
+
end
|
115
|
+
|
116
|
+
fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
|
117
|
+
unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
|
118
|
+
raise_parsing_error "(only last part can be fractional)"
|
119
|
+
end
|
120
|
+
|
121
|
+
true
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
|
+
require "active_support/core_ext/hash/transform_values"
|
5
|
+
|
6
|
+
module ActiveSupport
|
7
|
+
class Duration
|
8
|
+
# Serializes duration to string according to ISO 8601 Duration format.
|
9
|
+
class ISO8601Serializer # :nodoc:
|
10
|
+
def initialize(duration, precision: nil)
|
11
|
+
@duration = duration
|
12
|
+
@precision = precision
|
13
|
+
end
|
14
|
+
|
15
|
+
# Builds and returns output string.
|
16
|
+
def serialize
|
17
|
+
parts, sign = normalize
|
18
|
+
return "PT0S".freeze if parts.empty?
|
19
|
+
|
20
|
+
output = "P".dup
|
21
|
+
output << "#{parts[:years]}Y" if parts.key?(:years)
|
22
|
+
output << "#{parts[:months]}M" if parts.key?(:months)
|
23
|
+
output << "#{parts[:weeks]}W" if parts.key?(:weeks)
|
24
|
+
output << "#{parts[:days]}D" if parts.key?(:days)
|
25
|
+
time = "".dup
|
26
|
+
time << "#{parts[:hours]}H" if parts.key?(:hours)
|
27
|
+
time << "#{parts[:minutes]}M" if parts.key?(:minutes)
|
28
|
+
if parts.key?(:seconds)
|
29
|
+
time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
|
30
|
+
end
|
31
|
+
output << "T#{time}" unless time.empty?
|
32
|
+
"#{sign}#{output}"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Return pair of duration's parts and whole duration sign.
|
38
|
+
# Parts are summarized (as they can become repetitive due to addition, etc).
|
39
|
+
# Zero parts are removed as not significant.
|
40
|
+
# If all parts are negative it will negate all of them and return minus as a sign.
|
41
|
+
def normalize
|
42
|
+
parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
|
43
|
+
p[k] += v unless v.zero?
|
44
|
+
end
|
45
|
+
# If all parts are negative - let's make a negative duration
|
46
|
+
sign = ""
|
47
|
+
if parts.values.all? { |v| v < 0 }
|
48
|
+
sign = "-"
|
49
|
+
parts.transform_values!(&:-@)
|
50
|
+
end
|
51
|
+
[parts, sign]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,49 @@
|
|
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/module/delegation"
|
8
|
+
|
9
|
+
module ActiveSupport
|
10
|
+
class EncryptedConfiguration < EncryptedFile
|
11
|
+
delegate :[], :fetch, to: :config
|
12
|
+
delegate_missing_to :options
|
13
|
+
|
14
|
+
def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
|
15
|
+
super content_path: config_path, key_path: key_path,
|
16
|
+
env_key: env_key, raise_if_missing_key: raise_if_missing_key
|
17
|
+
end
|
18
|
+
|
19
|
+
# Allow a config to be started without a file present
|
20
|
+
def read
|
21
|
+
super
|
22
|
+
rescue ActiveSupport::EncryptedFile::MissingContentError
|
23
|
+
""
|
24
|
+
end
|
25
|
+
|
26
|
+
def write(contents)
|
27
|
+
deserialize(contents)
|
28
|
+
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def config
|
33
|
+
@config ||= deserialize(read).deep_symbolize_keys
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def options
|
38
|
+
@options ||= ActiveSupport::InheritableOptions.new(config)
|
39
|
+
end
|
40
|
+
|
41
|
+
def serialize(config)
|
42
|
+
config.present? ? YAML.dump(config) : ""
|
43
|
+
end
|
44
|
+
|
45
|
+
def deserialize(config)
|
46
|
+
config.present? ? YAML.load(config, content_path) : {}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "active_support/message_encryptor"
|
5
|
+
|
6
|
+
module ActiveSupport
|
7
|
+
class EncryptedFile
|
8
|
+
class MissingContentError < RuntimeError
|
9
|
+
def initialize(content_path)
|
10
|
+
super "Missing encrypted content file in #{content_path}."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class MissingKeyError < RuntimeError
|
15
|
+
def initialize(key_path:, env_key:)
|
16
|
+
super \
|
17
|
+
"Missing encryption key to decrypt file with. " +
|
18
|
+
"Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
CIPHER = "aes-128-gcm"
|
23
|
+
|
24
|
+
def self.generate_key
|
25
|
+
SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
|
30
|
+
|
31
|
+
def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
|
32
|
+
@content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path)
|
33
|
+
@env_key, @raise_if_missing_key = env_key, raise_if_missing_key
|
34
|
+
end
|
35
|
+
|
36
|
+
def key
|
37
|
+
read_env_key || read_key_file || handle_missing_key
|
38
|
+
end
|
39
|
+
|
40
|
+
def read
|
41
|
+
if !key.nil? && content_path.exist?
|
42
|
+
decrypt content_path.binread
|
43
|
+
else
|
44
|
+
raise MissingContentError, content_path
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write(contents)
|
49
|
+
IO.binwrite "#{content_path}.tmp", encrypt(contents)
|
50
|
+
FileUtils.mv "#{content_path}.tmp", content_path
|
51
|
+
end
|
52
|
+
|
53
|
+
def change(&block)
|
54
|
+
writing read, &block
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
private
|
59
|
+
def writing(contents)
|
60
|
+
tmp_file = "#{content_path.basename}.#{Process.pid}"
|
61
|
+
tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file)
|
62
|
+
tmp_path.binwrite contents
|
63
|
+
|
64
|
+
yield tmp_path
|
65
|
+
|
66
|
+
updated_contents = tmp_path.binread
|
67
|
+
|
68
|
+
write(updated_contents) if updated_contents != contents
|
69
|
+
ensure
|
70
|
+
FileUtils.rm(tmp_path) if tmp_path.exist?
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def encrypt(contents)
|
75
|
+
encryptor.encrypt_and_sign contents
|
76
|
+
end
|
77
|
+
|
78
|
+
def decrypt(contents)
|
79
|
+
encryptor.decrypt_and_verify contents
|
80
|
+
end
|
81
|
+
|
82
|
+
def encryptor
|
83
|
+
@encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def read_env_key
|
88
|
+
ENV[env_key]
|
89
|
+
end
|
90
|
+
|
91
|
+
def read_key_file
|
92
|
+
key_path.binread.strip if key_path.exist?
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_missing_key
|
96
|
+
raise MissingKeyError, key_path: key_path, env_key: env_key if raise_if_missing_key
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "pathname"
|
5
|
+
require "concurrent/atomic/atomic_boolean"
|
6
|
+
|
7
|
+
module ActiveSupport
|
8
|
+
# Allows you to "listen" to changes in a file system.
|
9
|
+
# The evented file updater does not hit disk when checking for updates
|
10
|
+
# instead it uses platform specific file system events to trigger a change
|
11
|
+
# in state.
|
12
|
+
#
|
13
|
+
# The file checker takes an array of files to watch or a hash specifying directories
|
14
|
+
# and file extensions to watch. It also takes a block that is called when
|
15
|
+
# EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated
|
16
|
+
# is run and there have been changes to the file system.
|
17
|
+
#
|
18
|
+
# Note: Forking will cause the first call to `updated?` to return `true`.
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" }
|
23
|
+
# checker.updated?
|
24
|
+
# # => false
|
25
|
+
# checker.execute_if_updated
|
26
|
+
# # => nil
|
27
|
+
#
|
28
|
+
# FileUtils.touch("/tmp/foo")
|
29
|
+
#
|
30
|
+
# checker.updated?
|
31
|
+
# # => true
|
32
|
+
# checker.execute_if_updated
|
33
|
+
# # => "changed"
|
34
|
+
#
|
35
|
+
class EventedFileUpdateChecker #:nodoc: all
|
36
|
+
def initialize(files, dirs = {}, &block)
|
37
|
+
unless block
|
38
|
+
raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker"
|
39
|
+
end
|
40
|
+
|
41
|
+
@ph = PathHelper.new
|
42
|
+
@files = files.map { |f| @ph.xpath(f) }.to_set
|
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
|
+
if (@dtw = directories_to_watch).any?
|
56
|
+
# Loading listen triggers warnings. These are originated by a legit
|
57
|
+
# usage of attr_* macros for private attributes, but adds a lot of noise
|
58
|
+
# to our test suite. Thus, we lazy load it and disable warnings locally.
|
59
|
+
silence_warnings do
|
60
|
+
begin
|
61
|
+
require "listen"
|
62
|
+
rescue LoadError => e
|
63
|
+
raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
boot!
|
68
|
+
end
|
69
|
+
|
70
|
+
def updated?
|
71
|
+
@boot_mutex.synchronize do
|
72
|
+
if @pid != Process.pid
|
73
|
+
boot!
|
74
|
+
@pid = Process.pid
|
75
|
+
@updated.make_true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@updated.true?
|
79
|
+
end
|
80
|
+
|
81
|
+
def execute
|
82
|
+
@updated.make_false
|
83
|
+
@block.call
|
84
|
+
end
|
85
|
+
|
86
|
+
def execute_if_updated
|
87
|
+
if updated?
|
88
|
+
yield if block_given?
|
89
|
+
execute
|
90
|
+
true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
def boot!
|
96
|
+
Listen.to(*@dtw, &method(:changed)).start
|
97
|
+
end
|
98
|
+
|
99
|
+
def changed(modified, added, removed)
|
100
|
+
unless updated?
|
101
|
+
@updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def watching?(file)
|
106
|
+
file = @ph.xpath(file)
|
107
|
+
|
108
|
+
if @files.member?(file)
|
109
|
+
true
|
110
|
+
elsif file.directory?
|
111
|
+
false
|
112
|
+
else
|
113
|
+
ext = @ph.normalize_extension(file.extname)
|
114
|
+
|
115
|
+
file.dirname.ascend do |dir|
|
116
|
+
if @dirs.fetch(dir, []).include?(ext)
|
117
|
+
break true
|
118
|
+
elsif dir == @lcsp || dir.root?
|
119
|
+
break false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def directories_to_watch
|
126
|
+
dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) }
|
127
|
+
dtw.compact!
|
128
|
+
dtw.uniq!
|
129
|
+
|
130
|
+
normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
|
131
|
+
dtw = dtw.reject do |path|
|
132
|
+
normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) }
|
133
|
+
end
|
134
|
+
|
135
|
+
@ph.filter_out_descendants(dtw)
|
136
|
+
end
|
137
|
+
|
138
|
+
class PathHelper
|
139
|
+
def xpath(path)
|
140
|
+
Pathname.new(path).expand_path
|
141
|
+
end
|
142
|
+
|
143
|
+
def normalize_extension(ext)
|
144
|
+
ext.to_s.sub(/\A\./, "")
|
145
|
+
end
|
146
|
+
|
147
|
+
# Given a collection of Pathname objects returns the longest subpath
|
148
|
+
# common to all of them, or +nil+ if there is none.
|
149
|
+
def longest_common_subpath(paths)
|
150
|
+
return if paths.empty?
|
151
|
+
|
152
|
+
lcsp = Pathname.new(paths[0])
|
153
|
+
|
154
|
+
paths[1..-1].each do |path|
|
155
|
+
until ascendant_of?(lcsp, path)
|
156
|
+
if lcsp.root?
|
157
|
+
# If we get here a root directory is not an ascendant of path.
|
158
|
+
# This may happen if there are paths in different drives on
|
159
|
+
# Windows.
|
160
|
+
return
|
161
|
+
else
|
162
|
+
lcsp = lcsp.parent
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
lcsp
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns the deepest existing ascendant, which could be the argument itself.
|
171
|
+
def existing_parent(dir)
|
172
|
+
dir.ascend do |ascendant|
|
173
|
+
break ascendant if ascendant.directory?
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Filters out directories which are descendants of others in the collection (stable).
|
178
|
+
def filter_out_descendants(dirs)
|
179
|
+
return dirs if dirs.length < 2
|
180
|
+
|
181
|
+
dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
|
182
|
+
descendants = []
|
183
|
+
|
184
|
+
until dirs_sorted_by_nparts.empty?
|
185
|
+
dir = dirs_sorted_by_nparts.shift
|
186
|
+
|
187
|
+
dirs_sorted_by_nparts.reject! do |possible_descendant|
|
188
|
+
ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Array#- preserves order.
|
193
|
+
dirs - descendants
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def ascendant_of?(base, other)
|
199
|
+
base != other && other.ascend do |ascendant|
|
200
|
+
break true if base == ascendant
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|