zeitwerk 2.4.1 → 2.5.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +221 -57
- data/lib/zeitwerk/autoloads.rb +69 -0
- data/lib/zeitwerk/explicit_namespace.rb +9 -3
- data/lib/zeitwerk/kernel.rb +6 -4
- data/lib/zeitwerk/loader/callbacks.rb +25 -6
- data/lib/zeitwerk/loader/config.rb +301 -0
- data/lib/zeitwerk/loader/helpers.rb +95 -0
- data/lib/zeitwerk/loader.rb +100 -394
- data/lib/zeitwerk/registry.rb +16 -7
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +13 -0
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c75f8cc8ed4e3df2f4bf5822402b5d1179e80d1bd2680efda63302f082dc366
|
4
|
+
data.tar.gz: ee91b83fb9c7e15c9502a3200c3d0a5cb785f5f2e9f75cd419e57bc335691784
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2ccad2ec4ca741a01432875eb683307eeb9840b278a3fff45ec74d3b1c2892c6953e7ef18b42cca40bfb09ca21f952c7aadf8653fc67f5e7a29b40e07e8bb74
|
7
|
+
data.tar.gz: b74645e665af729b8fd3a2fd6fcc6da86bcc953979f0f7db4b70fffa67e09e399615158acdfac996aaf083c28f538dde90f98b7682f9feb5331bdc498f2f24a5
|
data/README.md
CHANGED
@@ -3,44 +3,54 @@
|
|
3
3
|
|
4
4
|
|
5
5
|
[![Gem Version](https://img.shields.io/gem/v/zeitwerk.svg?style=for-the-badge)](https://rubygems.org/gems/zeitwerk)
|
6
|
-
[![Build Status](https://img.shields.io/
|
6
|
+
[![Build Status](https://img.shields.io/github/workflow/status/fxn/zeitwerk/CI?event=push&style=for-the-badge)](https://github.com/fxn/zeitwerk/actions?query=event%3Apush)
|
7
7
|
|
8
8
|
<!-- TOC -->
|
9
9
|
|
10
10
|
- [Introduction](#introduction)
|
11
11
|
- [Synopsis](#synopsis)
|
12
12
|
- [File structure](#file-structure)
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
- [The idea: File paths match constant paths](#the-idea-file-paths-match-constant-paths)
|
14
|
+
- [Inner simple constants](#inner-simple-constants)
|
15
|
+
- [Root directories and root namespaces](#root-directories-and-root-namespaces)
|
16
|
+
- [The default root namespace is `Object`](#the-default-root-namespace-is-object)
|
17
|
+
- [Custom root namespaces](#custom-root-namespaces)
|
16
18
|
- [Nested root directories](#nested-root-directories)
|
19
|
+
- [Implicit namespaces](#implicit-namespaces)
|
20
|
+
- [Explicit namespaces](#explicit-namespaces)
|
21
|
+
- [Collapsing directories](#collapsing-directories)
|
17
22
|
- [Usage](#usage)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
- [
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
- [
|
35
|
-
- [
|
36
|
-
- [
|
37
|
-
|
38
|
-
|
39
|
-
|
23
|
+
- [Setup](#setup)
|
24
|
+
- [Generic](#generic)
|
25
|
+
- [for_gem](#for_gem)
|
26
|
+
- [Autoloading](#autoloading)
|
27
|
+
- [Eager loading](#eager-loading)
|
28
|
+
- [Reloading](#reloading)
|
29
|
+
- [Inflection](#inflection)
|
30
|
+
- [Zeitwerk::Inflector](#zeitwerkinflector)
|
31
|
+
- [Zeitwerk::GemInflector](#zeitwerkgeminflector)
|
32
|
+
- [Custom inflector](#custom-inflector)
|
33
|
+
- [The on_load callback](#the-on_load-callback)
|
34
|
+
- [The on_unload callback](#the-on_unload-callback)
|
35
|
+
- [Technical details](#technical-details)
|
36
|
+
- [Logging](#logging)
|
37
|
+
- [Loader tag](#loader-tag)
|
38
|
+
- [Ignoring parts of the project](#ignoring-parts-of-the-project)
|
39
|
+
- [Use case: Files that do not follow the conventions](#use-case-files-that-do-not-follow-the-conventions)
|
40
|
+
- [Use case: The adapter pattern](#use-case-the-adapter-pattern)
|
41
|
+
- [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
|
42
|
+
- [Edge cases](#edge-cases)
|
43
|
+
- [Reopening third-party namespaces](#reopening-third-party-namespaces)
|
44
|
+
- [Rules of thumb](#rules-of-thumb)
|
45
|
+
- [Debuggers](#debuggers)
|
46
|
+
- [Break](#break)
|
47
|
+
- [Byebug](#byebug)
|
40
48
|
- [Pronunciation](#pronunciation)
|
41
49
|
- [Supported Ruby versions](#supported-ruby-versions)
|
42
50
|
- [Testing](#testing)
|
43
51
|
- [Motivation](#motivation)
|
52
|
+
- [Kerner#require is brittle](#kernerrequire-is-brittle)
|
53
|
+
- [Rails autoloading was brittle](#rails-autoloading-was-brittle)
|
44
54
|
- [Thanks](#thanks)
|
45
55
|
- [License](#license)
|
46
56
|
|
@@ -116,6 +126,9 @@ Zeitwerk::Loader.eager_load_all
|
|
116
126
|
<a id="markdown-file-structure" name="file-structure"></a>
|
117
127
|
## File structure
|
118
128
|
|
129
|
+
<a id="markdown-the-idea-file-paths-match-constant-paths" name="the-idea-file-paths-match-constant-paths"></a>
|
130
|
+
### The idea: File paths match constant paths
|
131
|
+
|
119
132
|
To have a file structure Zeitwerk can work with, just name files and directories after the name of the classes and modules they define:
|
120
133
|
|
121
134
|
```
|
@@ -125,25 +138,57 @@ lib/my_gem/bar_baz.rb -> MyGem::BarBaz
|
|
125
138
|
lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo
|
126
139
|
```
|
127
140
|
|
128
|
-
|
141
|
+
You can tune that a bit by [collapsing directories](#collapsing-directories), or by [ignoring parts of the project](#ignoring-parts-of-the-project), but that is the main idea.
|
142
|
+
|
143
|
+
<a id="markdown-inner-simple-constants" name="inner-simple-constants"></a>
|
144
|
+
### Inner simple constants
|
145
|
+
|
146
|
+
While a simple constant like `HttpCrawler::MAX_RETRIES` can be defined in its own file:
|
129
147
|
|
130
148
|
```ruby
|
131
|
-
|
132
|
-
|
149
|
+
# http_crawler/max_retries.rb
|
150
|
+
HttpCrawler::MAX_RETRIES = 10
|
133
151
|
```
|
134
152
|
|
135
|
-
|
153
|
+
that is not required, you can also define it the regular way:
|
136
154
|
|
155
|
+
```ruby
|
156
|
+
# http_crawler.rb
|
157
|
+
class HttpCrawler
|
158
|
+
MAX_RETRIES = 10
|
159
|
+
end
|
137
160
|
```
|
138
|
-
|
139
|
-
|
161
|
+
|
162
|
+
<a id="markdown-root-directories-and-root-namespaces" name="root-directories-and-root-namespaces"></a>
|
163
|
+
### Root directories and root namespaces
|
164
|
+
|
165
|
+
Every directory configured with `push_dir` is called a _root directory_, and they represent _root namespaces_.
|
166
|
+
|
167
|
+
<a id="markdown-the-default-root-namespace-is-object" name="the-default-root-namespace-is-object"></a>
|
168
|
+
#### The default root namespace is `Object`
|
169
|
+
|
170
|
+
By default, the namespace associated to a root directory is the top-level one: `Object`.
|
171
|
+
|
172
|
+
For example, given
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
loader.push_dir("#{__dir__}/models")
|
176
|
+
loader.push_dir("#{__dir__}/serializers"))
|
140
177
|
```
|
141
178
|
|
142
|
-
|
179
|
+
these are the expected classes and modules being defined by these files:
|
143
180
|
|
144
|
-
|
181
|
+
```
|
182
|
+
models/user.rb -> User
|
183
|
+
serializers/user_serializer.rb -> UserSerializer
|
184
|
+
```
|
185
|
+
|
186
|
+
<a id="markdown-custom-root-namespaces" name="custom-root-namespaces"></a>
|
187
|
+
#### Custom root namespaces
|
145
188
|
|
146
|
-
|
189
|
+
While `Object` is by far the most common root namespace, you can associate a different one to a particular root directory. The method `push_dir` accepts a class or module object in the optional `namespace` keyword argument.
|
190
|
+
|
191
|
+
For example, given:
|
147
192
|
|
148
193
|
```ruby
|
149
194
|
require "active_job"
|
@@ -151,9 +196,26 @@ require "active_job/queue_adapters"
|
|
151
196
|
loader.push_dir("#{__dir__}/adapters", namespace: ActiveJob::QueueAdapters)
|
152
197
|
```
|
153
198
|
|
154
|
-
|
199
|
+
a file defining `ActiveJob::QueueAdapters::MyQueueAdapter` does not need the conventional parent directories, you can (and have to) store the file directly below `adapters`:
|
155
200
|
|
156
|
-
|
201
|
+
```
|
202
|
+
adapters/my_queue_adapter.rb -> ActiveJob::QueueAdapters::MyQueueAdapter
|
203
|
+
```
|
204
|
+
|
205
|
+
Please, note that the given root namespace must be non-reloadable, though autoloaded constants in that namespace can be. That is, if you associate `app/api` with an existing `Api` module, that module should not be reloadable. However, if the project defines and autoloads the class `Api::Deliveries`, that one can be reloaded.
|
206
|
+
|
207
|
+
<a id="markdown-nested-root-directories" name="nested-root-directories"></a>
|
208
|
+
#### Nested root directories
|
209
|
+
|
210
|
+
Root directories should not be ideally nested, but Zeitwerk supports them because in Rails, for example, both `app/models` and `app/models/concerns` belong to the autoload paths.
|
211
|
+
|
212
|
+
Zeitwerk detects nested root directories, and treats them as roots only. In the example above, `concerns` is not considered to be a namespace below `app/models`. For example, the file:
|
213
|
+
|
214
|
+
```
|
215
|
+
app/models/concerns/geolocatable.rb
|
216
|
+
```
|
217
|
+
|
218
|
+
should define `Geolocatable`, not `Concerns::Geolocatable`.
|
157
219
|
|
158
220
|
<a id="markdown-implicit-namespaces" name="implicit-namespaces"></a>
|
159
221
|
### Implicit namespaces
|
@@ -215,19 +277,6 @@ To illustrate usage of glob patterns, if `actions` in the example above is part
|
|
215
277
|
loader.collapse("#{__dir__}/*/actions")
|
216
278
|
```
|
217
279
|
|
218
|
-
<a id="markdown-nested-root-directories" name="nested-root-directories"></a>
|
219
|
-
### Nested root directories
|
220
|
-
|
221
|
-
Root directories should not be ideally nested, but Zeitwerk supports them because in Rails, for example, both `app/models` and `app/models/concerns` belong to the autoload paths.
|
222
|
-
|
223
|
-
Zeitwerk detects nested root directories, and treats them as roots only. In the example above, `concerns` is not considered to be a namespace below `app/models`. For example, the file:
|
224
|
-
|
225
|
-
```
|
226
|
-
app/models/concerns/geolocatable.rb
|
227
|
-
```
|
228
|
-
|
229
|
-
should define `Geolocatable`, not `Concerns::Geolocatable`.
|
230
|
-
|
231
280
|
<a id="markdown-usage" name="usage"></a>
|
232
281
|
## Usage
|
233
282
|
|
@@ -386,11 +435,13 @@ On reloading, client code has to update anything that would otherwise be storing
|
|
386
435
|
<a id="markdown-inflection" name="inflection"></a>
|
387
436
|
### Inflection
|
388
437
|
|
389
|
-
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.
|
438
|
+
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.
|
390
439
|
|
391
440
|
<a id="markdown-zeitwerkinflector" name="zeitwerkinflector"></a>
|
392
441
|
#### Zeitwerk::Inflector
|
393
442
|
|
443
|
+
Each loader instantiated with `Zeitwerk::Loader.new` has an inflector of this type by default.
|
444
|
+
|
394
445
|
This is a very basic inflector that converts snake case to camel case:
|
395
446
|
|
396
447
|
```
|
@@ -417,16 +468,16 @@ loader.inflector.inflect "mysql_adapter" => "MySQLAdapter"
|
|
417
468
|
|
418
469
|
Overrides need to be configured before calling `setup`.
|
419
470
|
|
420
|
-
There are no inflection rules or global configuration that can affect this inflector. It is deterministic.
|
421
|
-
|
422
|
-
Loaders instantiated with `Zeitwerk::Loader.new` have an inflector of this type, independent of each other.
|
471
|
+
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.
|
423
472
|
|
424
473
|
<a id="markdown-zeitwerkgeminflector" name="zeitwerkgeminflector"></a>
|
425
474
|
#### Zeitwerk::GemInflector
|
426
475
|
|
476
|
+
Each loader instantiated with `Zeitwerk::Loader.for_gem` has an inflector of this type by default.
|
477
|
+
|
427
478
|
This inflector is like the basic one, except it expects `lib/my_gem/version.rb` to define `MyGem::VERSION`.
|
428
479
|
|
429
|
-
|
480
|
+
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.
|
430
481
|
|
431
482
|
<a id="markdown-custom-inflector" name="custom-inflector"></a>
|
432
483
|
#### Custom inflector
|
@@ -502,6 +553,109 @@ class MyGem::Inflector < Zeitwerk::GemInflector
|
|
502
553
|
end
|
503
554
|
```
|
504
555
|
|
556
|
+
<a id="markdown-the-on_load-callback" name="the-on_load-callback"></a>
|
557
|
+
### The on_load callback
|
558
|
+
|
559
|
+
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.
|
560
|
+
|
561
|
+
For example, let's imagine this class belongs to a Rails application:
|
562
|
+
|
563
|
+
```ruby
|
564
|
+
class SomeApiClient
|
565
|
+
class << self
|
566
|
+
attr_accessor :endpoint
|
567
|
+
end
|
568
|
+
end
|
569
|
+
```
|
570
|
+
|
571
|
+
With `on_load`, it is easy to schedule code at boot time that initializes `endpoint` according to the configuration:
|
572
|
+
|
573
|
+
```ruby
|
574
|
+
# config/environments/development.rb
|
575
|
+
loader.on_load("SomeApiClient") do |klass, _abspath|
|
576
|
+
klass.endpoint = "https://api.dev"
|
577
|
+
end
|
578
|
+
|
579
|
+
# config/environments/production.rb
|
580
|
+
loader.on_load("SomeApiClient") do |klass, _abspath|
|
581
|
+
klass.endpoint = "https://api.prod"
|
582
|
+
end
|
583
|
+
```
|
584
|
+
|
585
|
+
Some uses cases:
|
586
|
+
|
587
|
+
* 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.
|
588
|
+
* Delaying the execution of the block until the class is loaded for performance.
|
589
|
+
* 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.
|
590
|
+
|
591
|
+
`on_load` gets a target constant path as a string (e.g., "User", or "Service::NotificationsGateway"). When fired, its block receives the stored value, and the absolute path to the corresponding file or directory as a string. The callback is executed every time the target is loaded. That includes reloads.
|
592
|
+
|
593
|
+
Multiple callbacks on the same target are supported, and they run in order of definition.
|
594
|
+
|
595
|
+
The block is executed once the loader has loaded the target. In particular, if the target was already loaded when the callback is defined, the block won't run. But if you reload and load the target again, then it will. Normally, you'll want to define `on_load` callbacks before `setup`.
|
596
|
+
|
597
|
+
Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
|
598
|
+
|
599
|
+
It is also possible to be called when any constant managed by the loader is loaded:
|
600
|
+
|
601
|
+
```ruby
|
602
|
+
loader.on_load do |cpath, value, abspath|
|
603
|
+
# ...
|
604
|
+
end
|
605
|
+
```
|
606
|
+
|
607
|
+
The block gets the constant path as a string (e.g., "User", or "Foo::VERSION"), the value it stores (e.g., the class object stored in `User`, or "2.5.0"), and the absolute path to the corresponding file or directory as a string.
|
608
|
+
|
609
|
+
Multiple callbacks like these are supported, and they run in order of definition.
|
610
|
+
|
611
|
+
There are use cases for this last catch-all callback, but they are rare. If you just need to understand how things are being loaded for debugging purposes, please remember that `Zeitwerk::Loader#log!` logs plenty of information.
|
612
|
+
|
613
|
+
If both types of callbacks are defined, the specific ones run first.
|
614
|
+
|
615
|
+
<a id="markdown-the-on_unload-callback" name="the-on_unload-callback"></a>
|
616
|
+
### The on_unload callback
|
617
|
+
|
618
|
+
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.
|
619
|
+
|
620
|
+
For example, let's imagine that a `Country` class fetches a list of countries and caches them when it is loaded. You might want to clear that cache if unloaded:
|
621
|
+
|
622
|
+
```ruby
|
623
|
+
loader.on_unload("Country") do |klass, _abspath|
|
624
|
+
klass.clear_cache
|
625
|
+
end
|
626
|
+
```
|
627
|
+
|
628
|
+
`on_unload` gets a target constant path as a string (e.g., "User", or "Service::NotificationsGateway"). When fired, its block receives the stored value, and the absolute path to the corresponding file or directory as a string. The callback is executed every time the target is unloaded.
|
629
|
+
|
630
|
+
`on_unload` blocks are executed before the class is unloaded, but in the middle of unloading, which happens in an unspecified order. Therefore, **that callback should not refer to any reloadable constant because there is no guarantee the constant works there**. Those blocks should rely on objects only, as in the example above, or regular constants not managed by the loader. This remark is transitive, applies to any methods invoked within the block.
|
631
|
+
|
632
|
+
Multiple callbacks on the same target are supported, and they run in order of definition.
|
633
|
+
|
634
|
+
Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
|
635
|
+
|
636
|
+
It is also possible to be called when any constant managed by the loader is unloaded:
|
637
|
+
|
638
|
+
```ruby
|
639
|
+
loader.on_unload do |cpath, value, abspath|
|
640
|
+
# ...
|
641
|
+
end
|
642
|
+
```
|
643
|
+
|
644
|
+
The block gets the constant path as a string (e.g., "User", or "Foo::VERSION"), the value it stores (e.g., the class object stored in `User`, or "2.5.0"), and the absolute path to the corresponding file or directory as a string.
|
645
|
+
|
646
|
+
Multiple callbacks like these are supported, and they run in order of definition.
|
647
|
+
|
648
|
+
If both types of callbacks are defined, the specific ones run first.
|
649
|
+
|
650
|
+
<a id="markdown-technical-details" name="technical-details"></a>
|
651
|
+
#### Technical details
|
652
|
+
|
653
|
+
Zeitwerk uses the word "unload" to ease communication and for symmetry with `on_load`. However, in Ruby you cannot unload things for real. So, when does `on_unload` technically happen?
|
654
|
+
|
655
|
+
When unloading, Zeitwerk issues `Module#remove_const` calls. Classes and modules are no longer reachable through their constants, and `on_unload` callbacks are executed right before those calls.
|
656
|
+
|
657
|
+
Technically, though, the objects themselves are still alive, but if everything is used as expected and they are not stored in any non-reloadable place (don't do that), they are ready for garbage collection, which is when the real unloading happens.
|
658
|
+
|
505
659
|
<a id="markdown-logging" name="logging"></a>
|
506
660
|
### Logging
|
507
661
|
|
@@ -773,9 +927,19 @@ and run `bin/test`.
|
|
773
927
|
<a id="markdown-motivation" name="motivation"></a>
|
774
928
|
## Motivation
|
775
929
|
|
776
|
-
|
930
|
+
<a id="markdown-kernerrequire-is-brittle" name="kernerrequire-is-brittle"></a>
|
931
|
+
### Kerner#require is brittle
|
932
|
+
|
933
|
+
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.
|
934
|
+
|
935
|
+
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.
|
936
|
+
|
937
|
+
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.
|
938
|
+
|
939
|
+
<a id="markdown-rails-autoloading-was-brittle" name="rails-autoloading-was-brittle"></a>
|
940
|
+
### Rails autoloading was brittle
|
777
941
|
|
778
|
-
|
942
|
+
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. Zeitwerk is based on a different technique and fixed Rails autoloading starting with Rails 6.
|
779
943
|
|
780
944
|
<a id="markdown-thanks" name="thanks"></a>
|
781
945
|
## Thanks
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Zeitwerk
|
2
|
+
# @private
|
3
|
+
class Autoloads
|
4
|
+
# Maps crefs for which an autoload has been defined to the corresponding
|
5
|
+
# absolute path.
|
6
|
+
#
|
7
|
+
# [Object, :User] => "/Users/fxn/blog/app/models/user.rb"
|
8
|
+
# [Object, :Hotel] => "/Users/fxn/blog/app/models/hotel"
|
9
|
+
# ...
|
10
|
+
#
|
11
|
+
# This colection is transient, callbacks delete its entries as autoloads get
|
12
|
+
# executed.
|
13
|
+
#
|
14
|
+
# @sig Hash[[Module, Symbol], String]
|
15
|
+
attr_reader :c2a
|
16
|
+
|
17
|
+
# This is the inverse of c2a, for inverse lookups.
|
18
|
+
#
|
19
|
+
# @sig Hash[String, [Module, Symbol]]
|
20
|
+
attr_reader :a2c
|
21
|
+
|
22
|
+
# @sig () -> void
|
23
|
+
def initialize
|
24
|
+
@c2a = {}
|
25
|
+
@a2c = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# @sig (Module, Symbol, String) -> void
|
29
|
+
def define(parent, cname, abspath)
|
30
|
+
parent.autoload(cname, abspath)
|
31
|
+
cref = [parent, cname]
|
32
|
+
c2a[cref] = abspath
|
33
|
+
a2c[abspath] = cref
|
34
|
+
end
|
35
|
+
|
36
|
+
# @sig () { () -> [[Module, Symbol], String] } -> void
|
37
|
+
def each(&block)
|
38
|
+
c2a.each(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @sig (Module, Symbol) -> String?
|
42
|
+
def abspath_for(parent, cname)
|
43
|
+
c2a[[parent, cname]]
|
44
|
+
end
|
45
|
+
|
46
|
+
# @sig (String) -> [Module, Symbol]?
|
47
|
+
def cref_for(abspath)
|
48
|
+
a2c[abspath]
|
49
|
+
end
|
50
|
+
|
51
|
+
# @sig (String) -> [Module, Symbol]?
|
52
|
+
def delete(abspath)
|
53
|
+
cref = a2c.delete(abspath)
|
54
|
+
c2a.delete(cref)
|
55
|
+
cref
|
56
|
+
end
|
57
|
+
|
58
|
+
# @sig () -> void
|
59
|
+
def clear
|
60
|
+
c2a.clear
|
61
|
+
a2c.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
# @sig () -> bool
|
65
|
+
def empty?
|
66
|
+
c2a.empty? && a2c.empty?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|