activesupport 4.2.11.3 → 5.0.7.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.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +678 -348
- data/MIT-LICENSE +2 -2
- data/README.rdoc +2 -3
- data/lib/active_support/array_inquirer.rb +44 -0
- data/lib/active_support/backtrace_cleaner.rb +1 -1
- data/lib/active_support/benchmarkable.rb +1 -1
- data/lib/active_support/cache/file_store.rb +36 -22
- data/lib/active_support/cache/mem_cache_store.rb +63 -54
- data/lib/active_support/cache/memory_store.rb +16 -21
- data/lib/active_support/cache/null_store.rb +1 -4
- data/lib/active_support/cache/strategy/local_cache.rb +31 -20
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +4 -4
- data/lib/active_support/cache.rb +71 -87
- data/lib/active_support/callbacks.rb +109 -113
- data/lib/active_support/concern.rb +1 -1
- data/lib/active_support/concurrency/latch.rb +11 -12
- data/lib/active_support/concurrency/share_lock.rb +226 -0
- data/lib/active_support/configurable.rb +1 -0
- data/lib/active_support/core_ext/array/access.rb +27 -1
- data/lib/active_support/core_ext/array/conversions.rb +6 -4
- data/lib/active_support/core_ext/array/grouping.rb +9 -18
- data/lib/active_support/core_ext/array/inquiry.rb +17 -0
- data/lib/active_support/core_ext/array/wrap.rb +5 -4
- data/lib/active_support/core_ext/array.rb +1 -0
- data/lib/active_support/core_ext/big_decimal/conversions.rb +8 -10
- data/lib/active_support/core_ext/class/attribute.rb +10 -9
- data/lib/active_support/core_ext/class/subclasses.rb +3 -2
- data/lib/active_support/core_ext/class.rb +0 -1
- data/lib/active_support/core_ext/date/blank.rb +12 -0
- data/lib/active_support/core_ext/date/calculations.rb +1 -1
- data/lib/active_support/core_ext/date/conversions.rb +7 -6
- data/lib/active_support/core_ext/date.rb +1 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +100 -27
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -1
- data/lib/active_support/core_ext/date_and_time/zones.rb +3 -4
- data/lib/active_support/core_ext/date_time/blank.rb +12 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +14 -8
- data/lib/active_support/core_ext/date_time/conversions.rb +2 -0
- data/lib/active_support/core_ext/date_time.rb +1 -1
- data/lib/active_support/core_ext/enumerable.rb +75 -25
- data/lib/active_support/core_ext/file/atomic.rb +30 -25
- data/lib/active_support/core_ext/hash/conversions.rb +22 -2
- data/lib/active_support/core_ext/hash/deep_merge.rb +1 -1
- data/lib/active_support/core_ext/hash/except.rb +9 -8
- data/lib/active_support/core_ext/hash/indifferent_access.rb +1 -1
- data/lib/active_support/core_ext/hash/keys.rb +25 -21
- data/lib/active_support/core_ext/hash/slice.rb +1 -1
- data/lib/active_support/core_ext/hash/transform_values.rb +11 -5
- data/lib/active_support/core_ext/integer/time.rb +2 -2
- data/lib/active_support/core_ext/kernel/concern.rb +2 -0
- data/lib/active_support/core_ext/kernel/debugger.rb +3 -10
- data/lib/active_support/core_ext/kernel/reporting.rb +2 -84
- data/lib/active_support/core_ext/kernel.rb +0 -1
- data/lib/active_support/core_ext/load_error.rb +5 -2
- data/lib/active_support/core_ext/marshal.rb +7 -9
- data/lib/active_support/core_ext/module/aliasing.rb +6 -1
- data/lib/active_support/core_ext/module/anonymous.rb +10 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -5
- data/lib/active_support/core_ext/module/attribute_accessors.rb +15 -15
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
- data/lib/active_support/core_ext/module/concerning.rb +4 -4
- data/lib/active_support/core_ext/module/delegation.rb +11 -20
- data/lib/active_support/core_ext/module/deprecation.rb +2 -2
- data/lib/active_support/core_ext/module/introspection.rb +8 -2
- data/lib/active_support/core_ext/module/method_transplanting.rb +3 -13
- data/lib/active_support/core_ext/module/qualified_const.rb +30 -12
- data/lib/active_support/core_ext/module/remove_method.rb +23 -0
- data/lib/active_support/core_ext/module.rb +1 -0
- data/lib/active_support/core_ext/name_error.rb +15 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +20 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +78 -77
- data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
- data/lib/active_support/core_ext/numeric/time.rb +26 -6
- data/lib/active_support/core_ext/numeric.rb +1 -0
- data/lib/active_support/core_ext/object/blank.rb +15 -3
- data/lib/active_support/core_ext/object/deep_dup.rb +10 -3
- data/lib/active_support/core_ext/object/duplicable.rb +7 -12
- data/lib/active_support/core_ext/object/inclusion.rb +2 -2
- data/lib/active_support/core_ext/object/instance_variables.rb +1 -1
- data/lib/active_support/core_ext/object/json.rb +15 -7
- data/lib/active_support/core_ext/object/to_query.rb +1 -1
- data/lib/active_support/core_ext/object/try.rb +67 -21
- data/lib/active_support/core_ext/object/with_options.rb +1 -1
- data/lib/active_support/core_ext/object.rb +0 -1
- data/lib/active_support/core_ext/range/conversions.rb +18 -6
- data/lib/active_support/core_ext/range/each.rb +16 -18
- data/lib/active_support/core_ext/range/include_range.rb +20 -20
- data/lib/active_support/core_ext/securerandom.rb +23 -0
- data/lib/active_support/core_ext/string/behavior.rb +1 -1
- data/lib/active_support/core_ext/string/conversions.rb +3 -2
- data/lib/active_support/core_ext/string/filters.rb +1 -2
- data/lib/active_support/core_ext/string/inflections.rb +32 -5
- data/lib/active_support/core_ext/string/multibyte.rb +11 -7
- data/lib/active_support/core_ext/string/output_safety.rb +12 -14
- data/lib/active_support/core_ext/string/strip.rb +3 -6
- data/lib/active_support/core_ext/struct.rb +3 -6
- data/lib/active_support/core_ext/time/calculations.rb +18 -9
- data/lib/active_support/core_ext/time/conversions.rb +4 -2
- data/lib/active_support/core_ext/time/marshal.rb +2 -29
- data/lib/active_support/core_ext/time/zones.rb +36 -4
- data/lib/active_support/core_ext/time.rb +0 -1
- data/lib/active_support/core_ext/uri.rb +1 -3
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/dependencies/interlock.rb +55 -0
- data/lib/active_support/dependencies.rb +88 -95
- data/lib/active_support/deprecation/behaviors.rb +15 -1
- data/lib/active_support/deprecation/instance_delegator.rb +13 -0
- data/lib/active_support/deprecation/method_wrappers.rb +42 -16
- data/lib/active_support/deprecation/proxy_wrappers.rb +47 -24
- data/lib/active_support/deprecation/reporting.rb +23 -5
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/duration/iso8601_parser.rb +122 -0
- data/lib/active_support/duration/iso8601_serializer.rb +51 -0
- data/lib/active_support/duration.rb +90 -15
- data/lib/active_support/evented_file_update_checker.rb +199 -0
- data/lib/active_support/execution_wrapper.rb +126 -0
- data/lib/active_support/executor.rb +6 -0
- data/lib/active_support/file_update_checker.rb +23 -3
- data/lib/active_support/gem_version.rb +5 -5
- data/lib/active_support/gzip.rb +1 -1
- data/lib/active_support/hash_with_indifferent_access.rb +40 -11
- data/lib/active_support/i18n_railtie.rb +25 -4
- data/lib/active_support/inflector/inflections.rb +36 -5
- data/lib/active_support/inflector/methods.rb +97 -90
- data/lib/active_support/inflector/transliterate.rb +36 -21
- data/lib/active_support/json/decoding.rb +11 -10
- data/lib/active_support/json/encoding.rb +1 -51
- data/lib/active_support/key_generator.rb +7 -9
- data/lib/active_support/lazy_load_hooks.rb +46 -18
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber/test_helper.rb +3 -3
- data/lib/active_support/log_subscriber.rb +1 -1
- data/lib/active_support/logger.rb +3 -4
- data/lib/active_support/logger_silence.rb +2 -1
- data/lib/active_support/logger_thread_safe_level.rb +2 -3
- data/lib/active_support/message_encryptor.rb +7 -7
- data/lib/active_support/message_verifier.rb +70 -8
- data/lib/active_support/multibyte/chars.rb +12 -3
- data/lib/active_support/multibyte/unicode.rb +44 -21
- data/lib/active_support/notifications/fanout.rb +5 -5
- data/lib/active_support/notifications/instrumenter.rb +20 -2
- data/lib/active_support/notifications.rb +2 -2
- data/lib/active_support/number_helper/number_to_currency_converter.rb +7 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +8 -3
- data/lib/active_support/number_helper/number_to_human_converter.rb +6 -4
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +6 -2
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +11 -2
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +30 -25
- data/lib/active_support/number_helper.rb +90 -67
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +15 -1
- data/lib/active_support/per_thread_registry.rb +3 -0
- data/lib/active_support/rails.rb +2 -2
- data/lib/active_support/railtie.rb +6 -1
- data/lib/active_support/reloader.rb +129 -0
- data/lib/active_support/rescuable.rb +101 -47
- data/lib/active_support/string_inquirer.rb +1 -1
- data/lib/active_support/subscriber.rb +5 -10
- data/lib/active_support/tagged_logging.rb +8 -7
- data/lib/active_support/test_case.rb +17 -29
- data/lib/active_support/testing/assertions.rb +15 -13
- data/lib/active_support/testing/deprecation.rb +9 -8
- data/lib/active_support/testing/file_fixtures.rb +34 -0
- data/lib/active_support/testing/isolation.rb +22 -8
- data/lib/active_support/testing/method_call_assertions.rb +41 -0
- data/lib/active_support/testing/stream.rb +42 -0
- data/lib/active_support/testing/time_helpers.rb +3 -1
- data/lib/active_support/time_with_zone.rb +123 -33
- data/lib/active_support/values/time_zone.rb +101 -47
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/xml_mini/jdom.rb +1 -1
- data/lib/active_support/xml_mini/libxml.rb +2 -2
- data/lib/active_support/xml_mini/nokogiri.rb +2 -2
- data/lib/active_support.rb +11 -6
- metadata +36 -17
- data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -16
- 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/object/itself.rb +0 -15
- data/lib/active_support/core_ext/thread.rb +0 -86
@@ -20,20 +20,22 @@ module ActiveSupport
|
|
20
20
|
|
21
21
|
private
|
22
22
|
def method_missing(called, *args, &block)
|
23
|
-
warn
|
23
|
+
warn caller_locations, called, args
|
24
24
|
target.__send__(called, *args, &block)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
#
|
28
|
+
# DeprecatedObjectProxy transforms an object into a deprecated one. It
|
29
|
+
# takes an object, a deprecation message and optionally a deprecator. The
|
30
|
+
# deprecator defaults to +ActiveSupport::Deprecator+ if none is specified.
|
29
31
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
+
# deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated")
|
33
|
+
# # => #<Object:0x007fb9b34c34b0>
|
32
34
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
35
|
+
# deprecated_object.to_s
|
36
|
+
# DEPRECATION WARNING: This object is now deprecated.
|
37
|
+
# (Backtrace)
|
38
|
+
# # => "#<Object:0x007fb9b34c34b0>"
|
37
39
|
class DeprecatedObjectProxy < DeprecationProxy
|
38
40
|
def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
|
39
41
|
@object = object
|
@@ -51,13 +53,16 @@ module ActiveSupport
|
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
54
|
-
#
|
55
|
-
# deprecated instance
|
56
|
+
# DeprecatedInstanceVariableProxy transforms an instance variable into a
|
57
|
+
# deprecated one. It takes an instance of a class, a method on that class
|
58
|
+
# and an instance variable. It optionally takes a deprecator as the last
|
59
|
+
# argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none
|
60
|
+
# is specified.
|
56
61
|
#
|
57
62
|
# class Example
|
58
|
-
# def initialize
|
59
|
-
# @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request
|
60
|
-
# @_request = :
|
63
|
+
# def initialize
|
64
|
+
# @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request)
|
65
|
+
# @_request = :special_request
|
61
66
|
# end
|
62
67
|
#
|
63
68
|
# def request
|
@@ -69,12 +74,17 @@ module ActiveSupport
|
|
69
74
|
# end
|
70
75
|
# end
|
71
76
|
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
77
|
+
# example = Example.new
|
78
|
+
# # => #<Example:0x007fb9b31090b8 @_request=:special_request, @request=:special_request>
|
79
|
+
#
|
80
|
+
# example.old_request.to_s
|
81
|
+
# # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of
|
82
|
+
# @request.to_s
|
83
|
+
# (Backtrace information…)
|
84
|
+
# "special_request"
|
76
85
|
#
|
77
|
-
#
|
86
|
+
# example.request.to_s
|
87
|
+
# # => "special_request"
|
78
88
|
class DeprecatedInstanceVariableProxy < DeprecationProxy
|
79
89
|
def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance)
|
80
90
|
@instance = instance
|
@@ -93,15 +103,23 @@ module ActiveSupport
|
|
93
103
|
end
|
94
104
|
end
|
95
105
|
|
96
|
-
#
|
106
|
+
# DeprecatedConstantProxy transforms a constant into a deprecated one. It
|
107
|
+
# takes the names of an old (deprecated) constant and of a new constant
|
108
|
+
# (both in string form) and optionally a deprecator. The deprecator defaults
|
109
|
+
# to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant
|
110
|
+
# now returns the value of the new one.
|
111
|
+
#
|
112
|
+
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
|
97
113
|
#
|
98
|
-
#
|
99
|
-
# OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance)
|
114
|
+
# (In a later update, the original implementation of `PLANETS` has been removed.)
|
100
115
|
#
|
101
|
-
#
|
102
|
-
#
|
116
|
+
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
|
117
|
+
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
|
103
118
|
#
|
104
|
-
#
|
119
|
+
# PLANETS.map { |planet| planet.capitalize }
|
120
|
+
# # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
|
121
|
+
# (Backtrace information…)
|
122
|
+
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
|
105
123
|
class DeprecatedConstantProxy < DeprecationProxy
|
106
124
|
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
|
107
125
|
@old_const = old_const
|
@@ -109,6 +127,11 @@ module ActiveSupport
|
|
109
127
|
@deprecator = deprecator
|
110
128
|
end
|
111
129
|
|
130
|
+
# Returns the class of the new constant.
|
131
|
+
#
|
132
|
+
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
|
133
|
+
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
|
134
|
+
# PLANETS.class # => Array
|
112
135
|
def class
|
113
136
|
target.class
|
114
137
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
1
3
|
module ActiveSupport
|
2
4
|
class Deprecation
|
3
5
|
module Reporting
|
@@ -14,7 +16,7 @@ module ActiveSupport
|
|
14
16
|
def warn(message = nil, callstack = nil)
|
15
17
|
return if silenced
|
16
18
|
|
17
|
-
callstack ||=
|
19
|
+
callstack ||= caller_locations(2)
|
18
20
|
deprecation_message(callstack, message).tap do |m|
|
19
21
|
behavior.each { |b| b.call(m, callstack) }
|
20
22
|
end
|
@@ -37,7 +39,7 @@ module ActiveSupport
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
|
40
|
-
caller_backtrace ||=
|
42
|
+
caller_backtrace ||= caller_locations(2)
|
41
43
|
deprecated_method_warning(deprecated_method_name, message).tap do |msg|
|
42
44
|
warn(msg, caller_backtrace)
|
43
45
|
end
|
@@ -63,7 +65,6 @@ module ActiveSupport
|
|
63
65
|
|
64
66
|
def deprecation_message(callstack, message = nil)
|
65
67
|
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
|
66
|
-
message += '.' unless message =~ /\.$/
|
67
68
|
"DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
|
68
69
|
end
|
69
70
|
|
@@ -79,8 +80,19 @@ module ActiveSupport
|
|
79
80
|
end
|
80
81
|
|
81
82
|
def extract_callstack(callstack)
|
82
|
-
|
83
|
-
|
83
|
+
return _extract_callstack(callstack) if callstack.first.is_a? String
|
84
|
+
|
85
|
+
offending_line = callstack.find { |frame|
|
86
|
+
frame.absolute_path && !ignored_callstack(frame.absolute_path)
|
87
|
+
} || callstack.first
|
88
|
+
|
89
|
+
[offending_line.path, offending_line.lineno, offending_line.label]
|
90
|
+
end
|
91
|
+
|
92
|
+
def _extract_callstack(callstack)
|
93
|
+
warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE
|
94
|
+
offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first
|
95
|
+
|
84
96
|
if offending_line
|
85
97
|
if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
|
86
98
|
md.captures
|
@@ -89,6 +101,12 @@ module ActiveSupport
|
|
89
101
|
end
|
90
102
|
end
|
91
103
|
end
|
104
|
+
|
105
|
+
RAILS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/"
|
106
|
+
|
107
|
+
def ignored_callstack(path)
|
108
|
+
path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG['rubylibdir'])
|
109
|
+
end
|
92
110
|
end
|
93
111
|
end
|
94
112
|
end
|
@@ -32,7 +32,7 @@ module ActiveSupport
|
|
32
32
|
# and the second is a library name
|
33
33
|
#
|
34
34
|
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
|
35
|
-
def initialize(deprecation_horizon = '5.
|
35
|
+
def initialize(deprecation_horizon = '5.1', gem_name = 'Rails')
|
36
36
|
self.gem_name = gem_name
|
37
37
|
self.deprecation_horizon = deprecation_horizon
|
38
38
|
# By default, warnings are not silenced and debugging is off.
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
class Duration
|
5
|
+
# Parses a string formatted according to ISO 8601 Duration into the hash.
|
6
|
+
#
|
7
|
+
# See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
|
8
|
+
#
|
9
|
+
# This parser allows negative parts to be present in pattern.
|
10
|
+
class ISO8601Parser # :nodoc:
|
11
|
+
class ParsingError < ::ArgumentError; end
|
12
|
+
|
13
|
+
PERIOD_OR_COMMA = /\.|,/
|
14
|
+
PERIOD = '.'.freeze
|
15
|
+
COMMA = ','.freeze
|
16
|
+
|
17
|
+
SIGN_MARKER = /\A\-|\+|/
|
18
|
+
DATE_MARKER = /P/
|
19
|
+
TIME_MARKER = /T/
|
20
|
+
DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
|
21
|
+
TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/
|
22
|
+
|
23
|
+
DATE_TO_PART = { 'Y' => :years, 'M' => :months, 'W' => :weeks, 'D' => :days }
|
24
|
+
TIME_TO_PART = { 'H' => :hours, 'M' => :minutes, 'S' => :seconds }
|
25
|
+
|
26
|
+
DATE_COMPONENTS = [:years, :months, :days]
|
27
|
+
TIME_COMPONENTS = [:hours, :minutes, :seconds]
|
28
|
+
|
29
|
+
attr_reader :parts, :scanner
|
30
|
+
attr_accessor :mode, :sign
|
31
|
+
|
32
|
+
def initialize(string)
|
33
|
+
@scanner = StringScanner.new(string)
|
34
|
+
@parts = {}
|
35
|
+
@mode = :start
|
36
|
+
@sign = 1
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse!
|
40
|
+
while !finished?
|
41
|
+
case mode
|
42
|
+
when :start
|
43
|
+
if scan(SIGN_MARKER)
|
44
|
+
self.sign = (scanner.matched == '-') ? -1 : 1
|
45
|
+
self.mode = :sign
|
46
|
+
else
|
47
|
+
raise_parsing_error
|
48
|
+
end
|
49
|
+
|
50
|
+
when :sign
|
51
|
+
if scan(DATE_MARKER)
|
52
|
+
self.mode = :date
|
53
|
+
else
|
54
|
+
raise_parsing_error
|
55
|
+
end
|
56
|
+
|
57
|
+
when :date
|
58
|
+
if scan(TIME_MARKER)
|
59
|
+
self.mode = :time
|
60
|
+
elsif scan(DATE_COMPONENT)
|
61
|
+
parts[DATE_TO_PART[scanner[2]]] = number * sign
|
62
|
+
else
|
63
|
+
raise_parsing_error
|
64
|
+
end
|
65
|
+
|
66
|
+
when :time
|
67
|
+
if scan(TIME_COMPONENT)
|
68
|
+
parts[TIME_TO_PART[scanner[2]]] = number * sign
|
69
|
+
else
|
70
|
+
raise_parsing_error
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
validate!
|
77
|
+
parts
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def finished?
|
83
|
+
scanner.eos?
|
84
|
+
end
|
85
|
+
|
86
|
+
# Parses number which can be a float with either comma or period.
|
87
|
+
def number
|
88
|
+
scanner[1] =~ PERIOD_OR_COMMA ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
|
89
|
+
end
|
90
|
+
|
91
|
+
def scan(pattern)
|
92
|
+
scanner.scan(pattern)
|
93
|
+
end
|
94
|
+
|
95
|
+
def raise_parsing_error(reason = nil)
|
96
|
+
raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
|
97
|
+
end
|
98
|
+
|
99
|
+
# Checks for various semantic errors as stated in ISO 8601 standard.
|
100
|
+
def validate!
|
101
|
+
raise_parsing_error('is empty duration') if parts.empty?
|
102
|
+
|
103
|
+
# Mixing any of Y, M, D with W is invalid.
|
104
|
+
if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
|
105
|
+
raise_parsing_error('mixing weeks with other date parts not allowed')
|
106
|
+
end
|
107
|
+
|
108
|
+
# Specifying an empty T part is invalid.
|
109
|
+
if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
|
110
|
+
raise_parsing_error('time part marker is present but time part is empty')
|
111
|
+
end
|
112
|
+
|
113
|
+
fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
|
114
|
+
unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
|
115
|
+
raise_parsing_error '(only last part can be fractional)'
|
116
|
+
end
|
117
|
+
|
118
|
+
return true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
require 'active_support/core_ext/hash/transform_values'
|
3
|
+
|
4
|
+
module ActiveSupport
|
5
|
+
class Duration
|
6
|
+
# Serializes duration to string according to ISO 8601 Duration format.
|
7
|
+
class ISO8601Serializer
|
8
|
+
def initialize(duration, precision: nil)
|
9
|
+
@duration = duration
|
10
|
+
@precision = precision
|
11
|
+
end
|
12
|
+
|
13
|
+
# Builds and returns output string.
|
14
|
+
def serialize
|
15
|
+
output = 'P'
|
16
|
+
parts, sign = normalize
|
17
|
+
output << "#{parts[:years]}Y" if parts.key?(:years)
|
18
|
+
output << "#{parts[:months]}M" if parts.key?(:months)
|
19
|
+
output << "#{parts[:weeks]}W" if parts.key?(:weeks)
|
20
|
+
output << "#{parts[:days]}D" if parts.key?(:days)
|
21
|
+
time = ''
|
22
|
+
time << "#{parts[:hours]}H" if parts.key?(:hours)
|
23
|
+
time << "#{parts[:minutes]}M" if parts.key?(:minutes)
|
24
|
+
if parts.key?(:seconds)
|
25
|
+
time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
|
26
|
+
end
|
27
|
+
output << "T#{time}" if time.present?
|
28
|
+
"#{sign}#{output}"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Return pair of duration's parts and whole duration sign.
|
34
|
+
# Parts are summarized (as they can become repetitive due to addition, etc).
|
35
|
+
# Zero parts are removed as not significant.
|
36
|
+
# If all parts are negative it will negate all of them and return minus as a sign.
|
37
|
+
def normalize
|
38
|
+
parts = @duration.parts.each_with_object(Hash.new(0)) do |(k,v),p|
|
39
|
+
p[k] += v unless v.zero?
|
40
|
+
end
|
41
|
+
# If all parts are negative - let's make a negative duration
|
42
|
+
sign = ''
|
43
|
+
if parts.values.all? { |v| v < 0 }
|
44
|
+
sign = '-'
|
45
|
+
parts.transform_values!(&:-@)
|
46
|
+
end
|
47
|
+
[parts, sign]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -7,8 +7,82 @@ module ActiveSupport
|
|
7
7
|
#
|
8
8
|
# 1.month.ago # equivalent to Time.now.advance(months: -1)
|
9
9
|
class Duration
|
10
|
+
SECONDS_PER_MINUTE = 60
|
11
|
+
SECONDS_PER_HOUR = 3600
|
12
|
+
SECONDS_PER_DAY = 86400
|
13
|
+
SECONDS_PER_WEEK = 604800
|
14
|
+
SECONDS_PER_MONTH = 2592000 # 30 days
|
15
|
+
SECONDS_PER_YEAR = 31557600 # length of a julian year (365.2425 days)
|
16
|
+
|
17
|
+
PARTS_IN_SECONDS = {
|
18
|
+
seconds: 1,
|
19
|
+
minutes: SECONDS_PER_MINUTE,
|
20
|
+
hours: SECONDS_PER_HOUR,
|
21
|
+
days: SECONDS_PER_DAY,
|
22
|
+
weeks: SECONDS_PER_WEEK,
|
23
|
+
months: SECONDS_PER_MONTH,
|
24
|
+
years: SECONDS_PER_YEAR
|
25
|
+
}.freeze
|
26
|
+
|
10
27
|
attr_accessor :value, :parts
|
11
28
|
|
29
|
+
autoload :ISO8601Parser, 'active_support/duration/iso8601_parser'
|
30
|
+
autoload :ISO8601Serializer, 'active_support/duration/iso8601_serializer'
|
31
|
+
|
32
|
+
class << self
|
33
|
+
# Creates a new Duration from string formatted according to ISO 8601 Duration.
|
34
|
+
#
|
35
|
+
# See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
|
36
|
+
# This method allows negative parts to be present in pattern.
|
37
|
+
# If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
|
38
|
+
def parse(iso8601duration)
|
39
|
+
parts = ISO8601Parser.new(iso8601duration).parse!
|
40
|
+
new(calculate_total_seconds(parts), parts)
|
41
|
+
end
|
42
|
+
|
43
|
+
def ===(other) #:nodoc:
|
44
|
+
other.is_a?(Duration)
|
45
|
+
rescue ::NoMethodError
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def seconds(value) #:nodoc:
|
50
|
+
new(value, [[:seconds, value]])
|
51
|
+
end
|
52
|
+
|
53
|
+
def minutes(value) #:nodoc:
|
54
|
+
new(value * SECONDS_PER_MINUTE, [[:minutes, value]])
|
55
|
+
end
|
56
|
+
|
57
|
+
def hours(value) #:nodoc:
|
58
|
+
new(value * SECONDS_PER_HOUR, [[:hours, value]])
|
59
|
+
end
|
60
|
+
|
61
|
+
def days(value) #:nodoc:
|
62
|
+
new(value * SECONDS_PER_DAY, [[:days, value]])
|
63
|
+
end
|
64
|
+
|
65
|
+
def weeks(value) #:nodoc:
|
66
|
+
new(value * SECONDS_PER_WEEK, [[:weeks, value]])
|
67
|
+
end
|
68
|
+
|
69
|
+
def months(value) #:nodoc:
|
70
|
+
new(value * SECONDS_PER_MONTH, [[:months, value]])
|
71
|
+
end
|
72
|
+
|
73
|
+
def years(value) #:nodoc:
|
74
|
+
new(value * SECONDS_PER_YEAR, [[:years, value]])
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def calculate_total_seconds(parts)
|
80
|
+
parts.inject(0) do |total, (part, value)|
|
81
|
+
total + value * PARTS_IN_SECONDS[part]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
12
86
|
def initialize(value, parts) #:nodoc:
|
13
87
|
@value, @parts = value, parts
|
14
88
|
end
|
@@ -52,6 +126,10 @@ module ActiveSupport
|
|
52
126
|
end
|
53
127
|
end
|
54
128
|
|
129
|
+
# Returns the amount of seconds a duration covers as a string.
|
130
|
+
# For more information check to_i method.
|
131
|
+
#
|
132
|
+
# 1.day.to_s # => "86400"
|
55
133
|
def to_s
|
56
134
|
@value.to_s
|
57
135
|
end
|
@@ -90,12 +168,6 @@ module ActiveSupport
|
|
90
168
|
@value.hash
|
91
169
|
end
|
92
170
|
|
93
|
-
def self.===(other) #:nodoc:
|
94
|
-
other.is_a?(Duration)
|
95
|
-
rescue ::NoMethodError
|
96
|
-
false
|
97
|
-
end
|
98
|
-
|
99
171
|
# Calculates a new Time or Date that is as far in the future
|
100
172
|
# as this Duration represents.
|
101
173
|
def since(time = ::Time.current)
|
@@ -113,7 +185,7 @@ module ActiveSupport
|
|
113
185
|
def inspect #:nodoc:
|
114
186
|
parts.
|
115
187
|
reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
|
116
|
-
sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
|
188
|
+
sort_by {|unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit)}.
|
117
189
|
map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
|
118
190
|
to_sentence(locale: ::I18n.default_locale)
|
119
191
|
end
|
@@ -122,10 +194,16 @@ module ActiveSupport
|
|
122
194
|
to_i
|
123
195
|
end
|
124
196
|
|
125
|
-
def respond_to_missing?(method, include_private=false) #:nodoc
|
197
|
+
def respond_to_missing?(method, include_private=false) #:nodoc:
|
126
198
|
@value.respond_to?(method, include_private)
|
127
199
|
end
|
128
200
|
|
201
|
+
# Build ISO 8601 Duration string for this duration.
|
202
|
+
# The +precision+ parameter can be used to limit seconds' precision of duration.
|
203
|
+
def iso8601(precision: nil)
|
204
|
+
ISO8601Serializer.new(self, precision: precision).serialize
|
205
|
+
end
|
206
|
+
|
129
207
|
delegate :<=>, to: :value
|
130
208
|
|
131
209
|
protected
|
@@ -135,6 +213,10 @@ module ActiveSupport
|
|
135
213
|
if t.acts_like?(:time) || t.acts_like?(:date)
|
136
214
|
if type == :seconds
|
137
215
|
t.since(sign * number)
|
216
|
+
elsif type == :minutes
|
217
|
+
t.since(sign * number * 60)
|
218
|
+
elsif type == :hours
|
219
|
+
t.since(sign * number * 3600)
|
138
220
|
else
|
139
221
|
t.advance(type => sign * number)
|
140
222
|
end
|
@@ -146,13 +228,6 @@ module ActiveSupport
|
|
146
228
|
|
147
229
|
private
|
148
230
|
|
149
|
-
# We define it as a workaround to Ruby 2.0.0-p353 bug.
|
150
|
-
# For more information, check rails/rails#13055.
|
151
|
-
# Remove it when we drop support for 2.0.0-p353.
|
152
|
-
def ===(other) #:nodoc:
|
153
|
-
value === other
|
154
|
-
end
|
155
|
-
|
156
231
|
def method_missing(method, *args, &block) #:nodoc:
|
157
232
|
value.send(method, *args, &block)
|
158
233
|
end
|