zeitwerk 2.6.0 → 2.6.12
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 +351 -47
- data/lib/zeitwerk/error.rb +6 -0
- data/lib/zeitwerk/explicit_namespace.rb +14 -10
- data/lib/zeitwerk/gem_inflector.rb +2 -2
- data/lib/zeitwerk/gem_loader.rb +12 -8
- data/lib/zeitwerk/internal.rb +12 -0
- data/lib/zeitwerk/kernel.rb +6 -3
- data/lib/zeitwerk/loader/callbacks.rb +25 -17
- data/lib/zeitwerk/loader/config.rb +95 -51
- data/lib/zeitwerk/loader/eager_load.rb +234 -0
- data/lib/zeitwerk/loader/helpers.rb +74 -16
- data/lib/zeitwerk/loader.rb +196 -135
- data/lib/zeitwerk/registry.rb +2 -2
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +1 -0
- metadata +5 -3
data/lib/zeitwerk/loader.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "monitor"
|
3
4
|
require "set"
|
4
5
|
|
5
6
|
module Zeitwerk
|
@@ -7,11 +8,15 @@ module Zeitwerk
|
|
7
8
|
require_relative "loader/helpers"
|
8
9
|
require_relative "loader/callbacks"
|
9
10
|
require_relative "loader/config"
|
11
|
+
require_relative "loader/eager_load"
|
12
|
+
|
13
|
+
extend Internal
|
10
14
|
|
11
15
|
include RealModName
|
12
16
|
include Callbacks
|
13
17
|
include Helpers
|
14
18
|
include Config
|
19
|
+
include EagerLoad
|
15
20
|
|
16
21
|
MUTEX = Mutex.new
|
17
22
|
private_constant :MUTEX
|
@@ -24,9 +29,9 @@ module Zeitwerk
|
|
24
29
|
# "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, :Pricing]
|
25
30
|
# ...
|
26
31
|
#
|
27
|
-
# @private
|
28
32
|
# @sig Hash[String, [Module, Symbol]]
|
29
33
|
attr_reader :autoloads
|
34
|
+
internal :autoloads
|
30
35
|
|
31
36
|
# We keep track of autoloaded directories to remove them from the registry
|
32
37
|
# at the end of eager loading.
|
@@ -34,9 +39,9 @@ module Zeitwerk
|
|
34
39
|
# Files are removed as they are autoloaded, but directories need to wait due
|
35
40
|
# to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
|
36
41
|
#
|
37
|
-
# @private
|
38
42
|
# @sig Array[String]
|
39
43
|
attr_reader :autoloaded_dirs
|
44
|
+
internal :autoloaded_dirs
|
40
45
|
|
41
46
|
# Stores metadata needed for unloading. Its entries look like this:
|
42
47
|
#
|
@@ -50,11 +55,11 @@ module Zeitwerk
|
|
50
55
|
# If reloading is enabled, this hash is filled as constants are autoloaded
|
51
56
|
# or eager loaded. Otherwise, the collection remains empty.
|
52
57
|
#
|
53
|
-
# @private
|
54
58
|
# @sig Hash[String, [String, [Module, Symbol]]]
|
55
59
|
attr_reader :to_unload
|
60
|
+
internal :to_unload
|
56
61
|
|
57
|
-
# Maps constant paths
|
62
|
+
# Maps namespace constant paths to their respective directories.
|
58
63
|
#
|
59
64
|
# For example, given this mapping:
|
60
65
|
#
|
@@ -64,21 +69,32 @@ module Zeitwerk
|
|
64
69
|
# ...
|
65
70
|
# ]
|
66
71
|
#
|
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.
|
72
|
+
# when `Admin` gets defined we know that it plays the role of a namespace
|
73
|
+
# and that its children are spread over those directories. We'll visit them
|
74
|
+
# to set up the corresponding autoloads.
|
70
75
|
#
|
71
|
-
# @private
|
72
76
|
# @sig Hash[String, Array[String]]
|
73
|
-
attr_reader :
|
77
|
+
attr_reader :namespace_dirs
|
78
|
+
internal :namespace_dirs
|
79
|
+
|
80
|
+
# A shadowed file is a file managed by this loader that is ignored when
|
81
|
+
# setting autoloads because its matching constant is already taken.
|
82
|
+
#
|
83
|
+
# This private set is populated as we descend. For example, if the loader
|
84
|
+
# has only scanned the top-level, `shadowed_files` does not have shadowed
|
85
|
+
# files that may exist deep in the project tree yet.
|
86
|
+
#
|
87
|
+
# @sig Set[String]
|
88
|
+
attr_reader :shadowed_files
|
89
|
+
internal :shadowed_files
|
74
90
|
|
75
|
-
# @private
|
76
91
|
# @sig Mutex
|
77
92
|
attr_reader :mutex
|
93
|
+
private :mutex
|
78
94
|
|
79
|
-
# @
|
80
|
-
|
81
|
-
|
95
|
+
# @sig Monitor
|
96
|
+
attr_reader :dirs_autoload_monitor
|
97
|
+
private :dirs_autoload_monitor
|
82
98
|
|
83
99
|
def initialize
|
84
100
|
super
|
@@ -86,24 +102,26 @@ module Zeitwerk
|
|
86
102
|
@autoloads = {}
|
87
103
|
@autoloaded_dirs = []
|
88
104
|
@to_unload = {}
|
89
|
-
@
|
90
|
-
@
|
91
|
-
@mutex2 = Mutex.new
|
105
|
+
@namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] }
|
106
|
+
@shadowed_files = Set.new
|
92
107
|
@setup = false
|
93
108
|
@eager_loaded = false
|
94
109
|
|
110
|
+
@mutex = Mutex.new
|
111
|
+
@dirs_autoload_monitor = Monitor.new
|
112
|
+
|
95
113
|
Registry.register_loader(self)
|
96
114
|
end
|
97
115
|
|
98
|
-
# Sets autoloads in the root
|
116
|
+
# Sets autoloads in the root namespaces.
|
99
117
|
#
|
100
118
|
# @sig () -> void
|
101
119
|
def setup
|
102
120
|
mutex.synchronize do
|
103
121
|
break if @setup
|
104
122
|
|
105
|
-
|
106
|
-
|
123
|
+
actual_roots.each do |root_dir, root_namespace|
|
124
|
+
define_autoloads_for_dir(root_dir, root_namespace)
|
107
125
|
end
|
108
126
|
|
109
127
|
on_setup_callbacks.each(&:call)
|
@@ -120,12 +138,14 @@ module Zeitwerk
|
|
120
138
|
# unload them.
|
121
139
|
#
|
122
140
|
# This method is public but undocumented. Main interface is `reload`, which
|
123
|
-
# means `unload` + `setup`. This one is
|
141
|
+
# means `unload` + `setup`. This one is available to be used together with
|
124
142
|
# `unregister`, which is undocumented too.
|
125
143
|
#
|
126
144
|
# @sig () -> void
|
127
145
|
def unload
|
128
146
|
mutex.synchronize do
|
147
|
+
raise SetupRequired unless @setup
|
148
|
+
|
129
149
|
# We are going to keep track of the files that were required by our
|
130
150
|
# autoloads to later remove them from $LOADED_FEATURES, thus making them
|
131
151
|
# loadable by Kernel#require again.
|
@@ -181,10 +201,11 @@ module Zeitwerk
|
|
181
201
|
autoloads.clear
|
182
202
|
autoloaded_dirs.clear
|
183
203
|
to_unload.clear
|
184
|
-
|
204
|
+
namespace_dirs.clear
|
205
|
+
shadowed_files.clear
|
185
206
|
|
186
207
|
Registry.on_unload(self)
|
187
|
-
ExplicitNamespace.
|
208
|
+
ExplicitNamespace.__unregister_loader(self)
|
188
209
|
|
189
210
|
@setup = false
|
190
211
|
@eager_loaded = false
|
@@ -201,6 +222,7 @@ module Zeitwerk
|
|
201
222
|
# @sig () -> void
|
202
223
|
def reload
|
203
224
|
raise ReloadingDisabledError unless reloading_enabled?
|
225
|
+
raise SetupRequired unless @setup
|
204
226
|
|
205
227
|
unload
|
206
228
|
recompute_ignored_paths
|
@@ -208,57 +230,53 @@ module Zeitwerk
|
|
208
230
|
setup
|
209
231
|
end
|
210
232
|
|
211
|
-
|
212
|
-
|
213
|
-
|
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
|
233
|
+
# @sig (String | Pathname) -> String?
|
234
|
+
def cpath_expected_at(path)
|
235
|
+
abspath = File.expand_path(path)
|
221
236
|
|
222
|
-
|
237
|
+
raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
|
223
238
|
|
224
|
-
|
239
|
+
return unless dir?(abspath) || ruby?(abspath)
|
240
|
+
return if ignored_path?(abspath)
|
225
241
|
|
226
|
-
|
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
|
242
|
+
paths = []
|
230
243
|
|
231
|
-
|
232
|
-
|
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
|
244
|
+
if ruby?(abspath)
|
245
|
+
basename = File.basename(abspath, ".rb")
|
246
|
+
return if hidden?(basename)
|
251
247
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
248
|
+
paths << [basename, abspath]
|
249
|
+
walk_up_from = File.dirname(abspath)
|
250
|
+
else
|
251
|
+
walk_up_from = abspath
|
252
|
+
end
|
253
|
+
|
254
|
+
root_namespace = nil
|
256
255
|
|
257
|
-
|
256
|
+
walk_up(walk_up_from) do |dir|
|
257
|
+
break if root_namespace = roots[dir]
|
258
|
+
return if ignored_path?(dir)
|
258
259
|
|
259
|
-
|
260
|
+
basename = File.basename(dir)
|
261
|
+
return if hidden?(basename)
|
262
|
+
|
263
|
+
paths << [basename, abspath] unless collapse?(dir)
|
264
|
+
end
|
265
|
+
|
266
|
+
return unless root_namespace
|
267
|
+
|
268
|
+
if paths.empty?
|
269
|
+
real_mod_name(root_namespace)
|
270
|
+
else
|
271
|
+
cnames = paths.reverse_each.map { |b, a| cname_for(b, a) }
|
272
|
+
|
273
|
+
if root_namespace == Object
|
274
|
+
cnames.join("::")
|
275
|
+
else
|
276
|
+
"#{real_mod_name(root_namespace)}::#{cnames.join("::")}"
|
260
277
|
end
|
261
278
|
end
|
279
|
+
end
|
262
280
|
|
263
281
|
# Says if the given constant path would be unloaded on reload. This
|
264
282
|
# predicate returns `false` if reloading is disabled.
|
@@ -282,18 +300,29 @@ module Zeitwerk
|
|
282
300
|
# @sig () -> void
|
283
301
|
def unregister
|
284
302
|
Registry.unregister_loader(self)
|
285
|
-
ExplicitNamespace.
|
303
|
+
ExplicitNamespace.__unregister_loader(self)
|
304
|
+
end
|
305
|
+
|
306
|
+
# The return value of this predicate is only meaningful if the loader has
|
307
|
+
# scanned the file. This is the case in the spots where we use it.
|
308
|
+
#
|
309
|
+
# @sig (String) -> Boolean
|
310
|
+
internal def shadowed_file?(file)
|
311
|
+
shadowed_files.member?(file)
|
286
312
|
end
|
287
313
|
|
288
314
|
# --- Class methods ---------------------------------------------------------------------------
|
289
315
|
|
290
316
|
class << self
|
317
|
+
include RealModName
|
318
|
+
|
291
319
|
# @sig #call | #debug | nil
|
292
320
|
attr_accessor :default_logger
|
293
321
|
|
294
322
|
# This is a shortcut for
|
295
323
|
#
|
296
324
|
# require "zeitwerk"
|
325
|
+
#
|
297
326
|
# loader = Zeitwerk::Loader.new
|
298
327
|
# loader.tag = File.basename(__FILE__, ".rb")
|
299
328
|
# loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
@@ -308,14 +337,64 @@ module Zeitwerk
|
|
308
337
|
# @sig (bool) -> Zeitwerk::GemLoader
|
309
338
|
def for_gem(warn_on_extra_files: true)
|
310
339
|
called_from = caller_locations(1, 1).first.path
|
311
|
-
Registry.loader_for_gem(called_from, warn_on_extra_files: warn_on_extra_files)
|
340
|
+
Registry.loader_for_gem(called_from, namespace: Object, warn_on_extra_files: warn_on_extra_files)
|
341
|
+
end
|
342
|
+
|
343
|
+
# This is a shortcut for
|
344
|
+
#
|
345
|
+
# require "zeitwerk"
|
346
|
+
#
|
347
|
+
# loader = Zeitwerk::Loader.new
|
348
|
+
# loader.tag = namespace.name + "-" + File.basename(__FILE__, ".rb")
|
349
|
+
# loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
350
|
+
# loader.push_dir(__dir__, namespace: namespace)
|
351
|
+
#
|
352
|
+
# except that this method returns the same object in subsequent calls from
|
353
|
+
# the same file, in the unlikely case the gem wants to be able to reload.
|
354
|
+
#
|
355
|
+
# This method returns a subclass of Zeitwerk::Loader, but the exact type
|
356
|
+
# is private, client code can only rely on the interface.
|
357
|
+
#
|
358
|
+
# @sig (bool) -> Zeitwerk::GemLoader
|
359
|
+
def for_gem_extension(namespace)
|
360
|
+
unless namespace.is_a?(Module) # Note that Class < Module.
|
361
|
+
raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
|
362
|
+
end
|
363
|
+
|
364
|
+
unless real_mod_name(namespace)
|
365
|
+
raise Zeitwerk::Error, "extending anonymous namespaces is unsupported"
|
366
|
+
end
|
367
|
+
|
368
|
+
called_from = caller_locations(1, 1).first.path
|
369
|
+
Registry.loader_for_gem(called_from, namespace: namespace, warn_on_extra_files: false)
|
312
370
|
end
|
313
371
|
|
314
|
-
# Broadcasts `eager_load` to all loaders.
|
372
|
+
# Broadcasts `eager_load` to all loaders. Those that have not been setup
|
373
|
+
# are skipped.
|
315
374
|
#
|
316
375
|
# @sig () -> void
|
317
376
|
def eager_load_all
|
318
|
-
Registry.loaders.each
|
377
|
+
Registry.loaders.each do |loader|
|
378
|
+
begin
|
379
|
+
loader.eager_load
|
380
|
+
rescue SetupRequired
|
381
|
+
# This is fine, we eager load what can be eager loaded.
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# Broadcasts `eager_load_namespace` to all loaders. Those that have not
|
387
|
+
# been setup are skipped.
|
388
|
+
#
|
389
|
+
# @sig (Module) -> void
|
390
|
+
def eager_load_namespace(mod)
|
391
|
+
Registry.loaders.each do |loader|
|
392
|
+
begin
|
393
|
+
loader.eager_load_namespace(mod)
|
394
|
+
rescue SetupRequired
|
395
|
+
# This is fine, we eager load what can be eager loaded.
|
396
|
+
end
|
397
|
+
end
|
319
398
|
end
|
320
399
|
|
321
400
|
# Returns an array with the absolute paths of the root directories of all
|
@@ -327,77 +406,58 @@ module Zeitwerk
|
|
327
406
|
end
|
328
407
|
end
|
329
408
|
|
330
|
-
private # -------------------------------------------------------------------------------------
|
331
|
-
|
332
409
|
# @sig (String, Module) -> void
|
333
|
-
def
|
410
|
+
private def define_autoloads_for_dir(dir, parent)
|
334
411
|
ls(dir) do |basename, abspath|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
412
|
+
if ruby?(basename)
|
413
|
+
basename.delete_suffix!(".rb")
|
414
|
+
autoload_file(parent, cname_for(basename, abspath), abspath)
|
415
|
+
else
|
416
|
+
if collapse?(abspath)
|
417
|
+
define_autoloads_for_dir(abspath, parent)
|
340
418
|
else
|
341
|
-
|
342
|
-
# `app/models`, but both of them are root directories.
|
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)
|
348
|
-
cname = inflector.camelize(basename, abspath).to_sym
|
349
|
-
if collapse?(abspath)
|
350
|
-
set_autoloads_in_dir(abspath, parent)
|
351
|
-
else
|
352
|
-
autoload_subdir(parent, cname, abspath)
|
353
|
-
end
|
354
|
-
end
|
419
|
+
autoload_subdir(parent, cname_for(basename, abspath), abspath)
|
355
420
|
end
|
356
|
-
rescue ::NameError => error
|
357
|
-
path_type = ruby?(abspath) ? "file" : "directory"
|
358
|
-
|
359
|
-
raise NameError.new(<<~MESSAGE, error.name)
|
360
|
-
#{error.message} inferred by #{inflector.class} from #{path_type}
|
361
|
-
|
362
|
-
#{abspath}
|
363
|
-
|
364
|
-
Possible ways to address this:
|
365
|
-
|
366
|
-
* Tell Zeitwerk to ignore this particular #{path_type}.
|
367
|
-
* Tell Zeitwerk to ignore one of its parent directories.
|
368
|
-
* Rename the #{path_type} to comply with the naming conventions.
|
369
|
-
* Modify the inflector to handle this case.
|
370
|
-
MESSAGE
|
371
421
|
end
|
372
422
|
end
|
373
423
|
end
|
374
424
|
|
375
425
|
# @sig (Module, Symbol, String) -> void
|
376
|
-
def autoload_subdir(parent, cname, subdir)
|
426
|
+
private def autoload_subdir(parent, cname, subdir)
|
377
427
|
if autoload_path = autoload_path_set_by_me_for?(parent, cname)
|
378
428
|
cpath = cpath(parent, cname)
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
429
|
+
if ruby?(autoload_path)
|
430
|
+
# Scanning visited a Ruby file first, and now a directory for the same
|
431
|
+
# constant has been found. This means we are dealing with an explicit
|
432
|
+
# namespace whose definition was seen first.
|
433
|
+
#
|
434
|
+
# Registering is idempotent, and we have to keep the autoload pointing
|
435
|
+
# to the file. This may run again if more directories are found later
|
436
|
+
# on, no big deal.
|
437
|
+
register_explicit_namespace(cpath)
|
438
|
+
end
|
439
|
+
# If the existing autoload points to a file, it has to be preserved, if
|
440
|
+
# not, it is fine as it is. In either case, we do not need to override.
|
441
|
+
# Just remember the subdirectory conforms this namespace.
|
442
|
+
namespace_dirs[cpath] << subdir
|
384
443
|
elsif !cdef?(parent, cname)
|
385
444
|
# First time we find this namespace, set an autoload for it.
|
386
|
-
|
387
|
-
|
445
|
+
namespace_dirs[cpath(parent, cname)] << subdir
|
446
|
+
define_autoload(parent, cname, subdir)
|
388
447
|
else
|
389
448
|
# For whatever reason the constant that corresponds to this namespace has
|
390
449
|
# already been defined, we have to recurse.
|
391
450
|
log("the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
|
392
|
-
|
451
|
+
define_autoloads_for_dir(subdir, cget(parent, cname))
|
393
452
|
end
|
394
453
|
end
|
395
454
|
|
396
455
|
# @sig (Module, Symbol, String) -> void
|
397
|
-
def autoload_file(parent, cname, file)
|
456
|
+
private def autoload_file(parent, cname, file)
|
398
457
|
if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
|
399
458
|
# First autoload for a Ruby file wins, just ignore subsequent ones.
|
400
459
|
if ruby?(autoload_path)
|
460
|
+
shadowed_files << file
|
401
461
|
log("file #{file} is ignored because #{autoload_path} has precedence") if logger
|
402
462
|
else
|
403
463
|
promote_namespace_from_implicit_to_explicit(
|
@@ -408,9 +468,10 @@ module Zeitwerk
|
|
408
468
|
)
|
409
469
|
end
|
410
470
|
elsif cdef?(parent, cname)
|
471
|
+
shadowed_files << file
|
411
472
|
log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
|
412
473
|
else
|
413
|
-
|
474
|
+
define_autoload(parent, cname, file)
|
414
475
|
end
|
415
476
|
end
|
416
477
|
|
@@ -418,18 +479,18 @@ module Zeitwerk
|
|
418
479
|
# the file where we've found the namespace is explicitly defined.
|
419
480
|
#
|
420
481
|
# @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
|
421
|
-
def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
|
482
|
+
private def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
|
422
483
|
autoloads.delete(dir)
|
423
484
|
Registry.unregister_autoload(dir)
|
424
485
|
|
425
486
|
log("earlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger
|
426
487
|
|
427
|
-
|
488
|
+
define_autoload(parent, cname, file)
|
428
489
|
register_explicit_namespace(cpath(parent, cname))
|
429
490
|
end
|
430
491
|
|
431
492
|
# @sig (Module, Symbol, String) -> void
|
432
|
-
def
|
493
|
+
private def define_autoload(parent, cname, abspath)
|
433
494
|
parent.autoload(cname, abspath)
|
434
495
|
|
435
496
|
if logger
|
@@ -450,7 +511,7 @@ module Zeitwerk
|
|
450
511
|
end
|
451
512
|
|
452
513
|
# @sig (Module, Symbol) -> String?
|
453
|
-
def autoload_path_set_by_me_for?(parent, cname)
|
514
|
+
private def autoload_path_set_by_me_for?(parent, cname)
|
454
515
|
if autoload_path = strict_autoload_path(parent, cname)
|
455
516
|
autoload_path if autoloads.key?(autoload_path)
|
456
517
|
else
|
@@ -459,28 +520,28 @@ module Zeitwerk
|
|
459
520
|
end
|
460
521
|
|
461
522
|
# @sig (String) -> void
|
462
|
-
def register_explicit_namespace(cpath)
|
463
|
-
ExplicitNamespace.
|
523
|
+
private def register_explicit_namespace(cpath)
|
524
|
+
ExplicitNamespace.__register(cpath, self)
|
464
525
|
end
|
465
526
|
|
466
527
|
# @sig (String) -> void
|
467
|
-
def raise_if_conflicting_directory(dir)
|
528
|
+
private def raise_if_conflicting_directory(dir)
|
468
529
|
MUTEX.synchronize do
|
530
|
+
dir_slash = dir + "/"
|
531
|
+
|
469
532
|
Registry.loaders.each do |loader|
|
470
533
|
next if loader == self
|
471
|
-
next if loader.
|
534
|
+
next if loader.__ignores?(dir)
|
472
535
|
|
473
|
-
|
474
|
-
loader.root_dirs.each do |root_dir, _namespace|
|
536
|
+
loader.__roots.each_key do |root_dir|
|
475
537
|
next if ignores?(root_dir)
|
476
538
|
|
477
|
-
|
478
|
-
if
|
539
|
+
root_dir_slash = root_dir + "/"
|
540
|
+
if dir_slash.start_with?(root_dir_slash) || root_dir_slash.start_with?(dir_slash)
|
479
541
|
require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
|
480
542
|
raise Error,
|
481
|
-
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir
|
543
|
+
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
|
482
544
|
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
483
|
-
EOS
|
484
545
|
end
|
485
546
|
end
|
486
547
|
end
|
@@ -488,23 +549,23 @@ module Zeitwerk
|
|
488
549
|
end
|
489
550
|
|
490
551
|
# @sig (String, Object, String) -> void
|
491
|
-
def run_on_unload_callbacks(cpath, value, abspath)
|
552
|
+
private def run_on_unload_callbacks(cpath, value, abspath)
|
492
553
|
# Order matters. If present, run the most specific one.
|
493
554
|
on_unload_callbacks[cpath]&.each { |c| c.call(value, abspath) }
|
494
555
|
on_unload_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
|
495
556
|
end
|
496
557
|
|
497
558
|
# @sig (Module, Symbol) -> void
|
498
|
-
def unload_autoload(parent, cname)
|
499
|
-
parent
|
559
|
+
private def unload_autoload(parent, cname)
|
560
|
+
crem(parent, cname)
|
500
561
|
log("autoload for #{cpath(parent, cname)} removed") if logger
|
501
562
|
end
|
502
563
|
|
503
564
|
# @sig (Module, Symbol) -> void
|
504
|
-
def unload_cref(parent, cname)
|
565
|
+
private def unload_cref(parent, cname)
|
505
566
|
# Let's optimistically remove_const. The way we use it, this is going to
|
506
567
|
# succeed always if all is good.
|
507
|
-
parent
|
568
|
+
crem(parent, cname)
|
508
569
|
rescue ::NameError
|
509
570
|
# There are a few edge scenarios in which this may happen. If the constant
|
510
571
|
# is gone, that is OK, anyway.
|
data/lib/zeitwerk/registry.rb
CHANGED
@@ -86,8 +86,8 @@ module Zeitwerk
|
|
86
86
|
#
|
87
87
|
# @private
|
88
88
|
# @sig (String) -> Zeitwerk::Loader
|
89
|
-
def loader_for_gem(root_file, warn_on_extra_files:)
|
90
|
-
gem_loaders_by_root_file[root_file] ||= GemLoader.
|
89
|
+
def loader_for_gem(root_file, namespace:, warn_on_extra_files:)
|
90
|
+
gem_loaders_by_root_file[root_file] ||= GemLoader.__new(root_file, namespace: namespace, warn_on_extra_files: warn_on_extra_files)
|
91
91
|
end
|
92
92
|
|
93
93
|
# @private
|
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.12
|
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-09-25 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.16
|
63
65
|
signing_key:
|
64
66
|
specification_version: 4
|
65
67
|
summary: Efficient and thread-safe constant autoloader
|