zeitwerk 2.2.1 → 2.4.1
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 +175 -31
- data/lib/zeitwerk/explicit_namespace.rb +9 -8
- data/lib/zeitwerk/gem_inflector.rb +2 -4
- data/lib/zeitwerk/inflector.rb +4 -7
- data/lib/zeitwerk/kernel.rb +32 -2
- data/lib/zeitwerk/loader.rb +150 -125
- data/lib/zeitwerk/loader/callbacks.rb +4 -7
- data/lib/zeitwerk/real_mod_name.rb +1 -2
- data/lib/zeitwerk/registry.rb +12 -23
- data/lib/zeitwerk/version.rb +1 -1
- metadata +12 -8
data/lib/zeitwerk/loader.rb
CHANGED
@@ -9,13 +9,13 @@ module Zeitwerk
|
|
9
9
|
include Callbacks
|
10
10
|
include RealModName
|
11
11
|
|
12
|
-
# @
|
12
|
+
# @sig String
|
13
13
|
attr_reader :tag
|
14
14
|
|
15
|
-
# @
|
15
|
+
# @sig #camelize
|
16
16
|
attr_accessor :inflector
|
17
17
|
|
18
|
-
# @
|
18
|
+
# @sig #call | #debug | nil
|
19
19
|
attr_accessor :logger
|
20
20
|
|
21
21
|
# Absolute paths of the root directories. Stored in a hash to preserve
|
@@ -30,20 +30,20 @@ module Zeitwerk
|
|
30
30
|
# interface for it is `push_dir` and `dirs`.
|
31
31
|
#
|
32
32
|
# @private
|
33
|
-
# @
|
33
|
+
# @sig Hash[String, true]
|
34
34
|
attr_reader :root_dirs
|
35
35
|
|
36
36
|
# Absolute paths of files or directories that have to be preloaded.
|
37
37
|
#
|
38
38
|
# @private
|
39
|
-
# @
|
39
|
+
# @sig Array[String]
|
40
40
|
attr_reader :preloads
|
41
41
|
|
42
|
-
# Absolute paths of files, directories,
|
42
|
+
# Absolute paths of files, directories, or glob patterns to be totally
|
43
43
|
# ignored.
|
44
44
|
#
|
45
45
|
# @private
|
46
|
-
# @
|
46
|
+
# @sig Set[String]
|
47
47
|
attr_reader :ignored_glob_patterns
|
48
48
|
|
49
49
|
# The actual collection of absolute file and directory names at the time the
|
@@ -51,9 +51,22 @@ module Zeitwerk
|
|
51
51
|
# reload.
|
52
52
|
#
|
53
53
|
# @private
|
54
|
-
# @
|
54
|
+
# @sig Set[String]
|
55
55
|
attr_reader :ignored_paths
|
56
56
|
|
57
|
+
# Absolute paths of directories or glob patterns to be collapsed.
|
58
|
+
#
|
59
|
+
# @private
|
60
|
+
# @sig Set[String]
|
61
|
+
attr_reader :collapse_glob_patterns
|
62
|
+
|
63
|
+
# The actual collection of absolute directory names at the time the collapse
|
64
|
+
# glob patterns were expanded. Computed on setup, and recomputed on reload.
|
65
|
+
#
|
66
|
+
# @private
|
67
|
+
# @sig Set[String]
|
68
|
+
attr_reader :collapse_dirs
|
69
|
+
|
57
70
|
# Maps real absolute paths for which an autoload has been set ---and not
|
58
71
|
# executed--- to their corresponding parent class or module and constant
|
59
72
|
# name.
|
@@ -63,7 +76,7 @@ module Zeitwerk
|
|
63
76
|
# ...
|
64
77
|
#
|
65
78
|
# @private
|
66
|
-
# @
|
79
|
+
# @sig Hash[String, [Module, Symbol]]
|
67
80
|
attr_reader :autoloads
|
68
81
|
|
69
82
|
# We keep track of autoloaded directories to remove them from the registry
|
@@ -73,7 +86,7 @@ module Zeitwerk
|
|
73
86
|
# to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
|
74
87
|
#
|
75
88
|
# @private
|
76
|
-
# @
|
89
|
+
# @sig Array[String]
|
77
90
|
attr_reader :autoloaded_dirs
|
78
91
|
|
79
92
|
# Stores metadata needed for unloading. Its entries look like this:
|
@@ -89,7 +102,7 @@ module Zeitwerk
|
|
89
102
|
# or eager loaded. Otherwise, the collection remains empty.
|
90
103
|
#
|
91
104
|
# @private
|
92
|
-
# @
|
105
|
+
# @sig Hash[String, [String, [Module, Symbol]]]
|
93
106
|
attr_reader :to_unload
|
94
107
|
|
95
108
|
# Maps constant paths of namespaces to arrays of corresponding directories.
|
@@ -107,21 +120,21 @@ module Zeitwerk
|
|
107
120
|
# up the corresponding autoloads.
|
108
121
|
#
|
109
122
|
# @private
|
110
|
-
# @
|
123
|
+
# @sig Hash[String, Array[String]]
|
111
124
|
attr_reader :lazy_subdirs
|
112
125
|
|
113
126
|
# Absolute paths of files or directories not to be eager loaded.
|
114
127
|
#
|
115
128
|
# @private
|
116
|
-
# @
|
129
|
+
# @sig Set[String]
|
117
130
|
attr_reader :eager_load_exclusions
|
118
131
|
|
119
132
|
# @private
|
120
|
-
# @
|
133
|
+
# @sig Mutex
|
121
134
|
attr_reader :mutex
|
122
135
|
|
123
136
|
# @private
|
124
|
-
# @
|
137
|
+
# @sig Mutex
|
125
138
|
attr_reader :mutex2
|
126
139
|
|
127
140
|
def initialize
|
@@ -131,15 +144,17 @@ module Zeitwerk
|
|
131
144
|
@inflector = Inflector.new
|
132
145
|
@logger = self.class.default_logger
|
133
146
|
|
134
|
-
@root_dirs
|
135
|
-
@preloads
|
136
|
-
@ignored_glob_patterns
|
137
|
-
@ignored_paths
|
138
|
-
@
|
139
|
-
@
|
140
|
-
@
|
141
|
-
@
|
142
|
-
@
|
147
|
+
@root_dirs = {}
|
148
|
+
@preloads = []
|
149
|
+
@ignored_glob_patterns = Set.new
|
150
|
+
@ignored_paths = Set.new
|
151
|
+
@collapse_glob_patterns = Set.new
|
152
|
+
@collapse_dirs = Set.new
|
153
|
+
@autoloads = {}
|
154
|
+
@autoloaded_dirs = []
|
155
|
+
@to_unload = {}
|
156
|
+
@lazy_subdirs = {}
|
157
|
+
@eager_load_exclusions = Set.new
|
143
158
|
|
144
159
|
# TODO: find a better name for these mutexes.
|
145
160
|
@mutex = Mutex.new
|
@@ -154,7 +169,8 @@ module Zeitwerk
|
|
154
169
|
|
155
170
|
# Sets a tag for the loader, useful for logging.
|
156
171
|
#
|
157
|
-
# @
|
172
|
+
# @param tag [#to_s]
|
173
|
+
# @sig (#to_s) -> void
|
158
174
|
def tag=(tag)
|
159
175
|
@tag = tag.to_s
|
160
176
|
end
|
@@ -162,7 +178,7 @@ module Zeitwerk
|
|
162
178
|
# Absolute paths of the root directories. This is a read-only collection,
|
163
179
|
# please push here via `push_dir`.
|
164
180
|
#
|
165
|
-
# @
|
181
|
+
# @sig () -> Array[String]
|
166
182
|
def dirs
|
167
183
|
root_dirs.keys.freeze
|
168
184
|
end
|
@@ -173,14 +189,18 @@ module Zeitwerk
|
|
173
189
|
# the same process already manages that directory or one of its ascendants
|
174
190
|
# or descendants.
|
175
191
|
#
|
176
|
-
# @param path [<String, Pathname>]
|
177
192
|
# @raise [Zeitwerk::Error]
|
178
|
-
# @
|
179
|
-
def push_dir(path)
|
193
|
+
# @sig (String | Pathname, Module) -> void
|
194
|
+
def push_dir(path, namespace: Object)
|
195
|
+
# Note that Class < Module.
|
196
|
+
unless namespace.is_a?(Module)
|
197
|
+
raise Error, "#{namespace.inspect} is not a class or module object, should be"
|
198
|
+
end
|
199
|
+
|
180
200
|
abspath = File.expand_path(path)
|
181
201
|
if dir?(abspath)
|
182
202
|
raise_if_conflicting_directory(abspath)
|
183
|
-
root_dirs[abspath] =
|
203
|
+
root_dirs[abspath] = namespace
|
184
204
|
else
|
185
205
|
raise Error, "the root directory #{abspath} does not exist"
|
186
206
|
end
|
@@ -190,7 +210,7 @@ module Zeitwerk
|
|
190
210
|
# There is no way to undo this, either you want to reload or you don't.
|
191
211
|
#
|
192
212
|
# @raise [Zeitwerk::Error]
|
193
|
-
# @
|
213
|
+
# @sig () -> void
|
194
214
|
def enable_reloading
|
195
215
|
mutex.synchronize do
|
196
216
|
break if @reloading_enabled
|
@@ -203,15 +223,14 @@ module Zeitwerk
|
|
203
223
|
end
|
204
224
|
end
|
205
225
|
|
206
|
-
# @
|
226
|
+
# @sig () -> bool
|
207
227
|
def reloading_enabled?
|
208
228
|
@reloading_enabled
|
209
229
|
end
|
210
230
|
|
211
231
|
# Files or directories to be preloaded instead of lazy loaded.
|
212
232
|
#
|
213
|
-
# @
|
214
|
-
# @return [void]
|
233
|
+
# @sig (*(String | Pathname | Array[String | Pathname])) -> void
|
215
234
|
def preload(*paths)
|
216
235
|
mutex.synchronize do
|
217
236
|
expand_paths(paths).each do |abspath|
|
@@ -223,8 +242,7 @@ module Zeitwerk
|
|
223
242
|
|
224
243
|
# Configure files, directories, or glob patterns to be totally ignored.
|
225
244
|
#
|
226
|
-
# @
|
227
|
-
# @return [void]
|
245
|
+
# @sig (*(String | Pathname | Array[String | Pathname])) -> void
|
228
246
|
def ignore(*glob_patterns)
|
229
247
|
glob_patterns = expand_paths(glob_patterns)
|
230
248
|
mutex.synchronize do
|
@@ -233,14 +251,27 @@ module Zeitwerk
|
|
233
251
|
end
|
234
252
|
end
|
235
253
|
|
254
|
+
# Configure directories or glob patterns to be collapsed.
|
255
|
+
#
|
256
|
+
# @sig (*(String | Pathname | Array[String | Pathname])) -> void
|
257
|
+
def collapse(*glob_patterns)
|
258
|
+
glob_patterns = expand_paths(glob_patterns)
|
259
|
+
mutex.synchronize do
|
260
|
+
collapse_glob_patterns.merge(glob_patterns)
|
261
|
+
collapse_dirs.merge(expand_glob_patterns(glob_patterns))
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
236
265
|
# Sets autoloads in the root namespace and preloads files, if any.
|
237
266
|
#
|
238
|
-
# @
|
267
|
+
# @sig () -> void
|
239
268
|
def setup
|
240
269
|
mutex.synchronize do
|
241
270
|
break if @setup
|
242
271
|
|
243
|
-
actual_root_dirs.each
|
272
|
+
actual_root_dirs.each do |root_dir, namespace|
|
273
|
+
set_autoloads_in_dir(root_dir, namespace)
|
274
|
+
end
|
244
275
|
do_preload
|
245
276
|
|
246
277
|
@setup = true
|
@@ -255,7 +286,7 @@ module Zeitwerk
|
|
255
286
|
# unload them.
|
256
287
|
#
|
257
288
|
# @private
|
258
|
-
# @
|
289
|
+
# @sig () -> void
|
259
290
|
def unload
|
260
291
|
mutex.synchronize do
|
261
292
|
# We are going to keep track of the files that were required by our
|
@@ -306,7 +337,8 @@ module Zeitwerk
|
|
306
337
|
Registry.on_unload(self)
|
307
338
|
ExplicitNamespace.unregister(self)
|
308
339
|
|
309
|
-
@setup
|
340
|
+
@setup = false
|
341
|
+
@eager_loaded = false
|
310
342
|
end
|
311
343
|
end
|
312
344
|
|
@@ -317,11 +349,12 @@ module Zeitwerk
|
|
317
349
|
# client code in the README of the project.
|
318
350
|
#
|
319
351
|
# @raise [Zeitwerk::Error]
|
320
|
-
# @
|
352
|
+
# @sig () -> void
|
321
353
|
def reload
|
322
354
|
if reloading_enabled?
|
323
355
|
unload
|
324
356
|
recompute_ignored_paths
|
357
|
+
recompute_collapse_dirs
|
325
358
|
setup
|
326
359
|
else
|
327
360
|
raise ReloadingDisabledError, "can't reload, please call loader.enable_reloading before setup"
|
@@ -333,13 +366,16 @@ module Zeitwerk
|
|
333
366
|
# are not eager loaded. You can opt-out specifically in specific files and
|
334
367
|
# directories with `do_not_eager_load`.
|
335
368
|
#
|
336
|
-
# @
|
369
|
+
# @sig () -> void
|
337
370
|
def eager_load
|
338
371
|
mutex.synchronize do
|
339
372
|
break if @eager_loaded
|
340
373
|
|
341
|
-
queue =
|
342
|
-
|
374
|
+
queue = []
|
375
|
+
actual_root_dirs.each do |root_dir, namespace|
|
376
|
+
queue << [namespace, root_dir] unless eager_load_exclusions.member?(root_dir)
|
377
|
+
end
|
378
|
+
|
343
379
|
while to_eager_load = queue.shift
|
344
380
|
namespace, dir = to_eager_load
|
345
381
|
|
@@ -351,8 +387,12 @@ module Zeitwerk
|
|
351
387
|
cref[0].const_get(cref[1], false)
|
352
388
|
end
|
353
389
|
elsif dir?(abspath) && !root_dirs.key?(abspath)
|
354
|
-
|
355
|
-
|
390
|
+
if collapse_dirs.member?(abspath)
|
391
|
+
queue << [namespace, abspath]
|
392
|
+
else
|
393
|
+
cname = inflector.camelize(basename, abspath)
|
394
|
+
queue << [namespace.const_get(cname, false), abspath]
|
395
|
+
end
|
356
396
|
end
|
357
397
|
end
|
358
398
|
end
|
@@ -369,8 +409,7 @@ module Zeitwerk
|
|
369
409
|
# Let eager load ignore the given files or directories. The constants
|
370
410
|
# defined in those files are still autoloadable.
|
371
411
|
#
|
372
|
-
# @
|
373
|
-
# @return [void]
|
412
|
+
# @sig (*(String | Pathname | Array[String | Pathname])) -> void
|
374
413
|
def do_not_eager_load(*paths)
|
375
414
|
mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
|
376
415
|
end
|
@@ -378,8 +417,7 @@ module Zeitwerk
|
|
378
417
|
# Says if the given constant path would be unloaded on reload. This
|
379
418
|
# predicate returns `false` if reloading is disabled.
|
380
419
|
#
|
381
|
-
# @
|
382
|
-
# @return [Boolean]
|
420
|
+
# @sig (String) -> bool
|
383
421
|
def unloadable_cpath?(cpath)
|
384
422
|
to_unload.key?(cpath)
|
385
423
|
end
|
@@ -387,21 +425,20 @@ module Zeitwerk
|
|
387
425
|
# Returns an array with the constant paths that would be unloaded on reload.
|
388
426
|
# This predicate returns an empty array if reloading is disabled.
|
389
427
|
#
|
390
|
-
# @
|
428
|
+
# @sig () -> Array[String]
|
391
429
|
def unloadable_cpaths
|
392
430
|
to_unload.keys.freeze
|
393
431
|
end
|
394
432
|
|
395
433
|
# Logs to `$stdout`, handy shortcut for debugging.
|
396
434
|
#
|
397
|
-
# @
|
435
|
+
# @sig () -> void
|
398
436
|
def log!
|
399
437
|
@logger = ->(msg) { puts msg }
|
400
438
|
end
|
401
439
|
|
402
440
|
# @private
|
403
|
-
# @
|
404
|
-
# @return [Boolean]
|
441
|
+
# @sig (String) -> bool
|
405
442
|
def manages?(dir)
|
406
443
|
dir = dir + "/"
|
407
444
|
ignored_paths.each do |ignored_path|
|
@@ -418,11 +455,11 @@ module Zeitwerk
|
|
418
455
|
# --- Class methods ---------------------------------------------------------------------------
|
419
456
|
|
420
457
|
class << self
|
421
|
-
# @
|
458
|
+
# @sig #call | #debug | nil
|
422
459
|
attr_accessor :default_logger
|
423
460
|
|
424
461
|
# @private
|
425
|
-
# @
|
462
|
+
# @sig Mutex
|
426
463
|
attr_accessor :mutex
|
427
464
|
|
428
465
|
# This is a shortcut for
|
@@ -430,13 +467,13 @@ module Zeitwerk
|
|
430
467
|
# require "zeitwerk"
|
431
468
|
# loader = Zeitwerk::Loader.new
|
432
469
|
# loader.tag = File.basename(__FILE__, ".rb")
|
433
|
-
# loader.inflector = Zeitwerk::GemInflector.new
|
470
|
+
# loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
434
471
|
# loader.push_dir(__dir__)
|
435
472
|
#
|
436
473
|
# except that this method returns the same object in subsequent calls from
|
437
474
|
# the same file, in the unlikely case the gem wants to be able to reload.
|
438
475
|
#
|
439
|
-
# @
|
476
|
+
# @sig () -> Zeitwerk::Loader
|
440
477
|
def for_gem
|
441
478
|
called_from = caller_locations(1, 1).first.path
|
442
479
|
Registry.loader_for_gem(called_from)
|
@@ -444,7 +481,7 @@ module Zeitwerk
|
|
444
481
|
|
445
482
|
# Broadcasts `eager_load` to all loaders.
|
446
483
|
#
|
447
|
-
# @
|
484
|
+
# @sig () -> void
|
448
485
|
def eager_load_all
|
449
486
|
Registry.loaders.each(&:eager_load)
|
450
487
|
end
|
@@ -452,7 +489,7 @@ module Zeitwerk
|
|
452
489
|
# Returns an array with the absolute paths of the root directories of all
|
453
490
|
# registered loaders. This is a read-only collection.
|
454
491
|
#
|
455
|
-
# @
|
492
|
+
# @sig () -> Array[String]
|
456
493
|
def all_dirs
|
457
494
|
Registry.loaders.flat_map(&:dirs).freeze
|
458
495
|
end
|
@@ -462,21 +499,19 @@ module Zeitwerk
|
|
462
499
|
|
463
500
|
private # -------------------------------------------------------------------------------------
|
464
501
|
|
465
|
-
# @
|
502
|
+
# @sig () -> Array[String]
|
466
503
|
def actual_root_dirs
|
467
|
-
root_dirs.
|
504
|
+
root_dirs.reject do |root_dir, _namespace|
|
468
505
|
!dir?(root_dir) || ignored_paths.member?(root_dir)
|
469
506
|
end
|
470
507
|
end
|
471
508
|
|
472
|
-
# @
|
473
|
-
# @param parent [Module]
|
474
|
-
# @return [void]
|
509
|
+
# @sig (String, Module) -> void
|
475
510
|
def set_autoloads_in_dir(dir, parent)
|
476
511
|
ls(dir) do |basename, abspath|
|
477
512
|
begin
|
478
513
|
if ruby?(basename)
|
479
|
-
basename
|
514
|
+
basename[-3..-1] = ''
|
480
515
|
cname = inflector.camelize(basename, abspath).to_sym
|
481
516
|
autoload_file(parent, cname, abspath)
|
482
517
|
elsif dir?(abspath)
|
@@ -488,13 +523,17 @@ module Zeitwerk
|
|
488
523
|
# it counts only as root. The guard checks that.
|
489
524
|
unless root_dirs.key?(abspath)
|
490
525
|
cname = inflector.camelize(basename, abspath).to_sym
|
491
|
-
|
526
|
+
if collapse_dirs.member?(abspath)
|
527
|
+
set_autoloads_in_dir(abspath, parent)
|
528
|
+
else
|
529
|
+
autoload_subdir(parent, cname, abspath)
|
530
|
+
end
|
492
531
|
end
|
493
532
|
end
|
494
533
|
rescue ::NameError => error
|
495
534
|
path_type = ruby?(abspath) ? "file" : "directory"
|
496
535
|
|
497
|
-
raise NameError,
|
536
|
+
raise NameError.new(<<~MESSAGE, error.name)
|
498
537
|
#{error.message} inferred by #{inflector.class} from #{path_type}
|
499
538
|
|
500
539
|
#{abspath}
|
@@ -510,10 +549,7 @@ module Zeitwerk
|
|
510
549
|
end
|
511
550
|
end
|
512
551
|
|
513
|
-
# @
|
514
|
-
# @param cname [Symbol]
|
515
|
-
# @param subdir [String]
|
516
|
-
# @return [void]
|
552
|
+
# @sig (Module, Symbol, String) -> void
|
517
553
|
def autoload_subdir(parent, cname, subdir)
|
518
554
|
if autoload_path = autoload_for?(parent, cname)
|
519
555
|
cpath = cpath(parent, cname)
|
@@ -533,10 +569,7 @@ module Zeitwerk
|
|
533
569
|
end
|
534
570
|
end
|
535
571
|
|
536
|
-
# @
|
537
|
-
# @param cname [Symbol]
|
538
|
-
# @param file [String]
|
539
|
-
# @return [void]
|
572
|
+
# @sig (Module, Symbol, String) -> void
|
540
573
|
def autoload_file(parent, cname, file)
|
541
574
|
if autoload_path = autoload_for?(parent, cname)
|
542
575
|
# First autoload for a Ruby file wins, just ignore subsequent ones.
|
@@ -557,11 +590,10 @@ module Zeitwerk
|
|
557
590
|
end
|
558
591
|
end
|
559
592
|
|
560
|
-
#
|
561
|
-
#
|
562
|
-
#
|
563
|
-
# @
|
564
|
-
# @return [void]
|
593
|
+
# `dir` is the directory that would have autovivified a namespace. `file` is
|
594
|
+
# the file where we've found the namespace is explicitly defined.
|
595
|
+
#
|
596
|
+
# @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
|
565
597
|
def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
|
566
598
|
autoloads.delete(dir)
|
567
599
|
Registry.unregister_autoload(dir)
|
@@ -570,15 +602,15 @@ module Zeitwerk
|
|
570
602
|
register_explicit_namespace(cpath(parent, cname))
|
571
603
|
end
|
572
604
|
|
573
|
-
# @
|
574
|
-
# @param cname [Symbol]
|
575
|
-
# @param abspath [String]
|
576
|
-
# @return [void]
|
605
|
+
# @sig (Module, Symbol, String) -> void
|
577
606
|
def set_autoload(parent, cname, abspath)
|
578
607
|
# $LOADED_FEATURES stores real paths since Ruby 2.4.4. We set and save the
|
579
608
|
# real path to be able to delete it from $LOADED_FEATURES on unload, and to
|
580
609
|
# be able to do a lookup later in Kernel#require for manual require calls.
|
581
|
-
|
610
|
+
#
|
611
|
+
# We freeze realpath because that saves allocations in Module#autoload.
|
612
|
+
# See #125.
|
613
|
+
realpath = File.realpath(abspath).freeze
|
582
614
|
parent.autoload(cname, realpath)
|
583
615
|
if logger
|
584
616
|
if ruby?(realpath)
|
@@ -597,9 +629,7 @@ module Zeitwerk
|
|
597
629
|
end
|
598
630
|
end
|
599
631
|
|
600
|
-
# @
|
601
|
-
# @param cname [Symbol]
|
602
|
-
# @return [String, nil]
|
632
|
+
# @sig (Module, Symbol) -> String?
|
603
633
|
def autoload_for?(parent, cname)
|
604
634
|
strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
|
605
635
|
end
|
@@ -620,9 +650,7 @@ module Zeitwerk
|
|
620
650
|
#
|
621
651
|
# We need a way to strictly check in parent ignoring ancestors.
|
622
652
|
#
|
623
|
-
# @
|
624
|
-
# @param cname [Symbol]
|
625
|
-
# @return [String, nil]
|
653
|
+
# @sig (Module, Symbol) -> String?
|
626
654
|
if method(:autoload?).arity == 1
|
627
655
|
def strict_autoload_path(parent, cname)
|
628
656
|
parent.autoload?(cname) if cdef?(parent, cname)
|
@@ -636,15 +664,14 @@ module Zeitwerk
|
|
636
664
|
# This method is called this way because I prefer `preload` to be the method
|
637
665
|
# name to configure preloads in the public interface.
|
638
666
|
#
|
639
|
-
# @
|
667
|
+
# @sig () -> void
|
640
668
|
def do_preload
|
641
669
|
preloads.each do |abspath|
|
642
670
|
do_preload_abspath(abspath)
|
643
671
|
end
|
644
672
|
end
|
645
673
|
|
646
|
-
# @
|
647
|
-
# @return [void]
|
674
|
+
# @sig (String) -> void
|
648
675
|
def do_preload_abspath(abspath)
|
649
676
|
if ruby?(abspath)
|
650
677
|
do_preload_file(abspath)
|
@@ -653,85 +680,87 @@ module Zeitwerk
|
|
653
680
|
end
|
654
681
|
end
|
655
682
|
|
656
|
-
# @
|
657
|
-
# @return [void]
|
683
|
+
# @sig (String) -> void
|
658
684
|
def do_preload_dir(dir)
|
659
685
|
ls(dir) do |_basename, abspath|
|
660
686
|
do_preload_abspath(abspath)
|
661
687
|
end
|
662
688
|
end
|
663
689
|
|
664
|
-
# @
|
665
|
-
# @return [Boolean]
|
690
|
+
# @sig (String) -> bool
|
666
691
|
def do_preload_file(file)
|
667
692
|
log("preloading #{file}") if logger
|
668
693
|
require file
|
669
694
|
end
|
670
695
|
|
671
|
-
# @
|
672
|
-
# @param cname [Symbol]
|
673
|
-
# @return [String]
|
696
|
+
# @sig (Module, Symbol) -> String
|
674
697
|
def cpath(parent, cname)
|
675
698
|
parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
|
676
699
|
end
|
677
700
|
|
678
|
-
# @
|
679
|
-
# @yieldparam path [String, String]
|
680
|
-
# @return [void]
|
701
|
+
# @sig (String) { (String, String) -> void } -> void
|
681
702
|
def ls(dir)
|
682
703
|
Dir.foreach(dir) do |basename|
|
683
704
|
next if basename.start_with?(".")
|
705
|
+
|
684
706
|
abspath = File.join(dir, basename)
|
685
|
-
|
707
|
+
next if ignored_paths.member?(abspath)
|
708
|
+
|
709
|
+
# We freeze abspath because that saves allocations when passed later to
|
710
|
+
# File methods. See #125.
|
711
|
+
yield basename, abspath.freeze
|
686
712
|
end
|
687
713
|
end
|
688
714
|
|
689
|
-
# @
|
690
|
-
# @return [Boolean]
|
715
|
+
# @sig (String) -> bool
|
691
716
|
def ruby?(path)
|
692
717
|
path.end_with?(".rb")
|
693
718
|
end
|
694
719
|
|
695
|
-
# @
|
696
|
-
# @return [Boolean]
|
720
|
+
# @sig (String) -> bool
|
697
721
|
def dir?(path)
|
698
722
|
File.directory?(path)
|
699
723
|
end
|
700
724
|
|
701
|
-
# @
|
702
|
-
# @return [<String>]
|
725
|
+
# @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
|
703
726
|
def expand_paths(paths)
|
704
727
|
paths.flatten.map! { |path| File.expand_path(path) }
|
705
728
|
end
|
706
729
|
|
707
|
-
# @
|
708
|
-
# @return [<String>]
|
730
|
+
# @sig (Array[String]) -> Array[String]
|
709
731
|
def expand_glob_patterns(glob_patterns)
|
710
732
|
# Note that Dir.glob works with regular file names just fine. That is,
|
711
733
|
# glob patterns technically need no wildcards.
|
712
734
|
glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
|
713
735
|
end
|
714
736
|
|
715
|
-
# @
|
737
|
+
# @sig () -> void
|
716
738
|
def recompute_ignored_paths
|
717
739
|
ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
|
718
740
|
end
|
719
741
|
|
720
|
-
# @
|
721
|
-
|
742
|
+
# @sig () -> void
|
743
|
+
def recompute_collapse_dirs
|
744
|
+
collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
|
745
|
+
end
|
746
|
+
|
747
|
+
# @sig (String) -> void
|
722
748
|
def log(message)
|
723
749
|
method_name = logger.respond_to?(:debug) ? :debug : :call
|
724
750
|
logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
|
725
751
|
end
|
726
752
|
|
753
|
+
# @sig (Module, Symbol) -> bool
|
727
754
|
def cdef?(parent, cname)
|
728
755
|
parent.const_defined?(cname, false)
|
729
756
|
end
|
730
757
|
|
758
|
+
# @sig (String) -> void
|
731
759
|
def register_explicit_namespace(cpath)
|
732
760
|
ExplicitNamespace.register(cpath, self)
|
733
761
|
end
|
734
762
|
|
763
|
+
# @sig (String) -> void
|
735
764
|
def raise_if_conflicting_directory(dir)
|
736
765
|
self.class.mutex.synchronize do
|
737
766
|
Registry.loaders.each do |loader|
|
@@ -746,19 +775,15 @@ module Zeitwerk
|
|
746
775
|
end
|
747
776
|
end
|
748
777
|
|
749
|
-
# @
|
750
|
-
# @param cname [Symbol]
|
751
|
-
# @return [void]
|
778
|
+
# @sig (Module, Symbol) -> void
|
752
779
|
def unload_autoload(parent, cname)
|
753
|
-
parent.
|
780
|
+
parent.__send__(:remove_const, cname)
|
754
781
|
log("autoload for #{cpath(parent, cname)} removed") if logger
|
755
782
|
end
|
756
783
|
|
757
|
-
# @
|
758
|
-
# @param cname [Symbol]
|
759
|
-
# @return [void]
|
784
|
+
# @sig (Module, Symbol) -> void
|
760
785
|
def unload_cref(parent, cname)
|
761
|
-
parent.
|
786
|
+
parent.__send__(:remove_const, cname)
|
762
787
|
log("#{cpath(parent, cname)} unloaded") if logger
|
763
788
|
end
|
764
789
|
end
|