zeitwerk 2.6.13 → 2.7.2
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 +58 -45
- data/lib/zeitwerk/core_ext/module.rb +20 -0
- data/lib/zeitwerk/cref/map.rb +124 -0
- data/lib/zeitwerk/cref.rb +71 -0
- data/lib/zeitwerk/gem_loader.rb +1 -2
- data/lib/zeitwerk/internal.rb +1 -0
- data/lib/zeitwerk/loader/callbacks.rb +23 -25
- data/lib/zeitwerk/loader/config.rb +8 -8
- data/lib/zeitwerk/loader/eager_load.rb +13 -15
- data/lib/zeitwerk/loader/helpers.rb +23 -64
- data/lib/zeitwerk/loader.rb +196 -131
- data/lib/zeitwerk/null_inflector.rb +1 -0
- data/lib/zeitwerk/real_mod_name.rb +2 -8
- data/lib/zeitwerk/registry/explicit_namespaces.rb +64 -0
- data/lib/zeitwerk/registry/inceptions.rb +31 -0
- data/lib/zeitwerk/registry.rb +3 -56
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +4 -2
- metadata +10 -9
- data/lib/zeitwerk/explicit_namespace.rb +0 -93
- /data/lib/zeitwerk/{kernel.rb → core_ext/kernel.rb} +0 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zeitwerk::Registry
|
4
|
+
# This module is a registry for explicit namespaces.
|
5
|
+
#
|
6
|
+
# When a loader determines that a certain file should define an explicit
|
7
|
+
# namespace, it registers it here, associating its cref with itself.
|
8
|
+
#
|
9
|
+
# If the namespace is autoloaded, our const_added callback retrieves its
|
10
|
+
# loader by calling loader_for. That way, the loader is able to scan the
|
11
|
+
# subdirectories that conform the namespace and set autoloads for their
|
12
|
+
# expected constants just in time.
|
13
|
+
#
|
14
|
+
# Once autoloaded, the namespace is unregistered.
|
15
|
+
#
|
16
|
+
# The implementation assumes an explicit namespace is managed by one loader.
|
17
|
+
# Loaders that reopen namespaces owned by other projects are responsible for
|
18
|
+
# loading their constant before setup. This is documented.
|
19
|
+
module ExplicitNamespaces # :nodoc: all
|
20
|
+
# Maps crefs of explicit namespaces with their corresponding loader.
|
21
|
+
#
|
22
|
+
# Entries are added as the namespaces are found, and removed as they are
|
23
|
+
# autoloaded.
|
24
|
+
#
|
25
|
+
# @sig Zeitwerk::Cref::Map[Zeitwerk::Loader]
|
26
|
+
@loaders = Zeitwerk::Cref::Map.new
|
27
|
+
|
28
|
+
class << self
|
29
|
+
extend Zeitwerk::Internal
|
30
|
+
|
31
|
+
# Registers `cref` as being the constant path of an explicit namespace
|
32
|
+
# managed by `loader`.
|
33
|
+
#
|
34
|
+
# @sig (Zeitwerk::Cref, Zeitwerk::Loader) -> void
|
35
|
+
internal def register(cref, loader)
|
36
|
+
@loaders[cref] = loader
|
37
|
+
end
|
38
|
+
|
39
|
+
# @sig (Module, Symbol) -> Zeitwerk::Loader?
|
40
|
+
internal def loader_for(mod, cname)
|
41
|
+
@loaders.delete_mod_cname(mod, cname)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @sig (Zeitwerk::Loader) -> void
|
45
|
+
internal def unregister_loader(loader)
|
46
|
+
@loaders.delete_by_value(loader)
|
47
|
+
end
|
48
|
+
|
49
|
+
# This is an internal method only used by the test suite.
|
50
|
+
#
|
51
|
+
# @sig (Symbol | String) -> Zeitwerk::Loader?
|
52
|
+
internal def registered?(cref)
|
53
|
+
@loaders[cref]
|
54
|
+
end
|
55
|
+
|
56
|
+
# This is an internal method only used by the test suite.
|
57
|
+
#
|
58
|
+
# @sig () -> void
|
59
|
+
internal def clear
|
60
|
+
@loaders.clear
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -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
|
+
module Inceptions # :nodoc: all
|
6
|
+
# @sig Zeitwerk::Cref::Map[String]
|
7
|
+
@inceptions = Zeitwerk::Cref::Map.new
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# @sig (Zeitwerk::Cref, String) -> void
|
11
|
+
def register(cref, autoload_path)
|
12
|
+
@inceptions[cref] = autoload_path
|
13
|
+
end
|
14
|
+
|
15
|
+
# @sig (String) -> String?
|
16
|
+
def registered?(cref)
|
17
|
+
@inceptions[cref]
|
18
|
+
end
|
19
|
+
|
20
|
+
# @sig (String) -> void
|
21
|
+
def unregister(cref)
|
22
|
+
@inceptions.delete(cref)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @sig () -> void
|
26
|
+
def clear # for tests
|
27
|
+
@inceptions.clear
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/zeitwerk/registry.rb
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
module Zeitwerk
|
4
4
|
module Registry # :nodoc: all
|
5
|
+
require_relative "registry/explicit_namespaces"
|
6
|
+
require_relative "registry/inceptions"
|
7
|
+
|
5
8
|
class << self
|
6
9
|
# Keeps track of all loaders. Useful to broadcast messages and to prevent
|
7
10
|
# them from being garbage collected.
|
@@ -25,45 +28,6 @@ module Zeitwerk
|
|
25
28
|
# @sig Hash[String, Zeitwerk::Loader]
|
26
29
|
attr_reader :autoloads
|
27
30
|
|
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
|
-
# @private
|
64
|
-
# @sig Hash[String, [String, Zeitwerk::Loader]]
|
65
|
-
attr_reader :inceptions
|
66
|
-
|
67
31
|
# Registers a loader.
|
68
32
|
#
|
69
33
|
# @private
|
@@ -78,7 +42,6 @@ module Zeitwerk
|
|
78
42
|
loaders.delete(loader)
|
79
43
|
gem_loaders_by_root_file.delete_if { |_, l| l == loader }
|
80
44
|
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
|
@@ -102,20 +65,6 @@ module Zeitwerk
|
|
102
65
|
autoloads.delete(abspath)
|
103
66
|
end
|
104
67
|
|
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
68
|
# @private
|
120
69
|
# @sig (String) -> Zeitwerk::Loader?
|
121
70
|
def loader_for(path)
|
@@ -126,13 +75,11 @@ module Zeitwerk
|
|
126
75
|
# @sig (Zeitwerk::Loader) -> void
|
127
76
|
def on_unload(loader)
|
128
77
|
autoloads.delete_if { |_path, object| object == loader }
|
129
|
-
inceptions.delete_if { |_cpath, (_path, object)| object == loader }
|
130
78
|
end
|
131
79
|
end
|
132
80
|
|
133
81
|
@loaders = []
|
134
82
|
@gem_loaders_by_root_file = {}
|
135
83
|
@autoloads = {}
|
136
|
-
@inceptions = {}
|
137
84
|
end
|
138
85
|
end
|
data/lib/zeitwerk/version.rb
CHANGED
data/lib/zeitwerk.rb
CHANGED
@@ -3,17 +3,19 @@
|
|
3
3
|
module Zeitwerk
|
4
4
|
require_relative "zeitwerk/real_mod_name"
|
5
5
|
require_relative "zeitwerk/internal"
|
6
|
+
require_relative "zeitwerk/cref"
|
6
7
|
require_relative "zeitwerk/loader"
|
7
8
|
require_relative "zeitwerk/gem_loader"
|
8
9
|
require_relative "zeitwerk/registry"
|
9
|
-
require_relative "zeitwerk/explicit_namespace"
|
10
10
|
require_relative "zeitwerk/inflector"
|
11
11
|
require_relative "zeitwerk/gem_inflector"
|
12
12
|
require_relative "zeitwerk/null_inflector"
|
13
|
-
require_relative "zeitwerk/kernel"
|
14
13
|
require_relative "zeitwerk/error"
|
15
14
|
require_relative "zeitwerk/version"
|
16
15
|
|
16
|
+
require_relative "zeitwerk/core_ext/kernel"
|
17
|
+
require_relative "zeitwerk/core_ext/module"
|
18
|
+
|
17
19
|
# This is a dangerous method.
|
18
20
|
#
|
19
21
|
# @experimental
|
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.2
|
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: 2025-02-18 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: |2
|
14
13
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|
@@ -23,13 +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
|
27
|
+
- lib/zeitwerk/cref.rb
|
28
|
+
- lib/zeitwerk/cref/map.rb
|
26
29
|
- lib/zeitwerk/error.rb
|
27
|
-
- lib/zeitwerk/explicit_namespace.rb
|
28
30
|
- lib/zeitwerk/gem_inflector.rb
|
29
31
|
- lib/zeitwerk/gem_loader.rb
|
30
32
|
- lib/zeitwerk/inflector.rb
|
31
33
|
- lib/zeitwerk/internal.rb
|
32
|
-
- lib/zeitwerk/kernel.rb
|
33
34
|
- lib/zeitwerk/loader.rb
|
34
35
|
- lib/zeitwerk/loader/callbacks.rb
|
35
36
|
- lib/zeitwerk/loader/config.rb
|
@@ -38,6 +39,8 @@ files:
|
|
38
39
|
- lib/zeitwerk/null_inflector.rb
|
39
40
|
- lib/zeitwerk/real_mod_name.rb
|
40
41
|
- lib/zeitwerk/registry.rb
|
42
|
+
- lib/zeitwerk/registry/explicit_namespaces.rb
|
43
|
+
- lib/zeitwerk/registry/inceptions.rb
|
41
44
|
- lib/zeitwerk/version.rb
|
42
45
|
homepage: https://github.com/fxn/zeitwerk
|
43
46
|
licenses:
|
@@ -47,7 +50,6 @@ metadata:
|
|
47
50
|
changelog_uri: https://github.com/fxn/zeitwerk/blob/master/CHANGELOG.md
|
48
51
|
source_code_uri: https://github.com/fxn/zeitwerk
|
49
52
|
bug_tracker_uri: https://github.com/fxn/zeitwerk/issues
|
50
|
-
post_install_message:
|
51
53
|
rdoc_options: []
|
52
54
|
require_paths:
|
53
55
|
- lib
|
@@ -55,15 +57,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
57
|
requirements:
|
56
58
|
- - ">="
|
57
59
|
- !ruby/object:Gem::Version
|
58
|
-
version: '2
|
60
|
+
version: '3.2'
|
59
61
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
62
|
requirements:
|
61
63
|
- - ">="
|
62
64
|
- !ruby/object:Gem::Version
|
63
65
|
version: '0'
|
64
66
|
requirements: []
|
65
|
-
rubygems_version: 3.
|
66
|
-
signing_key:
|
67
|
+
rubygems_version: 3.6.4
|
67
68
|
specification_version: 4
|
68
69
|
summary: Efficient and thread-safe constant autoloader
|
69
70
|
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
|
File without changes
|