zeitwerk 2.6.0 → 2.6.6
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 +192 -10
- data/lib/zeitwerk/error.rb +6 -0
- data/lib/zeitwerk/explicit_namespace.rb +8 -11
- data/lib/zeitwerk/gem_loader.rb +1 -1
- data/lib/zeitwerk/internal.rb +12 -0
- data/lib/zeitwerk/kernel.rb +3 -0
- data/lib/zeitwerk/loader/callbacks.rb +4 -4
- data/lib/zeitwerk/loader/config.rb +84 -49
- data/lib/zeitwerk/loader/eager_load.rb +228 -0
- data/lib/zeitwerk/loader/helpers.rb +24 -16
- data/lib/zeitwerk/loader.rb +78 -87
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +1 -0
- metadata +4 -2
data/lib/zeitwerk/loader.rb
CHANGED
@@ -7,11 +7,13 @@ module Zeitwerk
|
|
7
7
|
require_relative "loader/helpers"
|
8
8
|
require_relative "loader/callbacks"
|
9
9
|
require_relative "loader/config"
|
10
|
+
require_relative "loader/eager_load"
|
10
11
|
|
11
12
|
include RealModName
|
12
13
|
include Callbacks
|
13
14
|
include Helpers
|
14
15
|
include Config
|
16
|
+
include EagerLoad
|
15
17
|
|
16
18
|
MUTEX = Mutex.new
|
17
19
|
private_constant :MUTEX
|
@@ -54,7 +56,7 @@ module Zeitwerk
|
|
54
56
|
# @sig Hash[String, [String, [Module, Symbol]]]
|
55
57
|
attr_reader :to_unload
|
56
58
|
|
57
|
-
# Maps constant paths
|
59
|
+
# Maps namespace constant paths to their respective directories.
|
58
60
|
#
|
59
61
|
# For example, given this mapping:
|
60
62
|
#
|
@@ -64,13 +66,24 @@ module Zeitwerk
|
|
64
66
|
# ...
|
65
67
|
# ]
|
66
68
|
#
|
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.
|
69
|
+
# when `Admin` gets defined we know that it plays the role of a namespace
|
70
|
+
# and that its children are spread over those directories. We'll visit them
|
71
|
+
# to set up the corresponding autoloads.
|
70
72
|
#
|
71
73
|
# @private
|
72
74
|
# @sig Hash[String, Array[String]]
|
73
|
-
attr_reader :
|
75
|
+
attr_reader :namespace_dirs
|
76
|
+
|
77
|
+
# A shadowed file is a file managed by this loader that is ignored when
|
78
|
+
# setting autoloads because its matching constant is already taken.
|
79
|
+
#
|
80
|
+
# This private set is populated as we descend. For example, if the loader
|
81
|
+
# has only scanned the top-level, `shadowed_files` does not have shadowed
|
82
|
+
# files that may exist deep in the project tree yet.
|
83
|
+
#
|
84
|
+
# @private
|
85
|
+
# @sig Set[String]
|
86
|
+
attr_reader :shadowed_files
|
74
87
|
|
75
88
|
# @private
|
76
89
|
# @sig Mutex
|
@@ -86,7 +99,8 @@ module Zeitwerk
|
|
86
99
|
@autoloads = {}
|
87
100
|
@autoloaded_dirs = []
|
88
101
|
@to_unload = {}
|
89
|
-
@
|
102
|
+
@namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] }
|
103
|
+
@shadowed_files = Set.new
|
90
104
|
@mutex = Mutex.new
|
91
105
|
@mutex2 = Mutex.new
|
92
106
|
@setup = false
|
@@ -95,15 +109,15 @@ module Zeitwerk
|
|
95
109
|
Registry.register_loader(self)
|
96
110
|
end
|
97
111
|
|
98
|
-
# Sets autoloads in the root
|
112
|
+
# Sets autoloads in the root namespaces.
|
99
113
|
#
|
100
114
|
# @sig () -> void
|
101
115
|
def setup
|
102
116
|
mutex.synchronize do
|
103
117
|
break if @setup
|
104
118
|
|
105
|
-
|
106
|
-
set_autoloads_in_dir(root_dir,
|
119
|
+
actual_roots.each do |root_dir, root_namespace|
|
120
|
+
set_autoloads_in_dir(root_dir, root_namespace)
|
107
121
|
end
|
108
122
|
|
109
123
|
on_setup_callbacks.each(&:call)
|
@@ -126,6 +140,8 @@ module Zeitwerk
|
|
126
140
|
# @sig () -> void
|
127
141
|
def unload
|
128
142
|
mutex.synchronize do
|
143
|
+
raise SetupRequired unless @setup
|
144
|
+
|
129
145
|
# We are going to keep track of the files that were required by our
|
130
146
|
# autoloads to later remove them from $LOADED_FEATURES, thus making them
|
131
147
|
# loadable by Kernel#require again.
|
@@ -181,10 +197,11 @@ module Zeitwerk
|
|
181
197
|
autoloads.clear
|
182
198
|
autoloaded_dirs.clear
|
183
199
|
to_unload.clear
|
184
|
-
|
200
|
+
namespace_dirs.clear
|
201
|
+
shadowed_files.clear
|
185
202
|
|
186
203
|
Registry.on_unload(self)
|
187
|
-
ExplicitNamespace.
|
204
|
+
ExplicitNamespace.__unregister_loader(self)
|
188
205
|
|
189
206
|
@setup = false
|
190
207
|
@eager_loaded = false
|
@@ -201,6 +218,7 @@ module Zeitwerk
|
|
201
218
|
# @sig () -> void
|
202
219
|
def reload
|
203
220
|
raise ReloadingDisabledError unless reloading_enabled?
|
221
|
+
raise SetupRequired unless @setup
|
204
222
|
|
205
223
|
unload
|
206
224
|
recompute_ignored_paths
|
@@ -208,58 +226,6 @@ module Zeitwerk
|
|
208
226
|
setup
|
209
227
|
end
|
210
228
|
|
211
|
-
# Eager loads all files in the root directories, recursively. Files do not
|
212
|
-
# need to be in `$LOAD_PATH`, absolute file names are used. Ignored files
|
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`.
|
216
|
-
#
|
217
|
-
# @sig (true | false) -> void
|
218
|
-
def eager_load(force: false)
|
219
|
-
mutex.synchronize do
|
220
|
-
break if @eager_loaded
|
221
|
-
|
222
|
-
log("eager load start") if logger
|
223
|
-
|
224
|
-
honour_exclusions = !force
|
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
|
230
|
-
|
231
|
-
while to_eager_load = queue.shift
|
232
|
-
namespace, dir = to_eager_load
|
233
|
-
|
234
|
-
ls(dir) do |basename, abspath|
|
235
|
-
next if honour_exclusions && excluded_from_eager_load?(abspath)
|
236
|
-
|
237
|
-
if ruby?(abspath)
|
238
|
-
if cref = autoloads[abspath]
|
239
|
-
cget(*cref)
|
240
|
-
end
|
241
|
-
elsif !root_dirs.key?(abspath)
|
242
|
-
if collapse?(abspath)
|
243
|
-
queue << [namespace, abspath]
|
244
|
-
else
|
245
|
-
cname = inflector.camelize(basename, abspath)
|
246
|
-
queue << [cget(namespace, cname), abspath]
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
autoloaded_dirs.each do |autoloaded_dir|
|
253
|
-
Registry.unregister_autoload(autoloaded_dir)
|
254
|
-
end
|
255
|
-
autoloaded_dirs.clear
|
256
|
-
|
257
|
-
@eager_loaded = true
|
258
|
-
|
259
|
-
log("eager load end") if logger
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
229
|
# Says if the given constant path would be unloaded on reload. This
|
264
230
|
# predicate returns `false` if reloading is disabled.
|
265
231
|
#
|
@@ -282,7 +248,16 @@ module Zeitwerk
|
|
282
248
|
# @sig () -> void
|
283
249
|
def unregister
|
284
250
|
Registry.unregister_loader(self)
|
285
|
-
ExplicitNamespace.
|
251
|
+
ExplicitNamespace.__unregister_loader(self)
|
252
|
+
end
|
253
|
+
|
254
|
+
# The return value of this predicate is only meaningful if the loader has
|
255
|
+
# scanned the file. This is the case in the spots where we use it.
|
256
|
+
#
|
257
|
+
# @private
|
258
|
+
# @sig (String) -> Boolean
|
259
|
+
def shadowed_file?(file)
|
260
|
+
shadowed_files.member?(file)
|
286
261
|
end
|
287
262
|
|
288
263
|
# --- Class methods ---------------------------------------------------------------------------
|
@@ -311,11 +286,32 @@ module Zeitwerk
|
|
311
286
|
Registry.loader_for_gem(called_from, warn_on_extra_files: warn_on_extra_files)
|
312
287
|
end
|
313
288
|
|
314
|
-
# Broadcasts `eager_load` to all loaders.
|
289
|
+
# Broadcasts `eager_load` to all loaders. Those that have not been setup
|
290
|
+
# are skipped.
|
315
291
|
#
|
316
292
|
# @sig () -> void
|
317
293
|
def eager_load_all
|
318
|
-
Registry.loaders.each
|
294
|
+
Registry.loaders.each do |loader|
|
295
|
+
begin
|
296
|
+
loader.eager_load
|
297
|
+
rescue SetupRequired
|
298
|
+
# This is fine, we eager load what can be eager loaded.
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Broadcasts `eager_load_namespace` to all loaders. Those that have not
|
304
|
+
# been setup are skipped.
|
305
|
+
#
|
306
|
+
# @sig (Module) -> void
|
307
|
+
def eager_load_namespace(mod)
|
308
|
+
Registry.loaders.each do |loader|
|
309
|
+
begin
|
310
|
+
loader.eager_load_namespace(mod)
|
311
|
+
rescue SetupRequired
|
312
|
+
# This is fine, we eager load what can be eager loaded.
|
313
|
+
end
|
314
|
+
end
|
319
315
|
end
|
320
316
|
|
321
317
|
# Returns an array with the absolute paths of the root directories of all
|
@@ -338,19 +334,11 @@ module Zeitwerk
|
|
338
334
|
cname = inflector.camelize(basename, abspath).to_sym
|
339
335
|
autoload_file(parent, cname, abspath)
|
340
336
|
else
|
341
|
-
|
342
|
-
|
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)
|
337
|
+
if collapse?(abspath)
|
338
|
+
set_autoloads_in_dir(abspath, parent)
|
339
|
+
else
|
348
340
|
cname = inflector.camelize(basename, abspath).to_sym
|
349
|
-
|
350
|
-
set_autoloads_in_dir(abspath, parent)
|
351
|
-
else
|
352
|
-
autoload_subdir(parent, cname, abspath)
|
353
|
-
end
|
341
|
+
autoload_subdir(parent, cname, abspath)
|
354
342
|
end
|
355
343
|
end
|
356
344
|
rescue ::NameError => error
|
@@ -380,10 +368,10 @@ module Zeitwerk
|
|
380
368
|
# We do not need to issue another autoload, the existing one is enough
|
381
369
|
# no matter if it is for a file or a directory. Just remember the
|
382
370
|
# subdirectory has to be visited if the namespace is used.
|
383
|
-
|
371
|
+
namespace_dirs[cpath] << subdir
|
384
372
|
elsif !cdef?(parent, cname)
|
385
373
|
# First time we find this namespace, set an autoload for it.
|
386
|
-
|
374
|
+
namespace_dirs[cpath(parent, cname)] << subdir
|
387
375
|
set_autoload(parent, cname, subdir)
|
388
376
|
else
|
389
377
|
# For whatever reason the constant that corresponds to this namespace has
|
@@ -398,6 +386,7 @@ module Zeitwerk
|
|
398
386
|
if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
|
399
387
|
# First autoload for a Ruby file wins, just ignore subsequent ones.
|
400
388
|
if ruby?(autoload_path)
|
389
|
+
shadowed_files << file
|
401
390
|
log("file #{file} is ignored because #{autoload_path} has precedence") if logger
|
402
391
|
else
|
403
392
|
promote_namespace_from_implicit_to_explicit(
|
@@ -408,6 +397,7 @@ module Zeitwerk
|
|
408
397
|
)
|
409
398
|
end
|
410
399
|
elsif cdef?(parent, cname)
|
400
|
+
shadowed_files << file
|
411
401
|
log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
|
412
402
|
else
|
413
403
|
set_autoload(parent, cname, file)
|
@@ -460,25 +450,26 @@ module Zeitwerk
|
|
460
450
|
|
461
451
|
# @sig (String) -> void
|
462
452
|
def register_explicit_namespace(cpath)
|
463
|
-
ExplicitNamespace.
|
453
|
+
ExplicitNamespace.__register(cpath, self)
|
464
454
|
end
|
465
455
|
|
466
456
|
# @sig (String) -> void
|
467
457
|
def raise_if_conflicting_directory(dir)
|
468
458
|
MUTEX.synchronize do
|
459
|
+
dir_slash = dir + "/"
|
460
|
+
|
469
461
|
Registry.loaders.each do |loader|
|
470
462
|
next if loader == self
|
471
|
-
next if loader.
|
463
|
+
next if loader.__ignores?(dir)
|
472
464
|
|
473
|
-
|
474
|
-
loader.root_dirs.each do |root_dir, _namespace|
|
465
|
+
loader.__roots.each_key do |root_dir|
|
475
466
|
next if ignores?(root_dir)
|
476
467
|
|
477
|
-
|
478
|
-
if
|
468
|
+
root_dir_slash = root_dir + "/"
|
469
|
+
if dir_slash.start_with?(root_dir_slash) || root_dir_slash.start_with?(dir_slash)
|
479
470
|
require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
|
480
471
|
raise Error,
|
481
|
-
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir
|
472
|
+
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
|
482
473
|
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
483
474
|
EOS
|
484
475
|
end
|
data/lib/zeitwerk/version.rb
CHANGED
data/lib/zeitwerk.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zeitwerk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.6.
|
4
|
+
version: 2.6.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Noria
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -28,10 +28,12 @@ files:
|
|
28
28
|
- lib/zeitwerk/gem_inflector.rb
|
29
29
|
- lib/zeitwerk/gem_loader.rb
|
30
30
|
- lib/zeitwerk/inflector.rb
|
31
|
+
- lib/zeitwerk/internal.rb
|
31
32
|
- lib/zeitwerk/kernel.rb
|
32
33
|
- lib/zeitwerk/loader.rb
|
33
34
|
- lib/zeitwerk/loader/callbacks.rb
|
34
35
|
- lib/zeitwerk/loader/config.rb
|
36
|
+
- lib/zeitwerk/loader/eager_load.rb
|
35
37
|
- lib/zeitwerk/loader/helpers.rb
|
36
38
|
- lib/zeitwerk/real_mod_name.rb
|
37
39
|
- lib/zeitwerk/registry.rb
|