zeitwerk 2.4.0 → 2.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +50 -3
- data/lib/zeitwerk/explicit_namespace.rb +9 -8
- data/lib/zeitwerk/gem_inflector.rb +2 -4
- data/lib/zeitwerk/inflector.rb +3 -6
- data/lib/zeitwerk/kernel.rb +2 -2
- data/lib/zeitwerk/loader.rb +91 -106
- data/lib/zeitwerk/loader/callbacks.rb +27 -12
- data/lib/zeitwerk/real_mod_name.rb +1 -2
- data/lib/zeitwerk/registry.rb +12 -23
- data/lib/zeitwerk/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f45a7c3caf48f06e10dd513a7b074da7ec059fa3299751d361514aadc83d995e
|
4
|
+
data.tar.gz: c8c29793e95245b47b1283891791d2c20365b8c694b3dcdfb7daab0b74c16bdb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6194d326b268c9333ed9d2ac1c62b65756fa9af844c5fb7ad8272ecec33aa439015506758bcd329e421508facb4153e04e45da0efb0a2a42001a6f2e0a0d3b6a
|
7
|
+
data.tar.gz: 656009f40e777f641ff1dc80f54b63559514439538c73965aa9226b6c3e33c6be71c07eb30adfe7f4cd4b3be72aa6e42918392ba27167fb9502b9aed037f36d3
|
data/README.md
CHANGED
@@ -25,6 +25,7 @@
|
|
25
25
|
- [Zeitwerk::Inflector](#zeitwerkinflector)
|
26
26
|
- [Zeitwerk::GemInflector](#zeitwerkgeminflector)
|
27
27
|
- [Custom inflector](#custom-inflector)
|
28
|
+
- [The on_load callback](#the-on_load-callback)
|
28
29
|
- [Logging](#logging)
|
29
30
|
- [Loader tag](#loader-tag)
|
30
31
|
- [Ignoring parts of the project](#ignoring-parts-of-the-project)
|
@@ -151,7 +152,7 @@ require "active_job/queue_adapters"
|
|
151
152
|
loader.push_dir("#{__dir__}/adapters", namespace: ActiveJob::QueueAdapters)
|
152
153
|
```
|
153
154
|
|
154
|
-
your adapter can be stored directly in that directory instead of the canonical
|
155
|
+
your adapter can be stored directly in that directory instead of the canonical `#{__dir__}/active_job/queue_adapters`.
|
155
156
|
|
156
157
|
Please, note that the given namespace must be non-reloadable, though autoloaded constants in that namespace can be. That is, if you associate `app/api` with an existing `Api` module, that module should not be reloadable. However, if the project defines and autoloads the class `Api::V2::Deliveries`, that one can be reloaded.
|
157
158
|
|
@@ -202,7 +203,7 @@ booking/actions/create.rb -> Booking::Create
|
|
202
203
|
To make it work that way, configure Zeitwerk to collapse said directory:
|
203
204
|
|
204
205
|
```ruby
|
205
|
-
loader.collapse("booking/actions")
|
206
|
+
loader.collapse("#{__dir__}/booking/actions")
|
206
207
|
```
|
207
208
|
|
208
209
|
This method accepts an arbitrary number of strings or `Pathname` objects, and also an array of them.
|
@@ -212,7 +213,7 @@ You can pass directories and glob patterns. Glob patterns are expanded when they
|
|
212
213
|
To illustrate usage of glob patterns, if `actions` in the example above is part of a standardized structure, you could use a wildcard:
|
213
214
|
|
214
215
|
```ruby
|
215
|
-
loader.collapse("
|
216
|
+
loader.collapse("#{__dir__}/*/actions")
|
216
217
|
```
|
217
218
|
|
218
219
|
<a id="markdown-nested-root-directories" name="nested-root-directories"></a>
|
@@ -502,6 +503,52 @@ class MyGem::Inflector < Zeitwerk::GemInflector
|
|
502
503
|
end
|
503
504
|
```
|
504
505
|
|
506
|
+
<a id="markdown-the-on_load-callback" name="the-on_load-callback"></a>
|
507
|
+
### The on_load callback
|
508
|
+
|
509
|
+
The usual place to run something when a file is loaded is the file itself. However, sometimes you'd like to be called, and this is possible with the `on_load` callback.
|
510
|
+
|
511
|
+
For example, let's imagine this class belongs to a Rails application:
|
512
|
+
|
513
|
+
```ruby
|
514
|
+
class SomeApiClient
|
515
|
+
class << self
|
516
|
+
attr_accessor :endpoint
|
517
|
+
end
|
518
|
+
end
|
519
|
+
```
|
520
|
+
|
521
|
+
With `on_load`, it is easy to schedule code at boot time that initializes `endpoint` according to the configuration:
|
522
|
+
|
523
|
+
```ruby
|
524
|
+
# config/environments/development.rb
|
525
|
+
loader.on_load("SomeApiClient") do
|
526
|
+
SomeApiClient.endpoint = "https://api.dev"
|
527
|
+
end
|
528
|
+
|
529
|
+
# config/environments/production.rb
|
530
|
+
loader.on_load("SomeApiClient") do
|
531
|
+
SomeApiClient.endpoint = "https://api.prod"
|
532
|
+
end
|
533
|
+
```
|
534
|
+
|
535
|
+
Uses cases:
|
536
|
+
|
537
|
+
* Doing something with an autoloadable class or module in a Rails application during initialization, in a way that plays well with reloading. As in the previous example.
|
538
|
+
* Delaying the execution of the block until the class is loaded for performance.
|
539
|
+
* Delaying the execution of the block until the class is loaded because it follows the adapter pattern and better not to load the class if the user does not need it.
|
540
|
+
* Etc.
|
541
|
+
|
542
|
+
However, let me stress that the easiest way to accomplish that is to write whatever you have to do in the actual target file. `on_load` use cases are edgy, use it only if appropriate.
|
543
|
+
|
544
|
+
`on_load` receives the name of the target class or module as a string. The given block is executed every time its corresponding file is loaded. That includes reloads.
|
545
|
+
|
546
|
+
Multiple callbacks on the same target are supported, and they run in order of definition.
|
547
|
+
|
548
|
+
The block is executed once the loader has loaded the target. In particular, if the target was already loaded when the callback is defined, the block won't run. But if you reload and load the target again, then it will. Normally, you'll want to define `on_load` callbacks before `setup`.
|
549
|
+
|
550
|
+
Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
|
551
|
+
|
505
552
|
<a id="markdown-logging" name="logging"></a>
|
506
553
|
### Logging
|
507
554
|
|
@@ -14,24 +14,22 @@ module Zeitwerk
|
|
14
14
|
# the file system, to the loader responsible for them.
|
15
15
|
#
|
16
16
|
# @private
|
17
|
-
# @
|
17
|
+
# @sig Hash[String, Zeitwerk::Loader]
|
18
18
|
attr_reader :cpaths
|
19
19
|
|
20
20
|
# @private
|
21
|
-
# @
|
21
|
+
# @sig Mutex
|
22
22
|
attr_reader :mutex
|
23
23
|
|
24
24
|
# @private
|
25
|
-
# @
|
25
|
+
# @sig TracePoint
|
26
26
|
attr_reader :tracer
|
27
27
|
|
28
28
|
# Asserts `cpath` corresponds to an explicit namespace for which `loader`
|
29
29
|
# is responsible.
|
30
30
|
#
|
31
31
|
# @private
|
32
|
-
# @
|
33
|
-
# @param loader [Zeitwerk::Loader]
|
34
|
-
# @return [void]
|
32
|
+
# @sig (String, Zeitwerk::Loader) -> void
|
35
33
|
def register(cpath, loader)
|
36
34
|
mutex.synchronize do
|
37
35
|
cpaths[cpath] = loader
|
@@ -42,19 +40,22 @@ module Zeitwerk
|
|
42
40
|
end
|
43
41
|
|
44
42
|
# @private
|
45
|
-
# @
|
46
|
-
# @return [void]
|
43
|
+
# @sig (Zeitwerk::Loader) -> void
|
47
44
|
def unregister(loader)
|
48
45
|
cpaths.delete_if { |_cpath, l| l == loader }
|
49
46
|
disable_tracer_if_unneeded
|
50
47
|
end
|
51
48
|
|
49
|
+
private
|
50
|
+
|
51
|
+
# @sig () -> void
|
52
52
|
def disable_tracer_if_unneeded
|
53
53
|
mutex.synchronize do
|
54
54
|
tracer.disable if cpaths.empty?
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
# @sig (TracePoint) -> void
|
58
59
|
def tracepoint_class_callback(event)
|
59
60
|
# If the class is a singleton class, we won't do anything with it so we
|
60
61
|
# can bail out immediately. This is several orders of magnitude faster
|
@@ -2,16 +2,14 @@
|
|
2
2
|
|
3
3
|
module Zeitwerk
|
4
4
|
class GemInflector < Inflector
|
5
|
-
# @
|
5
|
+
# @sig (String) -> void
|
6
6
|
def initialize(root_file)
|
7
7
|
namespace = File.basename(root_file, ".rb")
|
8
8
|
lib_dir = File.dirname(root_file)
|
9
9
|
@version_file = File.join(lib_dir, namespace, "version.rb")
|
10
10
|
end
|
11
11
|
|
12
|
-
# @
|
13
|
-
# @param abspath [String]
|
14
|
-
# @return [String]
|
12
|
+
# @sig (String, String) -> String
|
15
13
|
def camelize(basename, abspath)
|
16
14
|
abspath == @version_file ? "VERSION" : super
|
17
15
|
end
|
data/lib/zeitwerk/inflector.rb
CHANGED
@@ -11,9 +11,7 @@ module Zeitwerk
|
|
11
11
|
#
|
12
12
|
# Takes into account hard-coded mappings configured with `inflect`.
|
13
13
|
#
|
14
|
-
# @
|
15
|
-
# @param _abspath [String]
|
16
|
-
# @return [String]
|
14
|
+
# @sig (String, String) -> String
|
17
15
|
def camelize(basename, _abspath)
|
18
16
|
overrides[basename] || basename.split('_').each(&:capitalize!).join
|
19
17
|
end
|
@@ -30,8 +28,7 @@ module Zeitwerk
|
|
30
28
|
# inflector.camelize("mysql_adapter", abspath) # => "MySQLAdapter"
|
31
29
|
# inflector.camelize("users_controller", abspath) # => "UsersController"
|
32
30
|
#
|
33
|
-
# @
|
34
|
-
# @return [void]
|
31
|
+
# @sig (Hash[String, String]) -> void
|
35
32
|
def inflect(inflections)
|
36
33
|
overrides.merge!(inflections)
|
37
34
|
end
|
@@ -41,7 +38,7 @@ module Zeitwerk
|
|
41
38
|
# Hard-coded basename to constant name user maps that override the default
|
42
39
|
# inflection logic.
|
43
40
|
#
|
44
|
-
# @
|
41
|
+
# @sig () -> Hash[String, String]
|
45
42
|
def overrides
|
46
43
|
@overrides ||= {}
|
47
44
|
end
|
data/lib/zeitwerk/kernel.rb
CHANGED
@@ -19,8 +19,7 @@ module Kernel
|
|
19
19
|
# already existing ancestor chains.
|
20
20
|
alias_method :zeitwerk_original_require, :require
|
21
21
|
|
22
|
-
# @
|
23
|
-
# @return [Boolean]
|
22
|
+
# @sig (String) -> true | false
|
24
23
|
def require(path)
|
25
24
|
if loader = Zeitwerk::Registry.loader_for(path)
|
26
25
|
if path.end_with?(".rb")
|
@@ -29,6 +28,7 @@ module Kernel
|
|
29
28
|
end
|
30
29
|
else
|
31
30
|
loader.on_dir_autoloaded(path)
|
31
|
+
true
|
32
32
|
end
|
33
33
|
else
|
34
34
|
zeitwerk_original_require(path).tap do |required|
|
data/lib/zeitwerk/loader.rb
CHANGED
@@ -9,13 +9,13 @@ module Zeitwerk
|
|
9
9
|
include Callbacks
|
10
10
|
include RealModName
|
11
11
|
|
12
|
-
# @
|
12
|
+
# @sig String
|
13
13
|
attr_reader :tag
|
14
14
|
|
15
|
-
# @
|
15
|
+
# @sig #camelize
|
16
16
|
attr_accessor :inflector
|
17
17
|
|
18
|
-
# @
|
18
|
+
# @sig #call | #debug | nil
|
19
19
|
attr_accessor :logger
|
20
20
|
|
21
21
|
# Absolute paths of the root directories. Stored in a hash to preserve
|
@@ -30,20 +30,20 @@ module Zeitwerk
|
|
30
30
|
# interface for it is `push_dir` and `dirs`.
|
31
31
|
#
|
32
32
|
# @private
|
33
|
-
# @
|
33
|
+
# @sig Hash[String, true]
|
34
34
|
attr_reader :root_dirs
|
35
35
|
|
36
36
|
# Absolute paths of files or directories that have to be preloaded.
|
37
37
|
#
|
38
38
|
# @private
|
39
|
-
# @
|
39
|
+
# @sig Array[String]
|
40
40
|
attr_reader :preloads
|
41
41
|
|
42
42
|
# Absolute paths of files, directories, or glob patterns to be totally
|
43
43
|
# ignored.
|
44
44
|
#
|
45
45
|
# @private
|
46
|
-
# @
|
46
|
+
# @sig Set[String]
|
47
47
|
attr_reader :ignored_glob_patterns
|
48
48
|
|
49
49
|
# The actual collection of absolute file and directory names at the time the
|
@@ -51,20 +51,20 @@ module Zeitwerk
|
|
51
51
|
# reload.
|
52
52
|
#
|
53
53
|
# @private
|
54
|
-
# @
|
54
|
+
# @sig Set[String]
|
55
55
|
attr_reader :ignored_paths
|
56
56
|
|
57
57
|
# Absolute paths of directories or glob patterns to be collapsed.
|
58
58
|
#
|
59
59
|
# @private
|
60
|
-
# @
|
60
|
+
# @sig Set[String]
|
61
61
|
attr_reader :collapse_glob_patterns
|
62
62
|
|
63
63
|
# The actual collection of absolute directory names at the time the collapse
|
64
64
|
# glob patterns were expanded. Computed on setup, and recomputed on reload.
|
65
65
|
#
|
66
66
|
# @private
|
67
|
-
# @
|
67
|
+
# @sig Set[String]
|
68
68
|
attr_reader :collapse_dirs
|
69
69
|
|
70
70
|
# Maps real absolute paths for which an autoload has been set ---and not
|
@@ -76,7 +76,7 @@ module Zeitwerk
|
|
76
76
|
# ...
|
77
77
|
#
|
78
78
|
# @private
|
79
|
-
# @
|
79
|
+
# @sig Hash[String, [Module, Symbol]]
|
80
80
|
attr_reader :autoloads
|
81
81
|
|
82
82
|
# We keep track of autoloaded directories to remove them from the registry
|
@@ -86,7 +86,7 @@ module Zeitwerk
|
|
86
86
|
# to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
|
87
87
|
#
|
88
88
|
# @private
|
89
|
-
# @
|
89
|
+
# @sig Array[String]
|
90
90
|
attr_reader :autoloaded_dirs
|
91
91
|
|
92
92
|
# Stores metadata needed for unloading. Its entries look like this:
|
@@ -102,7 +102,7 @@ module Zeitwerk
|
|
102
102
|
# or eager loaded. Otherwise, the collection remains empty.
|
103
103
|
#
|
104
104
|
# @private
|
105
|
-
# @
|
105
|
+
# @sig Hash[String, [String, [Module, Symbol]]]
|
106
106
|
attr_reader :to_unload
|
107
107
|
|
108
108
|
# Maps constant paths of namespaces to arrays of corresponding directories.
|
@@ -120,21 +120,24 @@ module Zeitwerk
|
|
120
120
|
# up the corresponding autoloads.
|
121
121
|
#
|
122
122
|
# @private
|
123
|
-
# @
|
123
|
+
# @sig Hash[String, Array[String]]
|
124
124
|
attr_reader :lazy_subdirs
|
125
125
|
|
126
126
|
# Absolute paths of files or directories not to be eager loaded.
|
127
127
|
#
|
128
128
|
# @private
|
129
|
-
# @
|
129
|
+
# @sig Set[String]
|
130
130
|
attr_reader :eager_load_exclusions
|
131
131
|
|
132
|
+
# User-oriented callbacks to be fired when a constant is loaded.
|
133
|
+
attr_reader :on_load_callbacks
|
134
|
+
|
132
135
|
# @private
|
133
|
-
# @
|
136
|
+
# @sig Mutex
|
134
137
|
attr_reader :mutex
|
135
138
|
|
136
139
|
# @private
|
137
|
-
# @
|
140
|
+
# @sig Mutex
|
138
141
|
attr_reader :mutex2
|
139
142
|
|
140
143
|
def initialize
|
@@ -155,6 +158,7 @@ module Zeitwerk
|
|
155
158
|
@to_unload = {}
|
156
159
|
@lazy_subdirs = {}
|
157
160
|
@eager_load_exclusions = Set.new
|
161
|
+
@on_load_callbacks = {}
|
158
162
|
|
159
163
|
# TODO: find a better name for these mutexes.
|
160
164
|
@mutex = Mutex.new
|
@@ -170,7 +174,7 @@ module Zeitwerk
|
|
170
174
|
# Sets a tag for the loader, useful for logging.
|
171
175
|
#
|
172
176
|
# @param tag [#to_s]
|
173
|
-
# @
|
177
|
+
# @sig (#to_s) -> void
|
174
178
|
def tag=(tag)
|
175
179
|
@tag = tag.to_s
|
176
180
|
end
|
@@ -178,7 +182,7 @@ module Zeitwerk
|
|
178
182
|
# Absolute paths of the root directories. This is a read-only collection,
|
179
183
|
# please push here via `push_dir`.
|
180
184
|
#
|
181
|
-
# @
|
185
|
+
# @sig () -> Array[String]
|
182
186
|
def dirs
|
183
187
|
root_dirs.keys.freeze
|
184
188
|
end
|
@@ -189,10 +193,8 @@ module Zeitwerk
|
|
189
193
|
# the same process already manages that directory or one of its ascendants
|
190
194
|
# or descendants.
|
191
195
|
#
|
192
|
-
# @param path [<String, Pathname>]
|
193
|
-
# @param namespace [Class, Module]
|
194
196
|
# @raise [Zeitwerk::Error]
|
195
|
-
# @
|
197
|
+
# @sig (String | Pathname, Module) -> void
|
196
198
|
def push_dir(path, namespace: Object)
|
197
199
|
# Note that Class < Module.
|
198
200
|
unless namespace.is_a?(Module)
|
@@ -212,7 +214,7 @@ module Zeitwerk
|
|
212
214
|
# There is no way to undo this, either you want to reload or you don't.
|
213
215
|
#
|
214
216
|
# @raise [Zeitwerk::Error]
|
215
|
-
# @
|
217
|
+
# @sig () -> void
|
216
218
|
def enable_reloading
|
217
219
|
mutex.synchronize do
|
218
220
|
break if @reloading_enabled
|
@@ -225,15 +227,14 @@ module Zeitwerk
|
|
225
227
|
end
|
226
228
|
end
|
227
229
|
|
228
|
-
# @
|
230
|
+
# @sig () -> bool
|
229
231
|
def reloading_enabled?
|
230
232
|
@reloading_enabled
|
231
233
|
end
|
232
234
|
|
233
235
|
# Files or directories to be preloaded instead of lazy loaded.
|
234
236
|
#
|
235
|
-
# @
|
236
|
-
# @return [void]
|
237
|
+
# @sig (*(String | Pathname | Array[String | Pathname])) -> void
|
237
238
|
def preload(*paths)
|
238
239
|
mutex.synchronize do
|
239
240
|
expand_paths(paths).each do |abspath|
|
@@ -245,8 +246,7 @@ module Zeitwerk
|
|
245
246
|
|
246
247
|
# Configure files, directories, or glob patterns to be totally ignored.
|
247
248
|
#
|
248
|
-
# @
|
249
|
-
# @return [void]
|
249
|
+
# @sig (*(String | Pathname | Array[String | Pathname])) -> void
|
250
250
|
def ignore(*glob_patterns)
|
251
251
|
glob_patterns = expand_paths(glob_patterns)
|
252
252
|
mutex.synchronize do
|
@@ -257,8 +257,7 @@ module Zeitwerk
|
|
257
257
|
|
258
258
|
# Configure directories or glob patterns to be collapsed.
|
259
259
|
#
|
260
|
-
# @
|
261
|
-
# @return [void]
|
260
|
+
# @sig (*(String | Pathname | Array[String | Pathname])) -> void
|
262
261
|
def collapse(*glob_patterns)
|
263
262
|
glob_patterns = expand_paths(glob_patterns)
|
264
263
|
mutex.synchronize do
|
@@ -267,9 +266,27 @@ module Zeitwerk
|
|
267
266
|
end
|
268
267
|
end
|
269
268
|
|
269
|
+
# Configure a block to be invoked once a certain constant path is loaded.
|
270
|
+
# Supports multiple callbacks, and if there are many, they are executed in
|
271
|
+
# the order in which they were defined.
|
272
|
+
#
|
273
|
+
# loader.on_load("SomeApiClient") do
|
274
|
+
# SomeApiClient.endpoint = "https://api.dev"
|
275
|
+
# end
|
276
|
+
#
|
277
|
+
# @raise [TypeError]
|
278
|
+
# @sig (String) { () -> void } -> void
|
279
|
+
def on_load(cpath, &block)
|
280
|
+
raise TypeError, "on_load only accepts strings" unless cpath.is_a?(String)
|
281
|
+
|
282
|
+
mutex.synchronize do
|
283
|
+
(on_load_callbacks[cpath] ||= []) << block
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
270
287
|
# Sets autoloads in the root namespace and preloads files, if any.
|
271
288
|
#
|
272
|
-
# @
|
289
|
+
# @sig () -> void
|
273
290
|
def setup
|
274
291
|
mutex.synchronize do
|
275
292
|
break if @setup
|
@@ -291,7 +308,7 @@ module Zeitwerk
|
|
291
308
|
# unload them.
|
292
309
|
#
|
293
310
|
# @private
|
294
|
-
# @
|
311
|
+
# @sig () -> void
|
295
312
|
def unload
|
296
313
|
mutex.synchronize do
|
297
314
|
# We are going to keep track of the files that were required by our
|
@@ -354,7 +371,7 @@ module Zeitwerk
|
|
354
371
|
# client code in the README of the project.
|
355
372
|
#
|
356
373
|
# @raise [Zeitwerk::Error]
|
357
|
-
# @
|
374
|
+
# @sig () -> void
|
358
375
|
def reload
|
359
376
|
if reloading_enabled?
|
360
377
|
unload
|
@@ -371,7 +388,7 @@ module Zeitwerk
|
|
371
388
|
# are not eager loaded. You can opt-out specifically in specific files and
|
372
389
|
# directories with `do_not_eager_load`.
|
373
390
|
#
|
374
|
-
# @
|
391
|
+
# @sig () -> void
|
375
392
|
def eager_load
|
376
393
|
mutex.synchronize do
|
377
394
|
break if @eager_loaded
|
@@ -414,8 +431,7 @@ module Zeitwerk
|
|
414
431
|
# Let eager load ignore the given files or directories. The constants
|
415
432
|
# defined in those files are still autoloadable.
|
416
433
|
#
|
417
|
-
# @
|
418
|
-
# @return [void]
|
434
|
+
# @sig (*(String | Pathname | Array[String | Pathname])) -> void
|
419
435
|
def do_not_eager_load(*paths)
|
420
436
|
mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
|
421
437
|
end
|
@@ -423,8 +439,7 @@ module Zeitwerk
|
|
423
439
|
# Says if the given constant path would be unloaded on reload. This
|
424
440
|
# predicate returns `false` if reloading is disabled.
|
425
441
|
#
|
426
|
-
# @
|
427
|
-
# @return [Boolean]
|
442
|
+
# @sig (String) -> bool
|
428
443
|
def unloadable_cpath?(cpath)
|
429
444
|
to_unload.key?(cpath)
|
430
445
|
end
|
@@ -432,21 +447,20 @@ module Zeitwerk
|
|
432
447
|
# Returns an array with the constant paths that would be unloaded on reload.
|
433
448
|
# This predicate returns an empty array if reloading is disabled.
|
434
449
|
#
|
435
|
-
# @
|
450
|
+
# @sig () -> Array[String]
|
436
451
|
def unloadable_cpaths
|
437
452
|
to_unload.keys.freeze
|
438
453
|
end
|
439
454
|
|
440
455
|
# Logs to `$stdout`, handy shortcut for debugging.
|
441
456
|
#
|
442
|
-
# @
|
457
|
+
# @sig () -> void
|
443
458
|
def log!
|
444
459
|
@logger = ->(msg) { puts msg }
|
445
460
|
end
|
446
461
|
|
447
462
|
# @private
|
448
|
-
# @
|
449
|
-
# @return [Boolean]
|
463
|
+
# @sig (String) -> bool
|
450
464
|
def manages?(dir)
|
451
465
|
dir = dir + "/"
|
452
466
|
ignored_paths.each do |ignored_path|
|
@@ -463,11 +477,11 @@ module Zeitwerk
|
|
463
477
|
# --- Class methods ---------------------------------------------------------------------------
|
464
478
|
|
465
479
|
class << self
|
466
|
-
# @
|
480
|
+
# @sig #call | #debug | nil
|
467
481
|
attr_accessor :default_logger
|
468
482
|
|
469
483
|
# @private
|
470
|
-
# @
|
484
|
+
# @sig Mutex
|
471
485
|
attr_accessor :mutex
|
472
486
|
|
473
487
|
# This is a shortcut for
|
@@ -481,7 +495,7 @@ module Zeitwerk
|
|
481
495
|
# except that this method returns the same object in subsequent calls from
|
482
496
|
# the same file, in the unlikely case the gem wants to be able to reload.
|
483
497
|
#
|
484
|
-
# @
|
498
|
+
# @sig () -> Zeitwerk::Loader
|
485
499
|
def for_gem
|
486
500
|
called_from = caller_locations(1, 1).first.path
|
487
501
|
Registry.loader_for_gem(called_from)
|
@@ -489,7 +503,7 @@ module Zeitwerk
|
|
489
503
|
|
490
504
|
# Broadcasts `eager_load` to all loaders.
|
491
505
|
#
|
492
|
-
# @
|
506
|
+
# @sig () -> void
|
493
507
|
def eager_load_all
|
494
508
|
Registry.loaders.each(&:eager_load)
|
495
509
|
end
|
@@ -497,7 +511,7 @@ module Zeitwerk
|
|
497
511
|
# Returns an array with the absolute paths of the root directories of all
|
498
512
|
# registered loaders. This is a read-only collection.
|
499
513
|
#
|
500
|
-
# @
|
514
|
+
# @sig () -> Array[String]
|
501
515
|
def all_dirs
|
502
516
|
Registry.loaders.flat_map(&:dirs).freeze
|
503
517
|
end
|
@@ -507,16 +521,14 @@ module Zeitwerk
|
|
507
521
|
|
508
522
|
private # -------------------------------------------------------------------------------------
|
509
523
|
|
510
|
-
# @
|
524
|
+
# @sig () -> Array[String]
|
511
525
|
def actual_root_dirs
|
512
526
|
root_dirs.reject do |root_dir, _namespace|
|
513
527
|
!dir?(root_dir) || ignored_paths.member?(root_dir)
|
514
528
|
end
|
515
529
|
end
|
516
530
|
|
517
|
-
# @
|
518
|
-
# @param parent [Module]
|
519
|
-
# @return [void]
|
531
|
+
# @sig (String, Module) -> void
|
520
532
|
def set_autoloads_in_dir(dir, parent)
|
521
533
|
ls(dir) do |basename, abspath|
|
522
534
|
begin
|
@@ -559,10 +571,7 @@ module Zeitwerk
|
|
559
571
|
end
|
560
572
|
end
|
561
573
|
|
562
|
-
# @
|
563
|
-
# @param cname [Symbol]
|
564
|
-
# @param subdir [String]
|
565
|
-
# @return [void]
|
574
|
+
# @sig (Module, Symbol, String) -> void
|
566
575
|
def autoload_subdir(parent, cname, subdir)
|
567
576
|
if autoload_path = autoload_for?(parent, cname)
|
568
577
|
cpath = cpath(parent, cname)
|
@@ -582,10 +591,7 @@ module Zeitwerk
|
|
582
591
|
end
|
583
592
|
end
|
584
593
|
|
585
|
-
# @
|
586
|
-
# @param cname [Symbol]
|
587
|
-
# @param file [String]
|
588
|
-
# @return [void]
|
594
|
+
# @sig (Module, Symbol, String) -> void
|
589
595
|
def autoload_file(parent, cname, file)
|
590
596
|
if autoload_path = autoload_for?(parent, cname)
|
591
597
|
# First autoload for a Ruby file wins, just ignore subsequent ones.
|
@@ -606,11 +612,10 @@ module Zeitwerk
|
|
606
612
|
end
|
607
613
|
end
|
608
614
|
|
609
|
-
#
|
610
|
-
#
|
611
|
-
#
|
612
|
-
# @
|
613
|
-
# @return [void]
|
615
|
+
# `dir` is the directory that would have autovivified a namespace. `file` is
|
616
|
+
# the file where we've found the namespace is explicitly defined.
|
617
|
+
#
|
618
|
+
# @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
|
614
619
|
def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
|
615
620
|
autoloads.delete(dir)
|
616
621
|
Registry.unregister_autoload(dir)
|
@@ -619,10 +624,7 @@ module Zeitwerk
|
|
619
624
|
register_explicit_namespace(cpath(parent, cname))
|
620
625
|
end
|
621
626
|
|
622
|
-
# @
|
623
|
-
# @param cname [Symbol]
|
624
|
-
# @param abspath [String]
|
625
|
-
# @return [void]
|
627
|
+
# @sig (Module, Symbol, String) -> void
|
626
628
|
def set_autoload(parent, cname, abspath)
|
627
629
|
# $LOADED_FEATURES stores real paths since Ruby 2.4.4. We set and save the
|
628
630
|
# real path to be able to delete it from $LOADED_FEATURES on unload, and to
|
@@ -649,9 +651,7 @@ module Zeitwerk
|
|
649
651
|
end
|
650
652
|
end
|
651
653
|
|
652
|
-
# @
|
653
|
-
# @param cname [Symbol]
|
654
|
-
# @return [String, nil]
|
654
|
+
# @sig (Module, Symbol) -> String?
|
655
655
|
def autoload_for?(parent, cname)
|
656
656
|
strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
|
657
657
|
end
|
@@ -672,9 +672,7 @@ module Zeitwerk
|
|
672
672
|
#
|
673
673
|
# We need a way to strictly check in parent ignoring ancestors.
|
674
674
|
#
|
675
|
-
# @
|
676
|
-
# @param cname [Symbol]
|
677
|
-
# @return [String, nil]
|
675
|
+
# @sig (Module, Symbol) -> String?
|
678
676
|
if method(:autoload?).arity == 1
|
679
677
|
def strict_autoload_path(parent, cname)
|
680
678
|
parent.autoload?(cname) if cdef?(parent, cname)
|
@@ -688,15 +686,14 @@ module Zeitwerk
|
|
688
686
|
# This method is called this way because I prefer `preload` to be the method
|
689
687
|
# name to configure preloads in the public interface.
|
690
688
|
#
|
691
|
-
# @
|
689
|
+
# @sig () -> void
|
692
690
|
def do_preload
|
693
691
|
preloads.each do |abspath|
|
694
692
|
do_preload_abspath(abspath)
|
695
693
|
end
|
696
694
|
end
|
697
695
|
|
698
|
-
# @
|
699
|
-
# @return [void]
|
696
|
+
# @sig (String) -> void
|
700
697
|
def do_preload_abspath(abspath)
|
701
698
|
if ruby?(abspath)
|
702
699
|
do_preload_file(abspath)
|
@@ -705,31 +702,25 @@ module Zeitwerk
|
|
705
702
|
end
|
706
703
|
end
|
707
704
|
|
708
|
-
# @
|
709
|
-
# @return [void]
|
705
|
+
# @sig (String) -> void
|
710
706
|
def do_preload_dir(dir)
|
711
707
|
ls(dir) do |_basename, abspath|
|
712
708
|
do_preload_abspath(abspath)
|
713
709
|
end
|
714
710
|
end
|
715
711
|
|
716
|
-
# @
|
717
|
-
# @return [Boolean]
|
712
|
+
# @sig (String) -> bool
|
718
713
|
def do_preload_file(file)
|
719
714
|
log("preloading #{file}") if logger
|
720
715
|
require file
|
721
716
|
end
|
722
717
|
|
723
|
-
# @
|
724
|
-
# @param cname [Symbol]
|
725
|
-
# @return [String]
|
718
|
+
# @sig (Module, Symbol) -> String
|
726
719
|
def cpath(parent, cname)
|
727
720
|
parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
|
728
721
|
end
|
729
722
|
|
730
|
-
# @
|
731
|
-
# @yieldparam path [String, String]
|
732
|
-
# @return [void]
|
723
|
+
# @sig (String) { (String, String) -> void } -> void
|
733
724
|
def ls(dir)
|
734
725
|
Dir.foreach(dir) do |basename|
|
735
726
|
next if basename.start_with?(".")
|
@@ -743,57 +734,55 @@ module Zeitwerk
|
|
743
734
|
end
|
744
735
|
end
|
745
736
|
|
746
|
-
# @
|
747
|
-
# @return [Boolean]
|
737
|
+
# @sig (String) -> bool
|
748
738
|
def ruby?(path)
|
749
739
|
path.end_with?(".rb")
|
750
740
|
end
|
751
741
|
|
752
|
-
# @
|
753
|
-
# @return [Boolean]
|
742
|
+
# @sig (String) -> bool
|
754
743
|
def dir?(path)
|
755
744
|
File.directory?(path)
|
756
745
|
end
|
757
746
|
|
758
|
-
# @
|
759
|
-
# @return [<String>]
|
747
|
+
# @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
|
760
748
|
def expand_paths(paths)
|
761
749
|
paths.flatten.map! { |path| File.expand_path(path) }
|
762
750
|
end
|
763
751
|
|
764
|
-
# @
|
765
|
-
# @return [<String>]
|
752
|
+
# @sig (Array[String]) -> Array[String]
|
766
753
|
def expand_glob_patterns(glob_patterns)
|
767
754
|
# Note that Dir.glob works with regular file names just fine. That is,
|
768
755
|
# glob patterns technically need no wildcards.
|
769
756
|
glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
|
770
757
|
end
|
771
758
|
|
772
|
-
# @
|
759
|
+
# @sig () -> void
|
773
760
|
def recompute_ignored_paths
|
774
761
|
ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
|
775
762
|
end
|
776
763
|
|
777
|
-
# @
|
764
|
+
# @sig () -> void
|
778
765
|
def recompute_collapse_dirs
|
779
766
|
collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
|
780
767
|
end
|
781
768
|
|
782
|
-
# @
|
783
|
-
# @return [void]
|
769
|
+
# @sig (String) -> void
|
784
770
|
def log(message)
|
785
771
|
method_name = logger.respond_to?(:debug) ? :debug : :call
|
786
772
|
logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
|
787
773
|
end
|
788
774
|
|
775
|
+
# @sig (Module, Symbol) -> bool
|
789
776
|
def cdef?(parent, cname)
|
790
777
|
parent.const_defined?(cname, false)
|
791
778
|
end
|
792
779
|
|
780
|
+
# @sig (String) -> void
|
793
781
|
def register_explicit_namespace(cpath)
|
794
782
|
ExplicitNamespace.register(cpath, self)
|
795
783
|
end
|
796
784
|
|
785
|
+
# @sig (String) -> void
|
797
786
|
def raise_if_conflicting_directory(dir)
|
798
787
|
self.class.mutex.synchronize do
|
799
788
|
Registry.loaders.each do |loader|
|
@@ -808,19 +797,15 @@ module Zeitwerk
|
|
808
797
|
end
|
809
798
|
end
|
810
799
|
|
811
|
-
# @
|
812
|
-
# @param cname [Symbol]
|
813
|
-
# @return [void]
|
800
|
+
# @sig (Module, Symbol) -> void
|
814
801
|
def unload_autoload(parent, cname)
|
815
|
-
parent.
|
802
|
+
parent.__send__(:remove_const, cname)
|
816
803
|
log("autoload for #{cpath(parent, cname)} removed") if logger
|
817
804
|
end
|
818
805
|
|
819
|
-
# @
|
820
|
-
# @param cname [Symbol]
|
821
|
-
# @return [void]
|
806
|
+
# @sig (Module, Symbol) -> void
|
822
807
|
def unload_cref(parent, cname)
|
823
|
-
parent.
|
808
|
+
parent.__send__(:remove_const, cname)
|
824
809
|
log("#{cpath(parent, cname)} unloaded") if logger
|
825
810
|
end
|
826
811
|
end
|
@@ -4,26 +4,28 @@ module Zeitwerk::Loader::Callbacks
|
|
4
4
|
# Invoked from our decorated Kernel#require when a managed file is autoloaded.
|
5
5
|
#
|
6
6
|
# @private
|
7
|
-
# @
|
8
|
-
# @return [void]
|
7
|
+
# @sig (String) -> void
|
9
8
|
def on_file_autoloaded(file)
|
10
|
-
cref
|
11
|
-
|
9
|
+
cref = autoloads.delete(file)
|
10
|
+
cpath = cpath(*cref)
|
11
|
+
|
12
|
+
to_unload[cpath] = [file, cref] if reloading_enabled?
|
12
13
|
Zeitwerk::Registry.unregister_autoload(file)
|
13
14
|
|
14
15
|
if logger && cdef?(*cref)
|
15
|
-
log("constant #{cpath
|
16
|
+
log("constant #{cpath} loaded from file #{file}")
|
16
17
|
elsif !cdef?(*cref)
|
17
|
-
raise Zeitwerk::NameError.new("expected file #{file} to define constant #{cpath
|
18
|
+
raise Zeitwerk::NameError.new("expected file #{file} to define constant #{cpath}, but didn't", cref.last)
|
18
19
|
end
|
20
|
+
|
21
|
+
run_on_load_callbacks(cpath)
|
19
22
|
end
|
20
23
|
|
21
24
|
# Invoked from our decorated Kernel#require when a managed directory is
|
22
25
|
# autoloaded.
|
23
26
|
#
|
24
27
|
# @private
|
25
|
-
# @
|
26
|
-
# @return [void]
|
28
|
+
# @sig (String) -> void
|
27
29
|
def on_dir_autoloaded(dir)
|
28
30
|
# Module#autoload does not serialize concurrent requires, and we handle
|
29
31
|
# directories ourselves, so the callback needs to account for concurrency.
|
@@ -39,9 +41,10 @@ module Zeitwerk::Loader::Callbacks
|
|
39
41
|
mutex2.synchronize do
|
40
42
|
if cref = autoloads.delete(dir)
|
41
43
|
autovivified_module = cref[0].const_set(cref[1], Module.new)
|
42
|
-
|
44
|
+
cpath = autovivified_module.name
|
45
|
+
log("module #{cpath} autovivified from directory #{dir}") if logger
|
43
46
|
|
44
|
-
to_unload[
|
47
|
+
to_unload[cpath] = [dir, cref] if reloading_enabled?
|
45
48
|
|
46
49
|
# We don't unregister `dir` in the registry because concurrent threads
|
47
50
|
# wouldn't find a loader associated to it in Kernel#require and would
|
@@ -50,6 +53,8 @@ module Zeitwerk::Loader::Callbacks
|
|
50
53
|
autoloaded_dirs << dir
|
51
54
|
|
52
55
|
on_namespace_loaded(autovivified_module)
|
56
|
+
|
57
|
+
run_on_load_callbacks(cpath)
|
53
58
|
end
|
54
59
|
end
|
55
60
|
end
|
@@ -59,8 +64,7 @@ module Zeitwerk::Loader::Callbacks
|
|
59
64
|
# subdirectories, we descend into them now.
|
60
65
|
#
|
61
66
|
# @private
|
62
|
-
# @
|
63
|
-
# @return [void]
|
67
|
+
# @sig (Module) -> void
|
64
68
|
def on_namespace_loaded(namespace)
|
65
69
|
if subdirs = lazy_subdirs.delete(real_mod_name(namespace))
|
66
70
|
subdirs.each do |subdir|
|
@@ -68,4 +72,15 @@ module Zeitwerk::Loader::Callbacks
|
|
68
72
|
end
|
69
73
|
end
|
70
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# @sig (String) -> void
|
79
|
+
def run_on_load_callbacks(cpath)
|
80
|
+
# Very common, do not even compute a hash code.
|
81
|
+
return if on_load_callbacks.empty?
|
82
|
+
|
83
|
+
callbacks = reloading_enabled? ? on_load_callbacks[cpath] : on_load_callbacks.delete(cpath)
|
84
|
+
callbacks.each(&:call) if callbacks
|
85
|
+
end
|
71
86
|
end
|
@@ -7,8 +7,7 @@ module Zeitwerk::RealModName
|
|
7
7
|
#
|
8
8
|
# The name method can be overridden, hence the indirection in this method.
|
9
9
|
#
|
10
|
-
# @
|
11
|
-
# @return [String, nil]
|
10
|
+
# @sig (Module) -> String?
|
12
11
|
if UnboundMethod.method_defined?(:bind_call)
|
13
12
|
def real_mod_name(mod)
|
14
13
|
UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
|
data/lib/zeitwerk/registry.rb
CHANGED
@@ -7,14 +7,14 @@ module Zeitwerk
|
|
7
7
|
# them from being garbage collected.
|
8
8
|
#
|
9
9
|
# @private
|
10
|
-
# @
|
10
|
+
# @sig Array[Zeitwerk::Loader]
|
11
11
|
attr_reader :loaders
|
12
12
|
|
13
13
|
# Registers loaders created with `for_gem` to make the method idempotent
|
14
14
|
# in case of reload.
|
15
15
|
#
|
16
16
|
# @private
|
17
|
-
# @
|
17
|
+
# @sig Hash[String, Zeitwerk::Loader]
|
18
18
|
attr_reader :loaders_managing_gems
|
19
19
|
|
20
20
|
# Maps real paths to the loaders responsible for them.
|
@@ -23,7 +23,7 @@ module Zeitwerk
|
|
23
23
|
# invoke callbacks and autovivify modules.
|
24
24
|
#
|
25
25
|
# @private
|
26
|
-
# @
|
26
|
+
# @sig Hash[String, Zeitwerk::Loader]
|
27
27
|
attr_reader :autoloads
|
28
28
|
|
29
29
|
# This hash table addresses an edge case in which an autoload is ignored.
|
@@ -62,14 +62,13 @@ module Zeitwerk
|
|
62
62
|
# end
|
63
63
|
#
|
64
64
|
# @private
|
65
|
-
# @
|
65
|
+
# @sig Hash[String, [String, Zeitwerk::Loader]]
|
66
66
|
attr_reader :inceptions
|
67
67
|
|
68
68
|
# Registers a loader.
|
69
69
|
#
|
70
70
|
# @private
|
71
|
-
# @
|
72
|
-
# @return [void]
|
71
|
+
# @sig (Zeitwerk::Loader) -> void
|
73
72
|
def register_loader(loader)
|
74
73
|
loaders << loader
|
75
74
|
end
|
@@ -78,8 +77,7 @@ module Zeitwerk
|
|
78
77
|
# file. That is how Zeitwerk::Loader.for_gem is idempotent.
|
79
78
|
#
|
80
79
|
# @private
|
81
|
-
# @
|
82
|
-
# @return [Zeitwerk::Loader]
|
80
|
+
# @sig (String) -> Zeitwerk::Loader
|
83
81
|
def loader_for_gem(root_file)
|
84
82
|
loaders_managing_gems[root_file] ||= begin
|
85
83
|
Loader.new.tap do |loader|
|
@@ -91,32 +89,25 @@ module Zeitwerk
|
|
91
89
|
end
|
92
90
|
|
93
91
|
# @private
|
94
|
-
# @
|
95
|
-
# @param realpath [String]
|
96
|
-
# @return [void]
|
92
|
+
# @sig (Zeitwerk::Loader, String) -> String
|
97
93
|
def register_autoload(loader, realpath)
|
98
94
|
autoloads[realpath] = loader
|
99
95
|
end
|
100
96
|
|
101
97
|
# @private
|
102
|
-
# @
|
103
|
-
# @return [void]
|
98
|
+
# @sig (String) -> void
|
104
99
|
def unregister_autoload(realpath)
|
105
100
|
autoloads.delete(realpath)
|
106
101
|
end
|
107
102
|
|
108
103
|
# @private
|
109
|
-
# @
|
110
|
-
# @param realpath [String]
|
111
|
-
# @param loader [Zeitwerk::Loader]
|
112
|
-
# @return [void]
|
104
|
+
# @sig (String, String, Zeitwerk::Loader) -> void
|
113
105
|
def register_inception(cpath, realpath, loader)
|
114
106
|
inceptions[cpath] = [realpath, loader]
|
115
107
|
end
|
116
108
|
|
117
109
|
# @private
|
118
|
-
# @
|
119
|
-
# @return [String, nil]
|
110
|
+
# @sig (String) -> String?
|
120
111
|
def inception?(cpath)
|
121
112
|
if pair = inceptions[cpath]
|
122
113
|
pair.first
|
@@ -124,15 +115,13 @@ module Zeitwerk
|
|
124
115
|
end
|
125
116
|
|
126
117
|
# @private
|
127
|
-
# @
|
128
|
-
# @return [Zeitwerk::Loader, nil]
|
118
|
+
# @sig (String) -> Zeitwerk::Loader?
|
129
119
|
def loader_for(path)
|
130
120
|
autoloads[path]
|
131
121
|
end
|
132
122
|
|
133
123
|
# @private
|
134
|
-
# @
|
135
|
-
# @return [void]
|
124
|
+
# @sig (Zeitwerk::Loader) -> void
|
136
125
|
def on_unload(loader)
|
137
126
|
autoloads.delete_if { |_path, object| object == loader }
|
138
127
|
inceptions.delete_if { |_cpath, (_path, object)| object == loader }
|
data/lib/zeitwerk/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zeitwerk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.4.
|
4
|
+
version: 2.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Noria
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -41,7 +41,7 @@ metadata:
|
|
41
41
|
changelog_uri: https://github.com/fxn/zeitwerk/blob/master/CHANGELOG.md
|
42
42
|
source_code_uri: https://github.com/fxn/zeitwerk
|
43
43
|
bug_tracker_uri: https://github.com/fxn/zeitwerk/issues
|
44
|
-
post_install_message:
|
44
|
+
post_install_message:
|
45
45
|
rdoc_options: []
|
46
46
|
require_paths:
|
47
47
|
- lib
|
@@ -57,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
57
|
version: '0'
|
58
58
|
requirements: []
|
59
59
|
rubygems_version: 3.1.2
|
60
|
-
signing_key:
|
60
|
+
signing_key:
|
61
61
|
specification_version: 4
|
62
62
|
summary: Efficient and thread-safe constant autoloader
|
63
63
|
test_files: []
|