zeitwerk 2.6.16 → 2.7.3
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 +7 -43
- data/lib/zeitwerk/{kernel.rb → core_ext/kernel.rb} +3 -3
- data/lib/zeitwerk/core_ext/module.rb +20 -0
- data/lib/zeitwerk/cref/map.rb +159 -0
- data/lib/zeitwerk/cref.rb +22 -52
- data/lib/zeitwerk/error.rb +2 -0
- data/lib/zeitwerk/gem_inflector.rb +2 -2
- data/lib/zeitwerk/gem_loader.rb +4 -4
- data/lib/zeitwerk/inflector.rb +3 -3
- data/lib/zeitwerk/internal.rb +1 -0
- data/lib/zeitwerk/loader/callbacks.rb +19 -22
- data/lib/zeitwerk/loader/config.rb +37 -44
- data/lib/zeitwerk/loader/eager_load.rb +10 -10
- data/lib/zeitwerk/loader/helpers.rb +10 -13
- data/lib/zeitwerk/loader.rb +138 -98
- data/lib/zeitwerk/null_inflector.rb +1 -0
- data/lib/zeitwerk/real_mod_name.rb +9 -12
- data/lib/zeitwerk/registry/autoloads.rb +38 -0
- data/lib/zeitwerk/registry/explicit_namespaces.rb +61 -0
- data/lib/zeitwerk/registry/inceptions.rb +31 -0
- data/lib/zeitwerk/registry/loaders.rb +33 -0
- data/lib/zeitwerk/registry.rb +18 -93
- data/lib/zeitwerk/version.rb +2 -1
- data/lib/zeitwerk.rb +4 -3
- metadata +11 -9
- data/lib/zeitwerk/explicit_namespace.rb +0 -93
@@ -0,0 +1,31 @@
|
|
1
|
+
module Zeitwerk::Registry
|
2
|
+
# Loaders know their own inceptions, but there is a use case in which we need
|
3
|
+
# to know if a given cpath is an inception globally. This is what this
|
4
|
+
# registry is for.
|
5
|
+
class Inceptions # :nodoc:
|
6
|
+
#: () -> void
|
7
|
+
def initialize
|
8
|
+
@inceptions = Zeitwerk::Cref::Map.new #: Zeitwerk::Cref::Map[String]
|
9
|
+
end
|
10
|
+
|
11
|
+
#: (Zeitwerk::Cref, String) -> void
|
12
|
+
def register(cref, abspath)
|
13
|
+
@inceptions[cref] = abspath
|
14
|
+
end
|
15
|
+
|
16
|
+
#: (Zeitwerk::Cref) -> String?
|
17
|
+
def registered?(cref)
|
18
|
+
@inceptions[cref]
|
19
|
+
end
|
20
|
+
|
21
|
+
#: (Zeitwerk::Cref) -> void
|
22
|
+
def unregister(cref)
|
23
|
+
@inceptions.delete(cref)
|
24
|
+
end
|
25
|
+
|
26
|
+
#: () -> void
|
27
|
+
def clear # for tests
|
28
|
+
@inceptions.clear
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Zeitwerk::Registry
|
2
|
+
class Loaders # :nodoc:
|
3
|
+
#: () -> void
|
4
|
+
def initialize
|
5
|
+
@loaders = [] #: Array[Zeitwerk::Loader]
|
6
|
+
end
|
7
|
+
|
8
|
+
#: ({ (Zeitwerk::Loader) -> void }) -> void
|
9
|
+
def each(&block)
|
10
|
+
@loaders.each(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
#: (Zeitwerk::Loader) -> void
|
14
|
+
def register(loader)
|
15
|
+
@loaders << loader
|
16
|
+
end
|
17
|
+
|
18
|
+
#: (Zeitwerk::Loader) -> Zeitwerk::Loader?
|
19
|
+
def unregister(loader)
|
20
|
+
@loaders.delete(loader)
|
21
|
+
end
|
22
|
+
|
23
|
+
#: (Zeitwerk::Loader) -> bool
|
24
|
+
def registered?(loader) # for tests
|
25
|
+
@loaders.include?(loader)
|
26
|
+
end
|
27
|
+
|
28
|
+
#: () -> void
|
29
|
+
def clear # for tests
|
30
|
+
@loaders.clear
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/zeitwerk/registry.rb
CHANGED
@@ -2,18 +2,23 @@
|
|
2
2
|
|
3
3
|
module Zeitwerk
|
4
4
|
module Registry # :nodoc: all
|
5
|
+
require_relative "registry/autoloads"
|
6
|
+
require_relative "registry/explicit_namespaces"
|
7
|
+
require_relative "registry/inceptions"
|
8
|
+
require_relative "registry/loaders"
|
9
|
+
|
5
10
|
class << self
|
6
11
|
# Keeps track of all loaders. Useful to broadcast messages and to prevent
|
7
12
|
# them from being garbage collected.
|
8
13
|
#
|
9
14
|
# @private
|
10
|
-
|
15
|
+
#: Zeitwerk::Registry::Loaders
|
11
16
|
attr_reader :loaders
|
12
17
|
|
13
18
|
# Registers gem loaders to let `for_gem` be idempotent in case of reload.
|
14
19
|
#
|
15
20
|
# @private
|
16
|
-
|
21
|
+
#: Hash[String, Zeitwerk::Loader]
|
17
22
|
attr_reader :gem_loaders_by_root_file
|
18
23
|
|
19
24
|
# Maps absolute paths to the loaders responsible for them.
|
@@ -22,117 +27,37 @@ module Zeitwerk
|
|
22
27
|
# invoke callbacks and autovivify modules.
|
23
28
|
#
|
24
29
|
# @private
|
25
|
-
|
30
|
+
#: Zeitwerk::Registry::Autoloads
|
26
31
|
attr_reader :autoloads
|
27
32
|
|
28
|
-
# This hash table addresses an edge case in which an autoload is ignored.
|
29
|
-
#
|
30
|
-
# For example, let's suppose we want to autoload in a gem like this:
|
31
|
-
#
|
32
|
-
# # lib/my_gem.rb
|
33
|
-
# loader = Zeitwerk::Loader.new
|
34
|
-
# loader.push_dir(__dir__)
|
35
|
-
# loader.setup
|
36
|
-
#
|
37
|
-
# module MyGem
|
38
|
-
# end
|
39
|
-
#
|
40
|
-
# if you require "my_gem", as Bundler would do, this happens while setting
|
41
|
-
# up autoloads:
|
42
|
-
#
|
43
|
-
# 1. Object.autoload?(:MyGem) returns `nil` because the autoload for
|
44
|
-
# the constant is issued by Zeitwerk while the same file is being
|
45
|
-
# required.
|
46
|
-
# 2. The constant `MyGem` is undefined while setup runs.
|
47
|
-
#
|
48
|
-
# Therefore, a directory `lib/my_gem` would autovivify a module according to
|
49
|
-
# the existing information. But that would be wrong.
|
50
|
-
#
|
51
|
-
# To overcome this fundamental limitation, we keep track of the constant
|
52
|
-
# paths that are in this situation ---in the example above, "MyGem"--- and
|
53
|
-
# take this collection into account for the autovivification logic.
|
54
|
-
#
|
55
|
-
# Note that you cannot generally address this by moving the setup code
|
56
|
-
# below the constant definition, because we want libraries to be able to
|
57
|
-
# use managed constants in the module body:
|
58
|
-
#
|
59
|
-
# module MyGem
|
60
|
-
# include MyConcern
|
61
|
-
# end
|
62
|
-
#
|
63
33
|
# @private
|
64
|
-
|
65
|
-
attr_reader :
|
34
|
+
#: Zeitwerk::Registry::ExplicitNamespaces
|
35
|
+
attr_reader :explicit_namespaces
|
66
36
|
|
67
|
-
# Registers a loader.
|
68
|
-
#
|
69
37
|
# @private
|
70
|
-
|
71
|
-
|
72
|
-
loaders << loader
|
73
|
-
end
|
38
|
+
#: Zeitwerk::Registry::Inceptions
|
39
|
+
attr_reader :inceptions
|
74
40
|
|
75
41
|
# @private
|
76
|
-
|
42
|
+
#: (Zeitwerk::Loader) -> void
|
77
43
|
def unregister_loader(loader)
|
78
|
-
loaders.delete(loader)
|
79
44
|
gem_loaders_by_root_file.delete_if { |_, l| l == loader }
|
80
|
-
autoloads.delete_if { |_, l| l == loader }
|
81
|
-
inceptions.delete_if { |_, (_, l)| l == loader }
|
82
45
|
end
|
83
46
|
|
84
47
|
# This method returns always a loader, the same instance for the same root
|
85
48
|
# file. That is how Zeitwerk::Loader.for_gem is idempotent.
|
86
49
|
#
|
87
50
|
# @private
|
88
|
-
|
51
|
+
#: (String, namespace: Module, warn_on_extra_files: boolish) -> Zeitwerk::Loader
|
89
52
|
def loader_for_gem(root_file, namespace:, warn_on_extra_files:)
|
90
53
|
gem_loaders_by_root_file[root_file] ||= GemLoader.__new(root_file, namespace: namespace, warn_on_extra_files: warn_on_extra_files)
|
91
54
|
end
|
92
|
-
|
93
|
-
# @private
|
94
|
-
# @sig (Zeitwerk::Loader, String) -> String
|
95
|
-
def register_autoload(loader, abspath)
|
96
|
-
autoloads[abspath] = loader
|
97
|
-
end
|
98
|
-
|
99
|
-
# @private
|
100
|
-
# @sig (String) -> void
|
101
|
-
def unregister_autoload(abspath)
|
102
|
-
autoloads.delete(abspath)
|
103
|
-
end
|
104
|
-
|
105
|
-
# @private
|
106
|
-
# @sig (String, String, Zeitwerk::Loader) -> void
|
107
|
-
def register_inception(cpath, abspath, loader)
|
108
|
-
inceptions[cpath] = [abspath, loader]
|
109
|
-
end
|
110
|
-
|
111
|
-
# @private
|
112
|
-
# @sig (String) -> String?
|
113
|
-
def inception?(cpath)
|
114
|
-
if pair = inceptions[cpath]
|
115
|
-
pair.first
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# @private
|
120
|
-
# @sig (String) -> Zeitwerk::Loader?
|
121
|
-
def loader_for(path)
|
122
|
-
autoloads[path]
|
123
|
-
end
|
124
|
-
|
125
|
-
# @private
|
126
|
-
# @sig (Zeitwerk::Loader) -> void
|
127
|
-
def on_unload(loader)
|
128
|
-
autoloads.delete_if { |_path, object| object == loader }
|
129
|
-
inceptions.delete_if { |_cpath, (_path, object)| object == loader }
|
130
|
-
end
|
131
55
|
end
|
132
56
|
|
133
|
-
@loaders =
|
57
|
+
@loaders = Loaders.new
|
134
58
|
@gem_loaders_by_root_file = {}
|
135
|
-
@autoloads =
|
136
|
-
@
|
59
|
+
@autoloads = Autoloads.new
|
60
|
+
@explicit_namespaces = ExplicitNamespaces.new
|
61
|
+
@inceptions = Inceptions.new
|
137
62
|
end
|
138
63
|
end
|
data/lib/zeitwerk/version.rb
CHANGED
data/lib/zeitwerk.rb
CHANGED
@@ -7,18 +7,19 @@ module Zeitwerk
|
|
7
7
|
require_relative "zeitwerk/loader"
|
8
8
|
require_relative "zeitwerk/gem_loader"
|
9
9
|
require_relative "zeitwerk/registry"
|
10
|
-
require_relative "zeitwerk/explicit_namespace"
|
11
10
|
require_relative "zeitwerk/inflector"
|
12
11
|
require_relative "zeitwerk/gem_inflector"
|
13
12
|
require_relative "zeitwerk/null_inflector"
|
14
|
-
require_relative "zeitwerk/kernel"
|
15
13
|
require_relative "zeitwerk/error"
|
16
14
|
require_relative "zeitwerk/version"
|
17
15
|
|
16
|
+
require_relative "zeitwerk/core_ext/kernel"
|
17
|
+
require_relative "zeitwerk/core_ext/module"
|
18
|
+
|
18
19
|
# This is a dangerous method.
|
19
20
|
#
|
20
21
|
# @experimental
|
21
|
-
|
22
|
+
#: () -> void
|
22
23
|
def self.with_loader
|
23
24
|
loader = Zeitwerk::Loader.new
|
24
25
|
yield loader
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zeitwerk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Noria
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: |2
|
14
13
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -23,14 +22,15 @@ files:
|
|
23
22
|
- MIT-LICENSE
|
24
23
|
- README.md
|
25
24
|
- lib/zeitwerk.rb
|
25
|
+
- lib/zeitwerk/core_ext/kernel.rb
|
26
|
+
- lib/zeitwerk/core_ext/module.rb
|
26
27
|
- lib/zeitwerk/cref.rb
|
28
|
+
- lib/zeitwerk/cref/map.rb
|
27
29
|
- lib/zeitwerk/error.rb
|
28
|
-
- lib/zeitwerk/explicit_namespace.rb
|
29
30
|
- lib/zeitwerk/gem_inflector.rb
|
30
31
|
- lib/zeitwerk/gem_loader.rb
|
31
32
|
- lib/zeitwerk/inflector.rb
|
32
33
|
- lib/zeitwerk/internal.rb
|
33
|
-
- lib/zeitwerk/kernel.rb
|
34
34
|
- lib/zeitwerk/loader.rb
|
35
35
|
- lib/zeitwerk/loader/callbacks.rb
|
36
36
|
- lib/zeitwerk/loader/config.rb
|
@@ -39,6 +39,10 @@ files:
|
|
39
39
|
- lib/zeitwerk/null_inflector.rb
|
40
40
|
- lib/zeitwerk/real_mod_name.rb
|
41
41
|
- lib/zeitwerk/registry.rb
|
42
|
+
- lib/zeitwerk/registry/autoloads.rb
|
43
|
+
- lib/zeitwerk/registry/explicit_namespaces.rb
|
44
|
+
- lib/zeitwerk/registry/inceptions.rb
|
45
|
+
- lib/zeitwerk/registry/loaders.rb
|
42
46
|
- lib/zeitwerk/version.rb
|
43
47
|
homepage: https://github.com/fxn/zeitwerk
|
44
48
|
licenses:
|
@@ -48,7 +52,6 @@ metadata:
|
|
48
52
|
changelog_uri: https://github.com/fxn/zeitwerk/blob/master/CHANGELOG.md
|
49
53
|
source_code_uri: https://github.com/fxn/zeitwerk
|
50
54
|
bug_tracker_uri: https://github.com/fxn/zeitwerk/issues
|
51
|
-
post_install_message:
|
52
55
|
rdoc_options: []
|
53
56
|
require_paths:
|
54
57
|
- lib
|
@@ -56,15 +59,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
59
|
requirements:
|
57
60
|
- - ">="
|
58
61
|
- !ruby/object:Gem::Version
|
59
|
-
version: '2
|
62
|
+
version: '3.2'
|
60
63
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
64
|
requirements:
|
62
65
|
- - ">="
|
63
66
|
- !ruby/object:Gem::Version
|
64
67
|
version: '0'
|
65
68
|
requirements: []
|
66
|
-
rubygems_version: 3.
|
67
|
-
signing_key:
|
69
|
+
rubygems_version: 3.6.9
|
68
70
|
specification_version: 4
|
69
71
|
summary: Efficient and thread-safe constant autoloader
|
70
72
|
test_files: []
|
@@ -1,93 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Zeitwerk
|
4
|
-
# Centralizes the logic for the trace point used to detect the creation of
|
5
|
-
# explicit namespaces, needed to descend into matching subdirectories right
|
6
|
-
# after the constant has been defined.
|
7
|
-
#
|
8
|
-
# The implementation assumes an explicit namespace is managed by one loader.
|
9
|
-
# Loaders that reopen namespaces owned by other projects are responsible for
|
10
|
-
# loading their constant before setup. This is documented.
|
11
|
-
module ExplicitNamespace # :nodoc: all
|
12
|
-
class << self
|
13
|
-
include RealModName
|
14
|
-
extend Internal
|
15
|
-
|
16
|
-
# Maps constant paths that correspond to explicit namespaces according to
|
17
|
-
# the file system, to the loader responsible for them.
|
18
|
-
#
|
19
|
-
# @sig Hash[String, Zeitwerk::Loader]
|
20
|
-
attr_reader :cpaths
|
21
|
-
private :cpaths
|
22
|
-
|
23
|
-
# @sig Mutex
|
24
|
-
attr_reader :mutex
|
25
|
-
private :mutex
|
26
|
-
|
27
|
-
# @sig TracePoint
|
28
|
-
attr_reader :tracer
|
29
|
-
private :tracer
|
30
|
-
|
31
|
-
# Asserts `cpath` corresponds to an explicit namespace for which `loader`
|
32
|
-
# is responsible.
|
33
|
-
#
|
34
|
-
# @sig (String, Zeitwerk::Loader) -> void
|
35
|
-
internal def register(cpath, loader)
|
36
|
-
mutex.synchronize do
|
37
|
-
cpaths[cpath] = loader
|
38
|
-
# We check enabled? because, looking at the C source code, enabling an
|
39
|
-
# enabled tracer does not seem to be a simple no-op.
|
40
|
-
tracer.enable unless tracer.enabled?
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# @sig (Zeitwerk::Loader) -> void
|
45
|
-
internal def unregister_loader(loader)
|
46
|
-
cpaths.delete_if { |_cpath, l| l == loader }
|
47
|
-
disable_tracer_if_unneeded
|
48
|
-
end
|
49
|
-
|
50
|
-
# This is an internal method only used by the test suite.
|
51
|
-
#
|
52
|
-
# @sig (String) -> bool
|
53
|
-
internal def registered?(cpath)
|
54
|
-
cpaths.key?(cpath)
|
55
|
-
end
|
56
|
-
|
57
|
-
# @sig () -> void
|
58
|
-
private def disable_tracer_if_unneeded
|
59
|
-
mutex.synchronize do
|
60
|
-
tracer.disable if cpaths.empty?
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# @sig (TracePoint) -> void
|
65
|
-
private def tracepoint_class_callback(event)
|
66
|
-
# If the class is a singleton class, we won't do anything with it so we
|
67
|
-
# can bail out immediately. This is several orders of magnitude faster
|
68
|
-
# than accessing its name.
|
69
|
-
return if event.self.singleton_class?
|
70
|
-
|
71
|
-
# It might be tempting to return if name.nil?, to avoid the computation
|
72
|
-
# of a hash code and delete call. But Ruby does not trigger the :class
|
73
|
-
# event on Class.new or Module.new, so that would incur in an extra call
|
74
|
-
# for nothing.
|
75
|
-
#
|
76
|
-
# On the other hand, if we were called, cpaths is not empty. Otherwise
|
77
|
-
# the tracer is disabled. So we do need to go ahead with the hash code
|
78
|
-
# computation and delete call.
|
79
|
-
if loader = cpaths.delete(real_mod_name(event.self))
|
80
|
-
loader.on_namespace_loaded(event.self)
|
81
|
-
disable_tracer_if_unneeded
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
@cpaths = {}
|
87
|
-
@mutex = Mutex.new
|
88
|
-
|
89
|
-
# We go through a method instead of defining a block mainly to have a better
|
90
|
-
# label when profiling.
|
91
|
-
@tracer = TracePoint.new(:class, &method(:tracepoint_class_callback))
|
92
|
-
end
|
93
|
-
end
|