zeitwerk 2.1.8 → 2.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +6 -20
- data/lib/zeitwerk.rb +1 -0
- data/lib/zeitwerk/explicit_namespace.rb +19 -11
- data/lib/zeitwerk/loader.rb +47 -25
- data/lib/zeitwerk/loader/callbacks.rb +3 -1
- data/lib/zeitwerk/real_mod_name.rb +15 -0
- 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: e58e19aad2055cd64f4ac3018cd6425e8f29f83d765b2410a28bef02f3fee381
|
4
|
+
data.tar.gz: e23bb3b21e15667fb8be922b0ced718ba135a5610cc0c6965304701ff580e404
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e6e6271f1f9cce47fb81b50d495aeb1159819ee68b0675752bb329494d77524ac624cb067d96f638a1580c2ad342832e3eab9f8cf8ad6b296d83524ef590f03
|
7
|
+
data.tar.gz: 7b4c4bae3b1fdf0214eeb7881ab855ad1babd5a28d1ab640105405b073bc1fcc54b08df1d6d0c416584363804268d7428b082a9822d8a0870f59224462ded513
|
data/README.md
CHANGED
@@ -17,7 +17,6 @@
|
|
17
17
|
- [Setup](#setup)
|
18
18
|
- [Reloading](#reloading)
|
19
19
|
- [Eager loading](#eager-loading)
|
20
|
-
- [Preloading](#preloading)
|
21
20
|
- [Inflection](#inflection)
|
22
21
|
- [Zeitwerk::Inflector](#zeitwerkinflector)
|
23
22
|
- [Zeitwerk::GemInflector](#zeitwerkgeminflector)
|
@@ -61,12 +60,13 @@ Main interface for gems:
|
|
61
60
|
|
62
61
|
require "zeitwerk"
|
63
62
|
loader = Zeitwerk::Loader.for_gem
|
64
|
-
loader.setup
|
65
|
-
loader.eager_load # optionally
|
63
|
+
loader.setup # ready!
|
66
64
|
|
67
65
|
module MyGem
|
68
66
|
# ...
|
69
67
|
end
|
68
|
+
|
69
|
+
loader.eager_load # optionally
|
70
70
|
```
|
71
71
|
|
72
72
|
Main generic interface:
|
@@ -259,22 +259,6 @@ This may be handy in top-level services, like web applications.
|
|
259
259
|
|
260
260
|
Note that thanks to idempotence `Zeitwerk::Loader.eager_load_all` won't eager load twice if any of the instances already eager loaded.
|
261
261
|
|
262
|
-
<a id="markdown-preloading" name="preloading"></a>
|
263
|
-
### Preloading
|
264
|
-
|
265
|
-
Zeitwerk instances are able to preload files and directories.
|
266
|
-
|
267
|
-
```ruby
|
268
|
-
loader.preload("app/models/videogame.rb")
|
269
|
-
loader.preload("app/models/book.rb")
|
270
|
-
```
|
271
|
-
|
272
|
-
The call can happen before `setup` (preloads during setup), or after `setup` (preloads on the spot). Each reload preloads too.
|
273
|
-
|
274
|
-
This is a feature specifically thought for STIs in Rails, preloading the leafs of a STI tree ensures all classes are known when doing a query.
|
275
|
-
|
276
|
-
The example above depicts several calls are supported, but `preload` accepts multiple arguments and arrays of strings as well.
|
277
|
-
|
278
262
|
<a id="markdown-inflection" name="inflection"></a>
|
279
263
|
### Inflection
|
280
264
|
|
@@ -399,7 +383,7 @@ Zeitwerk ignores automatically any file or directory whose name starts with a do
|
|
399
383
|
|
400
384
|
However, sometimes it might still be convenient to tell Zeitwerk to completely ignore some particular Ruby file or directory. That is possible with `ignore`, which accepts an arbitrary number of strings or `Pathname` objects, and also an array of them.
|
401
385
|
|
402
|
-
You can ignore file names, directory names, and glob patterns. Glob patterns are expanded
|
386
|
+
You can ignore file names, directory names, and glob patterns. Glob patterns are expanded when they are added and again on each reload.
|
403
387
|
|
404
388
|
Let's see some use cases.
|
405
389
|
|
@@ -460,6 +444,8 @@ The chosen adapter, then, has to be loaded by hand somehow:
|
|
460
444
|
require "my_gem/db_adapters/#{config[:db_adapter]}"
|
461
445
|
```
|
462
446
|
|
447
|
+
Note that since the directory is ignored, the required adapter can instantiate another loader to manage its subtree, if desired. Such loader would coexist with the main one just fine.
|
448
|
+
|
463
449
|
<a id="markdown-use-case-test-files-mixed-with-implementation-files" name="use-case-test-files-mixed-with-implementation-files"></a>
|
464
450
|
#### Use case: Test files mixed with implementation files
|
465
451
|
|
data/lib/zeitwerk.rb
CHANGED
@@ -8,6 +8,8 @@ module Zeitwerk
|
|
8
8
|
# loading their constant before setup. This is documented.
|
9
9
|
module ExplicitNamespace # :nodoc: all
|
10
10
|
class << self
|
11
|
+
include RealModName
|
12
|
+
|
11
13
|
# Maps constant paths that correspond to explicit namespaces according to
|
12
14
|
# the file system, to the loader responsible for them.
|
13
15
|
#
|
@@ -52,21 +54,27 @@ module Zeitwerk
|
|
52
54
|
tracer.disable if cpaths.empty?
|
53
55
|
end
|
54
56
|
end
|
57
|
+
|
58
|
+
def tracepoint_class_callback(event)
|
59
|
+
# If the class is a singleton class, we won't do anything with it so we
|
60
|
+
# can bail out immediately. This is several orders of magnitude faster
|
61
|
+
# than accessing its name.
|
62
|
+
return if event.self.singleton_class?
|
63
|
+
|
64
|
+
# Note that it makes sense to compute the hash code unconditionally,
|
65
|
+
# because the trace point is disabled if cpaths is empty.
|
66
|
+
if loader = cpaths.delete(real_mod_name(event.self))
|
67
|
+
loader.on_namespace_loaded(event.self)
|
68
|
+
disable_tracer_if_unneeded
|
69
|
+
end
|
70
|
+
end
|
55
71
|
end
|
56
72
|
|
57
73
|
@cpaths = {}
|
58
74
|
@mutex = Mutex.new
|
59
|
-
@tracer = TracePoint.new(:class) do |event|
|
60
|
-
# If the class is a singleton class, we won't do anything with it so we can bail out immediately.
|
61
|
-
# This is several orders of magnitude faster than accessing `Module#name`.
|
62
|
-
next if event.self.singleton_class?
|
63
75
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
loader.on_namespace_loaded(event.self)
|
68
|
-
disable_tracer_if_unneeded
|
69
|
-
end
|
70
|
-
end
|
76
|
+
# We go through a method instead of defining a block mainly to have a better
|
77
|
+
# label when profiling.
|
78
|
+
@tracer = TracePoint.new(:class, &method(:tracepoint_class_callback))
|
71
79
|
end
|
72
80
|
end
|
data/lib/zeitwerk/loader.rb
CHANGED
@@ -7,6 +7,7 @@ module Zeitwerk
|
|
7
7
|
class Loader
|
8
8
|
require_relative "loader/callbacks"
|
9
9
|
include Callbacks
|
10
|
+
include RealModName
|
10
11
|
|
11
12
|
# @return [String]
|
12
13
|
attr_reader :tag
|
@@ -43,7 +44,7 @@ module Zeitwerk
|
|
43
44
|
#
|
44
45
|
# @private
|
45
46
|
# @return [Set<String>]
|
46
|
-
attr_reader :
|
47
|
+
attr_reader :ignored_glob_patterns
|
47
48
|
|
48
49
|
# The actual collection of absolute file and directory names at the time the
|
49
50
|
# ignored glob patterns were expanded. Computed on setup, and recomputed on
|
@@ -132,7 +133,7 @@ module Zeitwerk
|
|
132
133
|
|
133
134
|
@root_dirs = {}
|
134
135
|
@preloads = []
|
135
|
-
@
|
136
|
+
@ignored_glob_patterns = Set.new
|
136
137
|
@ignored_paths = Set.new
|
137
138
|
@autoloads = {}
|
138
139
|
@autoloaded_dirs = []
|
@@ -224,16 +225,12 @@ module Zeitwerk
|
|
224
225
|
#
|
225
226
|
# @param paths [<String, Pathname, <String, Pathname>>]
|
226
227
|
# @return [void]
|
227
|
-
def ignore(*
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
def expand_ignored_glob_patterns
|
234
|
-
# Note that Dir.glob works with regular file names just fine. That is,
|
235
|
-
# glob patterns technically need no wildcards.
|
236
|
-
ignored_paths.replace(ignored.flat_map { |path| Dir.glob(path) })
|
228
|
+
def ignore(*glob_patterns)
|
229
|
+
glob_patterns = expand_paths(glob_patterns)
|
230
|
+
mutex.synchronize do
|
231
|
+
ignored_glob_patterns.merge(glob_patterns)
|
232
|
+
ignored_paths.merge(expand_glob_patterns(glob_patterns))
|
233
|
+
end
|
237
234
|
end
|
238
235
|
|
239
236
|
# Sets autoloads in the root namespace and preloads files, if any.
|
@@ -243,7 +240,6 @@ module Zeitwerk
|
|
243
240
|
mutex.synchronize do
|
244
241
|
break if @setup
|
245
242
|
|
246
|
-
expand_ignored_glob_patterns
|
247
243
|
actual_root_dirs.each { |root_dir| set_autoloads_in_dir(root_dir, Object) }
|
248
244
|
do_preload
|
249
245
|
|
@@ -325,6 +321,7 @@ module Zeitwerk
|
|
325
321
|
def reload
|
326
322
|
if reloading_enabled?
|
327
323
|
unload
|
324
|
+
recompute_ignored_paths
|
328
325
|
setup
|
329
326
|
else
|
330
327
|
raise ReloadingDisabledError, "can't reload, please call loader.enable_reloading before setup"
|
@@ -402,6 +399,22 @@ module Zeitwerk
|
|
402
399
|
@logger = ->(msg) { puts msg }
|
403
400
|
end
|
404
401
|
|
402
|
+
# @private
|
403
|
+
# @param dir [String]
|
404
|
+
# @return [Boolean]
|
405
|
+
def manages?(dir)
|
406
|
+
dir = dir + "/"
|
407
|
+
ignored_paths.each do |ignored_path|
|
408
|
+
return false if dir.start_with?(ignored_path + "/")
|
409
|
+
end
|
410
|
+
|
411
|
+
root_dirs.each_key do |root_dir|
|
412
|
+
return true if root_dir.start_with?(dir) || dir.start_with?(root_dir + "/")
|
413
|
+
end
|
414
|
+
|
415
|
+
false
|
416
|
+
end
|
417
|
+
|
405
418
|
# --- Class methods ---------------------------------------------------------------------------
|
406
419
|
|
407
420
|
class << self
|
@@ -636,7 +649,7 @@ module Zeitwerk
|
|
636
649
|
# @param cname [Symbol]
|
637
650
|
# @return [String]
|
638
651
|
def cpath(parent, cname)
|
639
|
-
parent.equal?(Object) ? cname.to_s : "#{parent
|
652
|
+
parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
|
640
653
|
end
|
641
654
|
|
642
655
|
# @param dir [String]
|
@@ -665,7 +678,20 @@ module Zeitwerk
|
|
665
678
|
# @param paths [<String, Pathname, <String, Pathname>>]
|
666
679
|
# @return [<String>]
|
667
680
|
def expand_paths(paths)
|
668
|
-
|
681
|
+
paths.flatten.map! { |path| File.expand_path(path) }
|
682
|
+
end
|
683
|
+
|
684
|
+
# @param glob_patterns [<String>]
|
685
|
+
# @return [<String>]
|
686
|
+
def expand_glob_patterns(glob_patterns)
|
687
|
+
# Note that Dir.glob works with regular file names just fine. That is,
|
688
|
+
# glob patterns technically need no wildcards.
|
689
|
+
glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
|
690
|
+
end
|
691
|
+
|
692
|
+
# @return [void]
|
693
|
+
def recompute_ignored_paths
|
694
|
+
ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
|
669
695
|
end
|
670
696
|
|
671
697
|
# @param message [String]
|
@@ -686,16 +712,12 @@ module Zeitwerk
|
|
686
712
|
def raise_if_conflicting_directory(dir)
|
687
713
|
self.class.mutex.synchronize do
|
688
714
|
Registry.loaders.each do |loader|
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
|
696
|
-
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
697
|
-
EOS
|
698
|
-
end
|
715
|
+
if loader != self && loader.manages?(dir)
|
716
|
+
require "pp"
|
717
|
+
raise Error,
|
718
|
+
"loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
|
719
|
+
" which is already managed by\n\n#{loader.pretty_inspect}\n"
|
720
|
+
EOS
|
699
721
|
end
|
700
722
|
end
|
701
723
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
module Zeitwerk::Loader::Callbacks
|
2
|
+
include Zeitwerk::RealModName
|
3
|
+
|
2
4
|
# Invoked from our decorated Kernel#require when a managed file is autoloaded.
|
3
5
|
#
|
4
6
|
# @private
|
@@ -60,7 +62,7 @@ module Zeitwerk::Loader::Callbacks
|
|
60
62
|
# @param namespace [Module]
|
61
63
|
# @return [void]
|
62
64
|
def on_namespace_loaded(namespace)
|
63
|
-
if subdirs = lazy_subdirs.delete(namespace
|
65
|
+
if subdirs = lazy_subdirs.delete(real_mod_name(namespace))
|
64
66
|
subdirs.each do |subdir|
|
65
67
|
set_autoloads_in_dir(subdir, namespace)
|
66
68
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Zeitwerk::RealModName
|
2
|
+
UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
|
3
|
+
private_constant :UNBOUND_METHOD_MODULE_NAME
|
4
|
+
|
5
|
+
# Returns the real name of the class or module, as set after the first
|
6
|
+
# constant to which it was assigned (or nil).
|
7
|
+
#
|
8
|
+
# The name method can be overridden, hence the indirection in this method.
|
9
|
+
#
|
10
|
+
# @param mod [Class, Module]
|
11
|
+
# @return [String]
|
12
|
+
def real_mod_name(mod)
|
13
|
+
UNBOUND_METHOD_MODULE_NAME.bind(mod).call
|
14
|
+
end
|
15
|
+
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.9
|
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-07-16 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -29,6 +29,7 @@ files:
|
|
29
29
|
- lib/zeitwerk/kernel.rb
|
30
30
|
- lib/zeitwerk/loader.rb
|
31
31
|
- lib/zeitwerk/loader/callbacks.rb
|
32
|
+
- lib/zeitwerk/real_mod_name.rb
|
32
33
|
- lib/zeitwerk/registry.rb
|
33
34
|
- lib/zeitwerk/version.rb
|
34
35
|
homepage: https://github.com/fxn/zeitwerk
|