activesupport 5.0.0.beta3 → 5.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +171 -4
- data/lib/active_support.rb +12 -0
- data/lib/active_support/cache.rb +32 -23
- data/lib/active_support/cache/strategy/local_cache.rb +1 -1
- data/lib/active_support/callbacks.rb +4 -4
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/core_ext/array/grouping.rb +6 -10
- data/lib/active_support/core_ext/date/conversions.rb +1 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +2 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -0
- data/lib/active_support/core_ext/date_time.rb +1 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +30 -8
- data/lib/active_support/core_ext/date_time/compatibility.rb +5 -0
- data/lib/active_support/core_ext/enumerable.rb +16 -0
- data/lib/active_support/core_ext/hash/conversions.rb +1 -1
- data/lib/active_support/core_ext/hash/keys.rb +1 -1
- data/lib/active_support/core_ext/marshal.rb +5 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +2 -2
- data/lib/active_support/core_ext/module/delegation.rb +2 -5
- data/lib/active_support/core_ext/module/introspection.rb +4 -0
- data/lib/active_support/core_ext/object/blank.rb +7 -3
- data/lib/active_support/core_ext/object/json.rb +6 -0
- data/lib/active_support/core_ext/string/conversions.rb +3 -2
- data/lib/active_support/core_ext/string/inflections.rb +9 -0
- data/lib/active_support/core_ext/string/output_safety.rb +1 -1
- data/lib/active_support/core_ext/time.rb +1 -0
- data/lib/active_support/core_ext/time/calculations.rb +7 -0
- data/lib/active_support/core_ext/time/compatibility.rb +5 -0
- data/lib/active_support/dependencies.rb +9 -52
- data/lib/active_support/dependencies/interlock.rb +6 -8
- data/lib/active_support/deprecation/reporting.rb +0 -1
- data/lib/active_support/duration.rb +20 -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 +18 -13
- data/lib/active_support/execution_wrapper.rb +117 -0
- data/lib/active_support/executor.rb +6 -0
- data/lib/active_support/file_update_checker.rb +22 -2
- data/lib/active_support/gem_version.rb +1 -1
- data/lib/active_support/i18n_railtie.rb +2 -2
- data/lib/active_support/inflector/methods.rb +9 -0
- data/lib/active_support/number_helper.rb +8 -1
- data/lib/active_support/number_helper/number_to_delimited_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 +5 -3
- data/lib/active_support/reloader.rb +129 -0
- data/lib/active_support/rescuable.rb +10 -0
- data/lib/active_support/time_with_zone.rb +9 -14
- data/lib/active_support/values/time_zone.rb +14 -5
- metadata +12 -5
- data/lib/active_support/core_ext/date_time/zones.rb +0 -6
@@ -19,14 +19,12 @@ module ActiveSupport #:nodoc:
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def
|
27
|
-
@lock.
|
28
|
-
yield
|
29
|
-
end
|
22
|
+
def start_unloading
|
23
|
+
@lock.start_exclusive(purpose: :unload, compatible: [:load, :unload])
|
24
|
+
end
|
25
|
+
|
26
|
+
def done_unloading
|
27
|
+
@lock.stop_exclusive(compatible: [:load, :unload])
|
30
28
|
end
|
31
29
|
|
32
30
|
def start_running
|
@@ -65,7 +65,6 @@ module ActiveSupport
|
|
65
65
|
|
66
66
|
def deprecation_message(callstack, message = nil)
|
67
67
|
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
|
68
|
-
message += '.' unless message =~ /\.$/
|
69
68
|
"DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
|
70
69
|
end
|
71
70
|
|
@@ -9,6 +9,9 @@ module ActiveSupport
|
|
9
9
|
class Duration
|
10
10
|
attr_accessor :value, :parts
|
11
11
|
|
12
|
+
autoload :ISO8601Parser, 'active_support/duration/iso8601_parser'
|
13
|
+
autoload :ISO8601Serializer, 'active_support/duration/iso8601_serializer'
|
14
|
+
|
12
15
|
def initialize(value, parts) #:nodoc:
|
13
16
|
@value, @parts = value, parts
|
14
17
|
end
|
@@ -130,6 +133,23 @@ module ActiveSupport
|
|
130
133
|
@value.respond_to?(method, include_private)
|
131
134
|
end
|
132
135
|
|
136
|
+
# Creates a new Duration from string formatted according to ISO 8601 Duration.
|
137
|
+
#
|
138
|
+
# See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
|
139
|
+
# This method allows negative parts to be present in pattern.
|
140
|
+
# If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
|
141
|
+
def self.parse(iso8601duration)
|
142
|
+
parts = ISO8601Parser.new(iso8601duration).parse!
|
143
|
+
time = ::Time.current
|
144
|
+
new(time.advance(parts) - time, parts)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Build ISO 8601 Duration string for this duration.
|
148
|
+
# The +precision+ parameter can be used to limit seconds' precision of duration.
|
149
|
+
def iso8601(precision: nil)
|
150
|
+
ISO8601Serializer.new(self, precision: precision).serialize
|
151
|
+
end
|
152
|
+
|
133
153
|
delegate :<=>, to: :value
|
134
154
|
|
135
155
|
protected
|
@@ -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
|
@@ -21,7 +21,13 @@ module ActiveSupport
|
|
21
21
|
# Loading listen triggers warnings. These are originated by a legit
|
22
22
|
# usage of attr_* macros for private attributes, but adds a lot of noise
|
23
23
|
# to our test suite. Thus, we lazy load it and disable warnings locally.
|
24
|
-
silence_warnings
|
24
|
+
silence_warnings do
|
25
|
+
begin
|
26
|
+
require 'listen'
|
27
|
+
rescue LoadError => e
|
28
|
+
raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
|
29
|
+
end
|
30
|
+
end
|
25
31
|
Listen.to(*dtw, &method(:changed)).start
|
26
32
|
end
|
27
33
|
end
|
@@ -37,6 +43,7 @@ module ActiveSupport
|
|
37
43
|
|
38
44
|
def execute_if_updated
|
39
45
|
if updated?
|
46
|
+
yield if block_given?
|
40
47
|
execute
|
41
48
|
true
|
42
49
|
end
|
@@ -79,16 +86,6 @@ module ActiveSupport
|
|
79
86
|
end
|
80
87
|
|
81
88
|
class PathHelper
|
82
|
-
using Module.new {
|
83
|
-
refine Pathname do
|
84
|
-
def ascendant_of?(other)
|
85
|
-
self != other && other.ascend do |ascendant|
|
86
|
-
break true if self == ascendant
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
}
|
91
|
-
|
92
89
|
def xpath(path)
|
93
90
|
Pathname.new(path).expand_path
|
94
91
|
end
|
@@ -105,7 +102,7 @@ module ActiveSupport
|
|
105
102
|
lcsp = Pathname.new(paths[0])
|
106
103
|
|
107
104
|
paths[1..-1].each do |path|
|
108
|
-
until
|
105
|
+
until ascendant_of?(lcsp, path)
|
109
106
|
if lcsp.root?
|
110
107
|
# If we get here a root directory is not an ascendant of path.
|
111
108
|
# This may happen if there are paths in different drives on
|
@@ -138,13 +135,21 @@ module ActiveSupport
|
|
138
135
|
dir = dirs_sorted_by_nparts.shift
|
139
136
|
|
140
137
|
dirs_sorted_by_nparts.reject! do |possible_descendant|
|
141
|
-
|
138
|
+
ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
|
142
139
|
end
|
143
140
|
end
|
144
141
|
|
145
142
|
# Array#- preserves order.
|
146
143
|
dirs - descendants
|
147
144
|
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def ascendant_of?(base, other)
|
149
|
+
base != other && other.ascend do |ascendant|
|
150
|
+
break true if base == ascendant
|
151
|
+
end
|
152
|
+
end
|
148
153
|
end
|
149
154
|
end
|
150
155
|
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'active_support/callbacks'
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
class ExecutionWrapper
|
5
|
+
include ActiveSupport::Callbacks
|
6
|
+
|
7
|
+
Null = Object.new # :nodoc:
|
8
|
+
def Null.complete! # :nodoc:
|
9
|
+
end
|
10
|
+
|
11
|
+
define_callbacks :run
|
12
|
+
define_callbacks :complete
|
13
|
+
|
14
|
+
def self.to_run(*args, &block)
|
15
|
+
set_callback(:run, *args, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.to_complete(*args, &block)
|
19
|
+
set_callback(:complete, *args, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Register an object to be invoked during both the +run+ and
|
23
|
+
# +complete+ steps.
|
24
|
+
#
|
25
|
+
# +hook.complete+ will be passed the value returned from +hook.run+,
|
26
|
+
# and will only be invoked if +run+ has previously been called.
|
27
|
+
# (Mostly, this means it won't be invoked if an exception occurs in
|
28
|
+
# a preceding +to_run+ block; all ordinary +to_complete+ blocks are
|
29
|
+
# invoked in that situation.)
|
30
|
+
def self.register_hook(hook, outer: false)
|
31
|
+
if outer
|
32
|
+
run_args = [prepend: true]
|
33
|
+
complete_args = [:after]
|
34
|
+
else
|
35
|
+
run_args = complete_args = []
|
36
|
+
end
|
37
|
+
|
38
|
+
to_run(*run_args) do
|
39
|
+
hook_state[hook] = hook.run
|
40
|
+
end
|
41
|
+
to_complete(*complete_args) do
|
42
|
+
if hook_state.key?(hook)
|
43
|
+
hook.complete hook_state[hook]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Run this execution.
|
49
|
+
#
|
50
|
+
# Returns an instance, whose +complete!+ method *must* be invoked
|
51
|
+
# after the work has been performed.
|
52
|
+
#
|
53
|
+
# Where possible, prefer +wrap+.
|
54
|
+
def self.run!
|
55
|
+
if active?
|
56
|
+
Null
|
57
|
+
else
|
58
|
+
new.tap do |instance|
|
59
|
+
success = nil
|
60
|
+
begin
|
61
|
+
instance.run!
|
62
|
+
success = true
|
63
|
+
ensure
|
64
|
+
instance.complete! unless success
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Perform the work in the supplied block as an execution.
|
71
|
+
def self.wrap
|
72
|
+
return yield if active?
|
73
|
+
|
74
|
+
instance = run!
|
75
|
+
begin
|
76
|
+
yield
|
77
|
+
ensure
|
78
|
+
instance.complete!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class << self # :nodoc:
|
83
|
+
attr_accessor :active
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.inherited(other) # :nodoc:
|
87
|
+
super
|
88
|
+
other.active = Concurrent::Hash.new
|
89
|
+
end
|
90
|
+
|
91
|
+
self.active = Concurrent::Hash.new
|
92
|
+
|
93
|
+
def self.active? # :nodoc:
|
94
|
+
@active[Thread.current]
|
95
|
+
end
|
96
|
+
|
97
|
+
def run! # :nodoc:
|
98
|
+
self.class.active[Thread.current] = true
|
99
|
+
run_callbacks(:run)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Complete this in-flight execution. This method *must* be called
|
103
|
+
# exactly once on the result of any call to +run!+.
|
104
|
+
#
|
105
|
+
# Where possible, prefer +wrap+.
|
106
|
+
def complete!
|
107
|
+
run_callbacks(:complete)
|
108
|
+
ensure
|
109
|
+
self.class.active.delete Thread.current
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def hook_state
|
114
|
+
@_hook_state ||= {}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_support/core_ext/time/calculations'
|
2
|
+
|
1
3
|
module ActiveSupport
|
2
4
|
# FileUpdateChecker specifies the API used by Rails to watch files
|
3
5
|
# and control reloading. The API depends on four methods:
|
@@ -23,7 +25,7 @@ module ActiveSupport
|
|
23
25
|
# I18n.reload!
|
24
26
|
# end
|
25
27
|
#
|
26
|
-
#
|
28
|
+
# ActiveSupport::Reloader.to_prepare do
|
27
29
|
# i18n_reloader.execute_if_updated
|
28
30
|
# end
|
29
31
|
class FileUpdateChecker
|
@@ -81,6 +83,7 @@ module ActiveSupport
|
|
81
83
|
# Execute the block given if updated.
|
82
84
|
def execute_if_updated
|
83
85
|
if updated?
|
86
|
+
yield if block_given?
|
84
87
|
execute
|
85
88
|
true
|
86
89
|
else
|
@@ -111,7 +114,24 @@ module ActiveSupport
|
|
111
114
|
# reloading is not triggered.
|
112
115
|
def max_mtime(paths)
|
113
116
|
time_now = Time.now
|
114
|
-
|
117
|
+
max_mtime = nil
|
118
|
+
|
119
|
+
# Time comparisons are performed with #compare_without_coercion because
|
120
|
+
# AS redefines these operators in a way that is much slower and does not
|
121
|
+
# bring any benefit in this particular code.
|
122
|
+
#
|
123
|
+
# Read t1.compare_without_coercion(t2) < 0 as t1 < t2.
|
124
|
+
paths.each do |path|
|
125
|
+
mtime = File.mtime(path)
|
126
|
+
|
127
|
+
next if time_now.compare_without_coercion(mtime) < 0
|
128
|
+
|
129
|
+
if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0
|
130
|
+
max_mtime = mtime
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
max_mtime
|
115
135
|
end
|
116
136
|
|
117
137
|
def compile_glob(hash)
|