zeitwerk 2.5.0.beta5 → 2.5.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 +20 -8
- data/lib/zeitwerk/kernel.rb +10 -9
- data/lib/zeitwerk/loader/callbacks.rb +8 -5
- data/lib/zeitwerk/loader.rb +40 -33
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +0 -1
- metadata +4 -5
- data/lib/zeitwerk/autoloads.rb +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edfc3d6e8c62e95f5a008e6625b5b7f2ff7e0067e3d311ec352e1bf6c6922c27
|
4
|
+
data.tar.gz: 9087c6cc6524a8fd2b6843c283496e29cc5d9157421f6ba7994feb362f383333
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d31bf02d352d61024f553f21b14093bc4d8cfc9276c106e8a67b499044aaf12bdd076288c89828b342a1980f163706b62c82d9bd21c15cdc4cadacf117e5299
|
7
|
+
data.tar.gz: 89cc85a2efeb95760de195fc2acf854256dba89cdee2208992c33ba5d8cb78ad71adaddc757026660dbc4a2d562e1efa623572b91d2107e9cf30020ea83524ab
|
data/README.md
CHANGED
@@ -50,8 +50,8 @@
|
|
50
50
|
- [Rules of thumb](#rules-of-thumb)
|
51
51
|
- [Debuggers](#debuggers)
|
52
52
|
- [debug.rb](#debugrb)
|
53
|
-
- [Break](#break)
|
54
53
|
- [Byebug](#byebug)
|
54
|
+
- [Break](#break)
|
55
55
|
- [Pronunciation](#pronunciation)
|
56
56
|
- [Supported Ruby versions](#supported-ruby-versions)
|
57
57
|
- [Testing](#testing)
|
@@ -810,7 +810,9 @@ Kernel.module_eval do
|
|
810
810
|
end
|
811
811
|
```
|
812
812
|
|
813
|
-
|
813
|
+
`Kernel` is already defined by Ruby so the module cannot be autoloaded. Also, that file does not define a constant path after the path name. Therefore, Zeitwerk should not process it at all.
|
814
|
+
|
815
|
+
The extension can still coexist with the rest of the project, you only need to tell Zeitwerk to ignore it:
|
814
816
|
|
815
817
|
```ruby
|
816
818
|
kernel_ext = "#{__dir__}/my_gem/core_ext/kernel.rb"
|
@@ -826,6 +828,14 @@ loader.ignore(core_ext)
|
|
826
828
|
loader.setup
|
827
829
|
```
|
828
830
|
|
831
|
+
Now, that file has to be loaded manually with `require` or `require_relative`:
|
832
|
+
|
833
|
+
```ruby
|
834
|
+
require_relative "my_gem/core_ext/kernel"
|
835
|
+
```
|
836
|
+
|
837
|
+
and you can do that anytime, before configuring the loader, or after configuring the loader, does not matter.
|
838
|
+
|
829
839
|
<a id="markdown-use-case-the-adapter-pattern" name="use-case-the-adapter-pattern"></a>
|
830
840
|
#### Use case: The adapter pattern
|
831
841
|
|
@@ -973,17 +983,19 @@ With that, when Zeitwerk scans the file system and reaches the gem directories `
|
|
973
983
|
<a id="markdown-debugrb" name="debugrb"></a>
|
974
984
|
#### debug.rb
|
975
985
|
|
976
|
-
The new [debug.rb](https://github.com/ruby/debug) gem and Zeitwerk
|
986
|
+
The new [debug.rb](https://github.com/ruby/debug) gem and Zeitwerk are mostly compatible. This is the new debugger that is going to ship with Ruby 3.1.
|
977
987
|
|
978
|
-
|
979
|
-
#### Break
|
980
|
-
|
981
|
-
Zeitwerk works fine with [@gsamokovarov](https://github.com/gsamokovarov)'s [Break](https://github.com/gsamokovarov/break) debugger.
|
988
|
+
There's one exception, though: Due to a technical limitation of tracepoints, explicit namespaces are not autoloaded while expressions are evaluated in the REPL. See [ruby/debug#408](https://github.com/ruby/debug/issues/408).
|
982
989
|
|
983
990
|
<a id="markdown-byebug" name="byebug"></a>
|
984
991
|
#### Byebug
|
985
992
|
|
986
|
-
Zeitwerk and [Byebug](https://github.com/deivid-rodriguez/byebug)
|
993
|
+
Zeitwerk and [Byebug](https://github.com/deivid-rodriguez/byebug) have a similar edge incompatibility.
|
994
|
+
|
995
|
+
<a id="markdown-break" name="break"></a>
|
996
|
+
#### Break
|
997
|
+
|
998
|
+
Zeitwerk works fine with [@gsamokovarov](https://github.com/gsamokovarov)'s [Break](https://github.com/gsamokovarov/break) debugger.
|
987
999
|
|
988
1000
|
<a id="markdown-pronunciation" name="pronunciation"></a>
|
989
1001
|
## Pronunciation
|
data/lib/zeitwerk/kernel.rb
CHANGED
@@ -24,22 +24,23 @@ module Kernel
|
|
24
24
|
def require(path)
|
25
25
|
if loader = Zeitwerk::Registry.loader_for(path)
|
26
26
|
if path.end_with?(".rb")
|
27
|
-
zeitwerk_original_require(path)
|
28
|
-
|
29
|
-
|
27
|
+
required = zeitwerk_original_require(path)
|
28
|
+
loader.on_file_autoloaded(path) if required
|
29
|
+
required
|
30
30
|
else
|
31
31
|
loader.on_dir_autoloaded(path)
|
32
|
+
$LOADED_FEATURES << path
|
32
33
|
true
|
33
34
|
end
|
34
35
|
else
|
35
|
-
zeitwerk_original_require(path)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
36
|
+
required = zeitwerk_original_require(path)
|
37
|
+
if required
|
38
|
+
abspath = $LOADED_FEATURES.last
|
39
|
+
if loader = Zeitwerk::Registry.loader_for(abspath)
|
40
|
+
loader.on_file_autoloaded(abspath)
|
41
41
|
end
|
42
42
|
end
|
43
|
+
required
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
@@ -11,16 +11,19 @@ module Zeitwerk::Loader::Callbacks
|
|
11
11
|
cref = autoloads.delete(file)
|
12
12
|
cpath = cpath(*cref)
|
13
13
|
|
14
|
+
# If reloading is enabled, we need to put this constant for unloading
|
15
|
+
# regardless of what cdef? says. In Ruby < 3.1 the internal state is not
|
16
|
+
# fully cleared. Module#constants still includes it, and you need to
|
17
|
+
# remove_const. See https://github.com/ruby/ruby/pull/4715.
|
14
18
|
to_unload[cpath] = [file, cref] if reloading_enabled?
|
15
19
|
Zeitwerk::Registry.unregister_autoload(file)
|
16
20
|
|
17
|
-
if
|
18
|
-
log("constant #{cpath} loaded from file #{file}")
|
19
|
-
|
21
|
+
if cdef?(*cref)
|
22
|
+
log("constant #{cpath} loaded from file #{file}") if logger
|
23
|
+
run_on_load_callbacks(cpath, cget(*cref), file) unless on_load_callbacks.empty?
|
24
|
+
else
|
20
25
|
raise Zeitwerk::NameError.new("expected file #{file} to define constant #{cpath}, but didn't", cref.last)
|
21
26
|
end
|
22
|
-
|
23
|
-
run_on_load_callbacks(cpath, cget(*cref), file) unless on_load_callbacks.empty?
|
24
27
|
end
|
25
28
|
|
26
29
|
# Invoked from our decorated Kernel#require when a managed directory is
|
data/lib/zeitwerk/loader.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "set"
|
4
|
-
require "securerandom"
|
5
4
|
|
6
5
|
module Zeitwerk
|
7
6
|
class Loader
|
@@ -14,22 +13,16 @@ module Zeitwerk
|
|
14
13
|
include Helpers
|
15
14
|
include Config
|
16
15
|
|
17
|
-
#
|
18
|
-
# executed
|
16
|
+
# Maps absolute paths for which an autoload has been set ---and not
|
17
|
+
# executed--- to their corresponding parent class or module and constant
|
18
|
+
# name.
|
19
19
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
# and invoke user callbacks. If reloading is enabled, remember cref and
|
24
|
-
# abspath for later unloading logic.
|
25
|
-
#
|
26
|
-
# 2. When unloading, remove autoloads that have not been executed.
|
27
|
-
#
|
28
|
-
# 3. Eager load with a recursive const_get, rather than a recursive require,
|
29
|
-
# for consistency with lazy loading.
|
20
|
+
# "/Users/fxn/blog/app/models/user.rb" => [Object, :User],
|
21
|
+
# "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, :Pricing]
|
22
|
+
# ...
|
30
23
|
#
|
31
24
|
# @private
|
32
|
-
# @sig
|
25
|
+
# @sig Hash[String, [Module, Symbol]]
|
33
26
|
attr_reader :autoloads
|
34
27
|
|
35
28
|
# We keep track of autoloaded directories to remove them from the registry
|
@@ -87,7 +80,7 @@ module Zeitwerk
|
|
87
80
|
def initialize
|
88
81
|
super
|
89
82
|
|
90
|
-
@autoloads =
|
83
|
+
@autoloads = {}
|
91
84
|
@autoloaded_dirs = []
|
92
85
|
@to_unload = {}
|
93
86
|
@lazy_subdirs = Hash.new { |h, cpath| h[cpath] = [] }
|
@@ -130,23 +123,17 @@ module Zeitwerk
|
|
130
123
|
# @sig () -> void
|
131
124
|
def unload
|
132
125
|
mutex.synchronize do
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
#
|
137
|
-
# Directories are not stored in $LOADED_FEATURES, keeping track of files
|
138
|
-
# is enough.
|
139
|
-
unloaded_files = Set.new
|
140
|
-
|
141
|
-
autoloads.each do |(parent, cname), abspath|
|
126
|
+
abspaths_of_unloaded_crefs = Set.new
|
127
|
+
|
128
|
+
autoloads.each do |abspath, (parent, cname)|
|
142
129
|
if parent.autoload?(cname)
|
143
130
|
unload_autoload(parent, cname)
|
144
131
|
else
|
145
132
|
# Could happen if loaded with require_relative. That is unsupported,
|
146
133
|
# and the constant path would escape unloadable_cpath? This is just
|
147
134
|
# defensive code to clean things up as much as we are able to.
|
148
|
-
unload_cref(parent, cname)
|
149
|
-
|
135
|
+
unload_cref(parent, cname)
|
136
|
+
abspaths_of_unloaded_crefs.add(abspath)
|
150
137
|
end
|
151
138
|
end
|
152
139
|
|
@@ -156,11 +143,15 @@ module Zeitwerk
|
|
156
143
|
run_on_unload_callbacks(cpath, value, abspath)
|
157
144
|
end
|
158
145
|
|
159
|
-
unload_cref(parent, cname)
|
160
|
-
|
146
|
+
unload_cref(parent, cname)
|
147
|
+
abspaths_of_unloaded_crefs.add(abspath)
|
161
148
|
end
|
162
149
|
|
163
|
-
unless
|
150
|
+
unless abspaths_of_unloaded_crefs.empty?
|
151
|
+
# We remove these abspaths from $LOADED_FEATURES because, otherwise,
|
152
|
+
# `require`'s idempotence would prevent newly defined autoloads from
|
153
|
+
# loading them again.
|
154
|
+
#
|
164
155
|
# Bootsnap decorates Kernel#require to speed it up using a cache and
|
165
156
|
# this optimization does not check if $LOADED_FEATURES has the file.
|
166
157
|
#
|
@@ -172,7 +163,7 @@ module Zeitwerk
|
|
172
163
|
# Rails applications may depend on bootsnap, so for unloading to work
|
173
164
|
# in that setting it is preferable that we restrict our API choice to
|
174
165
|
# one of those methods.
|
175
|
-
$LOADED_FEATURES.reject! { |file|
|
166
|
+
$LOADED_FEATURES.reject! { |file| abspaths_of_unloaded_crefs.member?(file) }
|
176
167
|
end
|
177
168
|
|
178
169
|
autoloads.clear
|
@@ -234,7 +225,7 @@ module Zeitwerk
|
|
234
225
|
next if honour_exclusions && excluded_from_eager_load?(abspath)
|
235
226
|
|
236
227
|
if ruby?(abspath)
|
237
|
-
if cref = autoloads
|
228
|
+
if cref = autoloads[abspath]
|
238
229
|
cget(*cref)
|
239
230
|
end
|
240
231
|
elsif dir?(abspath) && !root_dirs.key?(abspath)
|
@@ -376,7 +367,7 @@ module Zeitwerk
|
|
376
367
|
|
377
368
|
# @sig (Module, Symbol, String) -> void
|
378
369
|
def autoload_subdir(parent, cname, subdir)
|
379
|
-
if autoload_path =
|
370
|
+
if autoload_path = autoload_path_set_by_me_for?(parent, cname)
|
380
371
|
cpath = cpath(parent, cname)
|
381
372
|
register_explicit_namespace(cpath) if ruby?(autoload_path)
|
382
373
|
# We do not need to issue another autoload, the existing one is enough
|
@@ -432,7 +423,7 @@ module Zeitwerk
|
|
432
423
|
|
433
424
|
# @sig (Module, Symbol, String) -> void
|
434
425
|
def set_autoload(parent, cname, abspath)
|
435
|
-
|
426
|
+
parent.autoload(cname, abspath)
|
436
427
|
|
437
428
|
if logger
|
438
429
|
if ruby?(abspath)
|
@@ -442,6 +433,7 @@ module Zeitwerk
|
|
442
433
|
end
|
443
434
|
end
|
444
435
|
|
436
|
+
autoloads[abspath] = [parent, cname]
|
445
437
|
Registry.register_autoload(self, abspath)
|
446
438
|
|
447
439
|
# See why in the documentation of Zeitwerk::Registry.inceptions.
|
@@ -450,6 +442,15 @@ module Zeitwerk
|
|
450
442
|
end
|
451
443
|
end
|
452
444
|
|
445
|
+
# @sig (Module, Symbol) -> String?
|
446
|
+
def autoload_path_set_by_me_for?(parent, cname)
|
447
|
+
if autoload_path = strict_autoload_path(parent, cname)
|
448
|
+
autoload_path if autoloads.key?(autoload_path)
|
449
|
+
else
|
450
|
+
Registry.inception?(cpath(parent, cname))
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
453
454
|
# @sig (String) -> void
|
454
455
|
def register_explicit_namespace(cpath)
|
455
456
|
ExplicitNamespace.register(cpath, self)
|
@@ -494,7 +495,13 @@ module Zeitwerk
|
|
494
495
|
|
495
496
|
# @sig (Module, Symbol) -> void
|
496
497
|
def unload_cref(parent, cname)
|
498
|
+
# Let's optimistically remove_const. The way we use it, this is going to
|
499
|
+
# succeed always if all is good.
|
497
500
|
parent.__send__(:remove_const, cname)
|
501
|
+
rescue ::NameError
|
502
|
+
# There are a few edge scenarios in which this may happen. If the constant
|
503
|
+
# is gone, that is OK, anyway.
|
504
|
+
else
|
498
505
|
log("#{cpath(parent, cname)} unloaded") if logger
|
499
506
|
end
|
500
507
|
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.5.
|
4
|
+
version: 2.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Noria
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -23,7 +23,6 @@ files:
|
|
23
23
|
- MIT-LICENSE
|
24
24
|
- README.md
|
25
25
|
- lib/zeitwerk.rb
|
26
|
-
- lib/zeitwerk/autoloads.rb
|
27
26
|
- lib/zeitwerk/error.rb
|
28
27
|
- lib/zeitwerk/explicit_namespace.rb
|
29
28
|
- lib/zeitwerk/gem_inflector.rb
|
@@ -55,9 +54,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
54
|
version: '2.5'
|
56
55
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
56
|
requirements:
|
58
|
-
- - "
|
57
|
+
- - ">="
|
59
58
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
59
|
+
version: '0'
|
61
60
|
requirements: []
|
62
61
|
rubygems_version: 3.2.22
|
63
62
|
signing_key:
|
data/lib/zeitwerk/autoloads.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Zeitwerk
|
4
|
-
# @private
|
5
|
-
class Autoloads
|
6
|
-
# Maps crefs for which an autoload has been defined to the corresponding
|
7
|
-
# absolute path.
|
8
|
-
#
|
9
|
-
# [Object, :User] => "/Users/fxn/blog/app/models/user.rb"
|
10
|
-
# [Object, :Hotel] => "/Users/fxn/blog/app/models/hotel"
|
11
|
-
# ...
|
12
|
-
#
|
13
|
-
# This colection is transient, callbacks delete its entries as autoloads get
|
14
|
-
# executed.
|
15
|
-
#
|
16
|
-
# @sig Hash[[Module, Symbol], String]
|
17
|
-
attr_reader :c2a
|
18
|
-
|
19
|
-
# This is the inverse of c2a, for inverse lookups.
|
20
|
-
#
|
21
|
-
# @sig Hash[String, [Module, Symbol]]
|
22
|
-
attr_reader :a2c
|
23
|
-
|
24
|
-
# @sig () -> void
|
25
|
-
def initialize
|
26
|
-
@c2a = {}
|
27
|
-
@a2c = {}
|
28
|
-
end
|
29
|
-
|
30
|
-
# @sig (Module, Symbol, String) -> void
|
31
|
-
def define(parent, cname, abspath)
|
32
|
-
parent.autoload(cname, abspath)
|
33
|
-
cref = [parent, cname]
|
34
|
-
c2a[cref] = abspath
|
35
|
-
a2c[abspath] = cref
|
36
|
-
end
|
37
|
-
|
38
|
-
# @sig () { () -> [[Module, Symbol], String] } -> void
|
39
|
-
def each(&block)
|
40
|
-
c2a.each(&block)
|
41
|
-
end
|
42
|
-
|
43
|
-
# @sig (Module, Symbol) -> String?
|
44
|
-
def abspath_for(parent, cname)
|
45
|
-
c2a[[parent, cname]]
|
46
|
-
end
|
47
|
-
|
48
|
-
# @sig (String) -> [Module, Symbol]?
|
49
|
-
def cref_for(abspath)
|
50
|
-
a2c[abspath]
|
51
|
-
end
|
52
|
-
|
53
|
-
# @sig (String) -> [Module, Symbol]?
|
54
|
-
def delete(abspath)
|
55
|
-
cref = a2c.delete(abspath)
|
56
|
-
c2a.delete(cref)
|
57
|
-
cref
|
58
|
-
end
|
59
|
-
|
60
|
-
# @sig () -> void
|
61
|
-
def clear
|
62
|
-
c2a.clear
|
63
|
-
a2c.clear
|
64
|
-
end
|
65
|
-
|
66
|
-
# @sig () -> bool
|
67
|
-
def empty?
|
68
|
-
c2a.empty? && a2c.empty?
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|