zeitwerk 2.6.3 → 2.6.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a554ef9e7a26b8edb48779c400a620aa694e54313626505d231fa3c72d4f66fe
4
- data.tar.gz: 779e40bff86dbb627aef9dfd3ed897620994f7578a3673b640fad2a449c61a61
3
+ metadata.gz: 220865ab50f0336d05c8b907a08a94a8bf92843ba688c1a9686163a190b0754e
4
+ data.tar.gz: a9d62662351c60a3b264a68726a84c41fc4b6f92eaae855fb4887e3eb4cf14a1
5
5
  SHA512:
6
- metadata.gz: a9038b69db15d5800aec43f5e6d2367df5ea04f19b00f0cd9a76c36eebac1f7cc2e085442e05b4ece448ef0a717b8f10c5c75367d63dbdbfb916a3e2bf505fed
7
- data.tar.gz: 065c3b61d2cc6fb82ae3e98ddca31c4d6842cb3d7f68187c822c7b6c97720cdbfa8de9c11b5b1ff749fc655d408cc98f72dd1107e08979a5249ba6451e2539ed
6
+ metadata.gz: b802eaabb27e6268eafa8d35d4402819551203c5fd2a1692d9a9bae65075bf668f5aecfaf3fa6c763987796b60fd5bde4704aebda4a8bfa1c74a526c27264ab5
7
+ data.tar.gz: c4a53a60b49e5cb83aee148271b26fee8de15fd072327e52363438b0531d9953961305375ec727cc3a0ff2c021254eb081056350165922bd9f6f8b1e1b3e96f3
data/README.md CHANGED
@@ -213,7 +213,7 @@ serializers/user_serializer.rb -> UserSerializer
213
213
  <a id="markdown-custom-root-namespaces" name="custom-root-namespaces"></a>
214
214
  #### Custom root namespaces
215
215
 
216
- While `Object` is by far the most common root namespace, you can associate a different one to a particular root directory. The method `push_dir` accepts a class or module object in the optional `namespace` keyword argument.
216
+ While `Object` is by far the most common root namespace, you can associate a different one to a particular root directory. The method `push_dir` accepts a non-anonymous class or module object in the optional `namespace` keyword argument.
217
217
 
218
218
  For example, given:
219
219
 
@@ -449,6 +449,8 @@ In gems, the method needs to be invoked after the main namespace has been define
449
449
 
450
450
  Eager loading is synchronized and idempotent.
451
451
 
452
+ Attempting to eager load without previously calling `setup` raises `Zeitwerk::SetupRequired`.
453
+
452
454
  <a id="markdown-eager-load-exclusions" name="eager-load-exclusions"></a>
453
455
  #### Eager load exclusions
454
456
 
@@ -492,6 +494,8 @@ The method checks if a regular eager load was already executed, in which case it
492
494
 
493
495
  Nested root directories which are descendants of the argument are skipped. Those subtrees are considered to be conceptually apart.
494
496
 
497
+ Attempting to eager load a directory without previously calling `setup` raises `Zeitwerk::SetupRequired`.
498
+
495
499
  <a id="markdown-eager-load-namespaces" name="eager-load-namespaces"></a>
496
500
  #### Eager load namespaces
497
501
 
@@ -527,6 +531,8 @@ The method checks if a regular eager load was already executed, in which case it
527
531
 
528
532
  If root directories are assigned to custom namespaces, the method behaves as you'd expect, according to the namespacing relationship between the custom namespace and the argument.
529
533
 
534
+ Attempting to eager load a namespace without previously calling `setup` raises `Zeitwerk::SetupRequired`.
535
+
530
536
  <a id="markdown-eager-load-namespaces-shared-by-several-loaders" name="eager-load-namespaces-shared-by-several-loaders"></a>
531
537
  #### Eager load namespaces shared by several loaders
532
538
 
@@ -540,6 +546,8 @@ This may be handy, for example, if a framework supports plugins and a shared nam
540
546
 
541
547
  Please, note that loaders only eager load namespaces they manage, as documented above. Therefore, this method does not allow you to eager load namespaces not managed by Zeitwerk loaders.
542
548
 
549
+ This method does not require that all registered loaders have `setup` already invoked, since that is out of your control. If there's any in that state, it is simply skipped.
550
+
543
551
  <a id="markdown-global-eager-load" name="global-eager-load"></a>
544
552
  #### Global eager load
545
553
 
@@ -555,6 +563,8 @@ Note that thanks to idempotence `Zeitwerk::Loader.eager_load_all` won't eager lo
555
563
 
556
564
  This method does not accept the `force` flag, since in general it wouldn't be a good idea to force eager loading in 3rd party code.
557
565
 
566
+ This method does not require that all registered loaders have `setup` already invoked, since that is out of your control. If there's any in that state, it is simply skipped.
567
+
558
568
  <a id="markdown-loading-individual-files" name="loading-individual-files"></a>
559
569
  ### Loading individual files
560
570
 
@@ -591,7 +601,7 @@ loader.reload
591
601
 
592
602
  There is no way to undo this, either you want to reload or you don't.
593
603
 
594
- Enabling reloading after setup raises `Zeitwerk::Error`. Attempting to reload without having it enabled raises `Zeitwerk::ReloadingDisabledError`.
604
+ Enabling reloading after setup raises `Zeitwerk::Error`. Attempting to reload without having it enabled raises `Zeitwerk::ReloadingDisabledError`. Attempting to reload without previously calling `setup` raises `Zeitwerk::SetupRequired`.
595
605
 
596
606
  Generally speaking, reloading is useful while developing running services like web applications. Gems that implement regular libraries, so to speak, or services running in testing or production environments, won't normally have a use case for reloading. If reloading is not enabled, Zeitwerk is able to use less memory.
597
607
 
@@ -12,4 +12,10 @@ module Zeitwerk
12
12
 
13
13
  class NameError < ::NameError
14
14
  end
15
+
16
+ class SetupRequired < Error
17
+ def initialize
18
+ super("please, finish your configuration and call Zeitwerk::Loader#setup once all is ready")
19
+ end
20
+ end
15
21
  end
@@ -11,28 +11,28 @@ module Zeitwerk
11
11
  module ExplicitNamespace # :nodoc: all
12
12
  class << self
13
13
  include RealModName
14
+ extend Internal
14
15
 
15
16
  # Maps constant paths that correspond to explicit namespaces according to
16
17
  # the file system, to the loader responsible for them.
17
18
  #
18
- # @private
19
19
  # @sig Hash[String, Zeitwerk::Loader]
20
20
  attr_reader :cpaths
21
+ private :cpaths
21
22
 
22
- # @private
23
23
  # @sig Mutex
24
24
  attr_reader :mutex
25
+ private :mutex
25
26
 
26
- # @private
27
27
  # @sig TracePoint
28
28
  attr_reader :tracer
29
+ private :tracer
29
30
 
30
31
  # Asserts `cpath` corresponds to an explicit namespace for which `loader`
31
32
  # is responsible.
32
33
  #
33
- # @private
34
34
  # @sig (String, Zeitwerk::Loader) -> void
35
- def register(cpath, loader)
35
+ internal def register(cpath, loader)
36
36
  mutex.synchronize do
37
37
  cpaths[cpath] = loader
38
38
  # We check enabled? because, looking at the C source code, enabling an
@@ -41,24 +41,21 @@ module Zeitwerk
41
41
  end
42
42
  end
43
43
 
44
- # @private
45
44
  # @sig (Zeitwerk::Loader) -> void
46
- def unregister_loader(loader)
45
+ internal def unregister_loader(loader)
47
46
  cpaths.delete_if { |_cpath, l| l == loader }
48
47
  disable_tracer_if_unneeded
49
48
  end
50
49
 
51
- private
52
-
53
50
  # @sig () -> void
54
- def disable_tracer_if_unneeded
51
+ private def disable_tracer_if_unneeded
55
52
  mutex.synchronize do
56
53
  tracer.disable if cpaths.empty?
57
54
  end
58
55
  end
59
56
 
60
57
  # @sig (TracePoint) -> void
61
- def tracepoint_class_callback(event)
58
+ private def tracepoint_class_callback(event)
62
59
  # If the class is a singleton class, we won't do anything with it so we
63
60
  # can bail out immediately. This is several orders of magnitude faster
64
61
  # than accessing its name.
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is a private module.
4
+ module Zeitwerk::Internal
5
+ def internal(method_name)
6
+ private method_name
7
+
8
+ mangled = "__#{method_name}"
9
+ alias_method mangled, method_name
10
+ public mangled
11
+ end
12
+ end
@@ -4,85 +4,91 @@ require "set"
4
4
  require "securerandom"
5
5
 
6
6
  module Zeitwerk::Loader::Config
7
- # Absolute paths of the root directories. Stored in a hash to preserve order,
8
- # easily handle duplicates, have a fast lookup needed for detecting nested
9
- # paths, and store namespaces as values.
7
+ extend Zeitwerk::Internal
8
+ include Zeitwerk::RealModName
9
+
10
+ # @sig #camelize
11
+ attr_accessor :inflector
12
+
13
+ # @sig #call | #debug | nil
14
+ attr_accessor :logger
15
+
16
+ # Absolute paths of the root directories, mapped to their respective root namespaces:
10
17
  #
11
18
  # "/Users/fxn/blog/app/channels" => Object,
12
19
  # "/Users/fxn/blog/app/adapters" => ActiveJob::QueueAdapters,
13
20
  # ...
14
21
  #
22
+ # Stored in a hash to preserve order, easily handle duplicates, and have a
23
+ # fast lookup by directory.
24
+ #
15
25
  # This is a private collection maintained by the loader. The public
16
26
  # interface for it is `push_dir` and `dirs`.
17
27
  #
18
- # @private
19
28
  # @sig Hash[String, Module]
20
- attr_reader :root_dirs
21
-
22
- # @sig #camelize
23
- attr_accessor :inflector
29
+ attr_reader :roots
30
+ internal :roots
24
31
 
25
32
  # Absolute paths of files, directories, or glob patterns to be totally
26
33
  # ignored.
27
34
  #
28
- # @private
29
35
  # @sig Set[String]
30
36
  attr_reader :ignored_glob_patterns
37
+ private :ignored_glob_patterns
31
38
 
32
39
  # The actual collection of absolute file and directory names at the time the
33
40
  # ignored glob patterns were expanded. Computed on setup, and recomputed on
34
41
  # reload.
35
42
  #
36
- # @private
37
43
  # @sig Set[String]
38
44
  attr_reader :ignored_paths
45
+ private :ignored_paths
39
46
 
40
47
  # Absolute paths of directories or glob patterns to be collapsed.
41
48
  #
42
- # @private
43
49
  # @sig Set[String]
44
50
  attr_reader :collapse_glob_patterns
51
+ private :collapse_glob_patterns
45
52
 
46
53
  # The actual collection of absolute directory names at the time the collapse
47
54
  # glob patterns were expanded. Computed on setup, and recomputed on reload.
48
55
  #
49
- # @private
50
56
  # @sig Set[String]
51
57
  attr_reader :collapse_dirs
58
+ private :collapse_dirs
52
59
 
53
60
  # Absolute paths of files or directories not to be eager loaded.
54
61
  #
55
- # @private
56
62
  # @sig Set[String]
57
63
  attr_reader :eager_load_exclusions
64
+ private :eager_load_exclusions
58
65
 
59
66
  # User-oriented callbacks to be fired on setup and on reload.
60
67
  #
61
- # @private
62
68
  # @sig Array[{ () -> void }]
63
69
  attr_reader :on_setup_callbacks
70
+ private :on_setup_callbacks
64
71
 
65
72
  # User-oriented callbacks to be fired when a constant is loaded.
66
73
  #
67
- # @private
68
74
  # @sig Hash[String, Array[{ (Object, String) -> void }]]
69
75
  # Hash[Symbol, Array[{ (String, Object, String) -> void }]]
70
76
  attr_reader :on_load_callbacks
77
+ private :on_load_callbacks
71
78
 
72
79
  # User-oriented callbacks to be fired before constants are removed.
73
80
  #
74
- # @private
75
81
  # @sig Hash[String, Array[{ (Object, String) -> void }]]
76
82
  # Hash[Symbol, Array[{ (String, Object, String) -> void }]]
77
83
  attr_reader :on_unload_callbacks
78
-
79
- # @sig #call | #debug | nil
80
- attr_accessor :logger
84
+ private :on_unload_callbacks
81
85
 
82
86
  def initialize
83
- @initialized_at = Time.now
84
- @root_dirs = {}
85
87
  @inflector = Zeitwerk::Inflector.new
88
+ @logger = self.class.default_logger
89
+ @tag = SecureRandom.hex(3)
90
+ @initialized_at = Time.now
91
+ @roots = {}
86
92
  @ignored_glob_patterns = Set.new
87
93
  @ignored_paths = Set.new
88
94
  @collapse_glob_patterns = Set.new
@@ -92,8 +98,6 @@ module Zeitwerk::Loader::Config
92
98
  @on_setup_callbacks = []
93
99
  @on_load_callbacks = {}
94
100
  @on_unload_callbacks = {}
95
- @logger = self.class.default_logger
96
- @tag = SecureRandom.hex(3)
97
101
  end
98
102
 
99
103
  # Pushes `path` to the list of root directories.
@@ -110,10 +114,14 @@ module Zeitwerk::Loader::Config
110
114
  raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
111
115
  end
112
116
 
117
+ unless real_mod_name(namespace)
118
+ raise Zeitwerk::Error, "root namespaces cannot be anonymous"
119
+ end
120
+
113
121
  abspath = File.expand_path(path)
114
122
  if dir?(abspath)
115
123
  raise_if_conflicting_directory(abspath)
116
- root_dirs[abspath] = namespace
124
+ roots[abspath] = namespace
117
125
  else
118
126
  raise Zeitwerk::Error, "the root directory #{abspath} does not exist"
119
127
  end
@@ -146,9 +154,9 @@ module Zeitwerk::Loader::Config
146
154
  # @sig () -> Array[String] | Hash[String, Module]
147
155
  def dirs(namespaces: false)
148
156
  if namespaces
149
- root_dirs.clone
157
+ roots.clone
150
158
  else
151
- root_dirs.keys
159
+ roots.keys
152
160
  end.freeze
153
161
  end
154
162
 
@@ -275,71 +283,73 @@ module Zeitwerk::Loader::Config
275
283
  # Returns true if the argument has been configured to be ignored, or is a
276
284
  # descendant of an ignored directory.
277
285
  #
278
- # @private
279
286
  # @sig (String) -> bool
280
- def ignores?(abspath)
287
+ internal def ignores?(abspath)
281
288
  # Common use case.
282
289
  return false if ignored_paths.empty?
283
290
 
284
291
  walk_up(abspath) do |abspath|
285
- return true if ignored_paths.member?(abspath)
286
- return false if root_dirs.key?(abspath)
292
+ return true if ignored_path?(abspath)
293
+ return false if roots.key?(abspath)
287
294
  end
288
295
 
289
296
  false
290
297
  end
291
298
 
292
- private
299
+ # @sig (String) -> bool
300
+ private def ignored_path?(abspath)
301
+ ignored_paths.member?(abspath)
302
+ end
293
303
 
294
304
  # @sig () -> Array[String]
295
- def actual_root_dirs
296
- root_dirs.reject do |root_dir, _namespace|
297
- !dir?(root_dir) || ignored_paths.member?(root_dir)
305
+ private def actual_roots
306
+ roots.reject do |root_dir, _root_namespace|
307
+ !dir?(root_dir) || ignored_path?(root_dir)
298
308
  end
299
309
  end
300
310
 
301
311
  # @sig (String) -> bool
302
- def root_dir?(dir)
303
- root_dirs.key?(dir)
312
+ private def root_dir?(dir)
313
+ roots.key?(dir)
304
314
  end
305
315
 
306
316
  # @sig (String) -> bool
307
- def excluded_from_eager_load?(abspath)
317
+ private def excluded_from_eager_load?(abspath)
308
318
  # Optimize this common use case.
309
319
  return false if eager_load_exclusions.empty?
310
320
 
311
321
  walk_up(abspath) do |abspath|
312
322
  return true if eager_load_exclusions.member?(abspath)
313
- return false if root_dirs.key?(abspath)
323
+ return false if roots.key?(abspath)
314
324
  end
315
325
 
316
326
  false
317
327
  end
318
328
 
319
329
  # @sig (String) -> bool
320
- def collapse?(dir)
330
+ private def collapse?(dir)
321
331
  collapse_dirs.member?(dir)
322
332
  end
323
333
 
324
334
  # @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
325
- def expand_paths(paths)
335
+ private def expand_paths(paths)
326
336
  paths.flatten.map! { |path| File.expand_path(path) }
327
337
  end
328
338
 
329
339
  # @sig (Array[String]) -> Array[String]
330
- def expand_glob_patterns(glob_patterns)
340
+ private def expand_glob_patterns(glob_patterns)
331
341
  # Note that Dir.glob works with regular file names just fine. That is,
332
342
  # glob patterns technically need no wildcards.
333
343
  glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
334
344
  end
335
345
 
336
346
  # @sig () -> void
337
- def recompute_ignored_paths
347
+ private def recompute_ignored_paths
338
348
  ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
339
349
  end
340
350
 
341
351
  # @sig () -> void
342
- def recompute_collapse_dirs
352
+ private def recompute_collapse_dirs
343
353
  collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
344
354
  end
345
355
  end
@@ -9,11 +9,12 @@ module Zeitwerk::Loader::EagerLoad
9
9
  def eager_load(force: false)
10
10
  mutex.synchronize do
11
11
  break if @eager_loaded
12
+ raise Zeitwerk::SetupRequired unless @setup
12
13
 
13
14
  log("eager load start") if logger
14
15
 
15
- actual_root_dirs.each do |root_dir, namespace|
16
- actual_eager_load_dir(root_dir, namespace, force: force)
16
+ actual_roots.each do |root_dir, root_namespace|
17
+ actual_eager_load_dir(root_dir, root_namespace, force: force)
17
18
  end
18
19
 
19
20
  autoloaded_dirs.each do |autoloaded_dir|
@@ -29,6 +30,8 @@ module Zeitwerk::Loader::EagerLoad
29
30
 
30
31
  # @sig (String | Pathname) -> void
31
32
  def eager_load_dir(path)
33
+ raise Zeitwerk::SetupRequired unless @setup
34
+
32
35
  abspath = File.expand_path(path)
33
36
 
34
37
  raise Zeitwerk::Error.new("#{abspath} is not a directory") unless dir?(abspath)
@@ -37,10 +40,10 @@ module Zeitwerk::Loader::EagerLoad
37
40
 
38
41
  root_namespace = nil
39
42
  walk_up(abspath) do |dir|
40
- return if ignored_paths.member?(dir)
43
+ return if ignored_path?(dir)
41
44
  return if eager_load_exclusions.member?(dir)
42
45
 
43
- break if root_namespace = root_dirs[dir]
46
+ break if root_namespace = roots[dir]
44
47
 
45
48
  unless collapse?(dir)
46
49
  basename = File.basename(dir)
@@ -67,29 +70,32 @@ module Zeitwerk::Loader::EagerLoad
67
70
 
68
71
  # @sig (Module) -> void
69
72
  def eager_load_namespace(mod)
73
+ raise Zeitwerk::SetupRequired unless @setup
74
+
70
75
  unless mod.is_a?(Module)
71
76
  raise Zeitwerk::Error, "#{mod.inspect} is not a class or module object"
72
77
  end
73
78
 
74
79
  return if @eager_loaded
75
80
 
76
- actual_root_dirs.each do |root_dir, root_namespace|
81
+ mod_name = real_mod_name(mod)
82
+ return unless mod_name
83
+
84
+ actual_roots.each do |root_dir, root_namespace|
77
85
  if mod.equal?(Object)
78
86
  # A shortcircuiting test depends on the invocation of this method.
79
87
  # Please keep them in sync if refactored.
80
88
  actual_eager_load_dir(root_dir, root_namespace)
81
89
  elsif root_namespace.equal?(Object)
82
- eager_load_child_namespace(mod, root_dir, root_namespace)
90
+ eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
83
91
  else
84
- mod_name = real_mod_name(mod)
85
92
  root_namespace_name = real_mod_name(root_namespace)
86
-
87
93
  if root_namespace_name.start_with?(mod_name + "::")
88
94
  actual_eager_load_dir(root_dir, root_namespace)
89
95
  elsif mod_name == root_namespace_name
90
96
  actual_eager_load_dir(root_dir, root_namespace)
91
97
  elsif mod_name.start_with?(root_namespace_name + "::")
92
- eager_load_child_namespace(mod, root_dir, root_namespace)
98
+ eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
93
99
  else
94
100
  # Unrelated constant hierarchies, do nothing.
95
101
  end
@@ -110,7 +116,7 @@ module Zeitwerk::Loader::EagerLoad
110
116
 
111
117
  raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
112
118
  raise Zeitwerk::Error.new("#{abspath} is not a Ruby file") if dir?(abspath) || !ruby?(abspath)
113
- raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_paths.member?(abspath)
119
+ raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)
114
120
 
115
121
  basename = File.basename(abspath, ".rb")
116
122
  base_cname = inflector.camelize(basename, abspath).to_sym
@@ -119,9 +125,9 @@ module Zeitwerk::Loader::EagerLoad
119
125
  cnames = []
120
126
 
121
127
  walk_up(File.dirname(abspath)) do |dir|
122
- raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_paths.member?(dir)
128
+ raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(dir)
123
129
 
124
- break if root_namespace = root_dirs[dir]
130
+ break if root_namespace = roots[dir]
125
131
 
126
132
  unless collapse?(dir)
127
133
  basename = File.basename(dir)
@@ -180,8 +186,8 @@ module Zeitwerk::Loader::EagerLoad
180
186
  # strict namespace descendendant of `root_namespace`.
181
187
  #
182
188
  # @sig (Module, String, Module, Boolean) -> void
183
- private def eager_load_child_namespace(child, root_dir, root_namespace)
184
- suffix = real_mod_name(child)
189
+ private def eager_load_child_namespace(child, child_name, root_dir, root_namespace)
190
+ suffix = child_name
185
191
  unless root_namespace.equal?(Object)
186
192
  suffix = suffix.delete_prefix(real_mod_name(root_namespace) + "::")
187
193
  end
@@ -202,7 +208,7 @@ module Zeitwerk::Loader::EagerLoad
202
208
  next unless dir?(abspath)
203
209
 
204
210
  if collapse?(abspath)
205
- current_dirs << abspath
211
+ dirs << abspath
206
212
  elsif segment == inflector.camelize(basename, abspath)
207
213
  next_dirs << abspath
208
214
  end
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk::Loader::Helpers
4
- private
5
-
6
4
  # --- Logging -----------------------------------------------------------------------------------
7
5
 
8
6
  # @sig (String) -> void
9
- def log(message)
7
+ private def log(message)
10
8
  method_name = logger.respond_to?(:debug) ? :debug : :call
11
9
  logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
12
10
  end
@@ -14,7 +12,7 @@ module Zeitwerk::Loader::Helpers
14
12
  # --- Files and directories ---------------------------------------------------------------------
15
13
 
16
14
  # @sig (String) { (String, String) -> void } -> void
17
- def ls(dir)
15
+ private def ls(dir)
18
16
  children = Dir.children(dir)
19
17
 
20
18
  # The order in which a directory is listed depends on the file system.
@@ -28,10 +26,10 @@ module Zeitwerk::Loader::Helpers
28
26
  next if hidden?(basename)
29
27
 
30
28
  abspath = File.join(dir, basename)
31
- next if ignored_paths.member?(abspath)
29
+ next if ignored_path?(abspath)
32
30
 
33
31
  if dir?(abspath)
34
- next if root_dirs.key?(abspath)
32
+ next if roots.key?(abspath)
35
33
  next if !has_at_least_one_ruby_file?(abspath)
36
34
  else
37
35
  next unless ruby?(abspath)
@@ -44,7 +42,7 @@ module Zeitwerk::Loader::Helpers
44
42
  end
45
43
 
46
44
  # @sig (String) -> bool
47
- def has_at_least_one_ruby_file?(dir)
45
+ private def has_at_least_one_ruby_file?(dir)
48
46
  to_visit = [dir]
49
47
 
50
48
  while dir = to_visit.shift
@@ -61,22 +59,22 @@ module Zeitwerk::Loader::Helpers
61
59
  end
62
60
 
63
61
  # @sig (String) -> bool
64
- def ruby?(path)
62
+ private def ruby?(path)
65
63
  path.end_with?(".rb")
66
64
  end
67
65
 
68
66
  # @sig (String) -> bool
69
- def dir?(path)
67
+ private def dir?(path)
70
68
  File.directory?(path)
71
69
  end
72
70
 
73
71
  # @sig (String) -> bool
74
- def hidden?(basename)
72
+ private def hidden?(basename)
75
73
  basename.start_with?(".")
76
74
  end
77
75
 
78
76
  # @sig (String) { (String) -> void } -> void
79
- def walk_up(abspath)
77
+ private def walk_up(abspath)
80
78
  loop do
81
79
  yield abspath
82
80
  abspath, basename = File.split(abspath)
@@ -104,11 +102,11 @@ module Zeitwerk::Loader::Helpers
104
102
  #
105
103
  # @sig (Module, Symbol) -> String?
106
104
  if method(:autoload?).arity == 1
107
- def strict_autoload_path(parent, cname)
105
+ private def strict_autoload_path(parent, cname)
108
106
  parent.autoload?(cname) if cdef?(parent, cname)
109
107
  end
110
108
  else
111
- def strict_autoload_path(parent, cname)
109
+ private def strict_autoload_path(parent, cname)
112
110
  parent.autoload?(cname, false)
113
111
  end
114
112
  end
@@ -117,23 +115,23 @@ module Zeitwerk::Loader::Helpers
117
115
  if Symbol.method_defined?(:name)
118
116
  # Symbol#name was introduced in Ruby 3.0. It returns always the same
119
117
  # frozen object, so we may save a few string allocations.
120
- def cpath(parent, cname)
118
+ private def cpath(parent, cname)
121
119
  Object == parent ? cname.name : "#{real_mod_name(parent)}::#{cname.name}"
122
120
  end
123
121
  else
124
- def cpath(parent, cname)
122
+ private def cpath(parent, cname)
125
123
  Object == parent ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
126
124
  end
127
125
  end
128
126
 
129
127
  # @sig (Module, Symbol) -> bool
130
- def cdef?(parent, cname)
128
+ private def cdef?(parent, cname)
131
129
  parent.const_defined?(cname, false)
132
130
  end
133
131
 
134
132
  # @raise [NameError]
135
133
  # @sig (Module, Symbol) -> Object
136
- def cget(parent, cname)
134
+ private def cget(parent, cname)
137
135
  parent.const_get(cname, false)
138
136
  end
139
137
  end
@@ -109,15 +109,15 @@ module Zeitwerk
109
109
  Registry.register_loader(self)
110
110
  end
111
111
 
112
- # Sets autoloads in the root namespace.
112
+ # Sets autoloads in the root namespaces.
113
113
  #
114
114
  # @sig () -> void
115
115
  def setup
116
116
  mutex.synchronize do
117
117
  break if @setup
118
118
 
119
- actual_root_dirs.each do |root_dir, namespace|
120
- set_autoloads_in_dir(root_dir, namespace)
119
+ actual_roots.each do |root_dir, root_namespace|
120
+ set_autoloads_in_dir(root_dir, root_namespace)
121
121
  end
122
122
 
123
123
  on_setup_callbacks.each(&:call)
@@ -140,6 +140,8 @@ module Zeitwerk
140
140
  # @sig () -> void
141
141
  def unload
142
142
  mutex.synchronize do
143
+ raise SetupRequired unless @setup
144
+
143
145
  # We are going to keep track of the files that were required by our
144
146
  # autoloads to later remove them from $LOADED_FEATURES, thus making them
145
147
  # loadable by Kernel#require again.
@@ -199,7 +201,7 @@ module Zeitwerk
199
201
  shadowed_files.clear
200
202
 
201
203
  Registry.on_unload(self)
202
- ExplicitNamespace.unregister_loader(self)
204
+ ExplicitNamespace.__unregister_loader(self)
203
205
 
204
206
  @setup = false
205
207
  @eager_loaded = false
@@ -216,6 +218,7 @@ module Zeitwerk
216
218
  # @sig () -> void
217
219
  def reload
218
220
  raise ReloadingDisabledError unless reloading_enabled?
221
+ raise SetupRequired unless @setup
219
222
 
220
223
  unload
221
224
  recompute_ignored_paths
@@ -245,7 +248,7 @@ module Zeitwerk
245
248
  # @sig () -> void
246
249
  def unregister
247
250
  Registry.unregister_loader(self)
248
- ExplicitNamespace.unregister_loader(self)
251
+ ExplicitNamespace.__unregister_loader(self)
249
252
  end
250
253
 
251
254
  # The return value of this predicate is only meaningful if the loader has
@@ -283,19 +286,31 @@ module Zeitwerk
283
286
  Registry.loader_for_gem(called_from, warn_on_extra_files: warn_on_extra_files)
284
287
  end
285
288
 
286
- # Broadcasts `eager_load` to all loaders.
289
+ # Broadcasts `eager_load` to all loaders. Those that have not been setup
290
+ # are skipped.
287
291
  #
288
292
  # @sig () -> void
289
293
  def eager_load_all
290
- Registry.loaders.each(&:eager_load)
294
+ Registry.loaders.each do |loader|
295
+ begin
296
+ loader.eager_load
297
+ rescue SetupRequired
298
+ # This is fine, we eager load what can be eager loaded.
299
+ end
300
+ end
291
301
  end
292
302
 
293
- # Broadcasts `eager_load_namespace` to all loaders.
303
+ # Broadcasts `eager_load_namespace` to all loaders. Those that have not
304
+ # been setup are skipped.
294
305
  #
295
306
  # @sig (Module) -> void
296
307
  def eager_load_namespace(mod)
297
308
  Registry.loaders.each do |loader|
298
- loader.eager_load_namespace(mod)
309
+ begin
310
+ loader.eager_load_namespace(mod)
311
+ rescue SetupRequired
312
+ # This is fine, we eager load what can be eager loaded.
313
+ end
299
314
  end
300
315
  end
301
316
 
@@ -435,22 +450,23 @@ module Zeitwerk
435
450
 
436
451
  # @sig (String) -> void
437
452
  def register_explicit_namespace(cpath)
438
- ExplicitNamespace.register(cpath, self)
453
+ ExplicitNamespace.__register(cpath, self)
439
454
  end
440
455
 
441
456
  # @sig (String) -> void
442
457
  def raise_if_conflicting_directory(dir)
443
458
  MUTEX.synchronize do
459
+ dir_slash = dir + "/"
460
+
444
461
  Registry.loaders.each do |loader|
445
462
  next if loader == self
446
- next if loader.ignores?(dir)
463
+ next if loader.__ignores?(dir)
447
464
 
448
- suffixed_dir = dir + "/"
449
- loader.root_dirs.each do |root_dir, _namespace|
465
+ loader.__roots.each_key do |root_dir|
450
466
  next if ignores?(root_dir)
451
467
 
452
- root_dir = root_dir + "/"
453
- if suffixed_dir.start_with?(root_dir) || root_dir.start_with?(suffixed_dir)
468
+ root_dir_slash = root_dir + "/"
469
+ if dir_slash.start_with?(root_dir_slash) || root_dir_slash.start_with?(dir_slash)
454
470
  require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
455
471
  raise Error,
456
472
  "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.6.3"
4
+ VERSION = "2.6.6"
5
5
  end
data/lib/zeitwerk.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Zeitwerk
4
4
  require_relative "zeitwerk/real_mod_name"
5
+ require_relative "zeitwerk/internal"
5
6
  require_relative "zeitwerk/loader"
6
7
  require_relative "zeitwerk/gem_loader"
7
8
  require_relative "zeitwerk/registry"
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.6.3
4
+ version: 2.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-31 00:00:00.000000000 Z
11
+ date: 2022-11-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -28,6 +28,7 @@ files:
28
28
  - lib/zeitwerk/gem_inflector.rb
29
29
  - lib/zeitwerk/gem_loader.rb
30
30
  - lib/zeitwerk/inflector.rb
31
+ - lib/zeitwerk/internal.rb
31
32
  - lib/zeitwerk/kernel.rb
32
33
  - lib/zeitwerk/loader.rb
33
34
  - lib/zeitwerk/loader/callbacks.rb