zeitwerk 2.1.5 → 2.1.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +36 -28
- data/lib/zeitwerk.rb +1 -0
- data/lib/zeitwerk/error.rb +3 -0
- data/lib/zeitwerk/explicit_namespace.rb +19 -11
- data/lib/zeitwerk/gem_inflector.rb +1 -1
- data/lib/zeitwerk/inflector.rb +1 -1
- data/lib/zeitwerk/loader.rb +105 -57
- data/lib/zeitwerk/loader/callbacks.rb +3 -1
- data/lib/zeitwerk/real_mod_name.rb +15 -0
- data/lib/zeitwerk/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3dbf7502d4126651ab645926f072af5f5164fcb495c06ae32e9748aa2807670e
|
4
|
+
data.tar.gz: e124bca82ed054d1ca304e95605ad011a2c6411792653c3c86b785a3cc956e06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7efe14aeb7e16563d6a1b69b2aac82d75dd1c4e79dae2fb4654e7c93742e15018f16fad4591f8763bf03b2f9d9d71a6e95209907a0765952dafa8dedc957d2ef
|
7
|
+
data.tar.gz: 8dea5ad3b1979cacb619b13385b337e81cc08efc39e0411f890229f0a9d17035fdfb17cf2ac84f6dcd3eeec4b4daa732e796e51fc1fdf707c210d9943481edcf
|
data/README.md
CHANGED
@@ -17,7 +17,6 @@
|
|
17
17
|
- [Setup](#setup)
|
18
18
|
- [Reloading](#reloading)
|
19
19
|
- [Eager loading](#eager-loading)
|
20
|
-
- [Preloading](#preloading)
|
21
20
|
- [Inflection](#inflection)
|
22
21
|
- [Zeitwerk::Inflector](#zeitwerkinflector)
|
23
22
|
- [Zeitwerk::GemInflector](#zeitwerkgeminflector)
|
@@ -29,6 +28,7 @@
|
|
29
28
|
- [Use case: The adapter pattern](#use-case-the-adapter-pattern)
|
30
29
|
- [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
|
31
30
|
- [Edge cases](#edge-cases)
|
31
|
+
- [Rules of thumb](#rules-of-thumb)
|
32
32
|
- [Pronunciation](#pronunciation)
|
33
33
|
- [Supported Ruby versions](#supported-ruby-versions)
|
34
34
|
- [Motivation](#motivation)
|
@@ -42,15 +42,13 @@
|
|
42
42
|
|
43
43
|
Zeitwerk is an efficient and thread-safe code loader for Ruby.
|
44
44
|
|
45
|
-
Given a conventional file structure, Zeitwerk
|
45
|
+
Given a [conventional file structure](#file-structure), Zeitwerk is able to load your project's classes and modules on demand (autoloading), or upfront (eager loading). You don't need to write `require` calls for your own files, rather, you can streamline your programming knowing that your classes and modules are available everywhere. This feature is efficient, thread-safe, and matches Ruby's semantics for constants.
|
46
46
|
|
47
|
-
Zeitwerk
|
47
|
+
Zeitwerk is also able to reload code, which may be handy while developing web applications. Coordination is needed to reload in a thread-safe manner. The documentation below explains how to do this.
|
48
48
|
|
49
|
-
The
|
49
|
+
The gem is designed so that any project, gem dependency, application, etc. can have their own independent loader, coexisting in the same process, managing their own project trees, and independent of each other. Each loader has its own configuration, inflector, and optional logger.
|
50
50
|
|
51
|
-
Zeitwerk
|
52
|
-
|
53
|
-
Finally, in some production setups it may be optimal to eager load all code upfront. Zeitwerk is able to do that too.
|
51
|
+
Internally, Zeitwerk issues `require` calls exclusively using absolute file names, so there are no costly file system lookups in `$LOAD_PATH`. Technically, the directories managed by Zeitwerk do not even need to be in `$LOAD_PATH`. Furthermore, Zeitwerk does only one single scan of the project tree, and it descends into subdirectories lazily, only if their namespaces are used.
|
54
52
|
|
55
53
|
<a id="markdown-synopsis" name="synopsis"></a>
|
56
54
|
## Synopsis
|
@@ -63,11 +61,12 @@ Main interface for gems:
|
|
63
61
|
require "zeitwerk"
|
64
62
|
loader = Zeitwerk::Loader.for_gem
|
65
63
|
loader.setup # ready!
|
66
|
-
# loader.eager_load, optionally
|
67
64
|
|
68
65
|
module MyGem
|
69
66
|
# ...
|
70
67
|
end
|
68
|
+
|
69
|
+
loader.eager_load # optionally
|
71
70
|
```
|
72
71
|
|
73
72
|
Main generic interface:
|
@@ -205,7 +204,7 @@ Zeitwerk works internally only with absolute paths to avoid costly file searches
|
|
205
204
|
<a id="markdown-reloading" name="reloading"></a>
|
206
205
|
### Reloading
|
207
206
|
|
208
|
-
|
207
|
+
Zeitwerk is able to reload code, but you need to enable this feature:
|
209
208
|
|
210
209
|
```ruby
|
211
210
|
loader = Zeitwerk::Loader.new
|
@@ -222,7 +221,7 @@ Enabling reloading after setup raises `Zeitwerk::Error`. Similarly, calling `rel
|
|
222
221
|
|
223
222
|
Generally speaking, reloading is useful while developing running services like web applications. Gems that implement regular libraries, so to speak, or services running in testing or production environments, won't normally have a use case for reloading. If reloading is not enabled, Zeitwerk is able to use less memory.
|
224
223
|
|
225
|
-
Reloading removes the currently loaded classes and modules
|
224
|
+
Reloading removes the currently loaded classes and modules and resets the loader so that it will pick whatever is in the file system now.
|
226
225
|
|
227
226
|
It is important to highlight that this is an instance method. Don't worry about project dependencies managed by Zeitwerk, their loaders are independent.
|
228
227
|
|
@@ -260,22 +259,6 @@ This may be handy in top-level services, like web applications.
|
|
260
259
|
|
261
260
|
Note that thanks to idempotence `Zeitwerk::Loader.eager_load_all` won't eager load twice if any of the instances already eager loaded.
|
262
261
|
|
263
|
-
<a id="markdown-preloading" name="preloading"></a>
|
264
|
-
### Preloading
|
265
|
-
|
266
|
-
Zeitwerk instances are able to preload files and directories.
|
267
|
-
|
268
|
-
```ruby
|
269
|
-
loader.preload("app/models/videogame.rb")
|
270
|
-
loader.preload("app/models/book.rb")
|
271
|
-
```
|
272
|
-
|
273
|
-
The call can happen before `setup` (preloads during setup), or after `setup` (preloads on the spot). Each reload preloads too.
|
274
|
-
|
275
|
-
This is a feature specifically thought for STIs in Rails, preloading the leafs of a STI tree ensures all classes are known when doing a query.
|
276
|
-
|
277
|
-
The example above depicts several calls are supported, but `preload` accepts multiple arguments and arrays of strings as well.
|
278
|
-
|
279
262
|
<a id="markdown-inflection" name="inflection"></a>
|
280
263
|
### Inflection
|
281
264
|
|
@@ -338,7 +321,15 @@ This needs to be done before calling `setup`.
|
|
338
321
|
<a id="markdown-logging" name="logging"></a>
|
339
322
|
### Logging
|
340
323
|
|
341
|
-
Zeitwerk is silent by default, but you can
|
324
|
+
Zeitwerk is silent by default, but you can ask loaders to trace their activity. Logging is meant just for troubleshooting, shouldn't normally be enabled.
|
325
|
+
|
326
|
+
The `log!` method is a quick shortcut to let the loader log to `$stdout`:
|
327
|
+
|
328
|
+
```
|
329
|
+
loader.log!
|
330
|
+
```
|
331
|
+
|
332
|
+
If you want more control, a logger can be configured as a callable
|
342
333
|
|
343
334
|
```ruby
|
344
335
|
loader.logger = method(:puts)
|
@@ -392,7 +383,7 @@ Zeitwerk ignores automatically any file or directory whose name starts with a do
|
|
392
383
|
|
393
384
|
However, sometimes it might still be convenient to tell Zeitwerk to completely ignore some particular Ruby file or directory. That is possible with `ignore`, which accepts an arbitrary number of strings or `Pathname` objects, and also an array of them.
|
394
385
|
|
395
|
-
You can ignore file names, directory names, and glob patterns. Glob patterns are expanded
|
386
|
+
You can ignore file names, directory names, and glob patterns. Glob patterns are expanded when they are added and again on each reload.
|
396
387
|
|
397
388
|
Let's see some use cases.
|
398
389
|
|
@@ -453,6 +444,8 @@ The chosen adapter, then, has to be loaded by hand somehow:
|
|
453
444
|
require "my_gem/db_adapters/#{config[:db_adapter]}"
|
454
445
|
```
|
455
446
|
|
447
|
+
Note that since the directory is ignored, the required adapter can instantiate another loader to manage its subtree, if desired. Such loader would coexist with the main one just fine.
|
448
|
+
|
456
449
|
<a id="markdown-use-case-test-files-mixed-with-implementation-files" name="use-case-test-files-mixed-with-implementation-files"></a>
|
457
450
|
#### Use case: Test files mixed with implementation files
|
458
451
|
|
@@ -493,6 +486,21 @@ Trip = Struct.new { ... } # NOT SUPPORTED
|
|
493
486
|
|
494
487
|
This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
|
495
488
|
|
489
|
+
<a id="markdown-rules-of-thumb" name="rules-of-thumb"></a>
|
490
|
+
### Rules of thumb
|
491
|
+
|
492
|
+
1. Different loaders should manage different directory trees. It is an error condition to configure overlapping root directories in different loaders.
|
493
|
+
|
494
|
+
2. Think the mere existence of a file is effectively like writing a `require` call for them, which is executed on demand (autoload) or upfront (eager load).
|
495
|
+
|
496
|
+
3. In that line, if two loaders manage files that translate to the same constant in the same namespace, the first one wins, the rest are ignored. Similar to what happens with `require` and `$LOAD_PATH`, only the first occurrence matters.
|
497
|
+
|
498
|
+
4. Projects that reopen a namespace defined by some dependency have to ensure said namespace is loaded before setup. That is, the project has to make sure it reopens, rather than define. This is often accomplished just loading the dependency.
|
499
|
+
|
500
|
+
5. Objects stored in reloadable constants should not be cached in places that are not reloaded. For example, non-reloadable classes should not subclass a reloadable class, or mixin a reloadable module. Otherwise, after reloading, those classes or module objects would become stale. Referring to constants in dynamic places like method calls or lambdas is fine.
|
501
|
+
|
502
|
+
6. In a given process, ideally, there should be at most one loader with reloading enabled. Technically, you can have more, but it may get tricky if one refers to constants managed by the other one. Do that only if you know what you are doing.
|
503
|
+
|
496
504
|
<a id="markdown-pronunciation" name="pronunciation"></a>
|
497
505
|
## Pronunciation
|
498
506
|
|
data/lib/zeitwerk.rb
CHANGED
data/lib/zeitwerk/error.rb
CHANGED
@@ -8,6 +8,8 @@ module Zeitwerk
|
|
8
8
|
# loading their constant before setup. This is documented.
|
9
9
|
module ExplicitNamespace # :nodoc: all
|
10
10
|
class << self
|
11
|
+
include RealModName
|
12
|
+
|
11
13
|
# Maps constant paths that correspond to explicit namespaces according to
|
12
14
|
# the file system, to the loader responsible for them.
|
13
15
|
#
|
@@ -52,21 +54,27 @@ module Zeitwerk
|
|
52
54
|
tracer.disable if cpaths.empty?
|
53
55
|
end
|
54
56
|
end
|
57
|
+
|
58
|
+
def tracepoint_class_callback(event)
|
59
|
+
# If the class is a singleton class, we won't do anything with it so we
|
60
|
+
# can bail out immediately. This is several orders of magnitude faster
|
61
|
+
# than accessing its name.
|
62
|
+
return if event.self.singleton_class?
|
63
|
+
|
64
|
+
# Note that it makes sense to compute the hash code unconditionally,
|
65
|
+
# because the trace point is disabled if cpaths is empty.
|
66
|
+
if loader = cpaths.delete(real_mod_name(event.self))
|
67
|
+
loader.on_namespace_loaded(event.self)
|
68
|
+
disable_tracer_if_unneeded
|
69
|
+
end
|
70
|
+
end
|
55
71
|
end
|
56
72
|
|
57
73
|
@cpaths = {}
|
58
74
|
@mutex = Mutex.new
|
59
|
-
@tracer = TracePoint.new(:class) do |event|
|
60
|
-
# If the class is a singleton class, we won't do anything with it so we can bail out immediately.
|
61
|
-
# This is several orders of magnitude faster than accessing `Module#name`.
|
62
|
-
next if event.self.singleton_class?
|
63
75
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
loader.on_namespace_loaded(event.self)
|
68
|
-
disable_tracer_if_unneeded
|
69
|
-
end
|
70
|
-
end
|
76
|
+
# We go through a method instead of defining a block mainly to have a better
|
77
|
+
# label when profiling.
|
78
|
+
@tracer = TracePoint.new(:class, &method(:tracepoint_class_callback))
|
71
79
|
end
|
72
80
|
end
|
data/lib/zeitwerk/inflector.rb
CHANGED
data/lib/zeitwerk/loader.rb
CHANGED
@@ -7,6 +7,7 @@ module Zeitwerk
|
|
7
7
|
class Loader
|
8
8
|
require_relative "loader/callbacks"
|
9
9
|
include Callbacks
|
10
|
+
include RealModName
|
10
11
|
|
11
12
|
# @return [String]
|
12
13
|
attr_reader :tag
|
@@ -43,7 +44,7 @@ module Zeitwerk
|
|
43
44
|
#
|
44
45
|
# @private
|
45
46
|
# @return [Set<String>]
|
46
|
-
attr_reader :
|
47
|
+
attr_reader :ignored_glob_patterns
|
47
48
|
|
48
49
|
# The actual collection of absolute file and directory names at the time the
|
49
50
|
# ignored glob patterns were expanded. Computed on setup, and recomputed on
|
@@ -132,7 +133,7 @@ module Zeitwerk
|
|
132
133
|
|
133
134
|
@root_dirs = {}
|
134
135
|
@preloads = []
|
135
|
-
@
|
136
|
+
@ignored_glob_patterns = Set.new
|
136
137
|
@ignored_paths = Set.new
|
137
138
|
@autoloads = {}
|
138
139
|
@autoloaded_dirs = []
|
@@ -224,16 +225,12 @@ module Zeitwerk
|
|
224
225
|
#
|
225
226
|
# @param paths [<String, Pathname, <String, Pathname>>]
|
226
227
|
# @return [void]
|
227
|
-
def ignore(*
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
def expand_ignored_glob_patterns
|
234
|
-
# Note that Dir.glob works with regular file names just fine. That is,
|
235
|
-
# glob patterns technically need no wildcards.
|
236
|
-
ignored_paths.replace(ignored.flat_map { |path| Dir.glob(path) })
|
228
|
+
def ignore(*glob_patterns)
|
229
|
+
glob_patterns = expand_paths(glob_patterns)
|
230
|
+
mutex.synchronize do
|
231
|
+
ignored_glob_patterns.merge(glob_patterns)
|
232
|
+
ignored_paths.merge(expand_glob_patterns(glob_patterns))
|
233
|
+
end
|
237
234
|
end
|
238
235
|
|
239
236
|
# Sets autoloads in the root namespace and preloads files, if any.
|
@@ -243,7 +240,6 @@ module Zeitwerk
|
|
243
240
|
mutex.synchronize do
|
244
241
|
break if @setup
|
245
242
|
|
246
|
-
expand_ignored_glob_patterns
|
247
243
|
actual_root_dirs.each { |root_dir| set_autoloads_in_dir(root_dir, Object) }
|
248
244
|
do_preload
|
249
245
|
|
@@ -325,6 +321,7 @@ module Zeitwerk
|
|
325
321
|
def reload
|
326
322
|
if reloading_enabled?
|
327
323
|
unload
|
324
|
+
recompute_ignored_paths
|
328
325
|
setup
|
329
326
|
else
|
330
327
|
raise ReloadingDisabledError, "can't reload, please call loader.enable_reloading before setup"
|
@@ -342,16 +339,20 @@ module Zeitwerk
|
|
342
339
|
break if @eager_loaded
|
343
340
|
|
344
341
|
queue = actual_root_dirs.reject { |dir| eager_load_exclusions.member?(dir) }
|
345
|
-
|
346
|
-
|
342
|
+
queue.map! { |dir| [Object, dir] }
|
343
|
+
while to_eager_load = queue.shift
|
344
|
+
namespace, dir = to_eager_load
|
347
345
|
|
348
|
-
|
346
|
+
ls(dir) do |basename, abspath|
|
349
347
|
next if eager_load_exclusions.member?(abspath)
|
350
348
|
|
351
349
|
if ruby?(abspath)
|
352
|
-
|
353
|
-
|
354
|
-
|
350
|
+
if cref = autoloads[File.realpath(abspath)]
|
351
|
+
cref[0].const_get(cref[1], false)
|
352
|
+
end
|
353
|
+
elsif dir?(abspath) && !root_dirs.key?(abspath)
|
354
|
+
cname = inflector.camelize(basename, abspath)
|
355
|
+
queue << [namespace.const_get(cname, false), abspath]
|
355
356
|
end
|
356
357
|
end
|
357
358
|
end
|
@@ -391,6 +392,29 @@ module Zeitwerk
|
|
391
392
|
to_unload.keys.freeze
|
392
393
|
end
|
393
394
|
|
395
|
+
# Logs to `$stdout`, handy shortcut for debugging.
|
396
|
+
#
|
397
|
+
# @return [void]
|
398
|
+
def log!
|
399
|
+
@logger = ->(msg) { puts msg }
|
400
|
+
end
|
401
|
+
|
402
|
+
# @private
|
403
|
+
# @param dir [String]
|
404
|
+
# @return [Boolean]
|
405
|
+
def manages?(dir)
|
406
|
+
dir = dir + "/"
|
407
|
+
ignored_paths.each do |ignored_path|
|
408
|
+
return false if dir.start_with?(ignored_path + "/")
|
409
|
+
end
|
410
|
+
|
411
|
+
root_dirs.each_key do |root_dir|
|
412
|
+
return true if root_dir.start_with?(dir) || dir.start_with?(root_dir + "/")
|
413
|
+
end
|
414
|
+
|
415
|
+
false
|
416
|
+
end
|
417
|
+
|
394
418
|
# --- Class methods ---------------------------------------------------------------------------
|
395
419
|
|
396
420
|
class << self
|
@@ -414,7 +438,7 @@ module Zeitwerk
|
|
414
438
|
#
|
415
439
|
# @return [Zeitwerk::Loader]
|
416
440
|
def for_gem
|
417
|
-
called_from = caller_locations.first.path
|
441
|
+
called_from = caller_locations(1, 1).first.path
|
418
442
|
Registry.loader_for_gem(called_from)
|
419
443
|
end
|
420
444
|
|
@@ -449,18 +473,39 @@ module Zeitwerk
|
|
449
473
|
# @param parent [Module]
|
450
474
|
# @return [void]
|
451
475
|
def set_autoloads_in_dir(dir, parent)
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
476
|
+
ls(dir) do |basename, abspath|
|
477
|
+
begin
|
478
|
+
if ruby?(basename)
|
479
|
+
basename.slice!(-3, 3)
|
480
|
+
cname = inflector.camelize(basename, abspath).to_sym
|
481
|
+
autoload_file(parent, cname, abspath)
|
482
|
+
elsif dir?(abspath)
|
483
|
+
# In a Rails application, `app/models/concerns` is a subdirectory of
|
484
|
+
# `app/models`, but both of them are root directories.
|
485
|
+
#
|
486
|
+
# To resolve the ambiguity file name -> constant path this introduces,
|
487
|
+
# the `app/models/concerns` directory is totally ignored as a namespace,
|
488
|
+
# it counts only as root. The guard checks that.
|
489
|
+
unless root_dirs.key?(abspath)
|
490
|
+
cname = inflector.camelize(basename, abspath).to_sym
|
491
|
+
autoload_subdir(parent, cname, abspath)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
rescue ::NameError => error
|
495
|
+
path_type = ruby?(abspath) ? "file" : "directory"
|
496
|
+
|
497
|
+
raise NameError, <<~MESSAGE
|
498
|
+
#{error.message} inferred by #{inflector.class} from #{path_type}
|
499
|
+
|
500
|
+
#{abspath}
|
501
|
+
|
502
|
+
Possible ways to address this:
|
503
|
+
|
504
|
+
* Tell Zeitwerk to ignore this particular #{path_type}.
|
505
|
+
* Tell Zeitwerk to ignore one of its parent directories.
|
506
|
+
* Rename the #{path_type} to comply with the naming conventions.
|
507
|
+
* Modify the inflector to handle this case.
|
508
|
+
MESSAGE
|
464
509
|
end
|
465
510
|
end
|
466
511
|
end
|
@@ -605,7 +650,7 @@ module Zeitwerk
|
|
605
650
|
# @param dir [String]
|
606
651
|
# @return [void]
|
607
652
|
def do_preload_dir(dir)
|
608
|
-
|
653
|
+
ls(dir) do |_basename, abspath|
|
609
654
|
do_preload_abspath(abspath)
|
610
655
|
end
|
611
656
|
end
|
@@ -621,17 +666,17 @@ module Zeitwerk
|
|
621
666
|
# @param cname [Symbol]
|
622
667
|
# @return [String]
|
623
668
|
def cpath(parent, cname)
|
624
|
-
parent.equal?(Object) ? cname.to_s : "#{parent
|
669
|
+
parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
|
625
670
|
end
|
626
671
|
|
627
672
|
# @param dir [String]
|
628
|
-
# @yieldparam path [String]
|
673
|
+
# @yieldparam path [String, String]
|
629
674
|
# @return [void]
|
630
|
-
def
|
631
|
-
Dir.foreach(dir) do |
|
632
|
-
next if
|
633
|
-
abspath = File.join(dir,
|
634
|
-
yield abspath unless ignored_paths.member?(abspath)
|
675
|
+
def ls(dir)
|
676
|
+
Dir.foreach(dir) do |basename|
|
677
|
+
next if basename.start_with?(".")
|
678
|
+
abspath = File.join(dir, basename)
|
679
|
+
yield basename, abspath unless ignored_paths.member?(abspath)
|
635
680
|
end
|
636
681
|
end
|
637
682
|
|
@@ -650,7 +695,20 @@ module Zeitwerk
|
|
650
695
|
# @param paths [<String, Pathname, <String, Pathname>>]
|
651
696
|
# @return [<String>]
|
652
697
|
def expand_paths(paths)
|
653
|
-
|
698
|
+
paths.flatten.map! { |path| File.expand_path(path) }
|
699
|
+
end
|
700
|
+
|
701
|
+
# @param glob_patterns [<String>]
|
702
|
+
# @return [<String>]
|
703
|
+
def expand_glob_patterns(glob_patterns)
|
704
|
+
# Note that Dir.glob works with regular file names just fine. That is,
|
705
|
+
# glob patterns technically need no wildcards.
|
706
|
+
glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
|
707
|
+
end
|
708
|
+
|
709
|
+
# @return [void]
|
710
|
+
def recompute_ignored_paths
|
711
|
+
ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
|
654
712
|
end
|
655
713
|
|
656
714
|
# @param message [String]
|
@@ -664,12 +722,6 @@ module Zeitwerk
|
|
664
722
|
parent.const_defined?(cname, false)
|
665
723
|
end
|
666
724
|
|
667
|
-
def const_get_if_autoload(abspath)
|
668
|
-
if cref = autoloads[File.realpath(abspath)]
|
669
|
-
cref[0].const_get(cref[1], false)
|
670
|
-
end
|
671
|
-
end
|
672
|
-
|
673
725
|
def register_explicit_namespace(cpath)
|
674
726
|
ExplicitNamespace.register(cpath, self)
|
675
727
|
end
|
@@ -677,16 +729,12 @@ module Zeitwerk
|
|
677
729
|
def raise_if_conflicting_directory(dir)
|
678
730
|
self.class.mutex.synchronize do
|
679
731
|
Registry.loaders.each do |loader|
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
|
687
|
-
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
688
|
-
EOS
|
689
|
-
end
|
732
|
+
if loader != self && loader.manages?(dir)
|
733
|
+
require "pp"
|
734
|
+
raise Error,
|
735
|
+
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
|
736
|
+
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
737
|
+
EOS
|
690
738
|
end
|
691
739
|
end
|
692
740
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
module Zeitwerk::Loader::Callbacks
|
2
|
+
include Zeitwerk::RealModName
|
3
|
+
|
2
4
|
# Invoked from our decorated Kernel#require when a managed file is autoloaded.
|
3
5
|
#
|
4
6
|
# @private
|
@@ -60,7 +62,7 @@ module Zeitwerk::Loader::Callbacks
|
|
60
62
|
# @param namespace [Module]
|
61
63
|
# @return [void]
|
62
64
|
def on_namespace_loaded(namespace)
|
63
|
-
if subdirs = lazy_subdirs.delete(namespace
|
65
|
+
if subdirs = lazy_subdirs.delete(real_mod_name(namespace))
|
64
66
|
subdirs.each do |subdir|
|
65
67
|
set_autoloads_in_dir(subdir, namespace)
|
66
68
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Zeitwerk::RealModName
|
2
|
+
UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
|
3
|
+
private_constant :UNBOUND_METHOD_MODULE_NAME
|
4
|
+
|
5
|
+
# Returns the real name of the class or module, as set after the first
|
6
|
+
# constant to which it was assigned (or nil).
|
7
|
+
#
|
8
|
+
# The name method can be overridden, hence the indirection in this method.
|
9
|
+
#
|
10
|
+
# @param mod [Class, Module]
|
11
|
+
# @return [String, nil]
|
12
|
+
def real_mod_name(mod)
|
13
|
+
UNBOUND_METHOD_MODULE_NAME.bind(mod).call
|
14
|
+
end
|
15
|
+
end
|
data/lib/zeitwerk/version.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.1.
|
4
|
+
version: 2.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Noria
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -29,6 +29,7 @@ files:
|
|
29
29
|
- lib/zeitwerk/kernel.rb
|
30
30
|
- lib/zeitwerk/loader.rb
|
31
31
|
- lib/zeitwerk/loader/callbacks.rb
|
32
|
+
- lib/zeitwerk/real_mod_name.rb
|
32
33
|
- lib/zeitwerk/registry.rb
|
33
34
|
- lib/zeitwerk/version.rb
|
34
35
|
homepage: https://github.com/fxn/zeitwerk
|
@@ -50,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
51
|
- !ruby/object:Gem::Version
|
51
52
|
version: '0'
|
52
53
|
requirements: []
|
53
|
-
rubygems_version: 3.0.
|
54
|
+
rubygems_version: 3.0.3
|
54
55
|
signing_key:
|
55
56
|
specification_version: 4
|
56
57
|
summary: Efficient and thread-safe constant autoloader
|