activesupport 6.0.0

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.
Files changed (250) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +572 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +40 -0
  5. data/lib/active_support.rb +96 -0
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/all.rb +5 -0
  8. data/lib/active_support/array_inquirer.rb +48 -0
  9. data/lib/active_support/backtrace_cleaner.rb +132 -0
  10. data/lib/active_support/benchmarkable.rb +51 -0
  11. data/lib/active_support/builder.rb +8 -0
  12. data/lib/active_support/cache.rb +830 -0
  13. data/lib/active_support/cache/file_store.rb +196 -0
  14. data/lib/active_support/cache/mem_cache_store.rb +212 -0
  15. data/lib/active_support/cache/memory_store.rb +174 -0
  16. data/lib/active_support/cache/null_store.rb +48 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +488 -0
  18. data/lib/active_support/cache/strategy/local_cache.rb +194 -0
  19. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  20. data/lib/active_support/callbacks.rb +856 -0
  21. data/lib/active_support/concern.rb +171 -0
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  23. data/lib/active_support/concurrency/share_lock.rb +227 -0
  24. data/lib/active_support/configurable.rb +146 -0
  25. data/lib/active_support/core_ext.rb +5 -0
  26. data/lib/active_support/core_ext/array.rb +9 -0
  27. data/lib/active_support/core_ext/array/access.rb +104 -0
  28. data/lib/active_support/core_ext/array/conversions.rb +213 -0
  29. data/lib/active_support/core_ext/array/extract.rb +21 -0
  30. data/lib/active_support/core_ext/array/extract_options.rb +31 -0
  31. data/lib/active_support/core_ext/array/grouping.rb +109 -0
  32. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  33. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -0
  34. data/lib/active_support/core_ext/array/wrap.rb +48 -0
  35. data/lib/active_support/core_ext/benchmark.rb +16 -0
  36. data/lib/active_support/core_ext/big_decimal.rb +3 -0
  37. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  38. data/lib/active_support/core_ext/class.rb +4 -0
  39. data/lib/active_support/core_ext/class/attribute.rb +141 -0
  40. data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
  41. data/lib/active_support/core_ext/class/subclasses.rb +54 -0
  42. data/lib/active_support/core_ext/date.rb +7 -0
  43. data/lib/active_support/core_ext/date/acts_like.rb +10 -0
  44. data/lib/active_support/core_ext/date/blank.rb +14 -0
  45. data/lib/active_support/core_ext/date/calculations.rb +146 -0
  46. data/lib/active_support/core_ext/date/conversions.rb +96 -0
  47. data/lib/active_support/core_ext/date/zones.rb +8 -0
  48. data/lib/active_support/core_ext/date_and_time/calculations.rb +351 -0
  49. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  50. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  51. data/lib/active_support/core_ext/date_time.rb +7 -0
  52. data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
  53. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  54. data/lib/active_support/core_ext/date_time/calculations.rb +211 -0
  55. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  56. data/lib/active_support/core_ext/date_time/conversions.rb +107 -0
  57. data/lib/active_support/core_ext/digest.rb +3 -0
  58. data/lib/active_support/core_ext/digest/uuid.rb +53 -0
  59. data/lib/active_support/core_ext/enumerable.rb +188 -0
  60. data/lib/active_support/core_ext/file.rb +3 -0
  61. data/lib/active_support/core_ext/file/atomic.rb +70 -0
  62. data/lib/active_support/core_ext/hash.rb +10 -0
  63. data/lib/active_support/core_ext/hash/compact.rb +5 -0
  64. data/lib/active_support/core_ext/hash/conversions.rb +263 -0
  65. data/lib/active_support/core_ext/hash/deep_merge.rb +34 -0
  66. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  67. data/lib/active_support/core_ext/hash/except.rb +24 -0
  68. data/lib/active_support/core_ext/hash/indifferent_access.rb +24 -0
  69. data/lib/active_support/core_ext/hash/keys.rb +143 -0
  70. data/lib/active_support/core_ext/hash/reverse_merge.rb +25 -0
  71. data/lib/active_support/core_ext/hash/slice.rb +26 -0
  72. data/lib/active_support/core_ext/hash/transform_values.rb +5 -0
  73. data/lib/active_support/core_ext/integer.rb +5 -0
  74. data/lib/active_support/core_ext/integer/inflections.rb +31 -0
  75. data/lib/active_support/core_ext/integer/multiple.rb +12 -0
  76. data/lib/active_support/core_ext/integer/time.rb +22 -0
  77. data/lib/active_support/core_ext/kernel.rb +5 -0
  78. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  79. data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
  80. data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
  81. data/lib/active_support/core_ext/load_error.rb +9 -0
  82. data/lib/active_support/core_ext/marshal.rb +24 -0
  83. data/lib/active_support/core_ext/module.rb +13 -0
  84. data/lib/active_support/core_ext/module/aliasing.rb +31 -0
  85. data/lib/active_support/core_ext/module/anonymous.rb +30 -0
  86. data/lib/active_support/core_ext/module/attr_internal.rb +38 -0
  87. data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
  88. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +144 -0
  89. data/lib/active_support/core_ext/module/concerning.rb +134 -0
  90. data/lib/active_support/core_ext/module/delegation.rb +313 -0
  91. data/lib/active_support/core_ext/module/deprecation.rb +25 -0
  92. data/lib/active_support/core_ext/module/introspection.rb +86 -0
  93. data/lib/active_support/core_ext/module/reachable.rb +6 -0
  94. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  95. data/lib/active_support/core_ext/module/remove_method.rb +17 -0
  96. data/lib/active_support/core_ext/name_error.rb +38 -0
  97. data/lib/active_support/core_ext/numeric.rb +5 -0
  98. data/lib/active_support/core_ext/numeric/bytes.rb +66 -0
  99. data/lib/active_support/core_ext/numeric/conversions.rb +136 -0
  100. data/lib/active_support/core_ext/numeric/inquiry.rb +5 -0
  101. data/lib/active_support/core_ext/numeric/time.rb +66 -0
  102. data/lib/active_support/core_ext/object.rb +16 -0
  103. data/lib/active_support/core_ext/object/acts_like.rb +21 -0
  104. data/lib/active_support/core_ext/object/blank.rb +155 -0
  105. data/lib/active_support/core_ext/object/conversions.rb +6 -0
  106. data/lib/active_support/core_ext/object/deep_dup.rb +55 -0
  107. data/lib/active_support/core_ext/object/duplicable.rb +49 -0
  108. data/lib/active_support/core_ext/object/inclusion.rb +29 -0
  109. data/lib/active_support/core_ext/object/instance_variables.rb +30 -0
  110. data/lib/active_support/core_ext/object/json.rb +228 -0
  111. data/lib/active_support/core_ext/object/to_param.rb +3 -0
  112. data/lib/active_support/core_ext/object/to_query.rb +89 -0
  113. data/lib/active_support/core_ext/object/try.rb +156 -0
  114. data/lib/active_support/core_ext/object/with_options.rb +82 -0
  115. data/lib/active_support/core_ext/range.rb +7 -0
  116. data/lib/active_support/core_ext/range/compare_range.rb +70 -0
  117. data/lib/active_support/core_ext/range/conversions.rb +41 -0
  118. data/lib/active_support/core_ext/range/each.rb +25 -0
  119. data/lib/active_support/core_ext/range/include_range.rb +9 -0
  120. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  121. data/lib/active_support/core_ext/range/overlaps.rb +10 -0
  122. data/lib/active_support/core_ext/regexp.rb +7 -0
  123. data/lib/active_support/core_ext/securerandom.rb +45 -0
  124. data/lib/active_support/core_ext/string.rb +15 -0
  125. data/lib/active_support/core_ext/string/access.rb +114 -0
  126. data/lib/active_support/core_ext/string/behavior.rb +8 -0
  127. data/lib/active_support/core_ext/string/conversions.rb +59 -0
  128. data/lib/active_support/core_ext/string/exclude.rb +13 -0
  129. data/lib/active_support/core_ext/string/filters.rb +145 -0
  130. data/lib/active_support/core_ext/string/indent.rb +45 -0
  131. data/lib/active_support/core_ext/string/inflections.rb +259 -0
  132. data/lib/active_support/core_ext/string/inquiry.rb +15 -0
  133. data/lib/active_support/core_ext/string/multibyte.rb +58 -0
  134. data/lib/active_support/core_ext/string/output_safety.rb +314 -0
  135. data/lib/active_support/core_ext/string/starts_ends_with.rb +6 -0
  136. data/lib/active_support/core_ext/string/strip.rb +27 -0
  137. data/lib/active_support/core_ext/string/zones.rb +16 -0
  138. data/lib/active_support/core_ext/time.rb +7 -0
  139. data/lib/active_support/core_ext/time/acts_like.rb +10 -0
  140. data/lib/active_support/core_ext/time/calculations.rb +344 -0
  141. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  142. data/lib/active_support/core_ext/time/conversions.rb +72 -0
  143. data/lib/active_support/core_ext/time/zones.rb +113 -0
  144. data/lib/active_support/core_ext/uri.rb +25 -0
  145. data/lib/active_support/current_attributes.rb +203 -0
  146. data/lib/active_support/dependencies.rb +806 -0
  147. data/lib/active_support/dependencies/autoload.rb +79 -0
  148. data/lib/active_support/dependencies/interlock.rb +57 -0
  149. data/lib/active_support/dependencies/zeitwerk_integration.rb +110 -0
  150. data/lib/active_support/deprecation.rb +46 -0
  151. data/lib/active_support/deprecation/behaviors.rb +109 -0
  152. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  153. data/lib/active_support/deprecation/instance_delegator.rb +39 -0
  154. data/lib/active_support/deprecation/method_wrappers.rb +78 -0
  155. data/lib/active_support/deprecation/proxy_wrappers.rb +173 -0
  156. data/lib/active_support/deprecation/reporting.rb +114 -0
  157. data/lib/active_support/descendants_tracker.rb +109 -0
  158. data/lib/active_support/digest.rb +20 -0
  159. data/lib/active_support/duration.rb +433 -0
  160. data/lib/active_support/duration/iso8601_parser.rb +124 -0
  161. data/lib/active_support/duration/iso8601_serializer.rb +54 -0
  162. data/lib/active_support/encrypted_configuration.rb +45 -0
  163. data/lib/active_support/encrypted_file.rb +100 -0
  164. data/lib/active_support/evented_file_update_checker.rb +235 -0
  165. data/lib/active_support/execution_wrapper.rb +129 -0
  166. data/lib/active_support/executor.rb +8 -0
  167. data/lib/active_support/file_update_checker.rb +163 -0
  168. data/lib/active_support/gem_version.rb +17 -0
  169. data/lib/active_support/gzip.rb +38 -0
  170. data/lib/active_support/hash_with_indifferent_access.rb +399 -0
  171. data/lib/active_support/i18n.rb +16 -0
  172. data/lib/active_support/i18n_railtie.rb +126 -0
  173. data/lib/active_support/inflections.rb +72 -0
  174. data/lib/active_support/inflector.rb +9 -0
  175. data/lib/active_support/inflector/inflections.rb +257 -0
  176. data/lib/active_support/inflector/methods.rb +398 -0
  177. data/lib/active_support/inflector/transliterate.rb +147 -0
  178. data/lib/active_support/json.rb +4 -0
  179. data/lib/active_support/json/decoding.rb +76 -0
  180. data/lib/active_support/json/encoding.rb +134 -0
  181. data/lib/active_support/key_generator.rb +41 -0
  182. data/lib/active_support/lazy_load_hooks.rb +82 -0
  183. data/lib/active_support/locale/en.rb +31 -0
  184. data/lib/active_support/locale/en.yml +135 -0
  185. data/lib/active_support/log_subscriber.rb +135 -0
  186. data/lib/active_support/log_subscriber/test_helper.rb +106 -0
  187. data/lib/active_support/logger.rb +93 -0
  188. data/lib/active_support/logger_silence.rb +45 -0
  189. data/lib/active_support/logger_thread_safe_level.rb +56 -0
  190. data/lib/active_support/message_encryptor.rb +227 -0
  191. data/lib/active_support/message_verifier.rb +205 -0
  192. data/lib/active_support/messages/metadata.rb +71 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  194. data/lib/active_support/messages/rotator.rb +56 -0
  195. data/lib/active_support/multibyte.rb +23 -0
  196. data/lib/active_support/multibyte/chars.rb +216 -0
  197. data/lib/active_support/multibyte/unicode.rb +157 -0
  198. data/lib/active_support/notifications.rb +253 -0
  199. data/lib/active_support/notifications/fanout.rb +244 -0
  200. data/lib/active_support/notifications/instrumenter.rb +164 -0
  201. data/lib/active_support/number_helper.rb +378 -0
  202. data/lib/active_support/number_helper/number_converter.rb +184 -0
  203. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  204. data/lib/active_support/number_helper/number_to_delimited_converter.rb +31 -0
  205. data/lib/active_support/number_helper/number_to_human_converter.rb +70 -0
  206. data/lib/active_support/number_helper/number_to_human_size_converter.rb +61 -0
  207. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  208. data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
  209. data/lib/active_support/number_helper/number_to_rounded_converter.rb +56 -0
  210. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  211. data/lib/active_support/option_merger.rb +27 -0
  212. data/lib/active_support/ordered_hash.rb +50 -0
  213. data/lib/active_support/ordered_options.rb +85 -0
  214. data/lib/active_support/parameter_filter.rb +129 -0
  215. data/lib/active_support/per_thread_registry.rb +60 -0
  216. data/lib/active_support/proxy_object.rb +15 -0
  217. data/lib/active_support/rails.rb +29 -0
  218. data/lib/active_support/railtie.rb +80 -0
  219. data/lib/active_support/reloader.rb +130 -0
  220. data/lib/active_support/rescuable.rb +174 -0
  221. data/lib/active_support/security_utils.rb +31 -0
  222. data/lib/active_support/string_inquirer.rb +34 -0
  223. data/lib/active_support/subscriber.rb +169 -0
  224. data/lib/active_support/tagged_logging.rb +88 -0
  225. data/lib/active_support/test_case.rb +163 -0
  226. data/lib/active_support/testing/assertions.rb +228 -0
  227. data/lib/active_support/testing/autorun.rb +7 -0
  228. data/lib/active_support/testing/constant_lookup.rb +51 -0
  229. data/lib/active_support/testing/declarative.rb +28 -0
  230. data/lib/active_support/testing/deprecation.rb +38 -0
  231. data/lib/active_support/testing/file_fixtures.rb +38 -0
  232. data/lib/active_support/testing/isolation.rb +110 -0
  233. data/lib/active_support/testing/method_call_assertions.rb +70 -0
  234. data/lib/active_support/testing/parallelization.rb +128 -0
  235. data/lib/active_support/testing/setup_and_teardown.rb +55 -0
  236. data/lib/active_support/testing/stream.rb +44 -0
  237. data/lib/active_support/testing/tagged_logging.rb +27 -0
  238. data/lib/active_support/testing/time_helpers.rb +200 -0
  239. data/lib/active_support/time.rb +20 -0
  240. data/lib/active_support/time_with_zone.rb +561 -0
  241. data/lib/active_support/values/time_zone.rb +570 -0
  242. data/lib/active_support/version.rb +10 -0
  243. data/lib/active_support/xml_mini.rb +202 -0
  244. data/lib/active_support/xml_mini/jdom.rb +183 -0
  245. data/lib/active_support/xml_mini/libxml.rb +80 -0
  246. data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
  247. data/lib/active_support/xml_mini/nokogiri.rb +83 -0
  248. data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
  249. data/lib/active_support/xml_mini/rexml.rb +130 -0
  250. metadata +385 -0
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module Multibyte
5
+ module Unicode
6
+ extend self
7
+
8
+ # A list of all available normalization forms.
9
+ # See https://www.unicode.org/reports/tr15/tr15-29.html for more
10
+ # information about normalization.
11
+ NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
12
+
13
+ NORMALIZATION_FORM_ALIASES = { # :nodoc:
14
+ c: :nfc,
15
+ d: :nfd,
16
+ kc: :nfkc,
17
+ kd: :nfkd
18
+ }
19
+
20
+ # The Unicode version that is supported by the implementation
21
+ UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
22
+
23
+ # The default normalization used for operations that require
24
+ # normalization. It can be set to any of the normalizations
25
+ # in NORMALIZATION_FORMS.
26
+ #
27
+ # ActiveSupport::Multibyte::Unicode.default_normalization_form = :c
28
+ attr_accessor :default_normalization_form
29
+ @default_normalization_form = :kc
30
+
31
+ # Unpack the string at grapheme boundaries. Returns a list of character
32
+ # lists.
33
+ #
34
+ # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]]
35
+ # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]]
36
+ def unpack_graphemes(string)
37
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
38
+ ActiveSupport::Multibyte::Unicode#unpack_graphemes is deprecated and will be
39
+ removed from Rails 6.1. Use string.scan(/\X/).map(&:codepoints) instead.
40
+ MSG
41
+
42
+ string.scan(/\X/).map(&:codepoints)
43
+ end
44
+
45
+ # Reverse operation of unpack_graphemes.
46
+ #
47
+ # Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि'
48
+ def pack_graphemes(unpacked)
49
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
50
+ ActiveSupport::Multibyte::Unicode#pack_graphemes is deprecated and will be
51
+ removed from Rails 6.1. Use array.flatten.pack("U*") instead.
52
+ MSG
53
+
54
+ unpacked.flatten.pack("U*")
55
+ end
56
+
57
+ # Decompose composed characters to the decomposed form.
58
+ def decompose(type, codepoints)
59
+ if type == :compatibility
60
+ codepoints.pack("U*").unicode_normalize(:nfkd).codepoints
61
+ else
62
+ codepoints.pack("U*").unicode_normalize(:nfd).codepoints
63
+ end
64
+ end
65
+
66
+ # Compose decomposed characters to the composed form.
67
+ def compose(codepoints)
68
+ codepoints.pack("U*").unicode_normalize(:nfc).codepoints
69
+ end
70
+
71
+ # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars.
72
+ if !defined?(Rubinius)
73
+ # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
74
+ # resulting in a valid UTF-8 string.
75
+ #
76
+ # Passing +true+ will forcibly tidy all bytes, assuming that the string's
77
+ # encoding is entirely CP1252 or ISO-8859-1.
78
+ def tidy_bytes(string, force = false)
79
+ return string if string.empty?
80
+ return recode_windows1252_chars(string) if force
81
+ string.scrub { |bad| recode_windows1252_chars(bad) }
82
+ end
83
+ else
84
+ def tidy_bytes(string, force = false)
85
+ return string if string.empty?
86
+ return recode_windows1252_chars(string) if force
87
+
88
+ # We can't transcode to the same format, so we choose a nearly-identical encoding.
89
+ # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to
90
+ # CP1252 when we get errors. The final string will be 'converted' back to UTF-8
91
+ # before returning.
92
+ reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE)
93
+
94
+ source = string.dup
95
+ out = "".force_encoding(Encoding::UTF_16LE)
96
+
97
+ loop do
98
+ reader.primitive_convert(source, out)
99
+ _, _, _, error_bytes, _ = reader.primitive_errinfo
100
+ break if error_bytes.nil?
101
+ out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace)
102
+ end
103
+
104
+ reader.finish
105
+
106
+ out.encode!(Encoding::UTF_8)
107
+ end
108
+ end
109
+
110
+ # Returns the KC normalization of the string by default. NFKC is
111
+ # considered the best normalization form for passing strings to databases
112
+ # and validations.
113
+ #
114
+ # * <tt>string</tt> - The string to perform normalization on.
115
+ # * <tt>form</tt> - The form you want to normalize in. Should be one of
116
+ # the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>.
117
+ # Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
118
+ def normalize(string, form = nil)
119
+ form ||= @default_normalization_form
120
+
121
+ # See https://www.unicode.org/reports/tr15, Table 1
122
+ if alias_form = NORMALIZATION_FORM_ALIASES[form]
123
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
124
+ ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
125
+ removed from Rails 6.1. Use String#unicode_normalize(:#{alias_form}) instead.
126
+ MSG
127
+
128
+ string.unicode_normalize(alias_form)
129
+ else
130
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
131
+ ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
132
+ removed from Rails 6.1. Use String#unicode_normalize instead.
133
+ MSG
134
+
135
+ raise ArgumentError, "#{form} is not a valid normalization variant", caller
136
+ end
137
+ end
138
+
139
+ %w(downcase upcase swapcase).each do |method|
140
+ define_method(method) do |string|
141
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
142
+ ActiveSupport::Multibyte::Unicode##{method} is deprecated and
143
+ will be removed from Rails 6.1. Use String methods directly.
144
+ MSG
145
+
146
+ string.send(method)
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ def recode_windows1252_chars(string)
153
+ string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace)
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/notifications/instrumenter"
4
+ require "active_support/notifications/fanout"
5
+ require "active_support/per_thread_registry"
6
+
7
+ module ActiveSupport
8
+ # = Notifications
9
+ #
10
+ # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for
11
+ # Ruby.
12
+ #
13
+ # == Instrumenters
14
+ #
15
+ # To instrument an event you just need to do:
16
+ #
17
+ # ActiveSupport::Notifications.instrument('render', extra: :information) do
18
+ # render plain: 'Foo'
19
+ # end
20
+ #
21
+ # That first executes the block and then notifies all subscribers once done.
22
+ #
23
+ # In the example above +render+ is the name of the event, and the rest is called
24
+ # the _payload_. The payload is a mechanism that allows instrumenters to pass
25
+ # extra information to subscribers. Payloads consist of a hash whose contents
26
+ # are arbitrary and generally depend on the event.
27
+ #
28
+ # == Subscribers
29
+ #
30
+ # You can consume those events and the information they provide by registering
31
+ # a subscriber.
32
+ #
33
+ # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
34
+ # name # => String, name of the event (such as 'render' from above)
35
+ # start # => Time, when the instrumented block started execution
36
+ # finish # => Time, when the instrumented block ended execution
37
+ # id # => String, unique ID for the instrumenter that fired the event
38
+ # payload # => Hash, the payload
39
+ # end
40
+ #
41
+ # For instance, let's store all "render" events in an array:
42
+ #
43
+ # events = []
44
+ #
45
+ # ActiveSupport::Notifications.subscribe('render') do |*args|
46
+ # events << ActiveSupport::Notifications::Event.new(*args)
47
+ # end
48
+ #
49
+ # That code returns right away, you are just subscribing to "render" events.
50
+ # The block is saved and will be called whenever someone instruments "render":
51
+ #
52
+ # ActiveSupport::Notifications.instrument('render', extra: :information) do
53
+ # render plain: 'Foo'
54
+ # end
55
+ #
56
+ # event = events.first
57
+ # event.name # => "render"
58
+ # event.duration # => 10 (in milliseconds)
59
+ # event.payload # => { extra: :information }
60
+ #
61
+ # The block in the <tt>subscribe</tt> call gets the name of the event, start
62
+ # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter
63
+ # (something like "535801666f04d0298cd6"), and a hash with the payload, in
64
+ # that order.
65
+ #
66
+ # If an exception happens during that particular instrumentation the payload will
67
+ # have a key <tt>:exception</tt> with an array of two elements as value: a string with
68
+ # the name of the exception class, and the exception message.
69
+ # The <tt>:exception_object</tt> key of the payload will have the exception
70
+ # itself as the value:
71
+ #
72
+ # event.payload[:exception] # => ["ArgumentError", "Invalid value"]
73
+ # event.payload[:exception_object] # => #<ArgumentError: Invalid value>
74
+ #
75
+ # As the earlier example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
76
+ # is able to take the arguments as they come and provide an object-oriented
77
+ # interface to that data.
78
+ #
79
+ # It is also possible to pass an object which responds to <tt>call</tt> method
80
+ # as the second parameter to the <tt>subscribe</tt> method instead of a block:
81
+ #
82
+ # module ActionController
83
+ # class PageRequest
84
+ # def call(name, started, finished, unique_id, payload)
85
+ # Rails.logger.debug ['notification:', name, started, finished, unique_id, payload].join(' ')
86
+ # end
87
+ # end
88
+ # end
89
+ #
90
+ # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new)
91
+ #
92
+ # resulting in the following output within the logs including a hash with the payload:
93
+ #
94
+ # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 {
95
+ # controller: "Devise::SessionsController",
96
+ # action: "new",
97
+ # params: {"action"=>"new", "controller"=>"devise/sessions"},
98
+ # format: :html,
99
+ # method: "GET",
100
+ # path: "/login/sign_in",
101
+ # status: 200,
102
+ # view_runtime: 279.3080806732178,
103
+ # db_runtime: 40.053
104
+ # }
105
+ #
106
+ # You can also subscribe to all events whose name matches a certain regexp:
107
+ #
108
+ # ActiveSupport::Notifications.subscribe(/render/) do |*args|
109
+ # ...
110
+ # end
111
+ #
112
+ # and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing
113
+ # to all events.
114
+ #
115
+ # == Temporary Subscriptions
116
+ #
117
+ # Sometimes you do not want to subscribe to an event for the entire life of
118
+ # the application. There are two ways to unsubscribe.
119
+ #
120
+ # WARNING: The instrumentation framework is designed for long-running subscribers,
121
+ # use this feature sparingly because it wipes some internal caches and that has
122
+ # a negative impact on performance.
123
+ #
124
+ # === Subscribe While a Block Runs
125
+ #
126
+ # You can subscribe to some event temporarily while some block runs. For
127
+ # example, in
128
+ #
129
+ # callback = lambda {|*args| ... }
130
+ # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
131
+ # ...
132
+ # end
133
+ #
134
+ # the callback will be called for all "sql.active_record" events instrumented
135
+ # during the execution of the block. The callback is unsubscribed automatically
136
+ # after that.
137
+ #
138
+ # === Manual Unsubscription
139
+ #
140
+ # The +subscribe+ method returns a subscriber object:
141
+ #
142
+ # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
143
+ # ...
144
+ # end
145
+ #
146
+ # To prevent that block from being called anymore, just unsubscribe passing
147
+ # that reference:
148
+ #
149
+ # ActiveSupport::Notifications.unsubscribe(subscriber)
150
+ #
151
+ # You can also unsubscribe by passing the name of the subscriber object. Note
152
+ # that this will unsubscribe all subscriptions with the given name:
153
+ #
154
+ # ActiveSupport::Notifications.unsubscribe("render")
155
+ #
156
+ # Subscribers using a regexp or other pattern-matching object will remain subscribed
157
+ # to all events that match their original pattern, unless those events match a string
158
+ # passed to `unsubscribe`:
159
+ #
160
+ # subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
161
+ # ActiveSupport::Notifications.unsubscribe('render_template.action_view')
162
+ # subscriber.matches?('render_template.action_view') # => false
163
+ # subscriber.matches?('render_partial.action_view') # => true
164
+ #
165
+ # == Default Queue
166
+ #
167
+ # Notifications ships with a queue implementation that consumes and publishes events
168
+ # to all log subscribers. You can use any queue implementation you want.
169
+ #
170
+ module Notifications
171
+ class << self
172
+ attr_accessor :notifier
173
+
174
+ def publish(name, *args)
175
+ notifier.publish(name, *args)
176
+ end
177
+
178
+ def instrument(name, payload = {})
179
+ if notifier.listening?(name)
180
+ instrumenter.instrument(name, payload) { yield payload if block_given? }
181
+ else
182
+ yield payload if block_given?
183
+ end
184
+ end
185
+
186
+ # Subscribe to a given event name with the passed +block+.
187
+ #
188
+ # You can subscribe to events by passing a String to match exact event
189
+ # names, or by passing a Regexp to match all events that match a pattern.
190
+ #
191
+ # ActiveSupport::Notifications.subscribe(/render/) do |*args|
192
+ # @event = ActiveSupport::Notifications::Event.new(*args)
193
+ # end
194
+ #
195
+ # The +block+ will receive five parameters with information about the event:
196
+ #
197
+ # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
198
+ # name # => String, name of the event (such as 'render' from above)
199
+ # start # => Time, when the instrumented block started execution
200
+ # finish # => Time, when the instrumented block ended execution
201
+ # id # => String, unique ID for the instrumenter that fired the event
202
+ # payload # => Hash, the payload
203
+ # end
204
+ #
205
+ # If the block passed to the method only takes one parameter,
206
+ # it will yield an event object to the block:
207
+ #
208
+ # ActiveSupport::Notifications.subscribe(/render/) do |event|
209
+ # @event = event
210
+ # end
211
+ def subscribe(*args, &block)
212
+ notifier.subscribe(*args, &block)
213
+ end
214
+
215
+ def subscribed(callback, *args, &block)
216
+ subscriber = subscribe(*args, &callback)
217
+ yield
218
+ ensure
219
+ unsubscribe(subscriber)
220
+ end
221
+
222
+ def unsubscribe(subscriber_or_name)
223
+ notifier.unsubscribe(subscriber_or_name)
224
+ end
225
+
226
+ def instrumenter
227
+ InstrumentationRegistry.instance.instrumenter_for(notifier)
228
+ end
229
+ end
230
+
231
+ # This class is a registry which holds all of the +Instrumenter+ objects
232
+ # in a particular thread local. To access the +Instrumenter+ object for a
233
+ # particular +notifier+, you can call the following method:
234
+ #
235
+ # InstrumentationRegistry.instrumenter_for(notifier)
236
+ #
237
+ # The instrumenters for multiple notifiers are held in a single instance of
238
+ # this class.
239
+ class InstrumentationRegistry # :nodoc:
240
+ extend ActiveSupport::PerThreadRegistry
241
+
242
+ def initialize
243
+ @registry = {}
244
+ end
245
+
246
+ def instrumenter_for(notifier)
247
+ @registry[notifier] ||= Instrumenter.new(notifier)
248
+ end
249
+ end
250
+
251
+ self.notifier = Fanout.new
252
+ end
253
+ end
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mutex_m"
4
+ require "concurrent/map"
5
+ require "set"
6
+
7
+ module ActiveSupport
8
+ module Notifications
9
+ # This is a default queue implementation that ships with Notifications.
10
+ # It just pushes events to all registered log subscribers.
11
+ #
12
+ # This class is thread safe. All methods are reentrant.
13
+ class Fanout
14
+ include Mutex_m
15
+
16
+ def initialize
17
+ @string_subscribers = Hash.new { |h, k| h[k] = [] }
18
+ @other_subscribers = []
19
+ @listeners_for = Concurrent::Map.new
20
+ super
21
+ end
22
+
23
+ def subscribe(pattern = nil, callable = nil, &block)
24
+ subscriber = Subscribers.new(pattern, callable || block)
25
+ synchronize do
26
+ if String === pattern
27
+ @string_subscribers[pattern] << subscriber
28
+ @listeners_for.delete(pattern)
29
+ else
30
+ @other_subscribers << subscriber
31
+ @listeners_for.clear
32
+ end
33
+ end
34
+ subscriber
35
+ end
36
+
37
+ def unsubscribe(subscriber_or_name)
38
+ synchronize do
39
+ case subscriber_or_name
40
+ when String
41
+ @string_subscribers[subscriber_or_name].clear
42
+ @listeners_for.delete(subscriber_or_name)
43
+ @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
44
+ else
45
+ pattern = subscriber_or_name.try(:pattern)
46
+ if String === pattern
47
+ @string_subscribers[pattern].delete(subscriber_or_name)
48
+ @listeners_for.delete(pattern)
49
+ else
50
+ @other_subscribers.delete(subscriber_or_name)
51
+ @listeners_for.clear
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def start(name, id, payload)
58
+ listeners_for(name).each { |s| s.start(name, id, payload) }
59
+ end
60
+
61
+ def finish(name, id, payload, listeners = listeners_for(name))
62
+ listeners.each { |s| s.finish(name, id, payload) }
63
+ end
64
+
65
+ def publish(name, *args)
66
+ listeners_for(name).each { |s| s.publish(name, *args) }
67
+ end
68
+
69
+ def listeners_for(name)
70
+ # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
71
+ @listeners_for[name] || synchronize do
72
+ # use synchronisation when accessing @subscribers
73
+ @listeners_for[name] ||=
74
+ @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
75
+ end
76
+ end
77
+
78
+ def listening?(name)
79
+ listeners_for(name).any?
80
+ end
81
+
82
+ # This is a sync queue, so there is no waiting.
83
+ def wait
84
+ end
85
+
86
+ module Subscribers # :nodoc:
87
+ def self.new(pattern, listener)
88
+ subscriber_class = Timed
89
+
90
+ if listener.respond_to?(:start) && listener.respond_to?(:finish)
91
+ subscriber_class = Evented
92
+ else
93
+ # Doing all this to detect a block like `proc { |x| }` vs
94
+ # `proc { |*x| }` or `proc { |**x| }`
95
+ if listener.respond_to?(:parameters)
96
+ params = listener.parameters
97
+ if params.length == 1 && params.first.first == :opt
98
+ subscriber_class = EventObject
99
+ end
100
+ end
101
+ end
102
+
103
+ wrap_all pattern, subscriber_class.new(pattern, listener)
104
+ end
105
+
106
+ def self.event_object_subscriber(pattern, block)
107
+ wrap_all pattern, EventObject.new(pattern, block)
108
+ end
109
+
110
+ def self.wrap_all(pattern, subscriber)
111
+ unless pattern
112
+ AllMessages.new(subscriber)
113
+ else
114
+ subscriber
115
+ end
116
+ end
117
+
118
+ class Matcher #:nodoc:
119
+ attr_reader :pattern, :exclusions
120
+
121
+ def self.wrap(pattern)
122
+ return pattern if String === pattern
123
+ new(pattern)
124
+ end
125
+
126
+ def initialize(pattern)
127
+ @pattern = pattern
128
+ @exclusions = Set.new
129
+ end
130
+
131
+ def unsubscribe!(name)
132
+ exclusions << -name if pattern === name
133
+ end
134
+
135
+ def ===(name)
136
+ pattern === name && !exclusions.include?(name)
137
+ end
138
+ end
139
+
140
+ class Evented #:nodoc:
141
+ attr_reader :pattern
142
+
143
+ def initialize(pattern, delegate)
144
+ @pattern = Matcher.wrap(pattern)
145
+ @delegate = delegate
146
+ @can_publish = delegate.respond_to?(:publish)
147
+ end
148
+
149
+ def publish(name, *args)
150
+ if @can_publish
151
+ @delegate.publish name, *args
152
+ end
153
+ end
154
+
155
+ def start(name, id, payload)
156
+ @delegate.start name, id, payload
157
+ end
158
+
159
+ def finish(name, id, payload)
160
+ @delegate.finish name, id, payload
161
+ end
162
+
163
+ def subscribed_to?(name)
164
+ pattern === name
165
+ end
166
+
167
+ def matches?(name)
168
+ pattern && pattern === name
169
+ end
170
+
171
+ def unsubscribe!(name)
172
+ pattern.unsubscribe!(name)
173
+ end
174
+ end
175
+
176
+ class Timed < Evented # :nodoc:
177
+ def publish(name, *args)
178
+ @delegate.call name, *args
179
+ end
180
+
181
+ def start(name, id, payload)
182
+ timestack = Thread.current[:_timestack] ||= []
183
+ timestack.push Time.now
184
+ end
185
+
186
+ def finish(name, id, payload)
187
+ timestack = Thread.current[:_timestack]
188
+ started = timestack.pop
189
+ @delegate.call(name, started, Time.now, id, payload)
190
+ end
191
+ end
192
+
193
+ class EventObject < Evented
194
+ def start(name, id, payload)
195
+ stack = Thread.current[:_event_stack] ||= []
196
+ event = build_event name, id, payload
197
+ event.start!
198
+ stack.push event
199
+ end
200
+
201
+ def finish(name, id, payload)
202
+ stack = Thread.current[:_event_stack]
203
+ event = stack.pop
204
+ event.finish!
205
+ @delegate.call event
206
+ end
207
+
208
+ private
209
+ def build_event(name, id, payload)
210
+ ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
211
+ end
212
+ end
213
+
214
+ class AllMessages # :nodoc:
215
+ def initialize(delegate)
216
+ @delegate = delegate
217
+ end
218
+
219
+ def start(name, id, payload)
220
+ @delegate.start name, id, payload
221
+ end
222
+
223
+ def finish(name, id, payload)
224
+ @delegate.finish name, id, payload
225
+ end
226
+
227
+ def publish(name, *args)
228
+ @delegate.publish name, *args
229
+ end
230
+
231
+ def subscribed_to?(name)
232
+ true
233
+ end
234
+
235
+ def unsubscribe!(*)
236
+ false
237
+ end
238
+
239
+ alias :matches? :===
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end