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