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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ac5e0ce2eba71a2099ead24de8762e3e737656ff4bcae57c83bb5797e922f72
4
- data.tar.gz: 85e7c762a5164301ba5bcce82a31e6c49bc7c6caff2b4abff23c425f10374c69
3
+ metadata.gz: a57a618cc6c371488c6b2a1360b3bfb37b4914e7c44addd688dd9ff6d245f92b
4
+ data.tar.gz: 927300a0bec3a2941e79e3065068cb3359ced9a368a23204e31eb76c1de347bc
5
5
  SHA512:
6
- metadata.gz: f258acabd660f54702eb904550a109b08bdacff2cecc92d610376376f77ccf70ed421e09c64144930d2a145ebbedb7e9c3c26ff21ad2c1b40dd0537d9947b4d4
7
- data.tar.gz: 1612dc2d505fe37b748d81a9055e36a91f9f73267ac273b39a4cbea3736cdd1baa4c557e3059d8f3a9a9807d450e29f3d75c7cb18c94c37ded70768444313336
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
- - [The on_load callback](#the-on_load-callback)
34
- - [The on_unload callback](#the-on_unload-callback)
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), and you can also tell Zeitwerk that certain files or directories are autoloadable, but should not be eager loaded:
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
- 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).
434
+ However, that can be overridden with `force`:
390
435
 
391
- Eager loading is synchronized and idempotent.
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
- If eager loading a file does not define the expected class or module, Zeitwerk raises `Zeitwerk::NameError`, which is a subclass of `NameError`.
442
+ The `force` flag does not affect ignored files and directories, those are still ignored.
394
443
 
395
- If you want to eager load yourself and all dependencies using Zeitwerk, you can broadcast the `eager_load` call to all instances:
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
- Loaders instantiated with `Zeitwerk::Loader.for_gem` have an inflector of this type, independent of each other.
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
- ### The on_load callback
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 an autoloadable class or module in a Rails application during initialization, in a way that plays well with reloading. As in the previous example.
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
- ### The on_unload callback
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 MRI 2.4.4 and above.
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
- 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. Zeitwerk provides a way to forget about `require` in your own code, just name things following conventions and done.
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
- On the other hand, autoloading in Rails is based on `const_missing`, which lacks fundamental information like the nesting and the resolution algorithm that was being used. Because of that, Rails autoloading is not able to match Ruby's semantics and that introduces a series of gotchas. The original goal of this project was to bring a better autoloading mechanism for Rails 6.
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zeitwerk
2
4
  # @private
3
5
  class Autoloads
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zeitwerk
2
4
  class Error < StandardError
3
5
  end
@@ -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 unregister(loader)
46
+ def unregister_loader(loader)
45
47
  cpaths.delete_if { |_cpath, l| l == loader }
46
48
  disable_tracer_if_unneeded
47
49
  end
@@ -3,7 +3,7 @@
3
3
  module Kernel
4
4
  module_function
5
5
 
6
- # We are going to decorate Kerner#require with two goals.
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
  module Zeitwerk::Loader::Callbacks
2
4
  include Zeitwerk::RealModName
3
5
 
@@ -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 manages?(dir)
251
- dir = dir + "/"
252
- ignored_paths.each do |ignored_path|
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zeitwerk::Loader::Helpers
2
4
  private
3
5
 
@@ -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
- # @private
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.unregister(self)
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 != self && loader.manages?(dir)
446
- require "pp"
447
- raise Error,
448
- "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
449
- " which is already managed by\n\n#{loader.pretty_inspect}\n"
450
- EOS
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zeitwerk::RealModName
2
4
  UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
3
5
  private_constant :UNBOUND_METHOD_MODULE_NAME
@@ -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
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.5.0.beta"
4
+ VERSION = "2.5.0.beta5"
5
5
  end
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.beta
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-08-03 00:00:00.000000000 Z
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.1.6
62
+ rubygems_version: 3.2.22
63
63
  signing_key:
64
64
  specification_version: 4
65
65
  summary: Efficient and thread-safe constant autoloader