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.

Files changed (236) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1013 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/lib/active_support.rb +99 -0
  6. data/lib/active_support/all.rb +3 -0
  7. data/lib/active_support/array_inquirer.rb +44 -0
  8. data/lib/active_support/backtrace_cleaner.rb +103 -0
  9. data/lib/active_support/benchmarkable.rb +49 -0
  10. data/lib/active_support/builder.rb +6 -0
  11. data/lib/active_support/cache.rb +701 -0
  12. data/lib/active_support/cache/file_store.rb +204 -0
  13. data/lib/active_support/cache/mem_cache_store.rb +207 -0
  14. data/lib/active_support/cache/memory_store.rb +167 -0
  15. data/lib/active_support/cache/null_store.rb +41 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +172 -0
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
  18. data/lib/active_support/callbacks.rb +791 -0
  19. data/lib/active_support/concern.rb +142 -0
  20. data/lib/active_support/concurrency/latch.rb +26 -0
  21. data/lib/active_support/concurrency/share_lock.rb +226 -0
  22. data/lib/active_support/configurable.rb +148 -0
  23. data/lib/active_support/core_ext.rb +4 -0
  24. data/lib/active_support/core_ext/array.rb +7 -0
  25. data/lib/active_support/core_ext/array/access.rb +90 -0
  26. data/lib/active_support/core_ext/array/conversions.rb +211 -0
  27. data/lib/active_support/core_ext/array/extract_options.rb +29 -0
  28. data/lib/active_support/core_ext/array/grouping.rb +107 -0
  29. data/lib/active_support/core_ext/array/inquiry.rb +17 -0
  30. data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -0
  31. data/lib/active_support/core_ext/array/wrap.rb +46 -0
  32. data/lib/active_support/core_ext/benchmark.rb +14 -0
  33. data/lib/active_support/core_ext/big_decimal.rb +1 -0
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  35. data/lib/active_support/core_ext/class.rb +2 -0
  36. data/lib/active_support/core_ext/class/attribute.rb +128 -0
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -0
  38. data/lib/active_support/core_ext/class/subclasses.rb +41 -0
  39. data/lib/active_support/core_ext/date.rb +5 -0
  40. data/lib/active_support/core_ext/date/acts_like.rb +8 -0
  41. data/lib/active_support/core_ext/date/blank.rb +12 -0
  42. data/lib/active_support/core_ext/date/calculations.rb +143 -0
  43. data/lib/active_support/core_ext/date/conversions.rb +95 -0
  44. data/lib/active_support/core_ext/date/zones.rb +6 -0
  45. data/lib/active_support/core_ext/date_and_time/calculations.rb +335 -0
  46. data/lib/active_support/core_ext/date_and_time/compatibility.rb +14 -0
  47. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  48. data/lib/active_support/core_ext/date_time.rb +5 -0
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +14 -0
  50. data/lib/active_support/core_ext/date_time/blank.rb +12 -0
  51. data/lib/active_support/core_ext/date_time/calculations.rb +199 -0
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
  53. data/lib/active_support/core_ext/date_time/conversions.rb +105 -0
  54. data/lib/active_support/core_ext/digest/uuid.rb +51 -0
  55. data/lib/active_support/core_ext/enumerable.rb +146 -0
  56. data/lib/active_support/core_ext/file.rb +1 -0
  57. data/lib/active_support/core_ext/file/atomic.rb +68 -0
  58. data/lib/active_support/core_ext/hash.rb +9 -0
  59. data/lib/active_support/core_ext/hash/compact.rb +24 -0
  60. data/lib/active_support/core_ext/hash/conversions.rb +262 -0
  61. data/lib/active_support/core_ext/hash/deep_merge.rb +38 -0
  62. data/lib/active_support/core_ext/hash/except.rb +22 -0
  63. data/lib/active_support/core_ext/hash/indifferent_access.rb +23 -0
  64. data/lib/active_support/core_ext/hash/keys.rb +170 -0
  65. data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -0
  66. data/lib/active_support/core_ext/hash/slice.rb +48 -0
  67. data/lib/active_support/core_ext/hash/transform_values.rb +29 -0
  68. data/lib/active_support/core_ext/integer.rb +3 -0
  69. data/lib/active_support/core_ext/integer/inflections.rb +29 -0
  70. data/lib/active_support/core_ext/integer/multiple.rb +10 -0
  71. data/lib/active_support/core_ext/integer/time.rb +29 -0
  72. data/lib/active_support/core_ext/kernel.rb +4 -0
  73. data/lib/active_support/core_ext/kernel/agnostics.rb +11 -0
  74. data/lib/active_support/core_ext/kernel/concern.rb +12 -0
  75. data/lib/active_support/core_ext/kernel/debugger.rb +3 -0
  76. data/lib/active_support/core_ext/kernel/reporting.rb +43 -0
  77. data/lib/active_support/core_ext/kernel/singleton_class.rb +6 -0
  78. data/lib/active_support/core_ext/load_error.rb +31 -0
  79. data/lib/active_support/core_ext/marshal.rb +22 -0
  80. data/lib/active_support/core_ext/module.rb +12 -0
  81. data/lib/active_support/core_ext/module/aliasing.rb +74 -0
  82. data/lib/active_support/core_ext/module/anonymous.rb +28 -0
  83. data/lib/active_support/core_ext/module/attr_internal.rb +36 -0
  84. data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
  85. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
  86. data/lib/active_support/core_ext/module/concerning.rb +135 -0
  87. data/lib/active_support/core_ext/module/delegation.rb +216 -0
  88. data/lib/active_support/core_ext/module/deprecation.rb +23 -0
  89. data/lib/active_support/core_ext/module/introspection.rb +68 -0
  90. data/lib/active_support/core_ext/module/method_transplanting.rb +3 -0
  91. data/lib/active_support/core_ext/module/qualified_const.rb +70 -0
  92. data/lib/active_support/core_ext/module/reachable.rb +8 -0
  93. data/lib/active_support/core_ext/module/remove_method.rb +35 -0
  94. data/lib/active_support/core_ext/name_error.rb +31 -0
  95. data/lib/active_support/core_ext/numeric.rb +4 -0
  96. data/lib/active_support/core_ext/numeric/bytes.rb +64 -0
  97. data/lib/active_support/core_ext/numeric/conversions.rb +144 -0
  98. data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
  99. data/lib/active_support/core_ext/numeric/time.rb +74 -0
  100. data/lib/active_support/core_ext/object.rb +14 -0
  101. data/lib/active_support/core_ext/object/acts_like.rb +10 -0
  102. data/lib/active_support/core_ext/object/blank.rb +143 -0
  103. data/lib/active_support/core_ext/object/conversions.rb +4 -0
  104. data/lib/active_support/core_ext/object/deep_dup.rb +53 -0
  105. data/lib/active_support/core_ext/object/duplicable.rb +124 -0
  106. data/lib/active_support/core_ext/object/inclusion.rb +27 -0
  107. data/lib/active_support/core_ext/object/instance_variables.rb +28 -0
  108. data/lib/active_support/core_ext/object/json.rb +205 -0
  109. data/lib/active_support/core_ext/object/to_param.rb +1 -0
  110. data/lib/active_support/core_ext/object/to_query.rb +84 -0
  111. data/lib/active_support/core_ext/object/try.rb +146 -0
  112. data/lib/active_support/core_ext/object/with_options.rb +69 -0
  113. data/lib/active_support/core_ext/range.rb +4 -0
  114. data/lib/active_support/core_ext/range/conversions.rb +31 -0
  115. data/lib/active_support/core_ext/range/each.rb +21 -0
  116. data/lib/active_support/core_ext/range/include_range.rb +23 -0
  117. data/lib/active_support/core_ext/range/overlaps.rb +8 -0
  118. data/lib/active_support/core_ext/regexp.rb +5 -0
  119. data/lib/active_support/core_ext/securerandom.rb +23 -0
  120. data/lib/active_support/core_ext/string.rb +13 -0
  121. data/lib/active_support/core_ext/string/access.rb +104 -0
  122. data/lib/active_support/core_ext/string/behavior.rb +6 -0
  123. data/lib/active_support/core_ext/string/conversions.rb +57 -0
  124. data/lib/active_support/core_ext/string/exclude.rb +11 -0
  125. data/lib/active_support/core_ext/string/filters.rb +102 -0
  126. data/lib/active_support/core_ext/string/indent.rb +43 -0
  127. data/lib/active_support/core_ext/string/inflections.rb +244 -0
  128. data/lib/active_support/core_ext/string/inquiry.rb +13 -0
  129. data/lib/active_support/core_ext/string/multibyte.rb +53 -0
  130. data/lib/active_support/core_ext/string/output_safety.rb +260 -0
  131. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -0
  132. data/lib/active_support/core_ext/string/strip.rb +23 -0
  133. data/lib/active_support/core_ext/string/zones.rb +14 -0
  134. data/lib/active_support/core_ext/struct.rb +3 -0
  135. data/lib/active_support/core_ext/time.rb +5 -0
  136. data/lib/active_support/core_ext/time/acts_like.rb +8 -0
  137. data/lib/active_support/core_ext/time/calculations.rb +290 -0
  138. data/lib/active_support/core_ext/time/compatibility.rb +14 -0
  139. data/lib/active_support/core_ext/time/conversions.rb +67 -0
  140. data/lib/active_support/core_ext/time/marshal.rb +3 -0
  141. data/lib/active_support/core_ext/time/zones.rb +111 -0
  142. data/lib/active_support/core_ext/uri.rb +24 -0
  143. data/lib/active_support/dependencies.rb +755 -0
  144. data/lib/active_support/dependencies/autoload.rb +77 -0
  145. data/lib/active_support/dependencies/interlock.rb +55 -0
  146. data/lib/active_support/deprecation.rb +43 -0
  147. data/lib/active_support/deprecation/behaviors.rb +90 -0
  148. data/lib/active_support/deprecation/instance_delegator.rb +37 -0
  149. data/lib/active_support/deprecation/method_wrappers.rb +70 -0
  150. data/lib/active_support/deprecation/proxy_wrappers.rb +149 -0
  151. data/lib/active_support/deprecation/reporting.rb +112 -0
  152. data/lib/active_support/descendants_tracker.rb +60 -0
  153. data/lib/active_support/duration.rb +235 -0
  154. data/lib/active_support/duration/iso8601_parser.rb +122 -0
  155. data/lib/active_support/duration/iso8601_serializer.rb +51 -0
  156. data/lib/active_support/evented_file_update_checker.rb +199 -0
  157. data/lib/active_support/execution_wrapper.rb +126 -0
  158. data/lib/active_support/executor.rb +6 -0
  159. data/lib/active_support/file_update_checker.rb +157 -0
  160. data/lib/active_support/gem_version.rb +15 -0
  161. data/lib/active_support/gzip.rb +36 -0
  162. data/lib/active_support/hash_with_indifferent_access.rb +329 -0
  163. data/lib/active_support/i18n.rb +13 -0
  164. data/lib/active_support/i18n_railtie.rb +115 -0
  165. data/lib/active_support/inflections.rb +70 -0
  166. data/lib/active_support/inflector.rb +7 -0
  167. data/lib/active_support/inflector/inflections.rb +242 -0
  168. data/lib/active_support/inflector/methods.rb +390 -0
  169. data/lib/active_support/inflector/transliterate.rb +112 -0
  170. data/lib/active_support/json.rb +2 -0
  171. data/lib/active_support/json/decoding.rb +74 -0
  172. data/lib/active_support/json/encoding.rb +127 -0
  173. data/lib/active_support/key_generator.rb +71 -0
  174. data/lib/active_support/lazy_load_hooks.rb +76 -0
  175. data/lib/active_support/locale/en.yml +135 -0
  176. data/lib/active_support/log_subscriber.rb +109 -0
  177. data/lib/active_support/log_subscriber/test_helper.rb +104 -0
  178. data/lib/active_support/logger.rb +106 -0
  179. data/lib/active_support/logger_silence.rb +28 -0
  180. data/lib/active_support/logger_thread_safe_level.rb +31 -0
  181. data/lib/active_support/message_encryptor.rb +114 -0
  182. data/lib/active_support/message_verifier.rb +134 -0
  183. data/lib/active_support/multibyte.rb +21 -0
  184. data/lib/active_support/multibyte/chars.rb +231 -0
  185. data/lib/active_support/multibyte/unicode.rb +413 -0
  186. data/lib/active_support/notifications.rb +212 -0
  187. data/lib/active_support/notifications/fanout.rb +157 -0
  188. data/lib/active_support/notifications/instrumenter.rb +91 -0
  189. data/lib/active_support/number_helper.rb +368 -0
  190. data/lib/active_support/number_helper/number_converter.rb +182 -0
  191. data/lib/active_support/number_helper/number_to_currency_converter.rb +44 -0
  192. data/lib/active_support/number_helper/number_to_delimited_converter.rb +28 -0
  193. data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
  194. data/lib/active_support/number_helper/number_to_human_size_converter.rb +62 -0
  195. data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
  196. data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
  197. data/lib/active_support/number_helper/number_to_rounded_converter.rb +92 -0
  198. data/lib/active_support/option_merger.rb +25 -0
  199. data/lib/active_support/ordered_hash.rb +48 -0
  200. data/lib/active_support/ordered_options.rb +81 -0
  201. data/lib/active_support/per_thread_registry.rb +58 -0
  202. data/lib/active_support/proxy_object.rb +13 -0
  203. data/lib/active_support/rails.rb +27 -0
  204. data/lib/active_support/railtie.rb +51 -0
  205. data/lib/active_support/reloader.rb +129 -0
  206. data/lib/active_support/rescuable.rb +173 -0
  207. data/lib/active_support/security_utils.rb +27 -0
  208. data/lib/active_support/string_inquirer.rb +26 -0
  209. data/lib/active_support/subscriber.rb +120 -0
  210. data/lib/active_support/tagged_logging.rb +77 -0
  211. data/lib/active_support/test_case.rb +88 -0
  212. data/lib/active_support/testing/assertions.rb +99 -0
  213. data/lib/active_support/testing/autorun.rb +5 -0
  214. data/lib/active_support/testing/constant_lookup.rb +50 -0
  215. data/lib/active_support/testing/declarative.rb +26 -0
  216. data/lib/active_support/testing/deprecation.rb +36 -0
  217. data/lib/active_support/testing/file_fixtures.rb +34 -0
  218. data/lib/active_support/testing/isolation.rb +115 -0
  219. data/lib/active_support/testing/method_call_assertions.rb +41 -0
  220. data/lib/active_support/testing/setup_and_teardown.rb +50 -0
  221. data/lib/active_support/testing/stream.rb +42 -0
  222. data/lib/active_support/testing/tagged_logging.rb +25 -0
  223. data/lib/active_support/testing/time_helpers.rb +136 -0
  224. data/lib/active_support/time.rb +18 -0
  225. data/lib/active_support/time_with_zone.rb +511 -0
  226. data/lib/active_support/values/time_zone.rb +484 -0
  227. data/lib/active_support/values/unicode_tables.dat +0 -0
  228. data/lib/active_support/version.rb +8 -0
  229. data/lib/active_support/xml_mini.rb +209 -0
  230. data/lib/active_support/xml_mini/jdom.rb +181 -0
  231. data/lib/active_support/xml_mini/libxml.rb +77 -0
  232. data/lib/active_support/xml_mini/libxmlsax.rb +82 -0
  233. data/lib/active_support/xml_mini/nokogiri.rb +81 -0
  234. data/lib/active_support/xml_mini/nokogirisax.rb +85 -0
  235. data/lib/active_support/xml_mini/rexml.rb +128 -0
  236. 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