zeitwerk 2.6.0 → 2.6.15
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 +430 -51
- 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 -9
- data/lib/zeitwerk/internal.rb +12 -0
- data/lib/zeitwerk/kernel.rb +6 -7
- 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 +92 -21
- data/lib/zeitwerk/loader.rb +224 -129
- data/lib/zeitwerk/null_inflector.rb +5 -0
- data/lib/zeitwerk/registry.rb +2 -2
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +2 -0
- metadata +6 -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,55 +230,85 @@ module Zeitwerk
|
|
208
230
|
setup
|
209
231
|
end
|
210
232
|
|
211
|
-
#
|
212
|
-
#
|
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`.
|
233
|
+
# Returns a hash that maps the absolute paths of the managed files and
|
234
|
+
# directories to their respective expected constant paths.
|
216
235
|
#
|
217
|
-
# @sig (
|
218
|
-
def
|
219
|
-
|
220
|
-
break if @eager_loaded
|
221
|
-
|
222
|
-
log("eager load start") if logger
|
236
|
+
# @sig () -> Hash[String, String]
|
237
|
+
def all_expected_cpaths
|
238
|
+
result = {}
|
223
239
|
|
224
|
-
|
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
|
240
|
+
actual_roots.each do |root_dir, root_namespace|
|
241
|
+
queue = [[root_dir, real_mod_name(root_namespace)]]
|
230
242
|
|
231
|
-
|
232
|
-
|
243
|
+
until queue.empty?
|
244
|
+
dir, cpath = queue.shift
|
245
|
+
result[dir] = cpath
|
233
246
|
|
234
|
-
|
235
|
-
next if honour_exclusions && excluded_from_eager_load?(abspath)
|
247
|
+
prefix = cpath == "Object" ? "" : cpath + "::"
|
236
248
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
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
|
242
254
|
if collapse?(abspath)
|
243
|
-
queue << [
|
255
|
+
queue << [abspath, cpath]
|
244
256
|
else
|
245
|
-
|
246
|
-
queue << [cget(namespace, cname), abspath]
|
257
|
+
queue << [abspath, prefix + inflector.camelize(basename, abspath)]
|
247
258
|
end
|
248
259
|
end
|
249
260
|
end
|
250
261
|
end
|
262
|
+
end
|
251
263
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
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 = []
|
256
277
|
|
257
|
-
|
278
|
+
if ruby?(abspath)
|
279
|
+
basename = File.basename(abspath, ".rb")
|
280
|
+
return if hidden?(basename)
|
258
281
|
|
259
|
-
|
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
|
260
312
|
end
|
261
313
|
end
|
262
314
|
|
@@ -282,18 +334,29 @@ module Zeitwerk
|
|
282
334
|
# @sig () -> void
|
283
335
|
def unregister
|
284
336
|
Registry.unregister_loader(self)
|
285
|
-
ExplicitNamespace.
|
337
|
+
ExplicitNamespace.__unregister_loader(self)
|
338
|
+
end
|
339
|
+
|
340
|
+
# The return value of this predicate is only meaningful if the loader has
|
341
|
+
# scanned the file. This is the case in the spots where we use it.
|
342
|
+
#
|
343
|
+
# @sig (String) -> Boolean
|
344
|
+
internal def shadowed_file?(file)
|
345
|
+
shadowed_files.member?(file)
|
286
346
|
end
|
287
347
|
|
288
348
|
# --- Class methods ---------------------------------------------------------------------------
|
289
349
|
|
290
350
|
class << self
|
351
|
+
include RealModName
|
352
|
+
|
291
353
|
# @sig #call | #debug | nil
|
292
354
|
attr_accessor :default_logger
|
293
355
|
|
294
356
|
# This is a shortcut for
|
295
357
|
#
|
296
358
|
# require "zeitwerk"
|
359
|
+
#
|
297
360
|
# loader = Zeitwerk::Loader.new
|
298
361
|
# loader.tag = File.basename(__FILE__, ".rb")
|
299
362
|
# loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
@@ -308,14 +371,64 @@ module Zeitwerk
|
|
308
371
|
# @sig (bool) -> Zeitwerk::GemLoader
|
309
372
|
def for_gem(warn_on_extra_files: true)
|
310
373
|
called_from = caller_locations(1, 1).first.path
|
311
|
-
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)
|
312
375
|
end
|
313
376
|
|
314
|
-
#
|
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)
|
404
|
+
end
|
405
|
+
|
406
|
+
# Broadcasts `eager_load` to all loaders. Those that have not been setup
|
407
|
+
# are skipped.
|
315
408
|
#
|
316
409
|
# @sig () -> void
|
317
410
|
def eager_load_all
|
318
|
-
Registry.loaders.each
|
411
|
+
Registry.loaders.each do |loader|
|
412
|
+
begin
|
413
|
+
loader.eager_load
|
414
|
+
rescue SetupRequired
|
415
|
+
# This is fine, we eager load what can be eager loaded.
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# Broadcasts `eager_load_namespace` to all loaders. Those that have not
|
421
|
+
# been setup are skipped.
|
422
|
+
#
|
423
|
+
# @sig (Module) -> void
|
424
|
+
def eager_load_namespace(mod)
|
425
|
+
Registry.loaders.each do |loader|
|
426
|
+
begin
|
427
|
+
loader.eager_load_namespace(mod)
|
428
|
+
rescue SetupRequired
|
429
|
+
# This is fine, we eager load what can be eager loaded.
|
430
|
+
end
|
431
|
+
end
|
319
432
|
end
|
320
433
|
|
321
434
|
# Returns an array with the absolute paths of the root directories of all
|
@@ -327,77 +440,58 @@ module Zeitwerk
|
|
327
440
|
end
|
328
441
|
end
|
329
442
|
|
330
|
-
private # -------------------------------------------------------------------------------------
|
331
|
-
|
332
443
|
# @sig (String, Module) -> void
|
333
|
-
def
|
334
|
-
ls(dir) do |basename, abspath|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
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
|
+
autoload_file(parent, cname_for(basename, abspath), abspath)
|
449
|
+
else
|
450
|
+
if collapse?(abspath)
|
451
|
+
define_autoloads_for_dir(abspath, parent)
|
340
452
|
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
|
453
|
+
autoload_subdir(parent, cname_for(basename, abspath), abspath)
|
355
454
|
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
455
|
end
|
372
456
|
end
|
373
457
|
end
|
374
458
|
|
375
459
|
# @sig (Module, Symbol, String) -> void
|
376
|
-
def autoload_subdir(parent, cname, subdir)
|
460
|
+
private def autoload_subdir(parent, cname, subdir)
|
377
461
|
if autoload_path = autoload_path_set_by_me_for?(parent, cname)
|
378
462
|
cpath = cpath(parent, cname)
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
463
|
+
if ruby?(autoload_path)
|
464
|
+
# Scanning visited a Ruby file first, and now a directory for the same
|
465
|
+
# constant has been found. This means we are dealing with an explicit
|
466
|
+
# namespace whose definition was seen first.
|
467
|
+
#
|
468
|
+
# Registering is idempotent, and we have to keep the autoload pointing
|
469
|
+
# to the file. This may run again if more directories are found later
|
470
|
+
# on, no big deal.
|
471
|
+
register_explicit_namespace(cpath)
|
472
|
+
end
|
473
|
+
# If the existing autoload points to a file, it has to be preserved, if
|
474
|
+
# not, it is fine as it is. In either case, we do not need to override.
|
475
|
+
# Just remember the subdirectory conforms this namespace.
|
476
|
+
namespace_dirs[cpath] << subdir
|
384
477
|
elsif !cdef?(parent, cname)
|
385
478
|
# First time we find this namespace, set an autoload for it.
|
386
|
-
|
387
|
-
|
479
|
+
namespace_dirs[cpath(parent, cname)] << subdir
|
480
|
+
define_autoload(parent, cname, subdir)
|
388
481
|
else
|
389
482
|
# For whatever reason the constant that corresponds to this namespace has
|
390
483
|
# already been defined, we have to recurse.
|
391
484
|
log("the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
|
392
|
-
|
485
|
+
define_autoloads_for_dir(subdir, cget(parent, cname))
|
393
486
|
end
|
394
487
|
end
|
395
488
|
|
396
489
|
# @sig (Module, Symbol, String) -> void
|
397
|
-
def autoload_file(parent, cname, file)
|
490
|
+
private def autoload_file(parent, cname, file)
|
398
491
|
if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
|
399
492
|
# First autoload for a Ruby file wins, just ignore subsequent ones.
|
400
493
|
if ruby?(autoload_path)
|
494
|
+
shadowed_files << file
|
401
495
|
log("file #{file} is ignored because #{autoload_path} has precedence") if logger
|
402
496
|
else
|
403
497
|
promote_namespace_from_implicit_to_explicit(
|
@@ -408,9 +502,10 @@ module Zeitwerk
|
|
408
502
|
)
|
409
503
|
end
|
410
504
|
elsif cdef?(parent, cname)
|
505
|
+
shadowed_files << file
|
411
506
|
log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
|
412
507
|
else
|
413
|
-
|
508
|
+
define_autoload(parent, cname, file)
|
414
509
|
end
|
415
510
|
end
|
416
511
|
|
@@ -418,18 +513,18 @@ module Zeitwerk
|
|
418
513
|
# the file where we've found the namespace is explicitly defined.
|
419
514
|
#
|
420
515
|
# @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
|
421
|
-
def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
|
516
|
+
private def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
|
422
517
|
autoloads.delete(dir)
|
423
518
|
Registry.unregister_autoload(dir)
|
424
519
|
|
425
520
|
log("earlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger
|
426
521
|
|
427
|
-
|
522
|
+
define_autoload(parent, cname, file)
|
428
523
|
register_explicit_namespace(cpath(parent, cname))
|
429
524
|
end
|
430
525
|
|
431
526
|
# @sig (Module, Symbol, String) -> void
|
432
|
-
def
|
527
|
+
private def define_autoload(parent, cname, abspath)
|
433
528
|
parent.autoload(cname, abspath)
|
434
529
|
|
435
530
|
if logger
|
@@ -450,7 +545,7 @@ module Zeitwerk
|
|
450
545
|
end
|
451
546
|
|
452
547
|
# @sig (Module, Symbol) -> String?
|
453
|
-
def autoload_path_set_by_me_for?(parent, cname)
|
548
|
+
private def autoload_path_set_by_me_for?(parent, cname)
|
454
549
|
if autoload_path = strict_autoload_path(parent, cname)
|
455
550
|
autoload_path if autoloads.key?(autoload_path)
|
456
551
|
else
|
@@ -459,28 +554,28 @@ module Zeitwerk
|
|
459
554
|
end
|
460
555
|
|
461
556
|
# @sig (String) -> void
|
462
|
-
def register_explicit_namespace(cpath)
|
463
|
-
ExplicitNamespace.
|
557
|
+
private def register_explicit_namespace(cpath)
|
558
|
+
ExplicitNamespace.__register(cpath, self)
|
464
559
|
end
|
465
560
|
|
466
561
|
# @sig (String) -> void
|
467
|
-
def raise_if_conflicting_directory(dir)
|
562
|
+
private def raise_if_conflicting_directory(dir)
|
468
563
|
MUTEX.synchronize do
|
564
|
+
dir_slash = dir + "/"
|
565
|
+
|
469
566
|
Registry.loaders.each do |loader|
|
470
567
|
next if loader == self
|
471
|
-
next if loader.
|
568
|
+
next if loader.__ignores?(dir)
|
472
569
|
|
473
|
-
|
474
|
-
loader.root_dirs.each do |root_dir, _namespace|
|
570
|
+
loader.__roots.each_key do |root_dir|
|
475
571
|
next if ignores?(root_dir)
|
476
572
|
|
477
|
-
|
478
|
-
if
|
573
|
+
root_dir_slash = root_dir + "/"
|
574
|
+
if dir_slash.start_with?(root_dir_slash) || root_dir_slash.start_with?(dir_slash)
|
479
575
|
require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
|
480
576
|
raise Error,
|
481
|
-
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir
|
577
|
+
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
|
482
578
|
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
483
|
-
EOS
|
484
579
|
end
|
485
580
|
end
|
486
581
|
end
|
@@ -488,23 +583,23 @@ module Zeitwerk
|
|
488
583
|
end
|
489
584
|
|
490
585
|
# @sig (String, Object, String) -> void
|
491
|
-
def run_on_unload_callbacks(cpath, value, abspath)
|
586
|
+
private def run_on_unload_callbacks(cpath, value, abspath)
|
492
587
|
# Order matters. If present, run the most specific one.
|
493
588
|
on_unload_callbacks[cpath]&.each { |c| c.call(value, abspath) }
|
494
589
|
on_unload_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
|
495
590
|
end
|
496
591
|
|
497
592
|
# @sig (Module, Symbol) -> void
|
498
|
-
def unload_autoload(parent, cname)
|
499
|
-
parent
|
593
|
+
private def unload_autoload(parent, cname)
|
594
|
+
crem(parent, cname)
|
500
595
|
log("autoload for #{cpath(parent, cname)} removed") if logger
|
501
596
|
end
|
502
597
|
|
503
598
|
# @sig (Module, Symbol) -> void
|
504
|
-
def unload_cref(parent, cname)
|
599
|
+
private def unload_cref(parent, cname)
|
505
600
|
# Let's optimistically remove_const. The way we use it, this is going to
|
506
601
|
# succeed always if all is good.
|
507
|
-
parent
|
602
|
+
crem(parent, cname)
|
508
603
|
rescue ::NameError
|
509
604
|
# There are a few edge scenarios in which this may happen. If the constant
|
510
605
|
# 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
@@ -2,12 +2,14 @@
|
|
2
2
|
|
3
3
|
module Zeitwerk
|
4
4
|
require_relative "zeitwerk/real_mod_name"
|
5
|
+
require_relative "zeitwerk/internal"
|
5
6
|
require_relative "zeitwerk/loader"
|
6
7
|
require_relative "zeitwerk/gem_loader"
|
7
8
|
require_relative "zeitwerk/registry"
|
8
9
|
require_relative "zeitwerk/explicit_namespace"
|
9
10
|
require_relative "zeitwerk/inflector"
|
10
11
|
require_relative "zeitwerk/gem_inflector"
|
12
|
+
require_relative "zeitwerk/null_inflector"
|
11
13
|
require_relative "zeitwerk/kernel"
|
12
14
|
require_relative "zeitwerk/error"
|
13
15
|
require_relative "zeitwerk/version"
|
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.15
|
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: 2024-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -28,11 +28,14 @@ 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
|
38
|
+
- lib/zeitwerk/null_inflector.rb
|
36
39
|
- lib/zeitwerk/real_mod_name.rb
|
37
40
|
- lib/zeitwerk/registry.rb
|
38
41
|
- lib/zeitwerk/version.rb
|
@@ -59,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
62
|
- !ruby/object:Gem::Version
|
60
63
|
version: '0'
|
61
64
|
requirements: []
|
62
|
-
rubygems_version: 3.
|
65
|
+
rubygems_version: 3.5.7
|
63
66
|
signing_key:
|
64
67
|
specification_version: 4
|
65
68
|
summary: Efficient and thread-safe constant autoloader
|