activesupport 4.2.0 → 5.2.0

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 (254) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +366 -232
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +4 -5
  5. data/lib/active_support.rb +17 -7
  6. data/lib/active_support/all.rb +5 -3
  7. data/lib/active_support/array_inquirer.rb +48 -0
  8. data/lib/active_support/backtrace_cleaner.rb +7 -5
  9. data/lib/active_support/benchmarkable.rb +6 -4
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache.rb +271 -177
  12. data/lib/active_support/cache/file_store.rb +41 -35
  13. data/lib/active_support/cache/mem_cache_store.rb +97 -88
  14. data/lib/active_support/cache/memory_store.rb +27 -30
  15. data/lib/active_support/cache/null_store.rb +7 -8
  16. data/lib/active_support/cache/redis_cache_store.rb +454 -0
  17. data/lib/active_support/cache/strategy/local_cache.rb +67 -34
  18. data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
  19. data/lib/active_support/callbacks.rb +654 -560
  20. data/lib/active_support/concern.rb +5 -3
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  22. data/lib/active_support/concurrency/share_lock.rb +227 -0
  23. data/lib/active_support/configurable.rb +8 -5
  24. data/lib/active_support/core_ext.rb +3 -1
  25. data/lib/active_support/core_ext/array.rb +9 -6
  26. data/lib/active_support/core_ext/array/access.rb +29 -1
  27. data/lib/active_support/core_ext/array/conversions.rb +22 -18
  28. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +11 -18
  30. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  31. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -3
  32. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  33. data/lib/active_support/core_ext/benchmark.rb +3 -1
  34. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  35. data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
  36. data/lib/active_support/core_ext/class.rb +4 -3
  37. data/lib/active_support/core_ext/class/attribute.rb +41 -22
  38. data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
  39. data/lib/active_support/core_ext/class/subclasses.rb +20 -8
  40. data/lib/active_support/core_ext/date.rb +6 -4
  41. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  42. data/lib/active_support/core_ext/date/blank.rb +14 -0
  43. data/lib/active_support/core_ext/date/calculations.rb +11 -9
  44. data/lib/active_support/core_ext/date/conversions.rb +31 -23
  45. data/lib/active_support/core_ext/date/zones.rb +4 -2
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +179 -56
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +12 -12
  49. data/lib/active_support/core_ext/date_time.rb +7 -4
  50. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  51. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  52. data/lib/active_support/core_ext/date_time/calculations.rb +58 -20
  53. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  54. data/lib/active_support/core_ext/date_time/conversions.rb +16 -12
  55. data/lib/active_support/core_ext/digest/uuid.rb +7 -5
  56. data/lib/active_support/core_ext/enumerable.rb +107 -28
  57. data/lib/active_support/core_ext/file.rb +3 -1
  58. data/lib/active_support/core_ext/file/atomic.rb +38 -31
  59. data/lib/active_support/core_ext/hash.rb +11 -9
  60. data/lib/active_support/core_ext/hash/compact.rb +24 -15
  61. data/lib/active_support/core_ext/hash/conversions.rb +63 -43
  62. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  63. data/lib/active_support/core_ext/hash/except.rb +11 -8
  64. data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
  65. data/lib/active_support/core_ext/hash/keys.rb +33 -27
  66. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  67. data/lib/active_support/core_ext/hash/slice.rb +8 -8
  68. data/lib/active_support/core_ext/hash/transform_values.rb +16 -7
  69. data/lib/active_support/core_ext/integer.rb +5 -3
  70. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  71. data/lib/active_support/core_ext/integer/multiple.rb +2 -0
  72. data/lib/active_support/core_ext/integer/time.rb +11 -33
  73. data/lib/active_support/core_ext/kernel.rb +6 -5
  74. data/lib/active_support/core_ext/kernel/agnostics.rb +2 -0
  75. data/lib/active_support/core_ext/kernel/concern.rb +5 -1
  76. data/lib/active_support/core_ext/kernel/reporting.rb +4 -83
  77. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  78. data/lib/active_support/core_ext/load_error.rb +3 -22
  79. data/lib/active_support/core_ext/marshal.rb +13 -10
  80. data/lib/active_support/core_ext/module.rb +14 -11
  81. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  82. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  83. data/lib/active_support/core_ext/module/attr_internal.rb +8 -9
  84. data/lib/active_support/core_ext/module/attribute_accessors.rb +43 -40
  85. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +150 -0
  86. data/lib/active_support/core_ext/module/concerning.rb +11 -12
  87. data/lib/active_support/core_ext/module/delegation.rb +121 -39
  88. data/lib/active_support/core_ext/module/deprecation.rb +4 -2
  89. data/lib/active_support/core_ext/module/introspection.rb +9 -9
  90. data/lib/active_support/core_ext/module/reachable.rb +5 -2
  91. data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
  92. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  93. data/lib/active_support/core_ext/name_error.rb +22 -2
  94. data/lib/active_support/core_ext/numeric.rb +6 -3
  95. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  96. data/lib/active_support/core_ext/numeric/conversions.rb +79 -74
  97. data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
  98. data/lib/active_support/core_ext/numeric/time.rb +35 -38
  99. data/lib/active_support/core_ext/object.rb +14 -13
  100. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  101. data/lib/active_support/core_ext/object/blank.rb +29 -4
  102. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  103. data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
  104. data/lib/active_support/core_ext/object/duplicable.rb +98 -45
  105. data/lib/active_support/core_ext/object/inclusion.rb +5 -3
  106. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  107. data/lib/active_support/core_ext/object/json.rb +49 -19
  108. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  109. data/lib/active_support/core_ext/object/to_query.rb +6 -4
  110. data/lib/active_support/core_ext/object/try.rb +70 -22
  111. data/lib/active_support/core_ext/object/with_options.rb +16 -3
  112. data/lib/active_support/core_ext/range.rb +7 -4
  113. data/lib/active_support/core_ext/range/conversions.rb +27 -7
  114. data/lib/active_support/core_ext/range/each.rb +19 -17
  115. data/lib/active_support/core_ext/range/include_range.rb +21 -19
  116. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  117. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  118. data/lib/active_support/core_ext/regexp.rb +6 -0
  119. data/lib/active_support/core_ext/securerandom.rb +25 -0
  120. data/lib/active_support/core_ext/string.rb +15 -13
  121. data/lib/active_support/core_ext/string/access.rb +9 -7
  122. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  123. data/lib/active_support/core_ext/string/conversions.rb +8 -5
  124. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  125. data/lib/active_support/core_ext/string/filters.rb +10 -8
  126. data/lib/active_support/core_ext/string/indent.rb +6 -4
  127. data/lib/active_support/core_ext/string/inflections.rb +61 -24
  128. data/lib/active_support/core_ext/string/inquiry.rb +3 -1
  129. data/lib/active_support/core_ext/string/multibyte.rb +15 -7
  130. data/lib/active_support/core_ext/string/output_safety.rb +35 -35
  131. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
  132. data/lib/active_support/core_ext/string/strip.rb +4 -5
  133. data/lib/active_support/core_ext/string/zones.rb +4 -2
  134. data/lib/active_support/core_ext/time.rb +7 -5
  135. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  136. data/lib/active_support/core_ext/time/calculations.rb +101 -51
  137. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  138. data/lib/active_support/core_ext/time/conversions.rb +20 -13
  139. data/lib/active_support/core_ext/time/zones.rb +41 -7
  140. data/lib/active_support/core_ext/uri.rb +5 -4
  141. data/lib/active_support/current_attributes.rb +195 -0
  142. data/lib/active_support/dependencies.rb +143 -160
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +57 -0
  145. data/lib/active_support/deprecation.rb +12 -9
  146. data/lib/active_support/deprecation/behaviors.rb +41 -12
  147. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  148. data/lib/active_support/deprecation/instance_delegator.rb +17 -2
  149. data/lib/active_support/deprecation/method_wrappers.rb +54 -21
  150. data/lib/active_support/deprecation/proxy_wrappers.rb +56 -28
  151. data/lib/active_support/deprecation/reporting.rb +32 -12
  152. data/lib/active_support/descendants_tracker.rb +2 -0
  153. data/lib/active_support/digest.rb +20 -0
  154. data/lib/active_support/duration.rb +326 -30
  155. data/lib/active_support/duration/iso8601_parser.rb +125 -0
  156. data/lib/active_support/duration/iso8601_serializer.rb +55 -0
  157. data/lib/active_support/encrypted_configuration.rb +49 -0
  158. data/lib/active_support/encrypted_file.rb +99 -0
  159. data/lib/active_support/evented_file_update_checker.rb +205 -0
  160. data/lib/active_support/execution_wrapper.rb +128 -0
  161. data/lib/active_support/executor.rb +8 -0
  162. data/lib/active_support/file_update_checker.rb +63 -37
  163. data/lib/active_support/gem_version.rb +4 -2
  164. data/lib/active_support/gzip.rb +7 -5
  165. data/lib/active_support/hash_with_indifferent_access.rb +130 -30
  166. data/lib/active_support/i18n.rb +8 -6
  167. data/lib/active_support/i18n_railtie.rb +34 -14
  168. data/lib/active_support/inflections.rb +13 -11
  169. data/lib/active_support/inflector.rb +7 -5
  170. data/lib/active_support/inflector/inflections.rb +61 -12
  171. data/lib/active_support/inflector/methods.rb +161 -136
  172. data/lib/active_support/inflector/transliterate.rb +48 -27
  173. data/lib/active_support/json.rb +4 -2
  174. data/lib/active_support/json/decoding.rb +16 -13
  175. data/lib/active_support/json/encoding.rb +15 -57
  176. data/lib/active_support/key_generator.rb +25 -25
  177. data/lib/active_support/lazy_load_hooks.rb +50 -20
  178. data/lib/active_support/locale/en.yml +2 -0
  179. data/lib/active_support/log_subscriber.rb +13 -10
  180. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  181. data/lib/active_support/logger.rb +54 -3
  182. data/lib/active_support/logger_silence.rb +12 -7
  183. data/lib/active_support/logger_thread_safe_level.rb +33 -0
  184. data/lib/active_support/message_encryptor.rb +173 -51
  185. data/lib/active_support/message_verifier.rb +150 -17
  186. data/lib/active_support/messages/metadata.rb +71 -0
  187. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  188. data/lib/active_support/messages/rotator.rb +56 -0
  189. data/lib/active_support/multibyte.rb +4 -2
  190. data/lib/active_support/multibyte/chars.rb +37 -24
  191. data/lib/active_support/multibyte/unicode.rb +100 -96
  192. data/lib/active_support/notifications.rb +11 -7
  193. data/lib/active_support/notifications/fanout.rb +10 -8
  194. data/lib/active_support/notifications/instrumenter.rb +27 -7
  195. data/lib/active_support/number_helper.rb +94 -68
  196. data/lib/active_support/number_helper/number_converter.rb +13 -11
  197. data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -9
  198. data/lib/active_support/number_helper/number_to_delimited_converter.rb +9 -3
  199. data/lib/active_support/number_helper/number_to_human_converter.rb +11 -9
  200. data/lib/active_support/number_helper/number_to_human_size_converter.rb +9 -8
  201. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  202. data/lib/active_support/number_helper/number_to_phone_converter.rb +13 -4
  203. data/lib/active_support/number_helper/number_to_rounded_converter.rb +23 -56
  204. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  205. data/lib/active_support/option_merger.rb +3 -1
  206. data/lib/active_support/ordered_hash.rb +6 -4
  207. data/lib/active_support/ordered_options.rb +22 -4
  208. data/lib/active_support/per_thread_registry.rb +13 -6
  209. data/lib/active_support/proxy_object.rb +2 -0
  210. data/lib/active_support/rails.rb +16 -8
  211. data/lib/active_support/railtie.rb +43 -9
  212. data/lib/active_support/reloader.rb +131 -0
  213. data/lib/active_support/rescuable.rb +108 -53
  214. data/lib/active_support/security_utils.rb +17 -6
  215. data/lib/active_support/string_inquirer.rb +11 -3
  216. data/lib/active_support/subscriber.rb +15 -14
  217. data/lib/active_support/tagged_logging.rb +14 -11
  218. data/lib/active_support/test_case.rb +18 -46
  219. data/lib/active_support/testing/assertions.rb +137 -20
  220. data/lib/active_support/testing/autorun.rb +4 -2
  221. data/lib/active_support/testing/constant_lookup.rb +2 -1
  222. data/lib/active_support/testing/declarative.rb +3 -1
  223. data/lib/active_support/testing/deprecation.rb +14 -10
  224. data/lib/active_support/testing/file_fixtures.rb +36 -0
  225. data/lib/active_support/testing/isolation.rb +34 -25
  226. data/lib/active_support/testing/method_call_assertions.rb +43 -0
  227. data/lib/active_support/testing/setup_and_teardown.rb +12 -3
  228. data/lib/active_support/testing/stream.rb +44 -0
  229. data/lib/active_support/testing/tagged_logging.rb +3 -1
  230. data/lib/active_support/testing/time_helpers.rb +96 -27
  231. data/lib/active_support/time.rb +14 -12
  232. data/lib/active_support/time_with_zone.rb +195 -53
  233. data/lib/active_support/values/time_zone.rb +200 -61
  234. data/lib/active_support/values/unicode_tables.dat +0 -0
  235. data/lib/active_support/version.rb +3 -1
  236. data/lib/active_support/xml_mini.rb +69 -51
  237. data/lib/active_support/xml_mini/jdom.rb +116 -113
  238. data/lib/active_support/xml_mini/libxml.rb +17 -16
  239. data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
  240. data/lib/active_support/xml_mini/nokogiri.rb +15 -15
  241. data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
  242. data/lib/active_support/xml_mini/rexml.rb +17 -16
  243. metadata +55 -43
  244. data/lib/active_support/concurrency/latch.rb +0 -27
  245. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -14
  246. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
  247. data/lib/active_support/core_ext/date_time/zones.rb +0 -6
  248. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  249. data/lib/active_support/core_ext/module/method_transplanting.rb +0 -11
  250. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  251. data/lib/active_support/core_ext/object/itself.rb +0 -15
  252. data/lib/active_support/core_ext/struct.rb +0 -6
  253. data/lib/active_support/core_ext/thread.rb +0 -86
  254. data/lib/active_support/core_ext/time/marshal.rb +0 -30
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+ require "active_support/core_ext/regexp"
5
+
6
+ module ActiveSupport
7
+ class Duration
8
+ # Parses a string formatted according to ISO 8601 Duration into the hash.
9
+ #
10
+ # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
11
+ #
12
+ # This parser allows negative parts to be present in pattern.
13
+ class ISO8601Parser # :nodoc:
14
+ class ParsingError < ::ArgumentError; end
15
+
16
+ PERIOD_OR_COMMA = /\.|,/
17
+ PERIOD = ".".freeze
18
+ COMMA = ",".freeze
19
+
20
+ SIGN_MARKER = /\A\-|\+|/
21
+ DATE_MARKER = /P/
22
+ TIME_MARKER = /T/
23
+ DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
24
+ TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/
25
+
26
+ DATE_TO_PART = { "Y" => :years, "M" => :months, "W" => :weeks, "D" => :days }
27
+ TIME_TO_PART = { "H" => :hours, "M" => :minutes, "S" => :seconds }
28
+
29
+ DATE_COMPONENTS = [:years, :months, :days]
30
+ TIME_COMPONENTS = [:hours, :minutes, :seconds]
31
+
32
+ attr_reader :parts, :scanner
33
+ attr_accessor :mode, :sign
34
+
35
+ def initialize(string)
36
+ @scanner = StringScanner.new(string)
37
+ @parts = {}
38
+ @mode = :start
39
+ @sign = 1
40
+ end
41
+
42
+ def parse!
43
+ while !finished?
44
+ case mode
45
+ when :start
46
+ if scan(SIGN_MARKER)
47
+ self.sign = (scanner.matched == "-") ? -1 : 1
48
+ self.mode = :sign
49
+ else
50
+ raise_parsing_error
51
+ end
52
+
53
+ when :sign
54
+ if scan(DATE_MARKER)
55
+ self.mode = :date
56
+ else
57
+ raise_parsing_error
58
+ end
59
+
60
+ when :date
61
+ if scan(TIME_MARKER)
62
+ self.mode = :time
63
+ elsif scan(DATE_COMPONENT)
64
+ parts[DATE_TO_PART[scanner[2]]] = number * sign
65
+ else
66
+ raise_parsing_error
67
+ end
68
+
69
+ when :time
70
+ if scan(TIME_COMPONENT)
71
+ parts[TIME_TO_PART[scanner[2]]] = number * sign
72
+ else
73
+ raise_parsing_error
74
+ end
75
+
76
+ end
77
+ end
78
+
79
+ validate!
80
+ parts
81
+ end
82
+
83
+ private
84
+
85
+ def finished?
86
+ scanner.eos?
87
+ end
88
+
89
+ # Parses number which can be a float with either comma or period.
90
+ def number
91
+ PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
92
+ end
93
+
94
+ def scan(pattern)
95
+ scanner.scan(pattern)
96
+ end
97
+
98
+ def raise_parsing_error(reason = nil)
99
+ raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
100
+ end
101
+
102
+ # Checks for various semantic errors as stated in ISO 8601 standard.
103
+ def validate!
104
+ raise_parsing_error("is empty duration") if parts.empty?
105
+
106
+ # Mixing any of Y, M, D with W is invalid.
107
+ if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
108
+ raise_parsing_error("mixing weeks with other date parts not allowed")
109
+ end
110
+
111
+ # Specifying an empty T part is invalid.
112
+ if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
113
+ raise_parsing_error("time part marker is present but time part is empty")
114
+ end
115
+
116
+ fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
117
+ unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
118
+ raise_parsing_error "(only last part can be fractional)"
119
+ end
120
+
121
+ true
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/blank"
4
+ require "active_support/core_ext/hash/transform_values"
5
+
6
+ module ActiveSupport
7
+ class Duration
8
+ # Serializes duration to string according to ISO 8601 Duration format.
9
+ class ISO8601Serializer # :nodoc:
10
+ def initialize(duration, precision: nil)
11
+ @duration = duration
12
+ @precision = precision
13
+ end
14
+
15
+ # Builds and returns output string.
16
+ def serialize
17
+ parts, sign = normalize
18
+ return "PT0S".freeze if parts.empty?
19
+
20
+ output = "P".dup
21
+ output << "#{parts[:years]}Y" if parts.key?(:years)
22
+ output << "#{parts[:months]}M" if parts.key?(:months)
23
+ output << "#{parts[:weeks]}W" if parts.key?(:weeks)
24
+ output << "#{parts[:days]}D" if parts.key?(:days)
25
+ time = "".dup
26
+ time << "#{parts[:hours]}H" if parts.key?(:hours)
27
+ time << "#{parts[:minutes]}M" if parts.key?(:minutes)
28
+ if parts.key?(:seconds)
29
+ time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
30
+ end
31
+ output << "T#{time}" unless time.empty?
32
+ "#{sign}#{output}"
33
+ end
34
+
35
+ private
36
+
37
+ # Return pair of duration's parts and whole duration sign.
38
+ # Parts are summarized (as they can become repetitive due to addition, etc).
39
+ # Zero parts are removed as not significant.
40
+ # If all parts are negative it will negate all of them and return minus as a sign.
41
+ def normalize
42
+ parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
43
+ p[k] += v unless v.zero?
44
+ end
45
+ # If all parts are negative - let's make a negative duration
46
+ sign = ""
47
+ if parts.values.all? { |v| v < 0 }
48
+ sign = "-"
49
+ parts.transform_values!(&:-@)
50
+ end
51
+ [parts, sign]
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "active_support/encrypted_file"
5
+ require "active_support/ordered_options"
6
+ require "active_support/core_ext/object/inclusion"
7
+ require "active_support/core_ext/module/delegation"
8
+
9
+ module ActiveSupport
10
+ class EncryptedConfiguration < EncryptedFile
11
+ delegate :[], :fetch, to: :config
12
+ delegate_missing_to :options
13
+
14
+ def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
15
+ super content_path: config_path, key_path: key_path,
16
+ env_key: env_key, raise_if_missing_key: raise_if_missing_key
17
+ end
18
+
19
+ # Allow a config to be started without a file present
20
+ def read
21
+ super
22
+ rescue ActiveSupport::EncryptedFile::MissingContentError
23
+ ""
24
+ end
25
+
26
+ def write(contents)
27
+ deserialize(contents)
28
+
29
+ super
30
+ end
31
+
32
+ def config
33
+ @config ||= deserialize(read).deep_symbolize_keys
34
+ end
35
+
36
+ private
37
+ def options
38
+ @options ||= ActiveSupport::InheritableOptions.new(config)
39
+ end
40
+
41
+ def serialize(config)
42
+ config.present? ? YAML.dump(config) : ""
43
+ end
44
+
45
+ def deserialize(config)
46
+ config.present? ? YAML.load(config, content_path) : {}
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "active_support/message_encryptor"
5
+
6
+ module ActiveSupport
7
+ class EncryptedFile
8
+ class MissingContentError < RuntimeError
9
+ def initialize(content_path)
10
+ super "Missing encrypted content file in #{content_path}."
11
+ end
12
+ end
13
+
14
+ class MissingKeyError < RuntimeError
15
+ def initialize(key_path:, env_key:)
16
+ super \
17
+ "Missing encryption key to decrypt file with. " +
18
+ "Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']."
19
+ end
20
+ end
21
+
22
+ CIPHER = "aes-128-gcm"
23
+
24
+ def self.generate_key
25
+ SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
26
+ end
27
+
28
+
29
+ attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
30
+
31
+ def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
32
+ @content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path)
33
+ @env_key, @raise_if_missing_key = env_key, raise_if_missing_key
34
+ end
35
+
36
+ def key
37
+ read_env_key || read_key_file || handle_missing_key
38
+ end
39
+
40
+ def read
41
+ if !key.nil? && content_path.exist?
42
+ decrypt content_path.binread
43
+ else
44
+ raise MissingContentError, content_path
45
+ end
46
+ end
47
+
48
+ def write(contents)
49
+ IO.binwrite "#{content_path}.tmp", encrypt(contents)
50
+ FileUtils.mv "#{content_path}.tmp", content_path
51
+ end
52
+
53
+ def change(&block)
54
+ writing read, &block
55
+ end
56
+
57
+
58
+ private
59
+ def writing(contents)
60
+ tmp_file = "#{content_path.basename}.#{Process.pid}"
61
+ tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file)
62
+ tmp_path.binwrite contents
63
+
64
+ yield tmp_path
65
+
66
+ updated_contents = tmp_path.binread
67
+
68
+ write(updated_contents) if updated_contents != contents
69
+ ensure
70
+ FileUtils.rm(tmp_path) if tmp_path.exist?
71
+ end
72
+
73
+
74
+ def encrypt(contents)
75
+ encryptor.encrypt_and_sign contents
76
+ end
77
+
78
+ def decrypt(contents)
79
+ encryptor.decrypt_and_verify contents
80
+ end
81
+
82
+ def encryptor
83
+ @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER)
84
+ end
85
+
86
+
87
+ def read_env_key
88
+ ENV[env_key]
89
+ end
90
+
91
+ def read_key_file
92
+ key_path.binread.strip if key_path.exist?
93
+ end
94
+
95
+ def handle_missing_key
96
+ raise MissingKeyError, key_path: key_path, env_key: env_key if raise_if_missing_key
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "pathname"
5
+ require "concurrent/atomic/atomic_boolean"
6
+
7
+ module ActiveSupport
8
+ # Allows you to "listen" to changes in a file system.
9
+ # The evented file updater does not hit disk when checking for updates
10
+ # instead it uses platform specific file system events to trigger a change
11
+ # in state.
12
+ #
13
+ # The file checker takes an array of files to watch or a hash specifying directories
14
+ # and file extensions to watch. It also takes a block that is called when
15
+ # EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated
16
+ # is run and there have been changes to the file system.
17
+ #
18
+ # Note: Forking will cause the first call to `updated?` to return `true`.
19
+ #
20
+ # Example:
21
+ #
22
+ # checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" }
23
+ # checker.updated?
24
+ # # => false
25
+ # checker.execute_if_updated
26
+ # # => nil
27
+ #
28
+ # FileUtils.touch("/tmp/foo")
29
+ #
30
+ # checker.updated?
31
+ # # => true
32
+ # checker.execute_if_updated
33
+ # # => "changed"
34
+ #
35
+ class EventedFileUpdateChecker #:nodoc: all
36
+ def initialize(files, dirs = {}, &block)
37
+ unless block
38
+ raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker"
39
+ end
40
+
41
+ @ph = PathHelper.new
42
+ @files = files.map { |f| @ph.xpath(f) }.to_set
43
+
44
+ @dirs = {}
45
+ dirs.each do |dir, exts|
46
+ @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
47
+ end
48
+
49
+ @block = block
50
+ @updated = Concurrent::AtomicBoolean.new(false)
51
+ @lcsp = @ph.longest_common_subpath(@dirs.keys)
52
+ @pid = Process.pid
53
+ @boot_mutex = Mutex.new
54
+
55
+ if (@dtw = directories_to_watch).any?
56
+ # Loading listen triggers warnings. These are originated by a legit
57
+ # usage of attr_* macros for private attributes, but adds a lot of noise
58
+ # to our test suite. Thus, we lazy load it and disable warnings locally.
59
+ silence_warnings do
60
+ begin
61
+ require "listen"
62
+ rescue LoadError => e
63
+ raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
64
+ end
65
+ end
66
+ end
67
+ boot!
68
+ end
69
+
70
+ def updated?
71
+ @boot_mutex.synchronize do
72
+ if @pid != Process.pid
73
+ boot!
74
+ @pid = Process.pid
75
+ @updated.make_true
76
+ end
77
+ end
78
+ @updated.true?
79
+ end
80
+
81
+ def execute
82
+ @updated.make_false
83
+ @block.call
84
+ end
85
+
86
+ def execute_if_updated
87
+ if updated?
88
+ yield if block_given?
89
+ execute
90
+ true
91
+ end
92
+ end
93
+
94
+ private
95
+ def boot!
96
+ Listen.to(*@dtw, &method(:changed)).start
97
+ end
98
+
99
+ def changed(modified, added, removed)
100
+ unless updated?
101
+ @updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
102
+ end
103
+ end
104
+
105
+ def watching?(file)
106
+ file = @ph.xpath(file)
107
+
108
+ if @files.member?(file)
109
+ true
110
+ elsif file.directory?
111
+ false
112
+ else
113
+ ext = @ph.normalize_extension(file.extname)
114
+
115
+ file.dirname.ascend do |dir|
116
+ if @dirs.fetch(dir, []).include?(ext)
117
+ break true
118
+ elsif dir == @lcsp || dir.root?
119
+ break false
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ def directories_to_watch
126
+ dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) }
127
+ dtw.compact!
128
+ dtw.uniq!
129
+
130
+ normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
131
+ dtw = dtw.reject do |path|
132
+ normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) }
133
+ end
134
+
135
+ @ph.filter_out_descendants(dtw)
136
+ end
137
+
138
+ class PathHelper
139
+ def xpath(path)
140
+ Pathname.new(path).expand_path
141
+ end
142
+
143
+ def normalize_extension(ext)
144
+ ext.to_s.sub(/\A\./, "")
145
+ end
146
+
147
+ # Given a collection of Pathname objects returns the longest subpath
148
+ # common to all of them, or +nil+ if there is none.
149
+ def longest_common_subpath(paths)
150
+ return if paths.empty?
151
+
152
+ lcsp = Pathname.new(paths[0])
153
+
154
+ paths[1..-1].each do |path|
155
+ until ascendant_of?(lcsp, path)
156
+ if lcsp.root?
157
+ # If we get here a root directory is not an ascendant of path.
158
+ # This may happen if there are paths in different drives on
159
+ # Windows.
160
+ return
161
+ else
162
+ lcsp = lcsp.parent
163
+ end
164
+ end
165
+ end
166
+
167
+ lcsp
168
+ end
169
+
170
+ # Returns the deepest existing ascendant, which could be the argument itself.
171
+ def existing_parent(dir)
172
+ dir.ascend do |ascendant|
173
+ break ascendant if ascendant.directory?
174
+ end
175
+ end
176
+
177
+ # Filters out directories which are descendants of others in the collection (stable).
178
+ def filter_out_descendants(dirs)
179
+ return dirs if dirs.length < 2
180
+
181
+ dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
182
+ descendants = []
183
+
184
+ until dirs_sorted_by_nparts.empty?
185
+ dir = dirs_sorted_by_nparts.shift
186
+
187
+ dirs_sorted_by_nparts.reject! do |possible_descendant|
188
+ ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
189
+ end
190
+ end
191
+
192
+ # Array#- preserves order.
193
+ dirs - descendants
194
+ end
195
+
196
+ private
197
+
198
+ def ascendant_of?(base, other)
199
+ base != other && other.ascend do |ascendant|
200
+ break true if base == ascendant
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end