activesupport 5.0.7.1
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 +7 -0
- data/CHANGELOG.md +1013 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +39 -0
- data/lib/active_support.rb +99 -0
- data/lib/active_support/all.rb +3 -0
- data/lib/active_support/array_inquirer.rb +44 -0
- data/lib/active_support/backtrace_cleaner.rb +103 -0
- data/lib/active_support/benchmarkable.rb +49 -0
- data/lib/active_support/builder.rb +6 -0
- data/lib/active_support/cache.rb +701 -0
- data/lib/active_support/cache/file_store.rb +204 -0
- data/lib/active_support/cache/mem_cache_store.rb +207 -0
- data/lib/active_support/cache/memory_store.rb +167 -0
- data/lib/active_support/cache/null_store.rb +41 -0
- data/lib/active_support/cache/strategy/local_cache.rb +172 -0
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
- data/lib/active_support/callbacks.rb +791 -0
- data/lib/active_support/concern.rb +142 -0
- data/lib/active_support/concurrency/latch.rb +26 -0
- data/lib/active_support/concurrency/share_lock.rb +226 -0
- data/lib/active_support/configurable.rb +148 -0
- data/lib/active_support/core_ext.rb +4 -0
- data/lib/active_support/core_ext/array.rb +7 -0
- data/lib/active_support/core_ext/array/access.rb +90 -0
- data/lib/active_support/core_ext/array/conversions.rb +211 -0
- data/lib/active_support/core_ext/array/extract_options.rb +29 -0
- data/lib/active_support/core_ext/array/grouping.rb +107 -0
- data/lib/active_support/core_ext/array/inquiry.rb +17 -0
- data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -0
- data/lib/active_support/core_ext/array/wrap.rb +46 -0
- data/lib/active_support/core_ext/benchmark.rb +14 -0
- data/lib/active_support/core_ext/big_decimal.rb +1 -0
- data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
- data/lib/active_support/core_ext/class.rb +2 -0
- data/lib/active_support/core_ext/class/attribute.rb +128 -0
- data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -0
- data/lib/active_support/core_ext/class/subclasses.rb +41 -0
- data/lib/active_support/core_ext/date.rb +5 -0
- data/lib/active_support/core_ext/date/acts_like.rb +8 -0
- data/lib/active_support/core_ext/date/blank.rb +12 -0
- data/lib/active_support/core_ext/date/calculations.rb +143 -0
- data/lib/active_support/core_ext/date/conversions.rb +95 -0
- data/lib/active_support/core_ext/date/zones.rb +6 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +335 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +14 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
- data/lib/active_support/core_ext/date_time.rb +5 -0
- data/lib/active_support/core_ext/date_time/acts_like.rb +14 -0
- data/lib/active_support/core_ext/date_time/blank.rb +12 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +199 -0
- data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +105 -0
- data/lib/active_support/core_ext/digest/uuid.rb +51 -0
- data/lib/active_support/core_ext/enumerable.rb +146 -0
- data/lib/active_support/core_ext/file.rb +1 -0
- data/lib/active_support/core_ext/file/atomic.rb +68 -0
- data/lib/active_support/core_ext/hash.rb +9 -0
- data/lib/active_support/core_ext/hash/compact.rb +24 -0
- data/lib/active_support/core_ext/hash/conversions.rb +262 -0
- data/lib/active_support/core_ext/hash/deep_merge.rb +38 -0
- data/lib/active_support/core_ext/hash/except.rb +22 -0
- data/lib/active_support/core_ext/hash/indifferent_access.rb +23 -0
- data/lib/active_support/core_ext/hash/keys.rb +170 -0
- data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -0
- data/lib/active_support/core_ext/hash/slice.rb +48 -0
- data/lib/active_support/core_ext/hash/transform_values.rb +29 -0
- data/lib/active_support/core_ext/integer.rb +3 -0
- data/lib/active_support/core_ext/integer/inflections.rb +29 -0
- data/lib/active_support/core_ext/integer/multiple.rb +10 -0
- data/lib/active_support/core_ext/integer/time.rb +29 -0
- data/lib/active_support/core_ext/kernel.rb +4 -0
- data/lib/active_support/core_ext/kernel/agnostics.rb +11 -0
- data/lib/active_support/core_ext/kernel/concern.rb +12 -0
- data/lib/active_support/core_ext/kernel/debugger.rb +3 -0
- data/lib/active_support/core_ext/kernel/reporting.rb +43 -0
- data/lib/active_support/core_ext/kernel/singleton_class.rb +6 -0
- data/lib/active_support/core_ext/load_error.rb +31 -0
- data/lib/active_support/core_ext/marshal.rb +22 -0
- data/lib/active_support/core_ext/module.rb +12 -0
- data/lib/active_support/core_ext/module/aliasing.rb +74 -0
- data/lib/active_support/core_ext/module/anonymous.rb +28 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +36 -0
- data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
- data/lib/active_support/core_ext/module/concerning.rb +135 -0
- data/lib/active_support/core_ext/module/delegation.rb +216 -0
- data/lib/active_support/core_ext/module/deprecation.rb +23 -0
- data/lib/active_support/core_ext/module/introspection.rb +68 -0
- data/lib/active_support/core_ext/module/method_transplanting.rb +3 -0
- data/lib/active_support/core_ext/module/qualified_const.rb +70 -0
- data/lib/active_support/core_ext/module/reachable.rb +8 -0
- data/lib/active_support/core_ext/module/remove_method.rb +35 -0
- data/lib/active_support/core_ext/name_error.rb +31 -0
- data/lib/active_support/core_ext/numeric.rb +4 -0
- data/lib/active_support/core_ext/numeric/bytes.rb +64 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +144 -0
- data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
- data/lib/active_support/core_ext/numeric/time.rb +74 -0
- data/lib/active_support/core_ext/object.rb +14 -0
- data/lib/active_support/core_ext/object/acts_like.rb +10 -0
- data/lib/active_support/core_ext/object/blank.rb +143 -0
- data/lib/active_support/core_ext/object/conversions.rb +4 -0
- data/lib/active_support/core_ext/object/deep_dup.rb +53 -0
- data/lib/active_support/core_ext/object/duplicable.rb +124 -0
- data/lib/active_support/core_ext/object/inclusion.rb +27 -0
- data/lib/active_support/core_ext/object/instance_variables.rb +28 -0
- data/lib/active_support/core_ext/object/json.rb +205 -0
- data/lib/active_support/core_ext/object/to_param.rb +1 -0
- data/lib/active_support/core_ext/object/to_query.rb +84 -0
- data/lib/active_support/core_ext/object/try.rb +146 -0
- data/lib/active_support/core_ext/object/with_options.rb +69 -0
- data/lib/active_support/core_ext/range.rb +4 -0
- data/lib/active_support/core_ext/range/conversions.rb +31 -0
- data/lib/active_support/core_ext/range/each.rb +21 -0
- data/lib/active_support/core_ext/range/include_range.rb +23 -0
- data/lib/active_support/core_ext/range/overlaps.rb +8 -0
- data/lib/active_support/core_ext/regexp.rb +5 -0
- data/lib/active_support/core_ext/securerandom.rb +23 -0
- data/lib/active_support/core_ext/string.rb +13 -0
- data/lib/active_support/core_ext/string/access.rb +104 -0
- data/lib/active_support/core_ext/string/behavior.rb +6 -0
- data/lib/active_support/core_ext/string/conversions.rb +57 -0
- data/lib/active_support/core_ext/string/exclude.rb +11 -0
- data/lib/active_support/core_ext/string/filters.rb +102 -0
- data/lib/active_support/core_ext/string/indent.rb +43 -0
- data/lib/active_support/core_ext/string/inflections.rb +244 -0
- data/lib/active_support/core_ext/string/inquiry.rb +13 -0
- data/lib/active_support/core_ext/string/multibyte.rb +53 -0
- data/lib/active_support/core_ext/string/output_safety.rb +260 -0
- data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -0
- data/lib/active_support/core_ext/string/strip.rb +23 -0
- data/lib/active_support/core_ext/string/zones.rb +14 -0
- data/lib/active_support/core_ext/struct.rb +3 -0
- data/lib/active_support/core_ext/time.rb +5 -0
- data/lib/active_support/core_ext/time/acts_like.rb +8 -0
- data/lib/active_support/core_ext/time/calculations.rb +290 -0
- data/lib/active_support/core_ext/time/compatibility.rb +14 -0
- data/lib/active_support/core_ext/time/conversions.rb +67 -0
- data/lib/active_support/core_ext/time/marshal.rb +3 -0
- data/lib/active_support/core_ext/time/zones.rb +111 -0
- data/lib/active_support/core_ext/uri.rb +24 -0
- data/lib/active_support/dependencies.rb +755 -0
- data/lib/active_support/dependencies/autoload.rb +77 -0
- data/lib/active_support/dependencies/interlock.rb +55 -0
- data/lib/active_support/deprecation.rb +43 -0
- data/lib/active_support/deprecation/behaviors.rb +90 -0
- data/lib/active_support/deprecation/instance_delegator.rb +37 -0
- data/lib/active_support/deprecation/method_wrappers.rb +70 -0
- data/lib/active_support/deprecation/proxy_wrappers.rb +149 -0
- data/lib/active_support/deprecation/reporting.rb +112 -0
- data/lib/active_support/descendants_tracker.rb +60 -0
- data/lib/active_support/duration.rb +235 -0
- data/lib/active_support/duration/iso8601_parser.rb +122 -0
- data/lib/active_support/duration/iso8601_serializer.rb +51 -0
- 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 +157 -0
- data/lib/active_support/gem_version.rb +15 -0
- data/lib/active_support/gzip.rb +36 -0
- data/lib/active_support/hash_with_indifferent_access.rb +329 -0
- data/lib/active_support/i18n.rb +13 -0
- data/lib/active_support/i18n_railtie.rb +115 -0
- data/lib/active_support/inflections.rb +70 -0
- data/lib/active_support/inflector.rb +7 -0
- data/lib/active_support/inflector/inflections.rb +242 -0
- data/lib/active_support/inflector/methods.rb +390 -0
- data/lib/active_support/inflector/transliterate.rb +112 -0
- data/lib/active_support/json.rb +2 -0
- data/lib/active_support/json/decoding.rb +74 -0
- data/lib/active_support/json/encoding.rb +127 -0
- data/lib/active_support/key_generator.rb +71 -0
- data/lib/active_support/lazy_load_hooks.rb +76 -0
- data/lib/active_support/locale/en.yml +135 -0
- data/lib/active_support/log_subscriber.rb +109 -0
- data/lib/active_support/log_subscriber/test_helper.rb +104 -0
- data/lib/active_support/logger.rb +106 -0
- data/lib/active_support/logger_silence.rb +28 -0
- data/lib/active_support/logger_thread_safe_level.rb +31 -0
- data/lib/active_support/message_encryptor.rb +114 -0
- data/lib/active_support/message_verifier.rb +134 -0
- data/lib/active_support/multibyte.rb +21 -0
- data/lib/active_support/multibyte/chars.rb +231 -0
- data/lib/active_support/multibyte/unicode.rb +413 -0
- data/lib/active_support/notifications.rb +212 -0
- data/lib/active_support/notifications/fanout.rb +157 -0
- data/lib/active_support/notifications/instrumenter.rb +91 -0
- data/lib/active_support/number_helper.rb +368 -0
- data/lib/active_support/number_helper/number_converter.rb +182 -0
- data/lib/active_support/number_helper/number_to_currency_converter.rb +44 -0
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +28 -0
- data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +62 -0
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
- data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +92 -0
- data/lib/active_support/option_merger.rb +25 -0
- data/lib/active_support/ordered_hash.rb +48 -0
- data/lib/active_support/ordered_options.rb +81 -0
- data/lib/active_support/per_thread_registry.rb +58 -0
- data/lib/active_support/proxy_object.rb +13 -0
- data/lib/active_support/rails.rb +27 -0
- data/lib/active_support/railtie.rb +51 -0
- data/lib/active_support/reloader.rb +129 -0
- data/lib/active_support/rescuable.rb +173 -0
- data/lib/active_support/security_utils.rb +27 -0
- data/lib/active_support/string_inquirer.rb +26 -0
- data/lib/active_support/subscriber.rb +120 -0
- data/lib/active_support/tagged_logging.rb +77 -0
- data/lib/active_support/test_case.rb +88 -0
- data/lib/active_support/testing/assertions.rb +99 -0
- data/lib/active_support/testing/autorun.rb +5 -0
- data/lib/active_support/testing/constant_lookup.rb +50 -0
- data/lib/active_support/testing/declarative.rb +26 -0
- data/lib/active_support/testing/deprecation.rb +36 -0
- data/lib/active_support/testing/file_fixtures.rb +34 -0
- data/lib/active_support/testing/isolation.rb +115 -0
- data/lib/active_support/testing/method_call_assertions.rb +41 -0
- data/lib/active_support/testing/setup_and_teardown.rb +50 -0
- data/lib/active_support/testing/stream.rb +42 -0
- data/lib/active_support/testing/tagged_logging.rb +25 -0
- data/lib/active_support/testing/time_helpers.rb +136 -0
- data/lib/active_support/time.rb +18 -0
- data/lib/active_support/time_with_zone.rb +511 -0
- data/lib/active_support/values/time_zone.rb +484 -0
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/version.rb +8 -0
- data/lib/active_support/xml_mini.rb +209 -0
- data/lib/active_support/xml_mini/jdom.rb +181 -0
- data/lib/active_support/xml_mini/libxml.rb +77 -0
- data/lib/active_support/xml_mini/libxmlsax.rb +82 -0
- data/lib/active_support/xml_mini/nokogiri.rb +81 -0
- data/lib/active_support/xml_mini/nokogirisax.rb +85 -0
- data/lib/active_support/xml_mini/rexml.rb +128 -0
- metadata +350 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
class Deprecation
|
5
|
+
module Reporting
|
6
|
+
# Whether to print a message (silent mode)
|
7
|
+
attr_accessor :silenced
|
8
|
+
# Name of gem where method is deprecated
|
9
|
+
attr_accessor :gem_name
|
10
|
+
|
11
|
+
# Outputs a deprecation warning to the output configured by
|
12
|
+
# <tt>ActiveSupport::Deprecation.behavior</tt>.
|
13
|
+
#
|
14
|
+
# ActiveSupport::Deprecation.warn('something broke!')
|
15
|
+
# # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
|
16
|
+
def warn(message = nil, callstack = nil)
|
17
|
+
return if silenced
|
18
|
+
|
19
|
+
callstack ||= caller_locations(2)
|
20
|
+
deprecation_message(callstack, message).tap do |m|
|
21
|
+
behavior.each { |b| b.call(m, callstack) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Silence deprecation warnings within the block.
|
26
|
+
#
|
27
|
+
# ActiveSupport::Deprecation.warn('something broke!')
|
28
|
+
# # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
|
29
|
+
#
|
30
|
+
# ActiveSupport::Deprecation.silence do
|
31
|
+
# ActiveSupport::Deprecation.warn('something broke!')
|
32
|
+
# end
|
33
|
+
# # => nil
|
34
|
+
def silence
|
35
|
+
old_silenced, @silenced = @silenced, true
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
@silenced = old_silenced
|
39
|
+
end
|
40
|
+
|
41
|
+
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
|
42
|
+
caller_backtrace ||= caller_locations(2)
|
43
|
+
deprecated_method_warning(deprecated_method_name, message).tap do |msg|
|
44
|
+
warn(msg, caller_backtrace)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
# Outputs a deprecation warning message
|
50
|
+
#
|
51
|
+
# ActiveSupport::Deprecation.deprecated_method_warning(:method_name)
|
52
|
+
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}"
|
53
|
+
# ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method)
|
54
|
+
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)"
|
55
|
+
# ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message")
|
56
|
+
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)"
|
57
|
+
def deprecated_method_warning(method_name, message = nil)
|
58
|
+
warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}"
|
59
|
+
case message
|
60
|
+
when Symbol then "#{warning} (use #{message} instead)"
|
61
|
+
when String then "#{warning} (#{message})"
|
62
|
+
else warning
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def deprecation_message(callstack, message = nil)
|
67
|
+
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
|
68
|
+
"DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def deprecation_caller_message(callstack)
|
72
|
+
file, line, method = extract_callstack(callstack)
|
73
|
+
if file
|
74
|
+
if line && method
|
75
|
+
"(called from #{method} at #{file}:#{line})"
|
76
|
+
else
|
77
|
+
"(called from #{file}:#{line})"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def extract_callstack(callstack)
|
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
|
+
|
96
|
+
if offending_line
|
97
|
+
if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
|
98
|
+
md.captures
|
99
|
+
else
|
100
|
+
offending_line
|
101
|
+
end
|
102
|
+
end
|
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
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
# This module provides an internal implementation to track descendants
|
3
|
+
# which is faster than iterating through ObjectSpace.
|
4
|
+
module DescendantsTracker
|
5
|
+
@@direct_descendants = {}
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def direct_descendants(klass)
|
9
|
+
@@direct_descendants[klass] || []
|
10
|
+
end
|
11
|
+
|
12
|
+
def descendants(klass)
|
13
|
+
arr = []
|
14
|
+
accumulate_descendants(klass, arr)
|
15
|
+
arr
|
16
|
+
end
|
17
|
+
|
18
|
+
def clear
|
19
|
+
if defined? ActiveSupport::Dependencies
|
20
|
+
@@direct_descendants.each do |klass, descendants|
|
21
|
+
if ActiveSupport::Dependencies.autoloaded?(klass)
|
22
|
+
@@direct_descendants.delete(klass)
|
23
|
+
else
|
24
|
+
descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
else
|
28
|
+
@@direct_descendants.clear
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# This is the only method that is not thread safe, but is only ever called
|
33
|
+
# during the eager loading phase.
|
34
|
+
def store_inherited(klass, descendant)
|
35
|
+
(@@direct_descendants[klass] ||= []) << descendant
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def accumulate_descendants(klass, acc)
|
40
|
+
if direct_descendants = @@direct_descendants[klass]
|
41
|
+
acc.concat(direct_descendants)
|
42
|
+
direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def inherited(base)
|
48
|
+
DescendantsTracker.store_inherited(self, base)
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
def direct_descendants
|
53
|
+
DescendantsTracker.direct_descendants(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def descendants
|
57
|
+
DescendantsTracker.descendants(self)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'active_support/core_ext/array/conversions'
|
2
|
+
require 'active_support/core_ext/object/acts_like'
|
3
|
+
|
4
|
+
module ActiveSupport
|
5
|
+
# Provides accurate date and time measurements using Date#advance and
|
6
|
+
# Time#advance, respectively. It mainly supports the methods on Numeric.
|
7
|
+
#
|
8
|
+
# 1.month.ago # equivalent to Time.now.advance(months: -1)
|
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
|
+
|
27
|
+
attr_accessor :value, :parts
|
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
|
+
|
86
|
+
def initialize(value, parts) #:nodoc:
|
87
|
+
@value, @parts = value, parts
|
88
|
+
end
|
89
|
+
|
90
|
+
# Adds another Duration or a Numeric to this Duration. Numeric values
|
91
|
+
# are treated as seconds.
|
92
|
+
def +(other)
|
93
|
+
if Duration === other
|
94
|
+
Duration.new(value + other.value, @parts + other.parts)
|
95
|
+
else
|
96
|
+
Duration.new(value + other, @parts + [[:seconds, other]])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Subtracts another Duration or a Numeric from this Duration. Numeric
|
101
|
+
# values are treated as seconds.
|
102
|
+
def -(other)
|
103
|
+
self + (-other)
|
104
|
+
end
|
105
|
+
|
106
|
+
def -@ #:nodoc:
|
107
|
+
Duration.new(-value, parts.map { |type,number| [type, -number] })
|
108
|
+
end
|
109
|
+
|
110
|
+
def is_a?(klass) #:nodoc:
|
111
|
+
Duration == klass || value.is_a?(klass)
|
112
|
+
end
|
113
|
+
alias :kind_of? :is_a?
|
114
|
+
|
115
|
+
def instance_of?(klass) # :nodoc:
|
116
|
+
Duration == klass || value.instance_of?(klass)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns +true+ if +other+ is also a Duration instance with the
|
120
|
+
# same +value+, or if <tt>other == value</tt>.
|
121
|
+
def ==(other)
|
122
|
+
if Duration === other
|
123
|
+
other.value == value
|
124
|
+
else
|
125
|
+
other == value
|
126
|
+
end
|
127
|
+
end
|
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"
|
133
|
+
def to_s
|
134
|
+
@value.to_s
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns the number of seconds that this Duration represents.
|
138
|
+
#
|
139
|
+
# 1.minute.to_i # => 60
|
140
|
+
# 1.hour.to_i # => 3600
|
141
|
+
# 1.day.to_i # => 86400
|
142
|
+
#
|
143
|
+
# Note that this conversion makes some assumptions about the
|
144
|
+
# duration of some periods, e.g. months are always 30 days
|
145
|
+
# and years are 365.25 days:
|
146
|
+
#
|
147
|
+
# # equivalent to 30.days.to_i
|
148
|
+
# 1.month.to_i # => 2592000
|
149
|
+
#
|
150
|
+
# # equivalent to 365.25.days.to_i
|
151
|
+
# 1.year.to_i # => 31557600
|
152
|
+
#
|
153
|
+
# In such cases, Ruby's core
|
154
|
+
# Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
|
155
|
+
# Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
|
156
|
+
# date and time arithmetic.
|
157
|
+
def to_i
|
158
|
+
@value.to_i
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns +true+ if +other+ is also a Duration instance, which has the
|
162
|
+
# same parts as this one.
|
163
|
+
def eql?(other)
|
164
|
+
Duration === other && other.value.eql?(value)
|
165
|
+
end
|
166
|
+
|
167
|
+
def hash
|
168
|
+
@value.hash
|
169
|
+
end
|
170
|
+
|
171
|
+
# Calculates a new Time or Date that is as far in the future
|
172
|
+
# as this Duration represents.
|
173
|
+
def since(time = ::Time.current)
|
174
|
+
sum(1, time)
|
175
|
+
end
|
176
|
+
alias :from_now :since
|
177
|
+
|
178
|
+
# Calculates a new Time or Date that is as far in the past
|
179
|
+
# as this Duration represents.
|
180
|
+
def ago(time = ::Time.current)
|
181
|
+
sum(-1, time)
|
182
|
+
end
|
183
|
+
alias :until :ago
|
184
|
+
|
185
|
+
def inspect #:nodoc:
|
186
|
+
parts.
|
187
|
+
reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
|
188
|
+
sort_by {|unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit)}.
|
189
|
+
map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
|
190
|
+
to_sentence(locale: ::I18n.default_locale)
|
191
|
+
end
|
192
|
+
|
193
|
+
def as_json(options = nil) #:nodoc:
|
194
|
+
to_i
|
195
|
+
end
|
196
|
+
|
197
|
+
def respond_to_missing?(method, include_private=false) #:nodoc:
|
198
|
+
@value.respond_to?(method, include_private)
|
199
|
+
end
|
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
|
+
|
207
|
+
delegate :<=>, to: :value
|
208
|
+
|
209
|
+
protected
|
210
|
+
|
211
|
+
def sum(sign, time = ::Time.current) #:nodoc:
|
212
|
+
parts.inject(time) do |t,(type,number)|
|
213
|
+
if t.acts_like?(:time) || t.acts_like?(:date)
|
214
|
+
if type == :seconds
|
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)
|
220
|
+
else
|
221
|
+
t.advance(type => sign * number)
|
222
|
+
end
|
223
|
+
else
|
224
|
+
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
def method_missing(method, *args, &block) #:nodoc:
|
232
|
+
value.send(method, *args, &block)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -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
|