zeitwerk 2.1.9 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +20 -0
- data/README.md +98 -10
- data/lib/zeitwerk/error.rb +3 -0
- data/lib/zeitwerk/gem_inflector.rb +2 -2
- data/lib/zeitwerk/inflector.rb +32 -2
- data/lib/zeitwerk/loader.rb +30 -13
- data/lib/zeitwerk/real_mod_name.rb +1 -1
- data/lib/zeitwerk/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2af50b4d74832ccd3c08e3445c09a941ed56758c57acd20e8d88436f79e16ea2
|
4
|
+
data.tar.gz: 23f693ca2b9fc44870c26991cd91c587031825e9d29c90dd5285f2c7a3731f6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d47e1a735c5314ec56f9c16d66695222209109dddb7da3a663969b852295eed82d8eee56fc48a48cb6b0604e508940f474533e98c7d824175c344420a64baec
|
7
|
+
data.tar.gz: be275743889771c745fc90d300a4cbd01911b5b4547c41ea1df6429af1357b9328fb69293a03a282677295f27528220f180f1ca943f43b5567677b856baa3e06
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2019–ω Xavier Noria
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
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/travis/com/fxn/zeitwerk
|
6
|
+
[![Build Status](https://img.shields.io/travis/com/fxn/zeitwerk/master?style=for-the-badge)](https://travis-ci.com/fxn/zeitwerk)
|
7
7
|
|
8
8
|
<!-- TOC -->
|
9
9
|
|
@@ -29,6 +29,7 @@
|
|
29
29
|
- [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
|
30
30
|
- [Edge cases](#edge-cases)
|
31
31
|
- [Rules of thumb](#rules-of-thumb)
|
32
|
+
- [Autoloading, explicit namespaces, and debuggers](#autoloading-explicit-namespaces-and-debuggers)
|
32
33
|
- [Pronunciation](#pronunciation)
|
33
34
|
- [Supported Ruby versions](#supported-ruby-versions)
|
34
35
|
- [Motivation](#motivation)
|
@@ -199,6 +200,22 @@ loader.setup
|
|
199
200
|
|
200
201
|
The loader returned by `Zeitwerk::Loader.for_gem` has the directory of the caller pushed, normally that is the absolute path of `lib`. In that sense, `for_gem` can be used also by projects with a gem structure, even if they are not technically gems. That is, you don't need a gemspec or anything.
|
201
202
|
|
203
|
+
If the main module of a library references project constants at the top-level, Zeitwerk has to be ready to load them. Their definitions, in turn, may reference other project constants. And this is recursive. Therefore, it is important that the `setup` call happens above the main module definition:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
# lib/my_gem.rb (main file)
|
207
|
+
|
208
|
+
require "zeitwerk"
|
209
|
+
loader = Zeitwerk::Loader.for_gem
|
210
|
+
loader.setup
|
211
|
+
|
212
|
+
module MyGem
|
213
|
+
# Since the setup has been performed, at this point we are already able
|
214
|
+
# to reference project constants, in this case MyGem::MyLogger.
|
215
|
+
include MyLogger
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
202
219
|
Zeitwerk works internally only with absolute paths to avoid costly file searches in `$LOAD_PATH`. Indeed, the root directories do not even need to belong to `$LOAD_PATH`, everything just works by design if they don't.
|
203
220
|
|
204
221
|
<a id="markdown-reloading" name="reloading"></a>
|
@@ -221,7 +238,7 @@ Enabling reloading after setup raises `Zeitwerk::Error`. Similarly, calling `rel
|
|
221
238
|
|
222
239
|
Generally speaking, reloading is useful while developing running services like web applications. Gems that implement regular libraries, so to speak, or services running in testing or production environments, won't normally have a use case for reloading. If reloading is not enabled, Zeitwerk is able to use less memory.
|
223
240
|
|
224
|
-
Reloading removes the currently loaded classes and modules
|
241
|
+
Reloading removes the currently loaded classes and modules and resets the loader so that it will pick whatever is in the file system now.
|
225
242
|
|
226
243
|
It is important to highlight that this is an instance method. Don't worry about project dependencies managed by Zeitwerk, their loaders are independent.
|
227
244
|
|
@@ -247,6 +264,8 @@ loader.setup
|
|
247
264
|
loader.eager_load # won't eager load the database adapters
|
248
265
|
```
|
249
266
|
|
267
|
+
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).
|
268
|
+
|
250
269
|
Eager loading is synchronized and idempotent.
|
251
270
|
|
252
271
|
If you want to eager load yourself and all dependencies using Zeitwerk, you can broadcast the `eager_load` call to all instances:
|
@@ -275,14 +294,34 @@ users_controller -> UsersController
|
|
275
294
|
html_parser -> HtmlParser
|
276
295
|
```
|
277
296
|
|
297
|
+
The camelize logic can be overridden easily for individual basenames:
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
loader.inflector.inflect(
|
301
|
+
"html_parser" => "HTMLParser",
|
302
|
+
"mysql_adapter" => "MySQLAdapter"
|
303
|
+
)
|
304
|
+
```
|
305
|
+
|
306
|
+
The `inflect` method can be invoked several times if you prefer this other style:
|
307
|
+
|
308
|
+
```ruby
|
309
|
+
loader.inflector.inflect "html_parser" => "HTMLParser"
|
310
|
+
loader.inflector.inflect "mysql_adapter" => "MySQLAdapter"
|
311
|
+
```
|
312
|
+
|
313
|
+
Overrides need to be configured before calling `setup`.
|
314
|
+
|
278
315
|
There are no inflection rules or global configuration that can affect this inflector. It is deterministic.
|
279
316
|
|
280
|
-
|
317
|
+
Loaders instantiated with `Zeitwerk::Loader.new` have an inflector of this type, independent of each other.
|
281
318
|
|
282
319
|
<a id="markdown-zeitwerkgeminflector" name="zeitwerkgeminflector"></a>
|
283
320
|
#### Zeitwerk::GemInflector
|
284
321
|
|
285
|
-
|
322
|
+
This inflector is like the basic one, except it expects `lib/my_gem/version.rb` to define `MyGem::VERSION`.
|
323
|
+
|
324
|
+
Loaders instantiated with `Zeitwerk::Loader.for_gem` have an inflector of this type, independent of each other.
|
286
325
|
|
287
326
|
<a id="markdown-custom-inflector" name="custom-inflector"></a>
|
288
327
|
#### Custom inflector
|
@@ -293,12 +332,9 @@ The inflectors that ship with Zeitwerk are deterministic and simple. But you can
|
|
293
332
|
# frozen_string_literal: true
|
294
333
|
|
295
334
|
class MyInflector < Zeitwerk::Inflector
|
296
|
-
def camelize(basename,
|
297
|
-
|
298
|
-
|
299
|
-
"API"
|
300
|
-
when "mysql_adapter"
|
301
|
-
"MySQLAdapter"
|
335
|
+
def camelize(basename, abspath)
|
336
|
+
if basename =~ /\Ahtml_(.*)/
|
337
|
+
"HTML" + super($1, abspath)
|
302
338
|
else
|
303
339
|
super
|
304
340
|
end
|
@@ -318,6 +354,49 @@ loader.inflector = MyInflector.new
|
|
318
354
|
|
319
355
|
This needs to be done before calling `setup`.
|
320
356
|
|
357
|
+
If a custom inflector definition in a gem takes too much space in the main file, you can extract it. For example, this is a simple pattern:
|
358
|
+
|
359
|
+
```ruby
|
360
|
+
# lib/my_gem/inflector.rb
|
361
|
+
module MyGem
|
362
|
+
class Inflector < Zeitwerk::GemInflector
|
363
|
+
...
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# lib/my_gem.rb
|
368
|
+
require "zeitwerk"
|
369
|
+
require_relative "my_gem/inflector"
|
370
|
+
|
371
|
+
loader = Zeitwerk::Loader.for_gem
|
372
|
+
loader.inflector = MyGem::Inflector.new(__FILE__)
|
373
|
+
loader.setup
|
374
|
+
|
375
|
+
module MyGem
|
376
|
+
# ...
|
377
|
+
end
|
378
|
+
```
|
379
|
+
|
380
|
+
Since `MyGem` is referenced before the namespace is defined in the main file, it is important to use this style:
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
# Correct, effectively defines MyGem.
|
384
|
+
module MyGem
|
385
|
+
class Inflector < Zeitwerk::GemInflector
|
386
|
+
# ...
|
387
|
+
end
|
388
|
+
end
|
389
|
+
```
|
390
|
+
|
391
|
+
instead of:
|
392
|
+
|
393
|
+
```ruby
|
394
|
+
# Raises uninitialized constant MyGem (NameError).
|
395
|
+
class MyGem::Inflector < Zeitwerk::GemInflector
|
396
|
+
# ...
|
397
|
+
end
|
398
|
+
```
|
399
|
+
|
321
400
|
<a id="markdown-logging" name="logging"></a>
|
322
401
|
### Logging
|
323
402
|
|
@@ -501,6 +580,15 @@ This only affects explicit namespaces, those idioms work well for any other ordi
|
|
501
580
|
|
502
581
|
6. In a given process, ideally, there should be at most one loader with reloading enabled. Technically, you can have more, but it may get tricky if one refers to constants managed by the other one. Do that only if you know what you are doing.
|
503
582
|
|
583
|
+
<a id="markdown-autoloading-explicit-namespaces-and-debuggers" name="autoloading-explicit-namespaces-and-debuggers"></a>
|
584
|
+
### Autoloading, explicit namespaces, and debuggers
|
585
|
+
|
586
|
+
As of this writing, Zeitwerk is unable to autoload classes or modules that belong to [explicit namespaces](#explicit-namespaces) inside debugger sessions. You'll get a `NameError`.
|
587
|
+
|
588
|
+
The root cause is that debuggers set trace points, and Zeitwerk does too to support explicit namespaces. A debugger session happens inside a trace point handler, and Ruby does not invoke other handlers from within a running handler. Therefore, the code that manages explicit namespaces in Zeitwerk does not get called by the interpreter. See [this issue](https://github.com/deivid-rodriguez/byebug/issues/564#issuecomment-499413606) for further details.
|
589
|
+
|
590
|
+
As a workaround, you can eager load. Zeitwerk tries hard to succeed or fail consistently both autoloading and eager loading, so switching to eager loading should not introduce any interference in your debugging logic, generally speaking.
|
591
|
+
|
504
592
|
<a id="markdown-pronunciation" name="pronunciation"></a>
|
505
593
|
## Pronunciation
|
506
594
|
|
data/lib/zeitwerk/error.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Zeitwerk
|
4
|
-
class GemInflector < Inflector
|
4
|
+
class GemInflector < Inflector
|
5
5
|
# @param root_file [String]
|
6
6
|
def initialize(root_file)
|
7
7
|
namespace = File.basename(root_file, ".rb")
|
@@ -13,7 +13,7 @@ module Zeitwerk
|
|
13
13
|
# @param abspath [String]
|
14
14
|
# @return [String]
|
15
15
|
def camelize(basename, abspath)
|
16
|
-
|
16
|
+
abspath == @version_file ? "VERSION" : super
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/lib/zeitwerk/inflector.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Zeitwerk
|
4
|
-
class Inflector
|
4
|
+
class Inflector
|
5
5
|
# Very basic snake case -> camel case conversion.
|
6
6
|
#
|
7
7
|
# inflector = Zeitwerk::Inflector.new
|
@@ -9,11 +9,41 @@ module Zeitwerk
|
|
9
9
|
# inflector.camelize("users_controller", ...) # => "UsersController"
|
10
10
|
# inflector.camelize("api", ...) # => "Api"
|
11
11
|
#
|
12
|
+
# Takes into account hard-coded mappings configured with `inflect`.
|
13
|
+
#
|
12
14
|
# @param basename [String]
|
13
15
|
# @param _abspath [String]
|
14
16
|
# @return [String]
|
15
17
|
def camelize(basename, _abspath)
|
16
|
-
basename.split('_').map!(&:capitalize).join
|
18
|
+
overrides[basename] || basename.split('_').map!(&:capitalize).join
|
19
|
+
end
|
20
|
+
|
21
|
+
# Configures hard-coded inflections:
|
22
|
+
#
|
23
|
+
# inflector = Zeitwerk::Inflector.new
|
24
|
+
# inflector.inflect(
|
25
|
+
# "html_parser" => "HTMLParser",
|
26
|
+
# "mysql_adapter" => "MySQLAdapter"
|
27
|
+
# )
|
28
|
+
#
|
29
|
+
# inflector.camelize("html_parser", abspath) # => "HTMLParser"
|
30
|
+
# inflector.camelize("mysql_adapter", abspath) # => "MySQLAdapter"
|
31
|
+
# inflector.camelize("users_controller", abspath) # => "PostsController"
|
32
|
+
#
|
33
|
+
# @param inflections [{String => String}]
|
34
|
+
# @return [void]
|
35
|
+
def inflect(inflections)
|
36
|
+
overrides.merge!(inflections)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Hard-coded basename to constant name user maps that override the default
|
42
|
+
# inflection logic.
|
43
|
+
#
|
44
|
+
# @return [{String => String}]
|
45
|
+
def overrides
|
46
|
+
@overrides ||= {}
|
17
47
|
end
|
18
48
|
end
|
19
49
|
end
|
data/lib/zeitwerk/loader.rb
CHANGED
@@ -474,21 +474,38 @@ module Zeitwerk
|
|
474
474
|
# @return [void]
|
475
475
|
def set_autoloads_in_dir(dir, parent)
|
476
476
|
ls(dir) do |basename, abspath|
|
477
|
-
|
478
|
-
basename
|
479
|
-
|
480
|
-
autoload_file(parent, cname, abspath)
|
481
|
-
elsif dir?(abspath)
|
482
|
-
# In a Rails application, `app/models/concerns` is a subdirectory of
|
483
|
-
# `app/models`, but both of them are root directories.
|
484
|
-
#
|
485
|
-
# To resolve the ambiguity file name -> constant path this introduces,
|
486
|
-
# the `app/models/concerns` directory is totally ignored as a namespace,
|
487
|
-
# it counts only as root. The guard checks that.
|
488
|
-
unless root_dirs.key?(abspath)
|
477
|
+
begin
|
478
|
+
if ruby?(basename)
|
479
|
+
basename.slice!(-3, 3)
|
489
480
|
cname = inflector.camelize(basename, abspath).to_sym
|
490
|
-
|
481
|
+
autoload_file(parent, cname, abspath)
|
482
|
+
elsif dir?(abspath)
|
483
|
+
# In a Rails application, `app/models/concerns` is a subdirectory of
|
484
|
+
# `app/models`, but both of them are root directories.
|
485
|
+
#
|
486
|
+
# To resolve the ambiguity file name -> constant path this introduces,
|
487
|
+
# the `app/models/concerns` directory is totally ignored as a namespace,
|
488
|
+
# it counts only as root. The guard checks that.
|
489
|
+
unless root_dirs.key?(abspath)
|
490
|
+
cname = inflector.camelize(basename, abspath).to_sym
|
491
|
+
autoload_subdir(parent, cname, abspath)
|
492
|
+
end
|
491
493
|
end
|
494
|
+
rescue ::NameError => error
|
495
|
+
path_type = ruby?(abspath) ? "file" : "directory"
|
496
|
+
|
497
|
+
raise NameError, <<~MESSAGE
|
498
|
+
#{error.message} inferred by #{inflector.class} from #{path_type}
|
499
|
+
|
500
|
+
#{abspath}
|
501
|
+
|
502
|
+
Possible ways to address this:
|
503
|
+
|
504
|
+
* Tell Zeitwerk to ignore this particular #{path_type}.
|
505
|
+
* Tell Zeitwerk to ignore one of its parent directories.
|
506
|
+
* Rename the #{path_type} to comply with the naming conventions.
|
507
|
+
* Modify the inflector to handle this case.
|
508
|
+
MESSAGE
|
492
509
|
end
|
493
510
|
end
|
494
511
|
end
|
@@ -8,7 +8,7 @@ module Zeitwerk::RealModName
|
|
8
8
|
# The name method can be overridden, hence the indirection in this method.
|
9
9
|
#
|
10
10
|
# @param mod [Class, Module]
|
11
|
-
# @return [String]
|
11
|
+
# @return [String, nil]
|
12
12
|
def real_mod_name(mod)
|
13
13
|
UNBOUND_METHOD_MODULE_NAME.bind(mod).call
|
14
14
|
end
|
data/lib/zeitwerk/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zeitwerk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Noria
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -20,6 +20,7 @@ executables: []
|
|
20
20
|
extensions: []
|
21
21
|
extra_rdoc_files: []
|
22
22
|
files:
|
23
|
+
- MIT-LICENSE
|
23
24
|
- README.md
|
24
25
|
- lib/zeitwerk.rb
|
25
26
|
- lib/zeitwerk/error.rb
|