zeitwerk 2.5.0.beta → 2.5.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +133 -18
- data/lib/zeitwerk/autoloads.rb +2 -0
- data/lib/zeitwerk/error.rb +2 -0
- data/lib/zeitwerk/explicit_namespace.rb +3 -1
- data/lib/zeitwerk/kernel.rb +1 -1
- data/lib/zeitwerk/loader/callbacks.rb +2 -0
- data/lib/zeitwerk/loader/config.rb +23 -10
- data/lib/zeitwerk/loader/helpers.rb +2 -0
- data/lib/zeitwerk/loader.rb +39 -13
- data/lib/zeitwerk/real_mod_name.rb +2 -0
- data/lib/zeitwerk/registry.rb +9 -0
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +11 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a57a618cc6c371488c6b2a1360b3bfb37b4914e7c44addd688dd9ff6d245f92b
|
4
|
+
data.tar.gz: 927300a0bec3a2941e79e3065068cb3359ced9a368a23204e31eb76c1de347bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c26a751d80bcf719e2705c30c24e78300141b8cfe4a57e3bed2a9bf26e80534755a0fe067b6f32e6c1f4e7bbaf7693232640a76ff710e868c07f11128e9da6d
|
7
|
+
data.tar.gz: 270a9053e7ecccf58366272d6e60d2d3967d80f3d9777a0825700efcacf95e468a31c29924436df4231a53a971d31b0b4157d9f8bb8b82a91bd6fb6b2fdfb592
|
data/README.md
CHANGED
@@ -19,19 +19,24 @@
|
|
19
19
|
- [Implicit namespaces](#implicit-namespaces)
|
20
20
|
- [Explicit namespaces](#explicit-namespaces)
|
21
21
|
- [Collapsing directories](#collapsing-directories)
|
22
|
+
- [Testing compliance](#testing-compliance)
|
22
23
|
- [Usage](#usage)
|
23
24
|
- [Setup](#setup)
|
24
25
|
- [Generic](#generic)
|
25
26
|
- [for_gem](#for_gem)
|
26
27
|
- [Autoloading](#autoloading)
|
27
28
|
- [Eager loading](#eager-loading)
|
29
|
+
- [Eager load exclusions](#eager-load-exclusions)
|
30
|
+
- [Global eager load](#global-eager-load)
|
28
31
|
- [Reloading](#reloading)
|
29
32
|
- [Inflection](#inflection)
|
30
33
|
- [Zeitwerk::Inflector](#zeitwerkinflector)
|
31
34
|
- [Zeitwerk::GemInflector](#zeitwerkgeminflector)
|
32
35
|
- [Custom inflector](#custom-inflector)
|
33
|
-
- [
|
34
|
-
|
36
|
+
- [Callbacks](#callbacks)
|
37
|
+
- [The on_setup callback](#the-on_setup-callback)
|
38
|
+
- [The on_load callback](#the-on_load-callback)
|
39
|
+
- [The on_unload callback](#the-on_unload-callback)
|
35
40
|
- [Technical details](#technical-details)
|
36
41
|
- [Logging](#logging)
|
37
42
|
- [Loader tag](#loader-tag)
|
@@ -40,15 +45,19 @@
|
|
40
45
|
- [Use case: The adapter pattern](#use-case-the-adapter-pattern)
|
41
46
|
- [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
|
42
47
|
- [Edge cases](#edge-cases)
|
48
|
+
- [Beware of circular dependencies](#beware-of-circular-dependencies)
|
43
49
|
- [Reopening third-party namespaces](#reopening-third-party-namespaces)
|
44
50
|
- [Rules of thumb](#rules-of-thumb)
|
45
51
|
- [Debuggers](#debuggers)
|
52
|
+
- [debug.rb](#debugrb)
|
46
53
|
- [Break](#break)
|
47
54
|
- [Byebug](#byebug)
|
48
55
|
- [Pronunciation](#pronunciation)
|
49
56
|
- [Supported Ruby versions](#supported-ruby-versions)
|
50
57
|
- [Testing](#testing)
|
51
58
|
- [Motivation](#motivation)
|
59
|
+
- [Kernel#require is brittle](#kernelrequire-is-brittle)
|
60
|
+
- [Rails autoloading was brittle](#rails-autoloading-was-brittle)
|
52
61
|
- [Thanks](#thanks)
|
53
62
|
- [License](#license)
|
54
63
|
|
@@ -157,6 +166,16 @@ class HttpCrawler
|
|
157
166
|
end
|
158
167
|
```
|
159
168
|
|
169
|
+
The first example needs a custom [inflection](https://github.com/fxn/zeitwerk#inflection) rule:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
loader.inflector.inflect("max_retries" => "MAX_RETRIES")
|
173
|
+
```
|
174
|
+
|
175
|
+
Otherwise, Zeitwerk would expect the file to define `MaxRetries`.
|
176
|
+
|
177
|
+
In the second example, no custom rule is needed.
|
178
|
+
|
160
179
|
<a id="markdown-root-directories-and-root-namespaces" name="root-directories-and-root-namespaces"></a>
|
161
180
|
### Root directories and root namespaces
|
162
181
|
|
@@ -275,6 +294,23 @@ To illustrate usage of glob patterns, if `actions` in the example above is part
|
|
275
294
|
loader.collapse("#{__dir__}/*/actions")
|
276
295
|
```
|
277
296
|
|
297
|
+
<a id="markdown-testing-compliance" name="testing-compliance"></a>
|
298
|
+
### Testing compliance
|
299
|
+
|
300
|
+
When a managed file is loaded, Zeitwerk verifies the expected constant is defined. If it is not, `Zeitwerk::NameError` is raised.
|
301
|
+
|
302
|
+
So, an easy way to ensure compliance in the test suite is to eager load the project:
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
begin
|
306
|
+
loader.eager_load(force: true)
|
307
|
+
rescue Zeitwerk::NameError => e
|
308
|
+
flunk e.message
|
309
|
+
else
|
310
|
+
assert true
|
311
|
+
end
|
312
|
+
```
|
313
|
+
|
278
314
|
<a id="markdown-usage" name="usage"></a>
|
279
315
|
## Usage
|
280
316
|
|
@@ -377,7 +413,16 @@ Zeitwerk instances are able to eager load their managed files:
|
|
377
413
|
loader.eager_load
|
378
414
|
```
|
379
415
|
|
380
|
-
That skips [ignored files and directories](#ignoring-parts-of-the-project)
|
416
|
+
That skips [ignored files and directories](#ignoring-parts-of-the-project).
|
417
|
+
|
418
|
+
In gems, the method needs to be invoked after the main namespace has been defined, as shown in [Synopsis](https://github.com/fxn/zeitwerk#synopsis).
|
419
|
+
|
420
|
+
Eager loading is synchronized and idempotent.
|
421
|
+
|
422
|
+
<a id="markdown-eager-load-exclusions" name="eager-load-exclusions"></a>
|
423
|
+
#### Eager load exclusions
|
424
|
+
|
425
|
+
You can tell Zeitwerk that certain files or directories are autoloadable, but should not be eager loaded:
|
381
426
|
|
382
427
|
```ruby
|
383
428
|
db_adapters = "#{__dir__}/my_gem/db_adapters"
|
@@ -386,13 +431,20 @@ loader.setup
|
|
386
431
|
loader.eager_load # won't eager load the database adapters
|
387
432
|
```
|
388
433
|
|
389
|
-
|
434
|
+
However, that can be overridden with `force`:
|
390
435
|
|
391
|
-
|
436
|
+
```ruby
|
437
|
+
loader.eager_load(force: true) # database adapters are eager loaded
|
438
|
+
```
|
439
|
+
|
440
|
+
Which may be handy if the project eager loads in the test suite to [ensure project layour compliance](#testing-compliance).
|
392
441
|
|
393
|
-
|
442
|
+
The `force` flag does not affect ignored files and directories, those are still ignored.
|
394
443
|
|
395
|
-
|
444
|
+
<a id="markdown-global-eager-load" name="global-eager-load"></a>
|
445
|
+
#### Global eager load
|
446
|
+
|
447
|
+
If you want to eager load yourself and all dependencies that use Zeitwerk, you can broadcast the `eager_load` call to all instances:
|
396
448
|
|
397
449
|
```ruby
|
398
450
|
Zeitwerk::Loader.eager_load_all
|
@@ -402,6 +454,8 @@ This may be handy in top-level services, like web applications.
|
|
402
454
|
|
403
455
|
Note that thanks to idempotence `Zeitwerk::Loader.eager_load_all` won't eager load twice if any of the instances already eager loaded.
|
404
456
|
|
457
|
+
This method does not accept the `force` flag, since in general it wouldn't be a good idea to force eager loading in 3rd party code.
|
458
|
+
|
405
459
|
<a id="markdown-reloading" name="reloading"></a>
|
406
460
|
### Reloading
|
407
461
|
|
@@ -433,11 +487,13 @@ On reloading, client code has to update anything that would otherwise be storing
|
|
433
487
|
<a id="markdown-inflection" name="inflection"></a>
|
434
488
|
### Inflection
|
435
489
|
|
436
|
-
Each individual loader needs an inflector to figure out which constant path would a given file or directory map to. Zeitwerk ships with two basic inflectors.
|
490
|
+
Each individual loader needs an inflector to figure out which constant path would a given file or directory map to. Zeitwerk ships with two basic inflectors, and you can define your own.
|
437
491
|
|
438
492
|
<a id="markdown-zeitwerkinflector" name="zeitwerkinflector"></a>
|
439
493
|
#### Zeitwerk::Inflector
|
440
494
|
|
495
|
+
Each loader instantiated with `Zeitwerk::Loader.new` has an inflector of this type by default.
|
496
|
+
|
441
497
|
This is a very basic inflector that converts snake case to camel case:
|
442
498
|
|
443
499
|
```
|
@@ -464,16 +520,16 @@ loader.inflector.inflect "mysql_adapter" => "MySQLAdapter"
|
|
464
520
|
|
465
521
|
Overrides need to be configured before calling `setup`.
|
466
522
|
|
467
|
-
There are no inflection rules or global configuration that can affect this inflector. It is deterministic.
|
468
|
-
|
469
|
-
Loaders instantiated with `Zeitwerk::Loader.new` have an inflector of this type, independent of each other.
|
523
|
+
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.
|
470
524
|
|
471
525
|
<a id="markdown-zeitwerkgeminflector" name="zeitwerkgeminflector"></a>
|
472
526
|
#### Zeitwerk::GemInflector
|
473
527
|
|
528
|
+
Each loader instantiated with `Zeitwerk::Loader.for_gem` has an inflector of this type by default.
|
529
|
+
|
474
530
|
This inflector is like the basic one, except it expects `lib/my_gem/version.rb` to define `MyGem::VERSION`.
|
475
531
|
|
476
|
-
|
532
|
+
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.
|
477
533
|
|
478
534
|
<a id="markdown-custom-inflector" name="custom-inflector"></a>
|
479
535
|
#### Custom inflector
|
@@ -549,8 +605,26 @@ class MyGem::Inflector < Zeitwerk::GemInflector
|
|
549
605
|
end
|
550
606
|
```
|
551
607
|
|
608
|
+
<a id="markdown-callbacks" name="callbacks"></a>
|
609
|
+
### Callbacks
|
610
|
+
|
611
|
+
<a id="markdown-the-on_setup-callback" name="the-on_setup-callback"></a>
|
612
|
+
#### The on_setup callback
|
613
|
+
|
614
|
+
The `on_setup` callback is fired on setup and on each reload:
|
615
|
+
|
616
|
+
```ruby
|
617
|
+
loader.on_setup do
|
618
|
+
# Ready to autoload here.
|
619
|
+
end
|
620
|
+
```
|
621
|
+
|
622
|
+
Multiple `on_setup` callbacks are supported, and they run in order of definition.
|
623
|
+
|
624
|
+
If `setup` was already executed, the callback is fired immediately.
|
625
|
+
|
552
626
|
<a id="markdown-the-on_load-callback" name="the-on_load-callback"></a>
|
553
|
-
|
627
|
+
#### The on_load callback
|
554
628
|
|
555
629
|
The usual place to run something when a file is loaded is the file itself. However, sometimes you'd like to be called, and this is possible with the `on_load` callback.
|
556
630
|
|
@@ -580,7 +654,7 @@ end
|
|
580
654
|
|
581
655
|
Some uses cases:
|
582
656
|
|
583
|
-
* Doing something with
|
657
|
+
* Doing something with a reloadable class or module in a Rails application during initialization, in a way that plays well with reloading. As in the previous example.
|
584
658
|
* Delaying the execution of the block until the class is loaded for performance.
|
585
659
|
* Delaying the execution of the block until the class is loaded because it follows the adapter pattern and better not to load the class if the user does not need it.
|
586
660
|
|
@@ -608,8 +682,10 @@ There are use cases for this last catch-all callback, but they are rare. If you
|
|
608
682
|
|
609
683
|
If both types of callbacks are defined, the specific ones run first.
|
610
684
|
|
685
|
+
Since `on_load` callbacks are executed right after files are loaded, even if the loading context seems to be far away, in practice **the block is subject to [circular dependencies](#beware-of-circular-dependencies)**. As a rule of thumb, as far as loading order and its interdependencies is concerned, you have to program as if the block was executed at the bottom of the file just loaded.
|
686
|
+
|
611
687
|
<a id="markdown-the-on_unload-callback" name="the-on_unload-callback"></a>
|
612
|
-
|
688
|
+
#### The on_unload callback
|
613
689
|
|
614
690
|
When reloading is enabled, you may occasionally need to execute something before a certain autoloaded class or module is unloaded. The `on_unload` callback allows you to do that.
|
615
691
|
|
@@ -820,6 +896,26 @@ Trip = Struct.new { ... } # NOT SUPPORTED
|
|
820
896
|
|
821
897
|
This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
|
822
898
|
|
899
|
+
<a id="markdown-beware-of-circular-dependencies" name="beware-of-circular-dependencies"></a>
|
900
|
+
### Beware of circular dependencies
|
901
|
+
|
902
|
+
In Ruby, you can't have certain top-level circular dependencies. Take for example:
|
903
|
+
|
904
|
+
```ruby
|
905
|
+
# c.rb
|
906
|
+
class C < D
|
907
|
+
end
|
908
|
+
|
909
|
+
# d.rb
|
910
|
+
class D
|
911
|
+
C
|
912
|
+
end
|
913
|
+
```
|
914
|
+
|
915
|
+
In order to define `C`, you need to load `D`. However, the body of `D` refers to `C`.
|
916
|
+
|
917
|
+
Circular dependencies like those do not work in plain Ruby, and therefore do not work in projects managed by Zeitwerk either.
|
918
|
+
|
823
919
|
<a id="markdown-reopening-third-party-namespaces" name="reopening-third-party-namespaces"></a>
|
824
920
|
### Reopening third-party namespaces
|
825
921
|
|
@@ -874,6 +970,11 @@ With that, when Zeitwerk scans the file system and reaches the gem directories `
|
|
874
970
|
<a id="markdown-debuggers" name="debuggers"></a>
|
875
971
|
### Debuggers
|
876
972
|
|
973
|
+
<a id="markdown-debugrb" name="debugrb"></a>
|
974
|
+
#### debug.rb
|
975
|
+
|
976
|
+
The new [debug.rb](https://github.com/ruby/debug) gem and Zeitwerk seem to be compatible, as far as I can tell. This is the new debugger that is going to ship with Ruby 3.1.
|
977
|
+
|
877
978
|
<a id="markdown-break" name="break"></a>
|
878
979
|
#### Break
|
879
980
|
|
@@ -892,7 +993,11 @@ Zeitwerk and [Byebug](https://github.com/deivid-rodriguez/byebug) are incompatib
|
|
892
993
|
<a id="markdown-supported-ruby-versions" name="supported-ruby-versions"></a>
|
893
994
|
## Supported Ruby versions
|
894
995
|
|
895
|
-
Zeitwerk works with
|
996
|
+
Zeitwerk works with CRuby 2.5 and above.
|
997
|
+
|
998
|
+
On TruffleRuby all is good except for thread-safety. Right now, in TruffleRuby `Module#autoload` does not block threads accessing a constant that is being autoloaded. CRuby prevents such access to avoid concurrent threads from seeing partial evaluations of the corresponding file. Zeitwerk inherits autoloading thread-safety from this property. This is not an issue if your project gets eager loaded, or if you lazy load in single-threaded environments. (See https://github.com/oracle/truffleruby/issues/2431.)
|
999
|
+
|
1000
|
+
JRuby 9.3.0.0 is almost there. As of this writing, the test suite of Zeitwerk passes on JRuby except for three tests. (See https://github.com/jruby/jruby/issues/6781.)
|
896
1001
|
|
897
1002
|
<a id="markdown-testing" name="testing"></a>
|
898
1003
|
## Testing
|
@@ -923,9 +1028,19 @@ and run `bin/test`.
|
|
923
1028
|
<a id="markdown-motivation" name="motivation"></a>
|
924
1029
|
## Motivation
|
925
1030
|
|
926
|
-
|
1031
|
+
<a id="markdown-kernelrequire-is-brittle" name="kernelrequire-is-brittle"></a>
|
1032
|
+
### Kernel#require is brittle
|
1033
|
+
|
1034
|
+
Since `require` has global side-effects, and there is no static way to verify that you have issued the `require` calls for code that your file depends on, in practice it is very easy to forget some. That introduces bugs that depend on the load order.
|
1035
|
+
|
1036
|
+
Also, if the project has namespaces, setting things up and getting client code to load things in a consistent way needs discipline. For example, `require "foo/bar"` may define `Foo`, instead of reopen it. That may be a broken window, giving place to superclass mismatches or partially-defined namespaces.
|
1037
|
+
|
1038
|
+
With Zeitwerk, you just name things following conventions and done. Things are available everywhere, and descend is always orderly. Without effort and without broken windows.
|
1039
|
+
|
1040
|
+
<a id="markdown-rails-autoloading-was-brittle" name="rails-autoloading-was-brittle"></a>
|
1041
|
+
### Rails autoloading was brittle
|
927
1042
|
|
928
|
-
|
1043
|
+
Autoloading in Rails was based on `const_missing` up to Rails 5. That callback lacks fundamental information like the nesting or the resolution algorithm being used. Because of that, Rails autoloading was not able to match Ruby's semantics, and that introduced a [series of issues](https://guides.rubyonrails.org/v5.2/autoloading_and_reloading_constants.html#common-gotchas). Zeitwerk is based on a different technique and fixed Rails autoloading starting with Rails 6.
|
929
1044
|
|
930
1045
|
<a id="markdown-thanks" name="thanks"></a>
|
931
1046
|
## Thanks
|
data/lib/zeitwerk/autoloads.rb
CHANGED
data/lib/zeitwerk/error.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Zeitwerk
|
2
4
|
# Centralizes the logic for the trace point used to detect the creation of
|
3
5
|
# explicit namespaces, needed to descend into matching subdirectories right
|
@@ -41,7 +43,7 @@ module Zeitwerk
|
|
41
43
|
|
42
44
|
# @private
|
43
45
|
# @sig (Zeitwerk::Loader) -> void
|
44
|
-
def
|
46
|
+
def unregister_loader(loader)
|
45
47
|
cpaths.delete_if { |_cpath, l| l == loader }
|
46
48
|
disable_tracer_if_unneeded
|
47
49
|
end
|
data/lib/zeitwerk/kernel.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Kernel
|
4
4
|
module_function
|
5
5
|
|
6
|
-
# We are going to decorate
|
6
|
+
# We are going to decorate Kernel#require with two goals.
|
7
7
|
#
|
8
8
|
# First, by intercepting Kernel#require calls, we are able to autovivify
|
9
9
|
# modules on required directories, and also do internal housekeeping when
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "set"
|
2
4
|
require "securerandom"
|
3
5
|
|
@@ -54,6 +56,12 @@ module Zeitwerk::Loader::Config
|
|
54
56
|
# @sig Set[String]
|
55
57
|
attr_reader :eager_load_exclusions
|
56
58
|
|
59
|
+
# User-oriented callbacks to be fired on setup and on reload.
|
60
|
+
#
|
61
|
+
# @private
|
62
|
+
# @sig Array[{ () -> void }]
|
63
|
+
attr_reader :on_setup_callbacks
|
64
|
+
|
57
65
|
# User-oriented callbacks to be fired when a constant is loaded.
|
58
66
|
#
|
59
67
|
# @private
|
@@ -81,6 +89,7 @@ module Zeitwerk::Loader::Config
|
|
81
89
|
@collapse_dirs = Set.new
|
82
90
|
@eager_load_exclusions = Set.new
|
83
91
|
@reloading_enabled = false
|
92
|
+
@on_setup_callbacks = []
|
84
93
|
@on_load_callbacks = {}
|
85
94
|
@on_unload_callbacks = {}
|
86
95
|
@logger = self.class.default_logger
|
@@ -188,6 +197,17 @@ module Zeitwerk::Loader::Config
|
|
188
197
|
end
|
189
198
|
end
|
190
199
|
|
200
|
+
# Configure a block to be called after setup and on each reload.
|
201
|
+
# If setup was already done, the block runs immediately.
|
202
|
+
#
|
203
|
+
# @sig () { () -> void } -> void
|
204
|
+
def on_setup(&block)
|
205
|
+
mutex.synchronize do
|
206
|
+
on_setup_callbacks << block
|
207
|
+
block.call if @setup
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
191
211
|
# Configure a block to be invoked once a certain constant path is loaded.
|
192
212
|
# Supports multiple callbacks, and if there are many, they are executed in
|
193
213
|
# the order in which they were defined.
|
@@ -247,17 +267,10 @@ module Zeitwerk::Loader::Config
|
|
247
267
|
|
248
268
|
# @private
|
249
269
|
# @sig (String) -> bool
|
250
|
-
def
|
251
|
-
|
252
|
-
|
253
|
-
return false if dir.start_with?(ignored_path + "/")
|
270
|
+
def ignores?(abspath)
|
271
|
+
ignored_paths.any? do |ignored_path|
|
272
|
+
ignored_path == abspath || (dir?(ignored_path) && abspath.start_with?(ignored_path + "/"))
|
254
273
|
end
|
255
|
-
|
256
|
-
root_dirs.each_key do |root_dir|
|
257
|
-
return true if root_dir.start_with?(dir) || dir.start_with?(root_dir + "/")
|
258
|
-
end
|
259
|
-
|
260
|
-
false
|
261
274
|
end
|
262
275
|
|
263
276
|
private
|
data/lib/zeitwerk/loader.rb
CHANGED
@@ -110,6 +110,8 @@ module Zeitwerk
|
|
110
110
|
set_autoloads_in_dir(root_dir, namespace)
|
111
111
|
end
|
112
112
|
|
113
|
+
on_setup_callbacks.each(&:call)
|
114
|
+
|
113
115
|
@setup = true
|
114
116
|
end
|
115
117
|
end
|
@@ -121,7 +123,10 @@ module Zeitwerk
|
|
121
123
|
# else, they are eligible for garbage collection, which would effectively
|
122
124
|
# unload them.
|
123
125
|
#
|
124
|
-
#
|
126
|
+
# This method is public but undocumented. Main interface is `reload`, which
|
127
|
+
# means `unload` + `setup`. This one is avaiable to be used together with
|
128
|
+
# `unregister`, which is undocumented too.
|
129
|
+
#
|
125
130
|
# @sig () -> void
|
126
131
|
def unload
|
127
132
|
mutex.synchronize do
|
@@ -176,7 +181,7 @@ module Zeitwerk
|
|
176
181
|
lazy_subdirs.clear
|
177
182
|
|
178
183
|
Registry.on_unload(self)
|
179
|
-
ExplicitNamespace.
|
184
|
+
ExplicitNamespace.unregister_loader(self)
|
180
185
|
|
181
186
|
@setup = false
|
182
187
|
@eager_loaded = false
|
@@ -205,25 +210,28 @@ module Zeitwerk
|
|
205
210
|
# Eager loads all files in the root directories, recursively. Files do not
|
206
211
|
# need to be in `$LOAD_PATH`, absolute file names are used. Ignored files
|
207
212
|
# are not eager loaded. You can opt-out specifically in specific files and
|
208
|
-
# directories with `do_not_eager_load
|
213
|
+
# directories with `do_not_eager_load`, and that can be overridden passing
|
214
|
+
# `force: true`.
|
209
215
|
#
|
210
|
-
# @sig () -> void
|
211
|
-
def eager_load
|
216
|
+
# @sig (true | false) -> void
|
217
|
+
def eager_load(force: false)
|
212
218
|
mutex.synchronize do
|
213
219
|
break if @eager_loaded
|
214
220
|
|
215
221
|
log("eager load start") if logger
|
216
222
|
|
223
|
+
honour_exclusions = !force
|
224
|
+
|
217
225
|
queue = []
|
218
226
|
actual_root_dirs.each do |root_dir, namespace|
|
219
|
-
queue << [namespace, root_dir] unless excluded_from_eager_load?(root_dir)
|
227
|
+
queue << [namespace, root_dir] unless honour_exclusions && excluded_from_eager_load?(root_dir)
|
220
228
|
end
|
221
229
|
|
222
230
|
while to_eager_load = queue.shift
|
223
231
|
namespace, dir = to_eager_load
|
224
232
|
|
225
233
|
ls(dir) do |basename, abspath|
|
226
|
-
next if excluded_from_eager_load?(abspath)
|
234
|
+
next if honour_exclusions && excluded_from_eager_load?(abspath)
|
227
235
|
|
228
236
|
if ruby?(abspath)
|
229
237
|
if cref = autoloads.cref_for(abspath)
|
@@ -267,6 +275,15 @@ module Zeitwerk
|
|
267
275
|
to_unload.keys.freeze
|
268
276
|
end
|
269
277
|
|
278
|
+
# This is a dangerous method.
|
279
|
+
#
|
280
|
+
# @experimental
|
281
|
+
# @sig () -> void
|
282
|
+
def unregister
|
283
|
+
Registry.unregister_loader(self)
|
284
|
+
ExplicitNamespace.unregister_loader(self)
|
285
|
+
end
|
286
|
+
|
270
287
|
# --- Class methods ---------------------------------------------------------------------------
|
271
288
|
|
272
289
|
class << self
|
@@ -442,12 +459,21 @@ module Zeitwerk
|
|
442
459
|
def raise_if_conflicting_directory(dir)
|
443
460
|
self.class.mutex.synchronize do
|
444
461
|
Registry.loaders.each do |loader|
|
445
|
-
if loader
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
462
|
+
next if loader == self
|
463
|
+
next if loader.ignores?(dir)
|
464
|
+
|
465
|
+
dir = dir + "/"
|
466
|
+
loader.root_dirs.each do |root_dir, _namespace|
|
467
|
+
next if ignores?(root_dir)
|
468
|
+
|
469
|
+
root_dir = root_dir + "/"
|
470
|
+
if dir.start_with?(root_dir) || root_dir.start_with?(dir)
|
471
|
+
require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
|
472
|
+
raise Error,
|
473
|
+
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir.chop}," \
|
474
|
+
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
475
|
+
EOS
|
476
|
+
end
|
451
477
|
end
|
452
478
|
end
|
453
479
|
end
|
data/lib/zeitwerk/registry.rb
CHANGED
@@ -73,6 +73,15 @@ module Zeitwerk
|
|
73
73
|
loaders << loader
|
74
74
|
end
|
75
75
|
|
76
|
+
# @private
|
77
|
+
# @sig (Zeitwerk::Loader) -> void
|
78
|
+
def unregister_loader(loader)
|
79
|
+
loaders.delete(loader)
|
80
|
+
loaders_managing_gems.delete_if { |_, l| l == loader }
|
81
|
+
autoloads.delete_if { |_, l| l == loader }
|
82
|
+
inceptions.delete_if { |_, (_, l)| l == loader }
|
83
|
+
end
|
84
|
+
|
76
85
|
# This method returns always a loader, the same instance for the same root
|
77
86
|
# file. That is how Zeitwerk::Loader.for_gem is idempotent.
|
78
87
|
#
|
data/lib/zeitwerk/version.rb
CHANGED
data/lib/zeitwerk.rb
CHANGED
@@ -11,4 +11,15 @@ module Zeitwerk
|
|
11
11
|
require_relative "zeitwerk/kernel"
|
12
12
|
require_relative "zeitwerk/error"
|
13
13
|
require_relative "zeitwerk/version"
|
14
|
+
|
15
|
+
# This is a dangerous method.
|
16
|
+
#
|
17
|
+
# @experimental
|
18
|
+
# @sig () -> void
|
19
|
+
def self.with_loader
|
20
|
+
loader = Zeitwerk::Loader.new
|
21
|
+
yield loader
|
22
|
+
ensure
|
23
|
+
loader.unregister
|
24
|
+
end
|
14
25
|
end
|
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.0.
|
4
|
+
version: 2.5.0.beta5
|
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-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -59,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: 1.3.1
|
61
61
|
requirements: []
|
62
|
-
rubygems_version: 3.
|
62
|
+
rubygems_version: 3.2.22
|
63
63
|
signing_key:
|
64
64
|
specification_version: 4
|
65
65
|
summary: Efficient and thread-safe constant autoloader
|