zeitwerk 2.6.6 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +239 -83
- data/lib/zeitwerk/{kernel.rb → core_ext/kernel.rb} +3 -7
- data/lib/zeitwerk/core_ext/module.rb +12 -0
- data/lib/zeitwerk/cref.rb +65 -0
- data/lib/zeitwerk/explicit_namespace.rb +54 -56
- data/lib/zeitwerk/gem_inflector.rb +2 -2
- data/lib/zeitwerk/gem_loader.rb +11 -8
- data/lib/zeitwerk/loader/callbacks.rb +33 -25
- data/lib/zeitwerk/loader/config.rb +20 -11
- data/lib/zeitwerk/loader/eager_load.rb +22 -18
- data/lib/zeitwerk/loader/helpers.rb +60 -51
- data/lib/zeitwerk/loader.rb +215 -113
- data/lib/zeitwerk/null_inflector.rb +5 -0
- data/lib/zeitwerk/real_mod_name.rb +2 -8
- data/lib/zeitwerk/registry.rb +7 -4
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +5 -1
- metadata +8 -5
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
|
@@ -9,6 +10,8 @@ module Zeitwerk
|
|
9
10
|
require_relative "loader/config"
|
10
11
|
require_relative "loader/eager_load"
|
11
12
|
|
13
|
+
extend Internal
|
14
|
+
|
12
15
|
include RealModName
|
13
16
|
include Callbacks
|
14
17
|
include Helpers
|
@@ -19,16 +22,15 @@ module Zeitwerk
|
|
19
22
|
private_constant :MUTEX
|
20
23
|
|
21
24
|
# Maps absolute paths for which an autoload has been set ---and not
|
22
|
-
# executed--- to their corresponding
|
23
|
-
# name.
|
25
|
+
# executed--- to their corresponding Zeitwerk::Cref object.
|
24
26
|
#
|
25
|
-
# "/Users/fxn/blog/app/models/user.rb" =>
|
26
|
-
# "/Users/fxn/blog/app/models/hotel/pricing.rb" =>
|
27
|
+
# "/Users/fxn/blog/app/models/user.rb" => #<Zeitwerk::Cref:... @mod=Object, @cname=:User, ...>,
|
28
|
+
# "/Users/fxn/blog/app/models/hotel/pricing.rb" => #<Zeitwerk::Cref:... @mod=Hotel, @cname=:Pricing, ...>,
|
27
29
|
# ...
|
28
30
|
#
|
29
|
-
# @
|
30
|
-
# @sig Hash[String, [Module, Symbol]]
|
31
|
+
# @sig Hash[String, Zeitwerk::Cref]
|
31
32
|
attr_reader :autoloads
|
33
|
+
internal :autoloads
|
32
34
|
|
33
35
|
# We keep track of autoloaded directories to remove them from the registry
|
34
36
|
# at the end of eager loading.
|
@@ -36,25 +38,27 @@ module Zeitwerk
|
|
36
38
|
# Files are removed as they are autoloaded, but directories need to wait due
|
37
39
|
# to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
|
38
40
|
#
|
39
|
-
# @private
|
40
41
|
# @sig Array[String]
|
41
42
|
attr_reader :autoloaded_dirs
|
43
|
+
internal :autoloaded_dirs
|
42
44
|
|
43
45
|
# Stores metadata needed for unloading. Its entries look like this:
|
44
46
|
#
|
45
|
-
# "Admin::Role" => [
|
47
|
+
# "Admin::Role" => [
|
48
|
+
# ".../admin/role.rb",
|
49
|
+
# #<Zeitwerk::Cref:... @mod=Admin, @cname=:Role, ...>
|
50
|
+
# ]
|
46
51
|
#
|
47
52
|
# The cpath as key helps implementing unloadable_cpath? The file name is
|
48
53
|
# stored in order to be able to delete it from $LOADED_FEATURES, and the
|
49
|
-
#
|
50
|
-
# or module object.
|
54
|
+
# cref is used to remove the constant from the parent class or module.
|
51
55
|
#
|
52
56
|
# If reloading is enabled, this hash is filled as constants are autoloaded
|
53
57
|
# or eager loaded. Otherwise, the collection remains empty.
|
54
58
|
#
|
55
|
-
# @
|
56
|
-
# @sig Hash[String, [String, [Module, Symbol]]]
|
59
|
+
# @sig Hash[String, [String, Zeitwerk::Cref]]
|
57
60
|
attr_reader :to_unload
|
61
|
+
internal :to_unload
|
58
62
|
|
59
63
|
# Maps namespace constant paths to their respective directories.
|
60
64
|
#
|
@@ -70,9 +74,9 @@ module Zeitwerk
|
|
70
74
|
# and that its children are spread over those directories. We'll visit them
|
71
75
|
# to set up the corresponding autoloads.
|
72
76
|
#
|
73
|
-
# @private
|
74
77
|
# @sig Hash[String, Array[String]]
|
75
78
|
attr_reader :namespace_dirs
|
79
|
+
internal :namespace_dirs
|
76
80
|
|
77
81
|
# A shadowed file is a file managed by this loader that is ignored when
|
78
82
|
# setting autoloads because its matching constant is already taken.
|
@@ -81,17 +85,17 @@ module Zeitwerk
|
|
81
85
|
# has only scanned the top-level, `shadowed_files` does not have shadowed
|
82
86
|
# files that may exist deep in the project tree yet.
|
83
87
|
#
|
84
|
-
# @private
|
85
88
|
# @sig Set[String]
|
86
89
|
attr_reader :shadowed_files
|
90
|
+
internal :shadowed_files
|
87
91
|
|
88
|
-
# @private
|
89
92
|
# @sig Mutex
|
90
93
|
attr_reader :mutex
|
94
|
+
private :mutex
|
91
95
|
|
92
|
-
# @
|
93
|
-
|
94
|
-
|
96
|
+
# @sig Monitor
|
97
|
+
attr_reader :dirs_autoload_monitor
|
98
|
+
private :dirs_autoload_monitor
|
95
99
|
|
96
100
|
def initialize
|
97
101
|
super
|
@@ -101,11 +105,12 @@ module Zeitwerk
|
|
101
105
|
@to_unload = {}
|
102
106
|
@namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] }
|
103
107
|
@shadowed_files = Set.new
|
104
|
-
@mutex = Mutex.new
|
105
|
-
@mutex2 = Mutex.new
|
106
108
|
@setup = false
|
107
109
|
@eager_loaded = false
|
108
110
|
|
111
|
+
@mutex = Mutex.new
|
112
|
+
@dirs_autoload_monitor = Monitor.new
|
113
|
+
|
109
114
|
Registry.register_loader(self)
|
110
115
|
end
|
111
116
|
|
@@ -117,7 +122,7 @@ module Zeitwerk
|
|
117
122
|
break if @setup
|
118
123
|
|
119
124
|
actual_roots.each do |root_dir, root_namespace|
|
120
|
-
|
125
|
+
define_autoloads_for_dir(root_dir, root_namespace)
|
121
126
|
end
|
122
127
|
|
123
128
|
on_setup_callbacks.each(&:call)
|
@@ -134,7 +139,7 @@ module Zeitwerk
|
|
134
139
|
# unload them.
|
135
140
|
#
|
136
141
|
# This method is public but undocumented. Main interface is `reload`, which
|
137
|
-
# means `unload` + `setup`. This one is
|
142
|
+
# means `unload` + `setup`. This one is available to be used together with
|
138
143
|
# `unregister`, which is undocumented too.
|
139
144
|
#
|
140
145
|
# @sig () -> void
|
@@ -150,22 +155,22 @@ module Zeitwerk
|
|
150
155
|
# is enough.
|
151
156
|
unloaded_files = Set.new
|
152
157
|
|
153
|
-
autoloads.each do |abspath,
|
154
|
-
if
|
155
|
-
unload_autoload(
|
158
|
+
autoloads.each do |abspath, cref|
|
159
|
+
if cref.autoload?
|
160
|
+
unload_autoload(cref)
|
156
161
|
else
|
157
162
|
# Could happen if loaded with require_relative. That is unsupported,
|
158
163
|
# and the constant path would escape unloadable_cpath? This is just
|
159
164
|
# defensive code to clean things up as much as we are able to.
|
160
|
-
unload_cref(
|
165
|
+
unload_cref(cref)
|
161
166
|
unloaded_files.add(abspath) if ruby?(abspath)
|
162
167
|
end
|
163
168
|
end
|
164
169
|
|
165
|
-
to_unload.each do |cpath, (abspath,
|
170
|
+
to_unload.each do |cpath, (abspath, cref)|
|
166
171
|
unless on_unload_callbacks.empty?
|
167
172
|
begin
|
168
|
-
value =
|
173
|
+
value = cref.get
|
169
174
|
rescue ::NameError
|
170
175
|
# Perhaps the user deleted the constant by hand, or perhaps an
|
171
176
|
# autoload failed to define the expected constant but the user
|
@@ -175,7 +180,7 @@ module Zeitwerk
|
|
175
180
|
end
|
176
181
|
end
|
177
182
|
|
178
|
-
unload_cref(
|
183
|
+
unload_cref(cref)
|
179
184
|
unloaded_files.add(abspath) if ruby?(abspath)
|
180
185
|
end
|
181
186
|
|
@@ -226,6 +231,87 @@ module Zeitwerk
|
|
226
231
|
setup
|
227
232
|
end
|
228
233
|
|
234
|
+
# Returns a hash that maps the absolute paths of the managed files and
|
235
|
+
# directories to their respective expected constant paths.
|
236
|
+
#
|
237
|
+
# @sig () -> Hash[String, String]
|
238
|
+
def all_expected_cpaths
|
239
|
+
result = {}
|
240
|
+
|
241
|
+
actual_roots.each do |root_dir, root_namespace|
|
242
|
+
queue = [[root_dir, real_mod_name(root_namespace)]]
|
243
|
+
|
244
|
+
while (dir, cpath = queue.shift)
|
245
|
+
result[dir] = cpath
|
246
|
+
|
247
|
+
prefix = cpath == "Object" ? "" : cpath + "::"
|
248
|
+
|
249
|
+
ls(dir) do |basename, abspath, ftype|
|
250
|
+
if ftype == :file
|
251
|
+
basename.delete_suffix!(".rb")
|
252
|
+
result[abspath] = prefix + inflector.camelize(basename, abspath)
|
253
|
+
else
|
254
|
+
if collapse?(abspath)
|
255
|
+
queue << [abspath, cpath]
|
256
|
+
else
|
257
|
+
queue << [abspath, prefix + inflector.camelize(basename, abspath)]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
result
|
265
|
+
end
|
266
|
+
|
267
|
+
# @sig (String | Pathname) -> String?
|
268
|
+
def cpath_expected_at(path)
|
269
|
+
abspath = File.expand_path(path)
|
270
|
+
|
271
|
+
raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
|
272
|
+
|
273
|
+
return unless dir?(abspath) || ruby?(abspath)
|
274
|
+
return if ignored_path?(abspath)
|
275
|
+
|
276
|
+
paths = []
|
277
|
+
|
278
|
+
if ruby?(abspath)
|
279
|
+
basename = File.basename(abspath, ".rb")
|
280
|
+
return if hidden?(basename)
|
281
|
+
|
282
|
+
paths << [basename, abspath]
|
283
|
+
walk_up_from = File.dirname(abspath)
|
284
|
+
else
|
285
|
+
walk_up_from = abspath
|
286
|
+
end
|
287
|
+
|
288
|
+
root_namespace = nil
|
289
|
+
|
290
|
+
walk_up(walk_up_from) do |dir|
|
291
|
+
break if root_namespace = roots[dir]
|
292
|
+
return if ignored_path?(dir)
|
293
|
+
|
294
|
+
basename = File.basename(dir)
|
295
|
+
return if hidden?(basename)
|
296
|
+
|
297
|
+
paths << [basename, abspath] unless collapse?(dir)
|
298
|
+
end
|
299
|
+
|
300
|
+
return unless root_namespace
|
301
|
+
|
302
|
+
if paths.empty?
|
303
|
+
real_mod_name(root_namespace)
|
304
|
+
else
|
305
|
+
cnames = paths.reverse_each.map { |b, a| cname_for(b, a) }
|
306
|
+
|
307
|
+
if root_namespace == Object
|
308
|
+
cnames.join("::")
|
309
|
+
else
|
310
|
+
"#{real_mod_name(root_namespace)}::#{cnames.join("::")}"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
229
315
|
# Says if the given constant path would be unloaded on reload. This
|
230
316
|
# predicate returns `false` if reloading is disabled.
|
231
317
|
#
|
@@ -254,21 +340,23 @@ module Zeitwerk
|
|
254
340
|
# The return value of this predicate is only meaningful if the loader has
|
255
341
|
# scanned the file. This is the case in the spots where we use it.
|
256
342
|
#
|
257
|
-
# @private
|
258
343
|
# @sig (String) -> Boolean
|
259
|
-
def shadowed_file?(file)
|
344
|
+
internal def shadowed_file?(file)
|
260
345
|
shadowed_files.member?(file)
|
261
346
|
end
|
262
347
|
|
263
348
|
# --- Class methods ---------------------------------------------------------------------------
|
264
349
|
|
265
350
|
class << self
|
351
|
+
include RealModName
|
352
|
+
|
266
353
|
# @sig #call | #debug | nil
|
267
354
|
attr_accessor :default_logger
|
268
355
|
|
269
356
|
# This is a shortcut for
|
270
357
|
#
|
271
358
|
# require "zeitwerk"
|
359
|
+
#
|
272
360
|
# loader = Zeitwerk::Loader.new
|
273
361
|
# loader.tag = File.basename(__FILE__, ".rb")
|
274
362
|
# loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
@@ -283,7 +371,36 @@ module Zeitwerk
|
|
283
371
|
# @sig (bool) -> Zeitwerk::GemLoader
|
284
372
|
def for_gem(warn_on_extra_files: true)
|
285
373
|
called_from = caller_locations(1, 1).first.path
|
286
|
-
Registry.loader_for_gem(called_from, warn_on_extra_files: warn_on_extra_files)
|
374
|
+
Registry.loader_for_gem(called_from, namespace: Object, warn_on_extra_files: warn_on_extra_files)
|
375
|
+
end
|
376
|
+
|
377
|
+
# This is a shortcut for
|
378
|
+
#
|
379
|
+
# require "zeitwerk"
|
380
|
+
#
|
381
|
+
# loader = Zeitwerk::Loader.new
|
382
|
+
# loader.tag = namespace.name + "-" + File.basename(__FILE__, ".rb")
|
383
|
+
# loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
384
|
+
# loader.push_dir(__dir__, namespace: namespace)
|
385
|
+
#
|
386
|
+
# except that this method returns the same object in subsequent calls from
|
387
|
+
# the same file, in the unlikely case the gem wants to be able to reload.
|
388
|
+
#
|
389
|
+
# This method returns a subclass of Zeitwerk::Loader, but the exact type
|
390
|
+
# is private, client code can only rely on the interface.
|
391
|
+
#
|
392
|
+
# @sig (bool) -> Zeitwerk::GemLoader
|
393
|
+
def for_gem_extension(namespace)
|
394
|
+
unless namespace.is_a?(Module) # Note that Class < Module.
|
395
|
+
raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
|
396
|
+
end
|
397
|
+
|
398
|
+
unless real_mod_name(namespace)
|
399
|
+
raise Zeitwerk::Error, "extending anonymous namespaces is unsupported"
|
400
|
+
end
|
401
|
+
|
402
|
+
called_from = caller_locations(1, 1).first.path
|
403
|
+
Registry.loader_for_gem(called_from, namespace: namespace, warn_on_extra_files: false)
|
287
404
|
end
|
288
405
|
|
289
406
|
# Broadcasts `eager_load` to all loaders. Those that have not been setup
|
@@ -323,138 +440,124 @@ module Zeitwerk
|
|
323
440
|
end
|
324
441
|
end
|
325
442
|
|
326
|
-
private # -------------------------------------------------------------------------------------
|
327
|
-
|
328
443
|
# @sig (String, Module) -> void
|
329
|
-
def
|
330
|
-
ls(dir) do |basename, abspath|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
444
|
+
private def define_autoloads_for_dir(dir, parent)
|
445
|
+
ls(dir) do |basename, abspath, ftype|
|
446
|
+
if ftype == :file
|
447
|
+
basename.delete_suffix!(".rb")
|
448
|
+
cref = Cref.new(parent, cname_for(basename, abspath))
|
449
|
+
autoload_file(cref, abspath)
|
450
|
+
else
|
451
|
+
if collapse?(abspath)
|
452
|
+
define_autoloads_for_dir(abspath, parent)
|
336
453
|
else
|
337
|
-
|
338
|
-
|
339
|
-
else
|
340
|
-
cname = inflector.camelize(basename, abspath).to_sym
|
341
|
-
autoload_subdir(parent, cname, abspath)
|
342
|
-
end
|
454
|
+
cref = Cref.new(parent, cname_for(basename, abspath))
|
455
|
+
autoload_subdir(cref, abspath)
|
343
456
|
end
|
344
|
-
rescue ::NameError => error
|
345
|
-
path_type = ruby?(abspath) ? "file" : "directory"
|
346
|
-
|
347
|
-
raise NameError.new(<<~MESSAGE, error.name)
|
348
|
-
#{error.message} inferred by #{inflector.class} from #{path_type}
|
349
|
-
|
350
|
-
#{abspath}
|
351
|
-
|
352
|
-
Possible ways to address this:
|
353
|
-
|
354
|
-
* Tell Zeitwerk to ignore this particular #{path_type}.
|
355
|
-
* Tell Zeitwerk to ignore one of its parent directories.
|
356
|
-
* Rename the #{path_type} to comply with the naming conventions.
|
357
|
-
* Modify the inflector to handle this case.
|
358
|
-
MESSAGE
|
359
457
|
end
|
360
458
|
end
|
361
459
|
end
|
362
460
|
|
363
461
|
# @sig (Module, Symbol, String) -> void
|
364
|
-
def autoload_subdir(
|
365
|
-
if autoload_path = autoload_path_set_by_me_for?(
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
462
|
+
private def autoload_subdir(cref, subdir)
|
463
|
+
if autoload_path = autoload_path_set_by_me_for?(cref)
|
464
|
+
if ruby?(autoload_path)
|
465
|
+
# Scanning visited a Ruby file first, and now a directory for the same
|
466
|
+
# constant has been found. This means we are dealing with an explicit
|
467
|
+
# namespace whose definition was seen first.
|
468
|
+
#
|
469
|
+
# Registering is idempotent, and we have to keep the autoload pointing
|
470
|
+
# to the file. This may run again if more directories are found later
|
471
|
+
# on, no big deal.
|
472
|
+
register_explicit_namespace(cref)
|
473
|
+
end
|
474
|
+
# If the existing autoload points to a file, it has to be preserved, if
|
475
|
+
# not, it is fine as it is. In either case, we do not need to override.
|
476
|
+
# Just remember the subdirectory conforms this namespace.
|
477
|
+
namespace_dirs[cref.path] << subdir
|
478
|
+
elsif !cref.defined?
|
373
479
|
# First time we find this namespace, set an autoload for it.
|
374
|
-
namespace_dirs[
|
375
|
-
|
480
|
+
namespace_dirs[cref.path] << subdir
|
481
|
+
define_autoload(cref, subdir)
|
376
482
|
else
|
377
483
|
# For whatever reason the constant that corresponds to this namespace has
|
378
484
|
# already been defined, we have to recurse.
|
379
|
-
log("the namespace #{
|
380
|
-
|
485
|
+
log("the namespace #{cref.path} already exists, descending into #{subdir}") if logger
|
486
|
+
define_autoloads_for_dir(subdir, cref.get)
|
381
487
|
end
|
382
488
|
end
|
383
489
|
|
384
490
|
# @sig (Module, Symbol, String) -> void
|
385
|
-
def autoload_file(
|
386
|
-
if autoload_path =
|
491
|
+
private def autoload_file(cref, file)
|
492
|
+
if autoload_path = cref.autoload? || Registry.inception?(cref.path)
|
387
493
|
# First autoload for a Ruby file wins, just ignore subsequent ones.
|
388
494
|
if ruby?(autoload_path)
|
389
495
|
shadowed_files << file
|
390
496
|
log("file #{file} is ignored because #{autoload_path} has precedence") if logger
|
391
497
|
else
|
392
|
-
promote_namespace_from_implicit_to_explicit(
|
393
|
-
dir: autoload_path,
|
394
|
-
file: file,
|
395
|
-
parent: parent,
|
396
|
-
cname: cname
|
397
|
-
)
|
498
|
+
promote_namespace_from_implicit_to_explicit(dir: autoload_path, file: file, cref: cref)
|
398
499
|
end
|
399
|
-
elsif
|
500
|
+
elsif cref.defined?
|
400
501
|
shadowed_files << file
|
401
|
-
log("file #{file} is ignored because #{
|
502
|
+
log("file #{file} is ignored because #{cref.path} is already defined") if logger
|
402
503
|
else
|
403
|
-
|
504
|
+
define_autoload(cref, file)
|
404
505
|
end
|
405
506
|
end
|
406
507
|
|
407
508
|
# `dir` is the directory that would have autovivified a namespace. `file` is
|
408
509
|
# the file where we've found the namespace is explicitly defined.
|
409
510
|
#
|
410
|
-
# @sig (dir: String, file: String,
|
411
|
-
def promote_namespace_from_implicit_to_explicit(dir:, file:,
|
511
|
+
# @sig (dir: String, file: String, cref: Zeitwerk::Cref) -> void
|
512
|
+
private def promote_namespace_from_implicit_to_explicit(dir:, file:, cref:)
|
412
513
|
autoloads.delete(dir)
|
413
514
|
Registry.unregister_autoload(dir)
|
414
515
|
|
415
|
-
log("earlier autoload for #{
|
516
|
+
log("earlier autoload for #{cref.path} discarded, it is actually an explicit namespace defined in #{file}") if logger
|
416
517
|
|
417
|
-
|
418
|
-
|
518
|
+
# Order matters: When Module#const_added is triggered by the autoload, we
|
519
|
+
# don't want the namespace to be registered yet.
|
520
|
+
define_autoload(cref, file)
|
521
|
+
register_explicit_namespace(cref)
|
419
522
|
end
|
420
523
|
|
421
524
|
# @sig (Module, Symbol, String) -> void
|
422
|
-
def
|
423
|
-
|
525
|
+
private def define_autoload(cref, abspath)
|
526
|
+
cref.autoload(abspath)
|
424
527
|
|
425
528
|
if logger
|
426
529
|
if ruby?(abspath)
|
427
|
-
log("autoload set for #{
|
530
|
+
log("autoload set for #{cref.path}, to be loaded from #{abspath}")
|
428
531
|
else
|
429
|
-
log("autoload set for #{
|
532
|
+
log("autoload set for #{cref.path}, to be autovivified from #{abspath}")
|
430
533
|
end
|
431
534
|
end
|
432
535
|
|
433
|
-
autoloads[abspath] =
|
536
|
+
autoloads[abspath] = cref
|
434
537
|
Registry.register_autoload(self, abspath)
|
435
538
|
|
436
539
|
# See why in the documentation of Zeitwerk::Registry.inceptions.
|
437
|
-
unless
|
438
|
-
Registry.register_inception(
|
540
|
+
unless cref.autoload?
|
541
|
+
Registry.register_inception(cref.path, abspath, self)
|
439
542
|
end
|
440
543
|
end
|
441
544
|
|
442
545
|
# @sig (Module, Symbol) -> String?
|
443
|
-
def autoload_path_set_by_me_for?(
|
444
|
-
if autoload_path =
|
546
|
+
private def autoload_path_set_by_me_for?(cref)
|
547
|
+
if autoload_path = cref.autoload?
|
445
548
|
autoload_path if autoloads.key?(autoload_path)
|
446
549
|
else
|
447
|
-
Registry.inception?(
|
550
|
+
Registry.inception?(cref.path, self)
|
448
551
|
end
|
449
552
|
end
|
450
553
|
|
451
|
-
# @sig (
|
452
|
-
def register_explicit_namespace(
|
453
|
-
ExplicitNamespace.__register(
|
554
|
+
# @sig (Zeitwerk::Cref) -> void
|
555
|
+
private def register_explicit_namespace(cref)
|
556
|
+
ExplicitNamespace.__register(cref.path, self)
|
454
557
|
end
|
455
558
|
|
456
559
|
# @sig (String) -> void
|
457
|
-
def raise_if_conflicting_directory(dir)
|
560
|
+
private def raise_if_conflicting_directory(dir)
|
458
561
|
MUTEX.synchronize do
|
459
562
|
dir_slash = dir + "/"
|
460
563
|
|
@@ -471,7 +574,6 @@ module Zeitwerk
|
|
471
574
|
raise Error,
|
472
575
|
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
|
473
576
|
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
474
|
-
EOS
|
475
577
|
end
|
476
578
|
end
|
477
579
|
end
|
@@ -479,28 +581,28 @@ module Zeitwerk
|
|
479
581
|
end
|
480
582
|
|
481
583
|
# @sig (String, Object, String) -> void
|
482
|
-
def run_on_unload_callbacks(cpath, value, abspath)
|
584
|
+
private def run_on_unload_callbacks(cpath, value, abspath)
|
483
585
|
# Order matters. If present, run the most specific one.
|
484
586
|
on_unload_callbacks[cpath]&.each { |c| c.call(value, abspath) }
|
485
587
|
on_unload_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
|
486
588
|
end
|
487
589
|
|
488
590
|
# @sig (Module, Symbol) -> void
|
489
|
-
def unload_autoload(
|
490
|
-
|
491
|
-
log("autoload for #{
|
591
|
+
private def unload_autoload(cref)
|
592
|
+
cref.remove
|
593
|
+
log("autoload for #{cref.path} removed") if logger
|
492
594
|
end
|
493
595
|
|
494
596
|
# @sig (Module, Symbol) -> void
|
495
|
-
def unload_cref(
|
597
|
+
private def unload_cref(cref)
|
496
598
|
# Let's optimistically remove_const. The way we use it, this is going to
|
497
599
|
# succeed always if all is good.
|
498
|
-
|
600
|
+
cref.remove
|
499
601
|
rescue ::NameError
|
500
602
|
# There are a few edge scenarios in which this may happen. If the constant
|
501
603
|
# is gone, that is OK, anyway.
|
502
604
|
else
|
503
|
-
log("#{
|
605
|
+
log("#{cref.path} unloaded") if logger
|
504
606
|
end
|
505
607
|
end
|
506
608
|
end
|
@@ -10,13 +10,7 @@ module Zeitwerk::RealModName
|
|
10
10
|
# The name method can be overridden, hence the indirection in this method.
|
11
11
|
#
|
12
12
|
# @sig (Module) -> String?
|
13
|
-
|
14
|
-
|
15
|
-
UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
|
16
|
-
end
|
17
|
-
else
|
18
|
-
def real_mod_name(mod)
|
19
|
-
UNBOUND_METHOD_MODULE_NAME.bind(mod).call
|
20
|
-
end
|
13
|
+
def real_mod_name(mod)
|
14
|
+
UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
|
21
15
|
end
|
22
16
|
end
|
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
|
@@ -110,9 +110,12 @@ module Zeitwerk
|
|
110
110
|
|
111
111
|
# @private
|
112
112
|
# @sig (String) -> String?
|
113
|
-
def inception?(cpath)
|
113
|
+
def inception?(cpath, registered_by_loader=nil)
|
114
114
|
if pair = inceptions[cpath]
|
115
|
-
pair
|
115
|
+
abspath, loader = pair
|
116
|
+
if registered_by_loader.nil? || registered_by_loader.equal?(loader)
|
117
|
+
abspath
|
118
|
+
end
|
116
119
|
end
|
117
120
|
end
|
118
121
|
|
data/lib/zeitwerk/version.rb
CHANGED
data/lib/zeitwerk.rb
CHANGED
@@ -3,16 +3,20 @@
|
|
3
3
|
module Zeitwerk
|
4
4
|
require_relative "zeitwerk/real_mod_name"
|
5
5
|
require_relative "zeitwerk/internal"
|
6
|
+
require_relative "zeitwerk/cref"
|
6
7
|
require_relative "zeitwerk/loader"
|
7
8
|
require_relative "zeitwerk/gem_loader"
|
8
9
|
require_relative "zeitwerk/registry"
|
9
10
|
require_relative "zeitwerk/explicit_namespace"
|
10
11
|
require_relative "zeitwerk/inflector"
|
11
12
|
require_relative "zeitwerk/gem_inflector"
|
12
|
-
require_relative "zeitwerk/
|
13
|
+
require_relative "zeitwerk/null_inflector"
|
13
14
|
require_relative "zeitwerk/error"
|
14
15
|
require_relative "zeitwerk/version"
|
15
16
|
|
17
|
+
require_relative "zeitwerk/core_ext/kernel"
|
18
|
+
require_relative "zeitwerk/core_ext/module"
|
19
|
+
|
16
20
|
# This is a dangerous method.
|
17
21
|
#
|
18
22
|
# @experimental
|