zeitwerk 2.1.8 → 2.1.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|