zeitwerk 2.6.1 → 2.6.7
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.
- checksums.yaml +4 -4
- data/README.md +184 -36
- data/lib/zeitwerk/error.rb +6 -0
- data/lib/zeitwerk/explicit_namespace.rb +14 -10
- data/lib/zeitwerk/gem_loader.rb +1 -1
- data/lib/zeitwerk/internal.rb +12 -0
- data/lib/zeitwerk/kernel.rb +3 -0
- data/lib/zeitwerk/loader/callbacks.rb +10 -10
- data/lib/zeitwerk/loader/config.rb +82 -46
- data/lib/zeitwerk/loader/eager_load.rb +228 -0
- data/lib/zeitwerk/loader/helpers.rb +30 -16
- data/lib/zeitwerk/loader.rb +112 -113
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +1 -0
- metadata +5 -3
data/lib/zeitwerk/loader.rb
CHANGED
@@ -7,11 +7,15 @@ module Zeitwerk
|
|
7
7
|
require_relative "loader/helpers"
|
8
8
|
require_relative "loader/callbacks"
|
9
9
|
require_relative "loader/config"
|
10
|
+
require_relative "loader/eager_load"
|
11
|
+
|
12
|
+
extend Internal
|
10
13
|
|
11
14
|
include RealModName
|
12
15
|
include Callbacks
|
13
16
|
include Helpers
|
14
17
|
include Config
|
18
|
+
include EagerLoad
|
15
19
|
|
16
20
|
MUTEX = Mutex.new
|
17
21
|
private_constant :MUTEX
|
@@ -24,9 +28,9 @@ module Zeitwerk
|
|
24
28
|
# "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, :Pricing]
|
25
29
|
# ...
|
26
30
|
#
|
27
|
-
# @private
|
28
31
|
# @sig Hash[String, [Module, Symbol]]
|
29
32
|
attr_reader :autoloads
|
33
|
+
internal :autoloads
|
30
34
|
|
31
35
|
# We keep track of autoloaded directories to remove them from the registry
|
32
36
|
# at the end of eager loading.
|
@@ -34,9 +38,9 @@ module Zeitwerk
|
|
34
38
|
# Files are removed as they are autoloaded, but directories need to wait due
|
35
39
|
# to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
|
36
40
|
#
|
37
|
-
# @private
|
38
41
|
# @sig Array[String]
|
39
42
|
attr_reader :autoloaded_dirs
|
43
|
+
internal :autoloaded_dirs
|
40
44
|
|
41
45
|
# Stores metadata needed for unloading. Its entries look like this:
|
42
46
|
#
|
@@ -50,11 +54,11 @@ module Zeitwerk
|
|
50
54
|
# If reloading is enabled, this hash is filled as constants are autoloaded
|
51
55
|
# or eager loaded. Otherwise, the collection remains empty.
|
52
56
|
#
|
53
|
-
# @private
|
54
57
|
# @sig Hash[String, [String, [Module, Symbol]]]
|
55
58
|
attr_reader :to_unload
|
59
|
+
internal :to_unload
|
56
60
|
|
57
|
-
# Maps constant paths
|
61
|
+
# Maps namespace constant paths to their respective directories.
|
58
62
|
#
|
59
63
|
# For example, given this mapping:
|
60
64
|
#
|
@@ -64,21 +68,32 @@ module Zeitwerk
|
|
64
68
|
# ...
|
65
69
|
# ]
|
66
70
|
#
|
67
|
-
# when `Admin` gets defined we know that it plays the role of a namespace
|
68
|
-
# that its children are spread over those directories. We'll visit them
|
69
|
-
# up the corresponding autoloads.
|
71
|
+
# when `Admin` gets defined we know that it plays the role of a namespace
|
72
|
+
# and that its children are spread over those directories. We'll visit them
|
73
|
+
# to set up the corresponding autoloads.
|
70
74
|
#
|
71
|
-
# @private
|
72
75
|
# @sig Hash[String, Array[String]]
|
73
|
-
attr_reader :
|
76
|
+
attr_reader :namespace_dirs
|
77
|
+
internal :namespace_dirs
|
78
|
+
|
79
|
+
# A shadowed file is a file managed by this loader that is ignored when
|
80
|
+
# setting autoloads because its matching constant is already taken.
|
81
|
+
#
|
82
|
+
# This private set is populated as we descend. For example, if the loader
|
83
|
+
# has only scanned the top-level, `shadowed_files` does not have shadowed
|
84
|
+
# files that may exist deep in the project tree yet.
|
85
|
+
#
|
86
|
+
# @sig Set[String]
|
87
|
+
attr_reader :shadowed_files
|
88
|
+
internal :shadowed_files
|
74
89
|
|
75
|
-
# @private
|
76
90
|
# @sig Mutex
|
77
91
|
attr_reader :mutex
|
92
|
+
private :mutex
|
78
93
|
|
79
|
-
# @private
|
80
94
|
# @sig Mutex
|
81
95
|
attr_reader :mutex2
|
96
|
+
private :mutex2
|
82
97
|
|
83
98
|
def initialize
|
84
99
|
super
|
@@ -86,7 +101,8 @@ module Zeitwerk
|
|
86
101
|
@autoloads = {}
|
87
102
|
@autoloaded_dirs = []
|
88
103
|
@to_unload = {}
|
89
|
-
@
|
104
|
+
@namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] }
|
105
|
+
@shadowed_files = Set.new
|
90
106
|
@mutex = Mutex.new
|
91
107
|
@mutex2 = Mutex.new
|
92
108
|
@setup = false
|
@@ -95,15 +111,15 @@ module Zeitwerk
|
|
95
111
|
Registry.register_loader(self)
|
96
112
|
end
|
97
113
|
|
98
|
-
# Sets autoloads in the root
|
114
|
+
# Sets autoloads in the root namespaces.
|
99
115
|
#
|
100
116
|
# @sig () -> void
|
101
117
|
def setup
|
102
118
|
mutex.synchronize do
|
103
119
|
break if @setup
|
104
120
|
|
105
|
-
|
106
|
-
set_autoloads_in_dir(root_dir,
|
121
|
+
actual_roots.each do |root_dir, root_namespace|
|
122
|
+
set_autoloads_in_dir(root_dir, root_namespace)
|
107
123
|
end
|
108
124
|
|
109
125
|
on_setup_callbacks.each(&:call)
|
@@ -120,12 +136,14 @@ module Zeitwerk
|
|
120
136
|
# unload them.
|
121
137
|
#
|
122
138
|
# This method is public but undocumented. Main interface is `reload`, which
|
123
|
-
# means `unload` + `setup`. This one is
|
139
|
+
# means `unload` + `setup`. This one is available to be used together with
|
124
140
|
# `unregister`, which is undocumented too.
|
125
141
|
#
|
126
142
|
# @sig () -> void
|
127
143
|
def unload
|
128
144
|
mutex.synchronize do
|
145
|
+
raise SetupRequired unless @setup
|
146
|
+
|
129
147
|
# We are going to keep track of the files that were required by our
|
130
148
|
# autoloads to later remove them from $LOADED_FEATURES, thus making them
|
131
149
|
# loadable by Kernel#require again.
|
@@ -181,10 +199,11 @@ module Zeitwerk
|
|
181
199
|
autoloads.clear
|
182
200
|
autoloaded_dirs.clear
|
183
201
|
to_unload.clear
|
184
|
-
|
202
|
+
namespace_dirs.clear
|
203
|
+
shadowed_files.clear
|
185
204
|
|
186
205
|
Registry.on_unload(self)
|
187
|
-
ExplicitNamespace.
|
206
|
+
ExplicitNamespace.__unregister_loader(self)
|
188
207
|
|
189
208
|
@setup = false
|
190
209
|
@eager_loaded = false
|
@@ -201,6 +220,7 @@ module Zeitwerk
|
|
201
220
|
# @sig () -> void
|
202
221
|
def reload
|
203
222
|
raise ReloadingDisabledError unless reloading_enabled?
|
223
|
+
raise SetupRequired unless @setup
|
204
224
|
|
205
225
|
unload
|
206
226
|
recompute_ignored_paths
|
@@ -208,58 +228,6 @@ module Zeitwerk
|
|
208
228
|
setup
|
209
229
|
end
|
210
230
|
|
211
|
-
# Eager loads all files in the root directories, recursively. Files do not
|
212
|
-
# need to be in `$LOAD_PATH`, absolute file names are used. Ignored files
|
213
|
-
# are not eager loaded. You can opt-out specifically in specific files and
|
214
|
-
# directories with `do_not_eager_load`, and that can be overridden passing
|
215
|
-
# `force: true`.
|
216
|
-
#
|
217
|
-
# @sig (true | false) -> void
|
218
|
-
def eager_load(force: false)
|
219
|
-
mutex.synchronize do
|
220
|
-
break if @eager_loaded
|
221
|
-
|
222
|
-
log("eager load start") if logger
|
223
|
-
|
224
|
-
honour_exclusions = !force
|
225
|
-
|
226
|
-
queue = []
|
227
|
-
actual_root_dirs.each do |root_dir, namespace|
|
228
|
-
queue << [namespace, root_dir] unless honour_exclusions && excluded_from_eager_load?(root_dir)
|
229
|
-
end
|
230
|
-
|
231
|
-
while to_eager_load = queue.shift
|
232
|
-
namespace, dir = to_eager_load
|
233
|
-
|
234
|
-
ls(dir) do |basename, abspath|
|
235
|
-
next if honour_exclusions && excluded_from_eager_load?(abspath)
|
236
|
-
|
237
|
-
if ruby?(abspath)
|
238
|
-
if cref = autoloads[abspath]
|
239
|
-
cget(*cref)
|
240
|
-
end
|
241
|
-
elsif !root_dirs.key?(abspath)
|
242
|
-
if collapse?(abspath)
|
243
|
-
queue << [namespace, abspath]
|
244
|
-
else
|
245
|
-
cname = inflector.camelize(basename, abspath)
|
246
|
-
queue << [cget(namespace, cname), abspath]
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
autoloaded_dirs.each do |autoloaded_dir|
|
253
|
-
Registry.unregister_autoload(autoloaded_dir)
|
254
|
-
end
|
255
|
-
autoloaded_dirs.clear
|
256
|
-
|
257
|
-
@eager_loaded = true
|
258
|
-
|
259
|
-
log("eager load end") if logger
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
231
|
# Says if the given constant path would be unloaded on reload. This
|
264
232
|
# predicate returns `false` if reloading is disabled.
|
265
233
|
#
|
@@ -282,7 +250,15 @@ module Zeitwerk
|
|
282
250
|
# @sig () -> void
|
283
251
|
def unregister
|
284
252
|
Registry.unregister_loader(self)
|
285
|
-
ExplicitNamespace.
|
253
|
+
ExplicitNamespace.__unregister_loader(self)
|
254
|
+
end
|
255
|
+
|
256
|
+
# The return value of this predicate is only meaningful if the loader has
|
257
|
+
# scanned the file. This is the case in the spots where we use it.
|
258
|
+
#
|
259
|
+
# @sig (String) -> Boolean
|
260
|
+
internal def shadowed_file?(file)
|
261
|
+
shadowed_files.member?(file)
|
286
262
|
end
|
287
263
|
|
288
264
|
# --- Class methods ---------------------------------------------------------------------------
|
@@ -311,11 +287,32 @@ module Zeitwerk
|
|
311
287
|
Registry.loader_for_gem(called_from, warn_on_extra_files: warn_on_extra_files)
|
312
288
|
end
|
313
289
|
|
314
|
-
# Broadcasts `eager_load` to all loaders.
|
290
|
+
# Broadcasts `eager_load` to all loaders. Those that have not been setup
|
291
|
+
# are skipped.
|
315
292
|
#
|
316
293
|
# @sig () -> void
|
317
294
|
def eager_load_all
|
318
|
-
Registry.loaders.each
|
295
|
+
Registry.loaders.each do |loader|
|
296
|
+
begin
|
297
|
+
loader.eager_load
|
298
|
+
rescue SetupRequired
|
299
|
+
# This is fine, we eager load what can be eager loaded.
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Broadcasts `eager_load_namespace` to all loaders. Those that have not
|
305
|
+
# been setup are skipped.
|
306
|
+
#
|
307
|
+
# @sig (Module) -> void
|
308
|
+
def eager_load_namespace(mod)
|
309
|
+
Registry.loaders.each do |loader|
|
310
|
+
begin
|
311
|
+
loader.eager_load_namespace(mod)
|
312
|
+
rescue SetupRequired
|
313
|
+
# This is fine, we eager load what can be eager loaded.
|
314
|
+
end
|
315
|
+
end
|
319
316
|
end
|
320
317
|
|
321
318
|
# Returns an array with the absolute paths of the root directories of all
|
@@ -327,10 +324,8 @@ module Zeitwerk
|
|
327
324
|
end
|
328
325
|
end
|
329
326
|
|
330
|
-
private # -------------------------------------------------------------------------------------
|
331
|
-
|
332
327
|
# @sig (String, Module) -> void
|
333
|
-
def set_autoloads_in_dir(dir, parent)
|
328
|
+
private def set_autoloads_in_dir(dir, parent)
|
334
329
|
ls(dir) do |basename, abspath|
|
335
330
|
begin
|
336
331
|
if ruby?(basename)
|
@@ -338,19 +333,11 @@ module Zeitwerk
|
|
338
333
|
cname = inflector.camelize(basename, abspath).to_sym
|
339
334
|
autoload_file(parent, cname, abspath)
|
340
335
|
else
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
# To resolve the ambiguity file name -> constant path this introduces,
|
345
|
-
# the `app/models/concerns` directory is totally ignored as a namespace,
|
346
|
-
# it counts only as root. The guard checks that.
|
347
|
-
unless root_dir?(abspath)
|
336
|
+
if collapse?(abspath)
|
337
|
+
set_autoloads_in_dir(abspath, parent)
|
338
|
+
else
|
348
339
|
cname = inflector.camelize(basename, abspath).to_sym
|
349
|
-
|
350
|
-
set_autoloads_in_dir(abspath, parent)
|
351
|
-
else
|
352
|
-
autoload_subdir(parent, cname, abspath)
|
353
|
-
end
|
340
|
+
autoload_subdir(parent, cname, abspath)
|
354
341
|
end
|
355
342
|
end
|
356
343
|
rescue ::NameError => error
|
@@ -373,17 +360,26 @@ module Zeitwerk
|
|
373
360
|
end
|
374
361
|
|
375
362
|
# @sig (Module, Symbol, String) -> void
|
376
|
-
def autoload_subdir(parent, cname, subdir)
|
363
|
+
private def autoload_subdir(parent, cname, subdir)
|
377
364
|
if autoload_path = autoload_path_set_by_me_for?(parent, cname)
|
378
365
|
cpath = cpath(parent, cname)
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
366
|
+
if ruby?(autoload_path)
|
367
|
+
# Scanning visited a Ruby file first, and now a directory for the same
|
368
|
+
# constant has been found. This means we are dealing with an explicit
|
369
|
+
# namespace whose definition was seen first.
|
370
|
+
#
|
371
|
+
# Registering is idempotent, and we have to keep the autoload pointing
|
372
|
+
# to the file. This may run again if more directories are found later
|
373
|
+
# on, no big deal.
|
374
|
+
register_explicit_namespace(cpath)
|
375
|
+
end
|
376
|
+
# If the existing autoload points to a file, it has to be preserved, if
|
377
|
+
# not, it is fine as it is. In either case, we do not need to override.
|
378
|
+
# Just remember the subdirectory conforms this namespace.
|
379
|
+
namespace_dirs[cpath] << subdir
|
384
380
|
elsif !cdef?(parent, cname)
|
385
381
|
# First time we find this namespace, set an autoload for it.
|
386
|
-
|
382
|
+
namespace_dirs[cpath(parent, cname)] << subdir
|
387
383
|
set_autoload(parent, cname, subdir)
|
388
384
|
else
|
389
385
|
# For whatever reason the constant that corresponds to this namespace has
|
@@ -394,10 +390,11 @@ module Zeitwerk
|
|
394
390
|
end
|
395
391
|
|
396
392
|
# @sig (Module, Symbol, String) -> void
|
397
|
-
def autoload_file(parent, cname, file)
|
393
|
+
private def autoload_file(parent, cname, file)
|
398
394
|
if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
|
399
395
|
# First autoload for a Ruby file wins, just ignore subsequent ones.
|
400
396
|
if ruby?(autoload_path)
|
397
|
+
shadowed_files << file
|
401
398
|
log("file #{file} is ignored because #{autoload_path} has precedence") if logger
|
402
399
|
else
|
403
400
|
promote_namespace_from_implicit_to_explicit(
|
@@ -408,6 +405,7 @@ module Zeitwerk
|
|
408
405
|
)
|
409
406
|
end
|
410
407
|
elsif cdef?(parent, cname)
|
408
|
+
shadowed_files << file
|
411
409
|
log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
|
412
410
|
else
|
413
411
|
set_autoload(parent, cname, file)
|
@@ -418,7 +416,7 @@ module Zeitwerk
|
|
418
416
|
# the file where we've found the namespace is explicitly defined.
|
419
417
|
#
|
420
418
|
# @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
|
421
|
-
def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
|
419
|
+
private def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
|
422
420
|
autoloads.delete(dir)
|
423
421
|
Registry.unregister_autoload(dir)
|
424
422
|
|
@@ -429,7 +427,7 @@ module Zeitwerk
|
|
429
427
|
end
|
430
428
|
|
431
429
|
# @sig (Module, Symbol, String) -> void
|
432
|
-
def set_autoload(parent, cname, abspath)
|
430
|
+
private def set_autoload(parent, cname, abspath)
|
433
431
|
parent.autoload(cname, abspath)
|
434
432
|
|
435
433
|
if logger
|
@@ -450,7 +448,7 @@ module Zeitwerk
|
|
450
448
|
end
|
451
449
|
|
452
450
|
# @sig (Module, Symbol) -> String?
|
453
|
-
def autoload_path_set_by_me_for?(parent, cname)
|
451
|
+
private def autoload_path_set_by_me_for?(parent, cname)
|
454
452
|
if autoload_path = strict_autoload_path(parent, cname)
|
455
453
|
autoload_path if autoloads.key?(autoload_path)
|
456
454
|
else
|
@@ -459,26 +457,27 @@ module Zeitwerk
|
|
459
457
|
end
|
460
458
|
|
461
459
|
# @sig (String) -> void
|
462
|
-
def register_explicit_namespace(cpath)
|
463
|
-
ExplicitNamespace.
|
460
|
+
private def register_explicit_namespace(cpath)
|
461
|
+
ExplicitNamespace.__register(cpath, self)
|
464
462
|
end
|
465
463
|
|
466
464
|
# @sig (String) -> void
|
467
|
-
def raise_if_conflicting_directory(dir)
|
465
|
+
private def raise_if_conflicting_directory(dir)
|
468
466
|
MUTEX.synchronize do
|
467
|
+
dir_slash = dir + "/"
|
468
|
+
|
469
469
|
Registry.loaders.each do |loader|
|
470
470
|
next if loader == self
|
471
|
-
next if loader.
|
471
|
+
next if loader.__ignores?(dir)
|
472
472
|
|
473
|
-
|
474
|
-
loader.root_dirs.each do |root_dir, _namespace|
|
473
|
+
loader.__roots.each_key do |root_dir|
|
475
474
|
next if ignores?(root_dir)
|
476
475
|
|
477
|
-
|
478
|
-
if
|
476
|
+
root_dir_slash = root_dir + "/"
|
477
|
+
if dir_slash.start_with?(root_dir_slash) || root_dir_slash.start_with?(dir_slash)
|
479
478
|
require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
|
480
479
|
raise Error,
|
481
|
-
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir
|
480
|
+
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
|
482
481
|
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
483
482
|
EOS
|
484
483
|
end
|
@@ -488,23 +487,23 @@ module Zeitwerk
|
|
488
487
|
end
|
489
488
|
|
490
489
|
# @sig (String, Object, String) -> void
|
491
|
-
def run_on_unload_callbacks(cpath, value, abspath)
|
490
|
+
private def run_on_unload_callbacks(cpath, value, abspath)
|
492
491
|
# Order matters. If present, run the most specific one.
|
493
492
|
on_unload_callbacks[cpath]&.each { |c| c.call(value, abspath) }
|
494
493
|
on_unload_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
|
495
494
|
end
|
496
495
|
|
497
496
|
# @sig (Module, Symbol) -> void
|
498
|
-
def unload_autoload(parent, cname)
|
499
|
-
parent
|
497
|
+
private def unload_autoload(parent, cname)
|
498
|
+
crem(parent, cname)
|
500
499
|
log("autoload for #{cpath(parent, cname)} removed") if logger
|
501
500
|
end
|
502
501
|
|
503
502
|
# @sig (Module, Symbol) -> void
|
504
|
-
def unload_cref(parent, cname)
|
503
|
+
private def unload_cref(parent, cname)
|
505
504
|
# Let's optimistically remove_const. The way we use it, this is going to
|
506
505
|
# succeed always if all is good.
|
507
|
-
parent
|
506
|
+
crem(parent, cname)
|
508
507
|
rescue ::NameError
|
509
508
|
# There are a few edge scenarios in which this may happen. If the constant
|
510
509
|
# is gone, that is OK, anyway.
|
data/lib/zeitwerk/version.rb
CHANGED
data/lib/zeitwerk.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.6.
|
4
|
+
version: 2.6.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Noria
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-10 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -28,10 +28,12 @@ files:
|
|
28
28
|
- lib/zeitwerk/gem_inflector.rb
|
29
29
|
- lib/zeitwerk/gem_loader.rb
|
30
30
|
- lib/zeitwerk/inflector.rb
|
31
|
+
- lib/zeitwerk/internal.rb
|
31
32
|
- lib/zeitwerk/kernel.rb
|
32
33
|
- lib/zeitwerk/loader.rb
|
33
34
|
- lib/zeitwerk/loader/callbacks.rb
|
34
35
|
- lib/zeitwerk/loader/config.rb
|
36
|
+
- lib/zeitwerk/loader/eager_load.rb
|
35
37
|
- lib/zeitwerk/loader/helpers.rb
|
36
38
|
- lib/zeitwerk/real_mod_name.rb
|
37
39
|
- lib/zeitwerk/registry.rb
|
@@ -59,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
61
|
- !ruby/object:Gem::Version
|
60
62
|
version: '0'
|
61
63
|
requirements: []
|
62
|
-
rubygems_version: 3.
|
64
|
+
rubygems_version: 3.4.3
|
63
65
|
signing_key:
|
64
66
|
specification_version: 4
|
65
67
|
summary: Efficient and thread-safe constant autoloader
|