zeitwerk 2.6.11 → 2.6.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +31 -5
- data/lib/zeitwerk/kernel.rb +3 -7
- data/lib/zeitwerk/loader/callbacks.rb +12 -5
- data/lib/zeitwerk/loader/eager_load.rb +1 -1
- data/lib/zeitwerk/loader.rb +8 -8
- data/lib/zeitwerk/null_inflector.rb +5 -0
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +1 -0
- 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: 5d4ce4eb7136dafe17130fc5d895e525d80ae947f02dd9420b5bc85a4d6f0dfd
|
4
|
+
data.tar.gz: edcac638955fef258ad36a358b78da6831a715ed46446848e39f9f1d8fb7de0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0a6e64c56635c9c6a8c9b24bdeb7509e4a7f14489f32aae18b02221c23b95b34096184c78d2d1509130ce75031ed0f3a7751b78e43d9b72122440f07cf980bc
|
7
|
+
data.tar.gz: 06440147c101a95ac2c8b7dcd43920a3efc3da2f58b902c157730a449d57b6ef74e0d3db7f3d2735be816a74ceb774e2d4075741556c7e2ba7fa00b8130c1723
|
data/README.md
CHANGED
@@ -40,6 +40,7 @@
|
|
40
40
|
- [Inflection](#inflection)
|
41
41
|
- [Zeitwerk::Inflector](#zeitwerkinflector)
|
42
42
|
- [Zeitwerk::GemInflector](#zeitwerkgeminflector)
|
43
|
+
- [Zeitwerk::NullInflector](#zeitwerknullinflector)
|
43
44
|
- [Custom inflector](#custom-inflector)
|
44
45
|
- [Callbacks](#callbacks)
|
45
46
|
- [The on_setup callback](#the-on_setup-callback)
|
@@ -373,7 +374,7 @@ require "zeitwerk"
|
|
373
374
|
loader = Zeitwerk::Loader.new
|
374
375
|
loader.tag = File.basename(__FILE__, ".rb")
|
375
376
|
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
376
|
-
loader.push_dir(
|
377
|
+
loader.push_dir(File.dirname(__FILE__))
|
377
378
|
```
|
378
379
|
|
379
380
|
If the main module references project constants at the top-level, Zeitwerk has to be ready to load them. Their definitions, in turn, may reference other project constants. And this is recursive. Therefore, it is important that the `setup` call happens above the main module definition:
|
@@ -579,7 +580,7 @@ root_dir2/my_app/routes
|
|
579
580
|
root_dir3/my_app/routes
|
580
581
|
```
|
581
582
|
|
582
|
-
where `
|
583
|
+
where `root_dir{1,2,3}` are root directories, eager loading `MyApp::Routes` will eager load the contents of the three corresponding directories.
|
583
584
|
|
584
585
|
There might exist external source trees implementing part of the namespace. This happens routinely, because top-level constants are stored in the globally shared `Object`. It happens also when deliberately [reopening third-party namespaces](#reopening-third-party-namespaces). Such external code is not eager loaded, the implementation is carefully scoped to what the receiver manages to avoid side-effects elsewhere.
|
585
586
|
|
@@ -774,6 +775,31 @@ This inflector is like the basic one, except it expects `lib/my_gem/version.rb`
|
|
774
775
|
|
775
776
|
The inflectors of different loaders are independent of each other. There are no global inflection rules or global configuration that can affect this inflector. It is deterministic.
|
776
777
|
|
778
|
+
<a id="markdown-zeitwerknullinflector" name="zeitwerknullinflector"></a>
|
779
|
+
#### Zeitwerk::NullInflector
|
780
|
+
|
781
|
+
This is an experimental inflector that simply returns its input unchanged.
|
782
|
+
|
783
|
+
```ruby
|
784
|
+
loader.inflector = Zeitwerk::NullInflector.new
|
785
|
+
```
|
786
|
+
|
787
|
+
In a project using this inflector, the names of files and directories are equal to the constants they define:
|
788
|
+
|
789
|
+
```
|
790
|
+
User.rb -> User
|
791
|
+
HTMLParser.rb -> HTMLParser
|
792
|
+
Admin/Role.rb -> Admin::Role
|
793
|
+
```
|
794
|
+
|
795
|
+
Point is, you think less. Names that typically need custom configuration like acronyms no longer require your attention. What you see is what you get, simple.
|
796
|
+
|
797
|
+
This inflector is experimental since Ruby usually goes for snake case in files and directories. But hey, if you fancy giving it a whirl, go for it!
|
798
|
+
|
799
|
+
The null inflector cannot be used in Rails applications because the `main` autoloader also manages engines. However, you could subclass the default inflector and override `camelize` to return the basename untouched if it starts with an uppercase letter. Generators would not create the expected file names, but you could still experiment to see how far this approach takes you.
|
800
|
+
|
801
|
+
In case-insensitive file systems, this inflector works as long as directory listings return the expected strings. Zeitwerk lists directories using Ruby APIs like `Dir.children` or `Dir.entries`.
|
802
|
+
|
777
803
|
<a id="markdown-custom-inflector" name="custom-inflector"></a>
|
778
804
|
#### Custom inflector
|
779
805
|
|
@@ -1006,7 +1032,7 @@ Zeitwerk::Loader.default_logger = method(:puts)
|
|
1006
1032
|
|
1007
1033
|
If there is a logger configured, you'll see traces when autoloads are set, files loaded, and modules autovivified. While reloading, removed autoloads and unloaded objects are also traced.
|
1008
1034
|
|
1009
|
-
As a curiosity, if your project has namespaces you'll notice in the traces Zeitwerk sets autoloads for _directories_.
|
1035
|
+
As a curiosity, if your project has namespaces you'll notice in the traces Zeitwerk sets autoloads for _directories_. This allows descending into subdirectories on demand, thus avoiding unnecessary tree walks.
|
1010
1036
|
|
1011
1037
|
<a id="markdown-loader-tag" name="loader-tag"></a>
|
1012
1038
|
#### Loader tag
|
@@ -1032,7 +1058,7 @@ Zeitwerk@my_gem: constant MyGem::Foo loaded from ...
|
|
1032
1058
|
<a id="markdown-ignoring-parts-of-the-project" name="ignoring-parts-of-the-project"></a>
|
1033
1059
|
### Ignoring parts of the project
|
1034
1060
|
|
1035
|
-
Zeitwerk ignores automatically any file or directory whose name starts with a dot, and any files that do not have extension ".rb".
|
1061
|
+
Zeitwerk ignores automatically any file or directory whose name starts with a dot, and any files that do not have the extension ".rb".
|
1036
1062
|
|
1037
1063
|
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.
|
1038
1064
|
|
@@ -1325,7 +1351,7 @@ The test suite passes on Windows with codepage `Windows-1252` if all the involve
|
|
1325
1351
|
|
1326
1352
|
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.
|
1327
1353
|
|
1328
|
-
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
|
1354
|
+
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 defines, the namespace. This is often accomplished by loading (e.g., `require`-ing) the dependency.
|
1329
1355
|
|
1330
1356
|
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.
|
1331
1357
|
|
data/lib/zeitwerk/kernel.rb
CHANGED
@@ -14,10 +14,6 @@ module Kernel
|
|
14
14
|
# should not require anything. But if someone has legacy require calls around,
|
15
15
|
# they will work as expected, and in a compatible way. This feature is by now
|
16
16
|
# EXPERIMENTAL and UNDOCUMENTED.
|
17
|
-
#
|
18
|
-
# We cannot decorate with prepend + super because Kernel has already been
|
19
|
-
# included in Object, and changes in ancestors don't get propagated into
|
20
|
-
# already existing ancestor chains on Ruby < 3.0.
|
21
17
|
alias_method :zeitwerk_original_require, :require
|
22
18
|
class << self
|
23
19
|
alias_method :zeitwerk_original_require, :require
|
@@ -28,10 +24,10 @@ module Kernel
|
|
28
24
|
if loader = Zeitwerk::Registry.loader_for(path)
|
29
25
|
if path.end_with?(".rb")
|
30
26
|
required = zeitwerk_original_require(path)
|
31
|
-
loader.
|
27
|
+
loader.__on_file_autoloaded(path) if required
|
32
28
|
required
|
33
29
|
else
|
34
|
-
loader.
|
30
|
+
loader.__on_dir_autoloaded(path)
|
35
31
|
true
|
36
32
|
end
|
37
33
|
else
|
@@ -39,7 +35,7 @@ module Kernel
|
|
39
35
|
if required
|
40
36
|
abspath = $LOADED_FEATURES.last
|
41
37
|
if loader = Zeitwerk::Registry.loader_for(abspath)
|
42
|
-
loader.
|
38
|
+
loader.__on_file_autoloaded(abspath)
|
43
39
|
end
|
44
40
|
end
|
45
41
|
required
|
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
module Zeitwerk::Loader::Callbacks
|
4
4
|
include Zeitwerk::RealModName
|
5
|
+
extend Zeitwerk::Internal
|
5
6
|
|
6
7
|
# Invoked from our decorated Kernel#require when a managed file is autoloaded.
|
7
8
|
#
|
8
|
-
# @private
|
9
9
|
# @sig (String) -> void
|
10
|
-
def on_file_autoloaded(file)
|
10
|
+
internal def on_file_autoloaded(file)
|
11
11
|
cref = autoloads.delete(file)
|
12
12
|
cpath = cpath(*cref)
|
13
13
|
|
@@ -20,8 +20,16 @@ module Zeitwerk::Loader::Callbacks
|
|
20
20
|
else
|
21
21
|
msg = "expected file #{file} to define constant #{cpath}, but didn't"
|
22
22
|
log(msg) if logger
|
23
|
+
|
24
|
+
# Ruby still keeps the autoload defined, but we remove it because the
|
25
|
+
# contract in Zeitwerk is more strict.
|
23
26
|
crem(*cref)
|
27
|
+
|
28
|
+
# Since the expected constant was not defined, there is nothing to unload.
|
29
|
+
# However, if the exception is rescued and reloading is enabled, we still
|
30
|
+
# need to deleted the file from $LOADED_FEATURES.
|
24
31
|
to_unload[cpath] = [file, cref] if reloading_enabled?
|
32
|
+
|
25
33
|
raise Zeitwerk::NameError.new(msg, cref.last)
|
26
34
|
end
|
27
35
|
end
|
@@ -29,9 +37,8 @@ module Zeitwerk::Loader::Callbacks
|
|
29
37
|
# Invoked from our decorated Kernel#require when a managed directory is
|
30
38
|
# autoloaded.
|
31
39
|
#
|
32
|
-
# @private
|
33
40
|
# @sig (String) -> void
|
34
|
-
def on_dir_autoloaded(dir)
|
41
|
+
internal def on_dir_autoloaded(dir)
|
35
42
|
# Module#autoload does not serialize concurrent requires in CRuby < 3.2, and
|
36
43
|
# we handle directories ourselves without going through Kernel#require, so
|
37
44
|
# the callback needs to account for concurrency.
|
@@ -74,7 +81,7 @@ module Zeitwerk::Loader::Callbacks
|
|
74
81
|
def on_namespace_loaded(namespace)
|
75
82
|
if dirs = namespace_dirs.delete(real_mod_name(namespace))
|
76
83
|
dirs.each do |dir|
|
77
|
-
|
84
|
+
define_autoloads_for_dir(dir, namespace)
|
78
85
|
end
|
79
86
|
end
|
80
87
|
end
|
@@ -171,7 +171,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
171
171
|
next if honour_exclusions && eager_load_exclusions.member?(abspath)
|
172
172
|
|
173
173
|
if ruby?(abspath)
|
174
|
-
if (cref = autoloads[abspath])
|
174
|
+
if (cref = autoloads[abspath])
|
175
175
|
cget(*cref)
|
176
176
|
end
|
177
177
|
else
|
data/lib/zeitwerk/loader.rb
CHANGED
@@ -121,7 +121,7 @@ module Zeitwerk
|
|
121
121
|
break if @setup
|
122
122
|
|
123
123
|
actual_roots.each do |root_dir, root_namespace|
|
124
|
-
|
124
|
+
define_autoloads_for_dir(root_dir, root_namespace)
|
125
125
|
end
|
126
126
|
|
127
127
|
on_setup_callbacks.each(&:call)
|
@@ -407,14 +407,14 @@ module Zeitwerk
|
|
407
407
|
end
|
408
408
|
|
409
409
|
# @sig (String, Module) -> void
|
410
|
-
private def
|
410
|
+
private def define_autoloads_for_dir(dir, parent)
|
411
411
|
ls(dir) do |basename, abspath|
|
412
412
|
if ruby?(basename)
|
413
413
|
basename.delete_suffix!(".rb")
|
414
414
|
autoload_file(parent, cname_for(basename, abspath), abspath)
|
415
415
|
else
|
416
416
|
if collapse?(abspath)
|
417
|
-
|
417
|
+
define_autoloads_for_dir(abspath, parent)
|
418
418
|
else
|
419
419
|
autoload_subdir(parent, cname_for(basename, abspath), abspath)
|
420
420
|
end
|
@@ -443,12 +443,12 @@ module Zeitwerk
|
|
443
443
|
elsif !cdef?(parent, cname)
|
444
444
|
# First time we find this namespace, set an autoload for it.
|
445
445
|
namespace_dirs[cpath(parent, cname)] << subdir
|
446
|
-
|
446
|
+
define_autoload(parent, cname, subdir)
|
447
447
|
else
|
448
448
|
# For whatever reason the constant that corresponds to this namespace has
|
449
449
|
# already been defined, we have to recurse.
|
450
450
|
log("the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
|
451
|
-
|
451
|
+
define_autoloads_for_dir(subdir, cget(parent, cname))
|
452
452
|
end
|
453
453
|
end
|
454
454
|
|
@@ -471,7 +471,7 @@ module Zeitwerk
|
|
471
471
|
shadowed_files << file
|
472
472
|
log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
|
473
473
|
else
|
474
|
-
|
474
|
+
define_autoload(parent, cname, file)
|
475
475
|
end
|
476
476
|
end
|
477
477
|
|
@@ -485,12 +485,12 @@ module Zeitwerk
|
|
485
485
|
|
486
486
|
log("earlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger
|
487
487
|
|
488
|
-
|
488
|
+
define_autoload(parent, cname, file)
|
489
489
|
register_explicit_namespace(cpath(parent, cname))
|
490
490
|
end
|
491
491
|
|
492
492
|
# @sig (Module, Symbol, String) -> void
|
493
|
-
private def
|
493
|
+
private def define_autoload(parent, cname, abspath)
|
494
494
|
parent.autoload(cname, abspath)
|
495
495
|
|
496
496
|
if logger
|
data/lib/zeitwerk/version.rb
CHANGED
data/lib/zeitwerk.rb
CHANGED
@@ -9,6 +9,7 @@ module Zeitwerk
|
|
9
9
|
require_relative "zeitwerk/explicit_namespace"
|
10
10
|
require_relative "zeitwerk/inflector"
|
11
11
|
require_relative "zeitwerk/gem_inflector"
|
12
|
+
require_relative "zeitwerk/null_inflector"
|
12
13
|
require_relative "zeitwerk/kernel"
|
13
14
|
require_relative "zeitwerk/error"
|
14
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.13
|
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-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -35,6 +35,7 @@ files:
|
|
35
35
|
- lib/zeitwerk/loader/config.rb
|
36
36
|
- lib/zeitwerk/loader/eager_load.rb
|
37
37
|
- lib/zeitwerk/loader/helpers.rb
|
38
|
+
- lib/zeitwerk/null_inflector.rb
|
38
39
|
- lib/zeitwerk/real_mod_name.rb
|
39
40
|
- lib/zeitwerk/registry.rb
|
40
41
|
- lib/zeitwerk/version.rb
|
@@ -61,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
62
|
- !ruby/object:Gem::Version
|
62
63
|
version: '0'
|
63
64
|
requirements: []
|
64
|
-
rubygems_version: 3.
|
65
|
+
rubygems_version: 3.5.5
|
65
66
|
signing_key:
|
66
67
|
specification_version: 4
|
67
68
|
summary: Efficient and thread-safe constant autoloader
|