zeitwerk 2.2.1

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.
@@ -0,0 +1,71 @@
1
+ module Zeitwerk::Loader::Callbacks
2
+ include Zeitwerk::RealModName
3
+
4
+ # Invoked from our decorated Kernel#require when a managed file is autoloaded.
5
+ #
6
+ # @private
7
+ # @param file [String]
8
+ # @return [void]
9
+ def on_file_autoloaded(file)
10
+ cref = autoloads.delete(file)
11
+ to_unload[cpath(*cref)] = [file, cref] if reloading_enabled?
12
+ Zeitwerk::Registry.unregister_autoload(file)
13
+
14
+ if logger && cdef?(*cref)
15
+ log("constant #{cpath(*cref)} loaded from file #{file}")
16
+ elsif !cdef?(*cref)
17
+ raise Zeitwerk::NameError, "expected file #{file} to define constant #{cpath(*cref)}, but didn't"
18
+ end
19
+ end
20
+
21
+ # Invoked from our decorated Kernel#require when a managed directory is
22
+ # autoloaded.
23
+ #
24
+ # @private
25
+ # @param dir [String]
26
+ # @return [void]
27
+ def on_dir_autoloaded(dir)
28
+ # Module#autoload does not serialize concurrent requires, and we handle
29
+ # directories ourselves, so the callback needs to account for concurrency.
30
+ #
31
+ # Multi-threading would introduce a race condition here in which thread t1
32
+ # autovivifies the module, and while autoloads for its children are being
33
+ # set, thread t2 autoloads the same namespace.
34
+ #
35
+ # Without the mutex and subsequent delete call, t2 would reset the module.
36
+ # That not only would reassign the constant (undesirable per se) but, worse,
37
+ # the module object created by t2 wouldn't have any of the autoloads for its
38
+ # children, since t1 would have correctly deleted its lazy_subdirs entry.
39
+ mutex2.synchronize do
40
+ if cref = autoloads.delete(dir)
41
+ autovivified_module = cref[0].const_set(cref[1], Module.new)
42
+ log("module #{autovivified_module.name} autovivified from directory #{dir}") if logger
43
+
44
+ to_unload[autovivified_module.name] = [dir, cref] if reloading_enabled?
45
+
46
+ # We don't unregister `dir` in the registry because concurrent threads
47
+ # wouldn't find a loader associated to it in Kernel#require and would
48
+ # try to require the directory. Instead, we are going to keep track of
49
+ # these to be able to unregister later if eager loading.
50
+ autoloaded_dirs << dir
51
+
52
+ on_namespace_loaded(autovivified_module)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Invoked when a class or module is created or reopened, either from the
58
+ # tracer or from module autovivification. If the namespace has matching
59
+ # subdirectories, we descend into them now.
60
+ #
61
+ # @private
62
+ # @param namespace [Module]
63
+ # @return [void]
64
+ def on_namespace_loaded(namespace)
65
+ if subdirs = lazy_subdirs.delete(real_mod_name(namespace))
66
+ subdirs.each do |subdir|
67
+ set_autoloads_in_dir(subdir, namespace)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,21 @@
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, nil]
12
+ if UnboundMethod.method_defined?(:bind_call)
13
+ def real_mod_name(mod)
14
+ UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
15
+ end
16
+ else
17
+ def real_mod_name(mod)
18
+ UNBOUND_METHOD_MODULE_NAME.bind(mod).call
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeitwerk
4
+ module Registry # :nodoc: all
5
+ class << self
6
+ # Keeps track of all loaders. Useful to broadcast messages and to prevent
7
+ # them from being garbage collected.
8
+ #
9
+ # @private
10
+ # @return [<Zeitwerk::Loader>]
11
+ attr_reader :loaders
12
+
13
+ # Registers loaders created with `for_gem` to make the method idempotent
14
+ # in case of reload.
15
+ #
16
+ # @private
17
+ # @return [{String => Zeitwerk::Loader}]
18
+ attr_reader :loaders_managing_gems
19
+
20
+ # Maps real paths to the loaders responsible for them.
21
+ #
22
+ # This information is used by our decorated `Kernel#require` to be able to
23
+ # invoke callbacks and autovivify modules.
24
+ #
25
+ # @private
26
+ # @return [{String => Zeitwerk::Loader}]
27
+ attr_reader :autoloads
28
+
29
+ # This hash table addresses an edge case in which an autoload is ignored.
30
+ #
31
+ # For example, let's suppose we want to autoload in a gem like this:
32
+ #
33
+ # # lib/my_gem.rb
34
+ # loader = Zeitwerk::Loader.new
35
+ # loader.push_dir(__dir__)
36
+ # loader.setup
37
+ #
38
+ # module MyGem
39
+ # end
40
+ #
41
+ # if you require "my_gem", as Bundler would do, this happens while setting
42
+ # up autoloads:
43
+ #
44
+ # 1. Object.autoload?(:MyGem) returns `nil` because the autoload for
45
+ # the constant is issued by Zeitwerk while the same file is being
46
+ # required.
47
+ # 2. The constant `MyGem` is undefined while setup runs.
48
+ #
49
+ # Therefore, a directory `lib/my_gem` would autovivify a module according to
50
+ # the existing information. But that would be wrong.
51
+ #
52
+ # To overcome this fundamental limitation, we keep track of the constant
53
+ # paths that are in this situation ---in the example above, "MyGem"--- and
54
+ # take this collection into account for the autovivification logic.
55
+ #
56
+ # Note that you cannot generally address this by moving the setup code
57
+ # below the constant definition, because we want libraries to be able to
58
+ # use managed constants in the module body:
59
+ #
60
+ # module MyGem
61
+ # include MyConcern
62
+ # end
63
+ #
64
+ # @private
65
+ # @return [{String => (String, Zeitwerk::Loader)}]
66
+ attr_reader :inceptions
67
+
68
+ # Registers a loader.
69
+ #
70
+ # @private
71
+ # @param loader [Zeitwerk::Loader]
72
+ # @return [void]
73
+ def register_loader(loader)
74
+ loaders << loader
75
+ end
76
+
77
+ # This method returns always a loader, the same instance for the same root
78
+ # file. That is how Zeitwerk::Loader.for_gem is idempotent.
79
+ #
80
+ # @private
81
+ # @param root_file [String]
82
+ # @return [Zeitwerk::Loader]
83
+ def loader_for_gem(root_file)
84
+ loaders_managing_gems[root_file] ||= begin
85
+ Loader.new.tap do |loader|
86
+ loader.tag = File.basename(root_file, ".rb")
87
+ loader.inflector = GemInflector.new(root_file)
88
+ loader.push_dir(File.dirname(root_file))
89
+ end
90
+ end
91
+ end
92
+
93
+ # @private
94
+ # @param loader [Zeitwerk::Loader]
95
+ # @param realpath [String]
96
+ # @return [void]
97
+ def register_autoload(loader, realpath)
98
+ autoloads[realpath] = loader
99
+ end
100
+
101
+ # @private
102
+ # @param realpath [String]
103
+ # @return [void]
104
+ def unregister_autoload(realpath)
105
+ autoloads.delete(realpath)
106
+ end
107
+
108
+ # @private
109
+ # @param cpath [String]
110
+ # @param realpath [String]
111
+ # @param loader [Zeitwerk::Loader]
112
+ # @return [void]
113
+ def register_inception(cpath, realpath, loader)
114
+ inceptions[cpath] = [realpath, loader]
115
+ end
116
+
117
+ # @private
118
+ # @param cpath [String]
119
+ # @return [String, nil]
120
+ def inception?(cpath)
121
+ if pair = inceptions[cpath]
122
+ pair.first
123
+ end
124
+ end
125
+
126
+ # @private
127
+ # @param path [String]
128
+ # @return [Zeitwerk::Loader, nil]
129
+ def loader_for(path)
130
+ autoloads[path]
131
+ end
132
+
133
+ # @private
134
+ # @param loader [Zeitwerk::Loader]
135
+ # @return [void]
136
+ def on_unload(loader)
137
+ autoloads.delete_if { |_path, object| object == loader }
138
+ inceptions.delete_if { |_cpath, (_path, object)| object == loader }
139
+ end
140
+ end
141
+
142
+ @loaders = []
143
+ @loaders_managing_gems = {}
144
+ @autoloads = {}
145
+ @inceptions = {}
146
+ end
147
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeitwerk
4
+ VERSION = "2.2.1"
5
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zeitwerk
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Xavier Noria
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-11-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ Zeitwerk implements constant autoloading with Ruby semantics. Each gem
15
+ and application may have their own independent autoloader, with its own
16
+ configuration, inflector, and logger. Supports autoloading, preloading,
17
+ reloading, and eager loading.
18
+ email: fxn@hashref.com
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - MIT-LICENSE
24
+ - README.md
25
+ - lib/zeitwerk.rb
26
+ - lib/zeitwerk/error.rb
27
+ - lib/zeitwerk/explicit_namespace.rb
28
+ - lib/zeitwerk/gem_inflector.rb
29
+ - lib/zeitwerk/inflector.rb
30
+ - lib/zeitwerk/kernel.rb
31
+ - lib/zeitwerk/loader.rb
32
+ - lib/zeitwerk/loader/callbacks.rb
33
+ - lib/zeitwerk/real_mod_name.rb
34
+ - lib/zeitwerk/registry.rb
35
+ - lib/zeitwerk/version.rb
36
+ homepage: https://github.com/fxn/zeitwerk
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 2.4.4
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.0.3
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Efficient and thread-safe constant autoloader
59
+ test_files: []