zeitwerk 2.4.0 → 2.5.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +209 -57
- data/lib/zeitwerk.rb +13 -0
- data/lib/zeitwerk/autoloads.rb +69 -0
- data/lib/zeitwerk/explicit_namespace.rb +18 -11
- data/lib/zeitwerk/gem_inflector.rb +2 -4
- data/lib/zeitwerk/inflector.rb +3 -6
- data/lib/zeitwerk/kernel.rb +7 -6
- data/lib/zeitwerk/loader.rb +116 -456
- data/lib/zeitwerk/loader/callbacks.rb +28 -12
- data/lib/zeitwerk/loader/config.rb +308 -0
- data/lib/zeitwerk/loader/helpers.rb +95 -0
- data/lib/zeitwerk/real_mod_name.rb +1 -2
- data/lib/zeitwerk/registry.rb +28 -30
- data/lib/zeitwerk/version.rb +1 -1
- metadata +12 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64ea17faefd07265c96bd8c49c9a4e14170612f85f868c03df91aa4511e473e3
|
4
|
+
data.tar.gz: fb98b0f841e49016039d73310d50563e8e6c3bbcf4c16f2d4441b241cb266647
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3dfdc023489a4b7306c2bbac6f56234ff4a80887820967ff691fd9b13609ea256ba5bca301870d5e5b35d26c09ece01158a984d3093fa63d0de0bd3d9cdff458
|
7
|
+
data.tar.gz: 4860a04e9e489251ce43e56f37c7213ac3dda7c301f9dff1826d7e5f2567b992bfac35f645dab67a356bb8a4024c29d0fd8431f91896f7a254bf747864dd8ef0
|
data/README.md
CHANGED
@@ -3,40 +3,48 @@
|
|
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)
|
@@ -116,6 +124,9 @@ Zeitwerk::Loader.eager_load_all
|
|
116
124
|
<a id="markdown-file-structure" name="file-structure"></a>
|
117
125
|
## File structure
|
118
126
|
|
127
|
+
<a id="markdown-the-idea-file-paths-match-constant-paths" name="the-idea-file-paths-match-constant-paths"></a>
|
128
|
+
### The idea: File paths match constant paths
|
129
|
+
|
119
130
|
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
131
|
|
121
132
|
```
|
@@ -125,25 +136,57 @@ lib/my_gem/bar_baz.rb -> MyGem::BarBaz
|
|
125
136
|
lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo
|
126
137
|
```
|
127
138
|
|
128
|
-
|
139
|
+
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.
|
140
|
+
|
141
|
+
<a id="markdown-inner-simple-constants" name="inner-simple-constants"></a>
|
142
|
+
### Inner simple constants
|
143
|
+
|
144
|
+
While a simple constant like `HttpCrawler::MAX_RETRIES` can be defined in its own file:
|
129
145
|
|
130
146
|
```ruby
|
131
|
-
|
132
|
-
|
147
|
+
# http_crawler/max_retries.rb
|
148
|
+
HttpCrawler::MAX_RETRIES = 10
|
133
149
|
```
|
134
150
|
|
135
|
-
|
151
|
+
that is not required, you can also define it the regular way:
|
136
152
|
|
153
|
+
```ruby
|
154
|
+
# http_crawler.rb
|
155
|
+
class HttpCrawler
|
156
|
+
MAX_RETRIES = 10
|
157
|
+
end
|
137
158
|
```
|
138
|
-
|
139
|
-
|
159
|
+
|
160
|
+
<a id="markdown-root-directories-and-root-namespaces" name="root-directories-and-root-namespaces"></a>
|
161
|
+
### Root directories and root namespaces
|
162
|
+
|
163
|
+
Every directory configured with `push_dir` is called a _root directory_, and they represent _root namespaces_.
|
164
|
+
|
165
|
+
<a id="markdown-the-default-root-namespace-is-object" name="the-default-root-namespace-is-object"></a>
|
166
|
+
#### The default root namespace is `Object`
|
167
|
+
|
168
|
+
By default, the namespace associated to a root directory is the top-level one: `Object`.
|
169
|
+
|
170
|
+
For example, given
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
loader.push_dir("#{__dir__}/models")
|
174
|
+
loader.push_dir("#{__dir__}/serializers"))
|
140
175
|
```
|
141
176
|
|
142
|
-
|
177
|
+
these are the expected classes and modules being defined by these files:
|
143
178
|
|
144
|
-
|
179
|
+
```
|
180
|
+
models/user.rb -> User
|
181
|
+
serializers/user_serializer.rb -> UserSerializer
|
182
|
+
```
|
183
|
+
|
184
|
+
<a id="markdown-custom-root-namespaces" name="custom-root-namespaces"></a>
|
185
|
+
#### Custom root namespaces
|
186
|
+
|
187
|
+
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.
|
145
188
|
|
146
|
-
|
189
|
+
For example, given:
|
147
190
|
|
148
191
|
```ruby
|
149
192
|
require "active_job"
|
@@ -151,9 +194,26 @@ require "active_job/queue_adapters"
|
|
151
194
|
loader.push_dir("#{__dir__}/adapters", namespace: ActiveJob::QueueAdapters)
|
152
195
|
```
|
153
196
|
|
154
|
-
|
197
|
+
a file defining `ActiveJob::QueueAdapters::MyQueueAdapter` does not need the conventional parent directories, you can (and have to) store the file directly below `adapters`:
|
198
|
+
|
199
|
+
```
|
200
|
+
adapters/my_queue_adapter.rb -> ActiveJob::QueueAdapters::MyQueueAdapter
|
201
|
+
```
|
155
202
|
|
156
|
-
Please, note that the given 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::
|
203
|
+
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.
|
204
|
+
|
205
|
+
<a id="markdown-nested-root-directories" name="nested-root-directories"></a>
|
206
|
+
#### Nested root directories
|
207
|
+
|
208
|
+
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.
|
209
|
+
|
210
|
+
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:
|
211
|
+
|
212
|
+
```
|
213
|
+
app/models/concerns/geolocatable.rb
|
214
|
+
```
|
215
|
+
|
216
|
+
should define `Geolocatable`, not `Concerns::Geolocatable`.
|
157
217
|
|
158
218
|
<a id="markdown-implicit-namespaces" name="implicit-namespaces"></a>
|
159
219
|
### Implicit namespaces
|
@@ -202,7 +262,7 @@ booking/actions/create.rb -> Booking::Create
|
|
202
262
|
To make it work that way, configure Zeitwerk to collapse said directory:
|
203
263
|
|
204
264
|
```ruby
|
205
|
-
loader.collapse("booking/actions")
|
265
|
+
loader.collapse("#{__dir__}/booking/actions")
|
206
266
|
```
|
207
267
|
|
208
268
|
This method accepts an arbitrary number of strings or `Pathname` objects, and also an array of them.
|
@@ -212,22 +272,9 @@ You can pass directories and glob patterns. Glob patterns are expanded when they
|
|
212
272
|
To illustrate usage of glob patterns, if `actions` in the example above is part of a standardized structure, you could use a wildcard:
|
213
273
|
|
214
274
|
```ruby
|
215
|
-
loader.collapse("
|
275
|
+
loader.collapse("#{__dir__}/*/actions")
|
216
276
|
```
|
217
277
|
|
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
278
|
<a id="markdown-usage" name="usage"></a>
|
232
279
|
## Usage
|
233
280
|
|
@@ -386,11 +433,13 @@ On reloading, client code has to update anything that would otherwise be storing
|
|
386
433
|
<a id="markdown-inflection" name="inflection"></a>
|
387
434
|
### Inflection
|
388
435
|
|
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.
|
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, and you can define your own.
|
390
437
|
|
391
438
|
<a id="markdown-zeitwerkinflector" name="zeitwerkinflector"></a>
|
392
439
|
#### Zeitwerk::Inflector
|
393
440
|
|
441
|
+
Each loader instantiated with `Zeitwerk::Loader.new` has an inflector of this type by default.
|
442
|
+
|
394
443
|
This is a very basic inflector that converts snake case to camel case:
|
395
444
|
|
396
445
|
```
|
@@ -417,16 +466,16 @@ loader.inflector.inflect "mysql_adapter" => "MySQLAdapter"
|
|
417
466
|
|
418
467
|
Overrides need to be configured before calling `setup`.
|
419
468
|
|
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.
|
469
|
+
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
470
|
|
424
471
|
<a id="markdown-zeitwerkgeminflector" name="zeitwerkgeminflector"></a>
|
425
472
|
#### Zeitwerk::GemInflector
|
426
473
|
|
474
|
+
Each loader instantiated with `Zeitwerk::Loader.for_gem` has an inflector of this type by default.
|
475
|
+
|
427
476
|
This inflector is like the basic one, except it expects `lib/my_gem/version.rb` to define `MyGem::VERSION`.
|
428
477
|
|
429
|
-
|
478
|
+
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
479
|
|
431
480
|
<a id="markdown-custom-inflector" name="custom-inflector"></a>
|
432
481
|
#### Custom inflector
|
@@ -502,6 +551,109 @@ class MyGem::Inflector < Zeitwerk::GemInflector
|
|
502
551
|
end
|
503
552
|
```
|
504
553
|
|
554
|
+
<a id="markdown-the-on_load-callback" name="the-on_load-callback"></a>
|
555
|
+
### The on_load callback
|
556
|
+
|
557
|
+
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.
|
558
|
+
|
559
|
+
For example, let's imagine this class belongs to a Rails application:
|
560
|
+
|
561
|
+
```ruby
|
562
|
+
class SomeApiClient
|
563
|
+
class << self
|
564
|
+
attr_accessor :endpoint
|
565
|
+
end
|
566
|
+
end
|
567
|
+
```
|
568
|
+
|
569
|
+
With `on_load`, it is easy to schedule code at boot time that initializes `endpoint` according to the configuration:
|
570
|
+
|
571
|
+
```ruby
|
572
|
+
# config/environments/development.rb
|
573
|
+
loader.on_load("SomeApiClient") do |klass, _abspath|
|
574
|
+
klass.endpoint = "https://api.dev"
|
575
|
+
end
|
576
|
+
|
577
|
+
# config/environments/production.rb
|
578
|
+
loader.on_load("SomeApiClient") do |klass, _abspath|
|
579
|
+
klass.endpoint = "https://api.prod"
|
580
|
+
end
|
581
|
+
```
|
582
|
+
|
583
|
+
Some uses cases:
|
584
|
+
|
585
|
+
* 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.
|
586
|
+
* Delaying the execution of the block until the class is loaded for performance.
|
587
|
+
* 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.
|
588
|
+
|
589
|
+
`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.
|
590
|
+
|
591
|
+
Multiple callbacks on the same target are supported, and they run in order of definition.
|
592
|
+
|
593
|
+
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`.
|
594
|
+
|
595
|
+
Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
|
596
|
+
|
597
|
+
It is also possible to be called when any constant managed by the loader is loaded:
|
598
|
+
|
599
|
+
```ruby
|
600
|
+
loader.on_load do |cpath, value, abspath|
|
601
|
+
# ...
|
602
|
+
end
|
603
|
+
```
|
604
|
+
|
605
|
+
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.
|
606
|
+
|
607
|
+
Multiple callbacks like these are supported, and they run in order of definition.
|
608
|
+
|
609
|
+
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.
|
610
|
+
|
611
|
+
If both types of callbacks are defined, the specific ones run first.
|
612
|
+
|
613
|
+
<a id="markdown-the-on_unload-callback" name="the-on_unload-callback"></a>
|
614
|
+
### The on_unload callback
|
615
|
+
|
616
|
+
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.
|
617
|
+
|
618
|
+
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:
|
619
|
+
|
620
|
+
```ruby
|
621
|
+
loader.on_unload("Country") do |klass, _abspath|
|
622
|
+
klass.clear_cache
|
623
|
+
end
|
624
|
+
```
|
625
|
+
|
626
|
+
`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.
|
627
|
+
|
628
|
+
`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.
|
629
|
+
|
630
|
+
Multiple callbacks on the same target are supported, and they run in order of definition.
|
631
|
+
|
632
|
+
Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
|
633
|
+
|
634
|
+
It is also possible to be called when any constant managed by the loader is unloaded:
|
635
|
+
|
636
|
+
```ruby
|
637
|
+
loader.on_unload do |cpath, value, abspath|
|
638
|
+
# ...
|
639
|
+
end
|
640
|
+
```
|
641
|
+
|
642
|
+
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.
|
643
|
+
|
644
|
+
Multiple callbacks like these are supported, and they run in order of definition.
|
645
|
+
|
646
|
+
If both types of callbacks are defined, the specific ones run first.
|
647
|
+
|
648
|
+
<a id="markdown-technical-details" name="technical-details"></a>
|
649
|
+
#### Technical details
|
650
|
+
|
651
|
+
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?
|
652
|
+
|
653
|
+
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.
|
654
|
+
|
655
|
+
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.
|
656
|
+
|
505
657
|
<a id="markdown-logging" name="logging"></a>
|
506
658
|
### Logging
|
507
659
|
|
data/lib/zeitwerk.rb
CHANGED
@@ -3,10 +3,23 @@
|
|
3
3
|
module Zeitwerk
|
4
4
|
require_relative "zeitwerk/real_mod_name"
|
5
5
|
require_relative "zeitwerk/loader"
|
6
|
+
require_relative "zeitwerk/autoloads"
|
6
7
|
require_relative "zeitwerk/registry"
|
7
8
|
require_relative "zeitwerk/explicit_namespace"
|
8
9
|
require_relative "zeitwerk/inflector"
|
9
10
|
require_relative "zeitwerk/gem_inflector"
|
10
11
|
require_relative "zeitwerk/kernel"
|
11
12
|
require_relative "zeitwerk/error"
|
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
|
12
25
|
end
|
@@ -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
|