zeitwerk 2.1.5 → 2.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +32 -10
- data/lib/zeitwerk/gem_inflector.rb +1 -1
- data/lib/zeitwerk/inflector.rb +1 -1
- data/lib/zeitwerk/loader.rb +32 -23
- data/lib/zeitwerk/version.rb +1 -1
- 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: 2cf7265c8e1a167cdf0107bdef2643d219317855b0f5a06b77048672e3a192a3
|
4
|
+
data.tar.gz: d1429158609c03b57f85b261290e61d88c7762f02693232336a0f4eb94bc7417
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3457b0e4afeb1e134db7815e538c8251aab11bda9b9feb8eec07257506786693f9f9ec0c0eb7a11c0663502f48778b37939cb47483d84b369ed82830518ff78a
|
7
|
+
data.tar.gz: 4996a053629f48004cb4c86f24a649295baaf1afbde0a2a5b5be0c3163205376659382ac3a9322586e2b86fee200c8bf97c5bb8faa2ae17c19aa0b169457af09
|
data/README.md
CHANGED
@@ -29,6 +29,7 @@
|
|
29
29
|
- [Use case: The adapter pattern](#use-case-the-adapter-pattern)
|
30
30
|
- [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
|
31
31
|
- [Edge cases](#edge-cases)
|
32
|
+
- [Rules of thumb](#rules-of-thumb)
|
32
33
|
- [Pronunciation](#pronunciation)
|
33
34
|
- [Supported Ruby versions](#supported-ruby-versions)
|
34
35
|
- [Motivation](#motivation)
|
@@ -42,15 +43,13 @@
|
|
42
43
|
|
43
44
|
Zeitwerk is an efficient and thread-safe code loader for Ruby.
|
44
45
|
|
45
|
-
Given a conventional file structure, Zeitwerk
|
46
|
+
Given a [conventional file structure](#file-structure), Zeitwerk is able to load your project's classes and modules on demand (autoloading), or upfront (eager loading). You don't need to write `require` calls for your own files, rather, you can streamline your programming knowing that your classes and modules are available everywhere. This feature is efficient, thread-safe, and matches Ruby's semantics for constants.
|
46
47
|
|
47
|
-
Zeitwerk
|
48
|
+
Zeitwerk is also able to reload code, which may be handy while developing web applications. Coordination is needed to reload in a thread-safe manner. The documentation below explains how to do this.
|
48
49
|
|
49
|
-
The
|
50
|
+
The gem is designed so that any project, gem dependency, application, etc. can have their own independent loader, coexisting in the same process, managing their own project trees, and independent of each other. Each loader has its own configuration, inflector, and optional logger.
|
50
51
|
|
51
|
-
Zeitwerk
|
52
|
-
|
53
|
-
Finally, in some production setups it may be optimal to eager load all code upfront. Zeitwerk is able to do that too.
|
52
|
+
Internally, Zeitwerk issues `require` calls exclusively using absolute file names, so there are no costly file system lookups in `$LOAD_PATH`. Technically, the directories managed by Zeitwerk do not even need to be in `$LOAD_PATH`. Furthermore, Zeitwerk does only one single scan of the project tree, and it descends into subdirectories lazily, only if their namespaces are used.
|
54
53
|
|
55
54
|
<a id="markdown-synopsis" name="synopsis"></a>
|
56
55
|
## Synopsis
|
@@ -62,8 +61,8 @@ Main interface for gems:
|
|
62
61
|
|
63
62
|
require "zeitwerk"
|
64
63
|
loader = Zeitwerk::Loader.for_gem
|
65
|
-
loader.setup
|
66
|
-
|
64
|
+
loader.setup # ready!
|
65
|
+
loader.eager_load # optionally
|
67
66
|
|
68
67
|
module MyGem
|
69
68
|
# ...
|
@@ -205,7 +204,7 @@ Zeitwerk works internally only with absolute paths to avoid costly file searches
|
|
205
204
|
<a id="markdown-reloading" name="reloading"></a>
|
206
205
|
### Reloading
|
207
206
|
|
208
|
-
|
207
|
+
Zeitwerk is able to reload code, but you need to enable this feature:
|
209
208
|
|
210
209
|
```ruby
|
211
210
|
loader = Zeitwerk::Loader.new
|
@@ -338,7 +337,15 @@ This needs to be done before calling `setup`.
|
|
338
337
|
<a id="markdown-logging" name="logging"></a>
|
339
338
|
### Logging
|
340
339
|
|
341
|
-
Zeitwerk is silent by default, but you can
|
340
|
+
Zeitwerk is silent by default, but you can ask loaders to trace their activity. Logging is meant just for troubleshooting, shouldn't normally be enabled.
|
341
|
+
|
342
|
+
The `log!` method is a quick shortcut to let the loader log to `$stdout`:
|
343
|
+
|
344
|
+
```
|
345
|
+
loader.log!
|
346
|
+
```
|
347
|
+
|
348
|
+
If you want more control, a logger can be configured as a callable
|
342
349
|
|
343
350
|
```ruby
|
344
351
|
loader.logger = method(:puts)
|
@@ -493,6 +500,21 @@ Trip = Struct.new { ... } # NOT SUPPORTED
|
|
493
500
|
|
494
501
|
This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
|
495
502
|
|
503
|
+
<a id="markdown-rules-of-thumb" name="rules-of-thumb"></a>
|
504
|
+
### Rules of thumb
|
505
|
+
|
506
|
+
1. Different loaders should manage different directory trees. It is an error condition to configure overlapping root directories in different loaders.
|
507
|
+
|
508
|
+
2. Think the mere existence of a file is effectively like writing a `require` call for them, which is executed on demand (autoload) or upfront (eager load).
|
509
|
+
|
510
|
+
3. In that line, if two loaders manage files that translate to the same constant in the same namespace, the first one wins, the rest are ignored. Similar to what happens with `require` and `$LOAD_PATH`, only the first occurrence matters.
|
511
|
+
|
512
|
+
4. Projects that reopen a namespace defined by some dependency have to ensure said namespace is loaded before setup. That is, the project has to make sure it reopens, rather than define. This is often accomplished just loading the dependency.
|
513
|
+
|
514
|
+
5. Objects stored in reloadable constants should not be cached in places that are not reloaded. For example, non-reloadable classes should not subclass a reloadable class, or mixin a reloadable module. Otherwise, after reloading, those classes or module objects would become stale. Referring to constants in dynamic places like method calls or lambdas is fine.
|
515
|
+
|
516
|
+
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.
|
517
|
+
|
496
518
|
<a id="markdown-pronunciation" name="pronunciation"></a>
|
497
519
|
## Pronunciation
|
498
520
|
|
data/lib/zeitwerk/inflector.rb
CHANGED
data/lib/zeitwerk/loader.rb
CHANGED
@@ -342,16 +342,20 @@ module Zeitwerk
|
|
342
342
|
break if @eager_loaded
|
343
343
|
|
344
344
|
queue = actual_root_dirs.reject { |dir| eager_load_exclusions.member?(dir) }
|
345
|
-
|
346
|
-
|
345
|
+
queue.map! { |dir| [Object, dir] }
|
346
|
+
while to_eager_load = queue.shift
|
347
|
+
namespace, dir = to_eager_load
|
347
348
|
|
348
|
-
|
349
|
+
ls(dir) do |basename, abspath|
|
349
350
|
next if eager_load_exclusions.member?(abspath)
|
350
351
|
|
351
352
|
if ruby?(abspath)
|
352
|
-
|
353
|
+
if cref = autoloads[File.realpath(abspath)]
|
354
|
+
cref[0].const_get(cref[1], false)
|
355
|
+
end
|
353
356
|
elsif dir?(abspath)
|
354
|
-
|
357
|
+
cname = inflector.camelize(basename, abspath)
|
358
|
+
queue << [namespace.const_get(cname, false), abspath]
|
355
359
|
end
|
356
360
|
end
|
357
361
|
end
|
@@ -391,6 +395,13 @@ module Zeitwerk
|
|
391
395
|
to_unload.keys.freeze
|
392
396
|
end
|
393
397
|
|
398
|
+
# Logs to `$stdout`, handy shortcut for debugging.
|
399
|
+
#
|
400
|
+
# @return [void]
|
401
|
+
def log!
|
402
|
+
@logger = ->(msg) { puts msg }
|
403
|
+
end
|
404
|
+
|
394
405
|
# --- Class methods ---------------------------------------------------------------------------
|
395
406
|
|
396
407
|
class << self
|
@@ -414,7 +425,7 @@ module Zeitwerk
|
|
414
425
|
#
|
415
426
|
# @return [Zeitwerk::Loader]
|
416
427
|
def for_gem
|
417
|
-
called_from = caller_locations.first.path
|
428
|
+
called_from = caller_locations(1, 1).first.path
|
418
429
|
Registry.loader_for_gem(called_from)
|
419
430
|
end
|
420
431
|
|
@@ -449,9 +460,10 @@ module Zeitwerk
|
|
449
460
|
# @param parent [Module]
|
450
461
|
# @return [void]
|
451
462
|
def set_autoloads_in_dir(dir, parent)
|
452
|
-
|
453
|
-
|
454
|
-
|
463
|
+
ls(dir) do |basename, abspath|
|
464
|
+
if ruby?(basename)
|
465
|
+
basename.slice!(-3, 3)
|
466
|
+
cname = inflector.camelize(basename, abspath).to_sym
|
455
467
|
autoload_file(parent, cname, abspath)
|
456
468
|
elsif dir?(abspath)
|
457
469
|
# In a Rails application, `app/models/concerns` is a subdirectory of
|
@@ -460,7 +472,10 @@ module Zeitwerk
|
|
460
472
|
# To resolve the ambiguity file name -> constant path this introduces,
|
461
473
|
# the `app/models/concerns` directory is totally ignored as a namespace,
|
462
474
|
# it counts only as root. The guard checks that.
|
463
|
-
|
475
|
+
unless root_dirs.key?(abspath)
|
476
|
+
cname = inflector.camelize(basename, abspath).to_sym
|
477
|
+
autoload_subdir(parent, cname, abspath)
|
478
|
+
end
|
464
479
|
end
|
465
480
|
end
|
466
481
|
end
|
@@ -605,7 +620,7 @@ module Zeitwerk
|
|
605
620
|
# @param dir [String]
|
606
621
|
# @return [void]
|
607
622
|
def do_preload_dir(dir)
|
608
|
-
|
623
|
+
ls(dir) do |_basename, abspath|
|
609
624
|
do_preload_abspath(abspath)
|
610
625
|
end
|
611
626
|
end
|
@@ -625,13 +640,13 @@ module Zeitwerk
|
|
625
640
|
end
|
626
641
|
|
627
642
|
# @param dir [String]
|
628
|
-
# @yieldparam path [String]
|
643
|
+
# @yieldparam path [String, String]
|
629
644
|
# @return [void]
|
630
|
-
def
|
631
|
-
Dir.foreach(dir) do |
|
632
|
-
next if
|
633
|
-
abspath = File.join(dir,
|
634
|
-
yield abspath unless ignored_paths.member?(abspath)
|
645
|
+
def ls(dir)
|
646
|
+
Dir.foreach(dir) do |basename|
|
647
|
+
next if basename.start_with?(".")
|
648
|
+
abspath = File.join(dir, basename)
|
649
|
+
yield basename, abspath unless ignored_paths.member?(abspath)
|
635
650
|
end
|
636
651
|
end
|
637
652
|
|
@@ -664,12 +679,6 @@ module Zeitwerk
|
|
664
679
|
parent.const_defined?(cname, false)
|
665
680
|
end
|
666
681
|
|
667
|
-
def const_get_if_autoload(abspath)
|
668
|
-
if cref = autoloads[File.realpath(abspath)]
|
669
|
-
cref[0].const_get(cref[1], false)
|
670
|
-
end
|
671
|
-
end
|
672
|
-
|
673
682
|
def register_explicit_namespace(cpath)
|
674
683
|
ExplicitNamespace.register(cpath, self)
|
675
684
|
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.1.
|
4
|
+
version: 2.1.7
|
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-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -50,7 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
50
|
- !ruby/object:Gem::Version
|
51
51
|
version: '0'
|
52
52
|
requirements: []
|
53
|
-
rubygems_version: 3.0.
|
53
|
+
rubygems_version: 3.0.3
|
54
54
|
signing_key:
|
55
55
|
specification_version: 4
|
56
56
|
summary: Efficient and thread-safe constant autoloader
|