zeitwerk 2.6.7 → 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.
@@ -0,0 +1,6 @@
1
+ class Zeitwerk::NullInflector
2
+ #: (String, String) -> String
3
+ def camelize(basename, _abspath)
4
+ basename
5
+ end
6
+ end
@@ -1,22 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk::RealModName
4
+ #: UnboundMethod
4
5
  UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
5
6
  private_constant :UNBOUND_METHOD_MODULE_NAME
6
7
 
7
- # Returns the real name of the class or module, as set after the first
8
- # constant to which it was assigned (or nil).
8
+ # Returns the real name of the class or module.
9
9
  #
10
- # The name method can be overridden, hence the indirection in this method.
10
+ # We need this indirection becasue the `name` method can be overridden, and
11
+ # because in practice what we really need is the constant paths of modules
12
+ # with a permanent name, not so much what the user considers to be the name of
13
+ # a certain class or module of theirs.
11
14
  #
12
- # @sig (Module) -> String?
13
- if UnboundMethod.method_defined?(:bind_call)
14
- def real_mod_name(mod)
15
- UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
16
- end
17
- else
18
- def real_mod_name(mod)
19
- UNBOUND_METHOD_MODULE_NAME.bind(mod).call
20
- end
15
+ #: (Module) -> String?
16
+ def real_mod_name(mod)
17
+ UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
21
18
  end
22
19
  end
@@ -0,0 +1,38 @@
1
+ module Zeitwerk::Registry
2
+ class Autoloads # :nodoc:
3
+ #: () -> void
4
+ def initialize
5
+ @autoloads = {} #: Hash[String, Zeitwerk::Loader]
6
+ end
7
+
8
+ #: (String, Zeitwerk::Loader) -> Zeitwerk::Loader
9
+ def register(abspath, loader)
10
+ @autoloads[abspath] = loader
11
+ end
12
+
13
+ #: (String) -> Zeitwerk::Loader?
14
+ def registered?(path)
15
+ @autoloads[path]
16
+ end
17
+
18
+ #: (String) -> Zeitwerk::Loader?
19
+ def unregister(abspath)
20
+ @autoloads.delete(abspath)
21
+ end
22
+
23
+ #: (Zeitwerk::Loader) -> void
24
+ def unregister_loader(loader)
25
+ @autoloads.delete_if { _2 == loader }
26
+ end
27
+
28
+ #: () -> bool
29
+ def empty? # for tests
30
+ @autoloads.empty?
31
+ end
32
+
33
+ #: () -> void
34
+ def clear # for tests
35
+ @autoloads.clear
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeitwerk::Registry
4
+ # 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
+ #
20
+ # **This is a private module.**
21
+ class ExplicitNamespaces # :nodoc: all
22
+ #: () -> void
23
+ def initialize
24
+ # Maps crefs of explicit namespaces with their corresponding loader.
25
+ #
26
+ # Entries are added as the namespaces are found, and removed as they are
27
+ # autoloaded.
28
+ @loaders = Zeitwerk::Cref::Map.new
29
+ end
30
+
31
+ # Registers `cref` as being the constant path of an explicit namespace
32
+ # managed by `loader`.
33
+ #
34
+ #: (Zeitwerk::Cref, Zeitwerk::Loader) -> void
35
+ def register(cref, loader)
36
+ @loaders[cref] = loader
37
+ end
38
+
39
+ #: (Module, Symbol) -> Zeitwerk::Loader?
40
+ def loader_for(mod, cname)
41
+ @loaders.delete_mod_cname(mod, cname)
42
+ end
43
+
44
+ #: (Zeitwerk::Loader) -> void
45
+ 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
+ #: (Zeitwerk::Cref) -> Zeitwerk::Loader?
52
+ def registered?(cref)
53
+ @loaders[cref]
54
+ end
55
+
56
+ #: () -> void
57
+ def clear # for tests
58
+ @loaders.clear
59
+ end
60
+ end
61
+ 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
+ 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
@@ -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
- # @sig Array[Zeitwerk::Loader]
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
- # @sig Hash[String, Zeitwerk::Loader]
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
- # @sig Hash[String, Zeitwerk::Loader]
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
- # @sig Hash[String, [String, Zeitwerk::Loader]]
65
- attr_reader :inceptions
34
+ #: Zeitwerk::Registry::ExplicitNamespaces
35
+ attr_reader :explicit_namespaces
66
36
 
67
- # Registers a loader.
68
- #
69
37
  # @private
70
- # @sig (Zeitwerk::Loader) -> void
71
- def register_loader(loader)
72
- loaders << loader
73
- end
38
+ #: Zeitwerk::Registry::Inceptions
39
+ attr_reader :inceptions
74
40
 
75
41
  # @private
76
- # @sig (Zeitwerk::Loader) -> void
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
- # @sig (String) -> Zeitwerk::Loader
89
- def loader_for_gem(root_file, warn_on_extra_files:)
90
- gem_loaders_by_root_file[root_file] ||= GemLoader._new(root_file, warn_on_extra_files: warn_on_extra_files)
91
- 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 }
51
+ #: (String, namespace: Module, warn_on_extra_files: boolish) -> Zeitwerk::Loader
52
+ def loader_for_gem(root_file, namespace:, warn_on_extra_files:)
53
+ gem_loaders_by_root_file[root_file] ||= GemLoader.__new(root_file, namespace: namespace, warn_on_extra_files: warn_on_extra_files)
130
54
  end
131
55
  end
132
56
 
133
- @loaders = []
57
+ @loaders = Loaders.new
134
58
  @gem_loaders_by_root_file = {}
135
- @autoloads = {}
136
- @inceptions = {}
59
+ @autoloads = Autoloads.new
60
+ @explicit_namespaces = ExplicitNamespaces.new
61
+ @inceptions = Inceptions.new
137
62
  end
138
63
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.6.7"
4
+ #: String
5
+ VERSION = "2.7.3"
5
6
  end
data/lib/zeitwerk.rb CHANGED
@@ -3,20 +3,23 @@
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
- require_relative "zeitwerk/kernel"
12
+ require_relative "zeitwerk/null_inflector"
13
13
  require_relative "zeitwerk/error"
14
14
  require_relative "zeitwerk/version"
15
15
 
16
+ require_relative "zeitwerk/core_ext/kernel"
17
+ require_relative "zeitwerk/core_ext/module"
18
+
16
19
  # This is a dangerous method.
17
20
  #
18
21
  # @experimental
19
- # @sig () -> void
22
+ #: () -> void
20
23
  def self.with_loader
21
24
  loader = Zeitwerk::Loader.new
22
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.6.7
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: 2023-02-10 00:00:00.000000000 Z
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,20 +22,27 @@ 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
36
37
  - lib/zeitwerk/loader/eager_load.rb
37
38
  - lib/zeitwerk/loader/helpers.rb
39
+ - lib/zeitwerk/null_inflector.rb
38
40
  - lib/zeitwerk/real_mod_name.rb
39
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
40
46
  - lib/zeitwerk/version.rb
41
47
  homepage: https://github.com/fxn/zeitwerk
42
48
  licenses:
@@ -46,7 +52,6 @@ metadata:
46
52
  changelog_uri: https://github.com/fxn/zeitwerk/blob/master/CHANGELOG.md
47
53
  source_code_uri: https://github.com/fxn/zeitwerk
48
54
  bug_tracker_uri: https://github.com/fxn/zeitwerk/issues
49
- post_install_message:
50
55
  rdoc_options: []
51
56
  require_paths:
52
57
  - lib
@@ -54,15 +59,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
54
59
  requirements:
55
60
  - - ">="
56
61
  - !ruby/object:Gem::Version
57
- version: '2.5'
62
+ version: '3.2'
58
63
  required_rubygems_version: !ruby/object:Gem::Requirement
59
64
  requirements:
60
65
  - - ">="
61
66
  - !ruby/object:Gem::Version
62
67
  version: '0'
63
68
  requirements: []
64
- rubygems_version: 3.4.3
65
- signing_key:
69
+ rubygems_version: 3.6.9
66
70
  specification_version: 4
67
71
  summary: Efficient and thread-safe constant autoloader
68
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