zeitwerk 2.6.2 → 2.6.10
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 +177 -45
- data/lib/zeitwerk/error.rb +6 -0
- data/lib/zeitwerk/explicit_namespace.rb +14 -10
- data/lib/zeitwerk/gem_inflector.rb +2 -2
- data/lib/zeitwerk/gem_loader.rb +11 -7
- data/lib/zeitwerk/internal.rb +12 -0
- data/lib/zeitwerk/loader/callbacks.rb +6 -6
- data/lib/zeitwerk/loader/config.rb +68 -49
- data/lib/zeitwerk/loader/eager_load.rb +30 -18
- data/lib/zeitwerk/loader/helpers.rb +65 -17
- data/lib/zeitwerk/loader.rb +157 -73
- data/lib/zeitwerk/registry.rb +2 -2
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +1 -0
- metadata +4 -3
|
@@ -4,85 +4,91 @@ require "set"
|
|
|
4
4
|
require "securerandom"
|
|
5
5
|
|
|
6
6
|
module Zeitwerk::Loader::Config
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 :
|
|
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.
|
|
@@ -105,15 +109,18 @@ module Zeitwerk::Loader::Config
|
|
|
105
109
|
# @raise [Zeitwerk::Error]
|
|
106
110
|
# @sig (String | Pathname, Module) -> void
|
|
107
111
|
def push_dir(path, namespace: Object)
|
|
108
|
-
# Note that Class < Module.
|
|
109
|
-
unless namespace.is_a?(Module)
|
|
112
|
+
unless namespace.is_a?(Module) # Note that Class < Module.
|
|
110
113
|
raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
|
|
111
114
|
end
|
|
112
115
|
|
|
116
|
+
unless real_mod_name(namespace)
|
|
117
|
+
raise Zeitwerk::Error, "root namespaces cannot be anonymous"
|
|
118
|
+
end
|
|
119
|
+
|
|
113
120
|
abspath = File.expand_path(path)
|
|
114
121
|
if dir?(abspath)
|
|
115
122
|
raise_if_conflicting_directory(abspath)
|
|
116
|
-
|
|
123
|
+
roots[abspath] = namespace
|
|
117
124
|
else
|
|
118
125
|
raise Zeitwerk::Error, "the root directory #{abspath} does not exist"
|
|
119
126
|
end
|
|
@@ -141,14 +148,24 @@ module Zeitwerk::Loader::Config
|
|
|
141
148
|
# instead. Keys are the absolute paths of the root directories as strings,
|
|
142
149
|
# values are their corresponding namespaces, class or module objects.
|
|
143
150
|
#
|
|
151
|
+
# If `ignored` is falsey (default), ignored root directories are filtered out.
|
|
152
|
+
#
|
|
144
153
|
# These are read-only collections, please add to them with `push_dir`.
|
|
145
154
|
#
|
|
146
155
|
# @sig () -> Array[String] | Hash[String, Module]
|
|
147
|
-
def dirs(namespaces: false)
|
|
156
|
+
def dirs(namespaces: false, ignored: false)
|
|
148
157
|
if namespaces
|
|
149
|
-
|
|
158
|
+
if ignored || ignored_paths.empty?
|
|
159
|
+
roots.clone
|
|
160
|
+
else
|
|
161
|
+
roots.reject { |root_dir, _namespace| ignored_path?(root_dir) }
|
|
162
|
+
end
|
|
150
163
|
else
|
|
151
|
-
|
|
164
|
+
if ignored || ignored_paths.empty?
|
|
165
|
+
roots.keys
|
|
166
|
+
else
|
|
167
|
+
roots.keys.reject { |root_dir| ignored_path?(root_dir) }
|
|
168
|
+
end
|
|
152
169
|
end.freeze
|
|
153
170
|
end
|
|
154
171
|
|
|
@@ -275,71 +292,73 @@ module Zeitwerk::Loader::Config
|
|
|
275
292
|
# Returns true if the argument has been configured to be ignored, or is a
|
|
276
293
|
# descendant of an ignored directory.
|
|
277
294
|
#
|
|
278
|
-
# @private
|
|
279
295
|
# @sig (String) -> bool
|
|
280
|
-
def ignores?(abspath)
|
|
296
|
+
internal def ignores?(abspath)
|
|
281
297
|
# Common use case.
|
|
282
298
|
return false if ignored_paths.empty?
|
|
283
299
|
|
|
284
|
-
walk_up(abspath) do |
|
|
285
|
-
return true if
|
|
286
|
-
return false if
|
|
300
|
+
walk_up(abspath) do |path|
|
|
301
|
+
return true if ignored_path?(path)
|
|
302
|
+
return false if roots.key?(path)
|
|
287
303
|
end
|
|
288
304
|
|
|
289
305
|
false
|
|
290
306
|
end
|
|
291
307
|
|
|
292
|
-
|
|
308
|
+
# @sig (String) -> bool
|
|
309
|
+
private def ignored_path?(abspath)
|
|
310
|
+
ignored_paths.member?(abspath)
|
|
311
|
+
end
|
|
293
312
|
|
|
294
313
|
# @sig () -> Array[String]
|
|
295
|
-
def
|
|
296
|
-
|
|
297
|
-
!dir?(root_dir) ||
|
|
314
|
+
private def actual_roots
|
|
315
|
+
roots.reject do |root_dir, _root_namespace|
|
|
316
|
+
!dir?(root_dir) || ignored_path?(root_dir)
|
|
298
317
|
end
|
|
299
318
|
end
|
|
300
319
|
|
|
301
320
|
# @sig (String) -> bool
|
|
302
|
-
def root_dir?(dir)
|
|
303
|
-
|
|
321
|
+
private def root_dir?(dir)
|
|
322
|
+
roots.key?(dir)
|
|
304
323
|
end
|
|
305
324
|
|
|
306
325
|
# @sig (String) -> bool
|
|
307
|
-
def excluded_from_eager_load?(abspath)
|
|
326
|
+
private def excluded_from_eager_load?(abspath)
|
|
308
327
|
# Optimize this common use case.
|
|
309
328
|
return false if eager_load_exclusions.empty?
|
|
310
329
|
|
|
311
|
-
walk_up(abspath) do |
|
|
312
|
-
return true if eager_load_exclusions.member?(
|
|
313
|
-
return false if
|
|
330
|
+
walk_up(abspath) do |path|
|
|
331
|
+
return true if eager_load_exclusions.member?(path)
|
|
332
|
+
return false if roots.key?(path)
|
|
314
333
|
end
|
|
315
334
|
|
|
316
335
|
false
|
|
317
336
|
end
|
|
318
337
|
|
|
319
338
|
# @sig (String) -> bool
|
|
320
|
-
def collapse?(dir)
|
|
339
|
+
private def collapse?(dir)
|
|
321
340
|
collapse_dirs.member?(dir)
|
|
322
341
|
end
|
|
323
342
|
|
|
324
343
|
# @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
|
|
325
|
-
def expand_paths(paths)
|
|
344
|
+
private def expand_paths(paths)
|
|
326
345
|
paths.flatten.map! { |path| File.expand_path(path) }
|
|
327
346
|
end
|
|
328
347
|
|
|
329
348
|
# @sig (Array[String]) -> Array[String]
|
|
330
|
-
def expand_glob_patterns(glob_patterns)
|
|
349
|
+
private def expand_glob_patterns(glob_patterns)
|
|
331
350
|
# Note that Dir.glob works with regular file names just fine. That is,
|
|
332
351
|
# glob patterns technically need no wildcards.
|
|
333
352
|
glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
|
|
334
353
|
end
|
|
335
354
|
|
|
336
355
|
# @sig () -> void
|
|
337
|
-
def recompute_ignored_paths
|
|
356
|
+
private def recompute_ignored_paths
|
|
338
357
|
ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
|
|
339
358
|
end
|
|
340
359
|
|
|
341
360
|
# @sig () -> void
|
|
342
|
-
def recompute_collapse_dirs
|
|
361
|
+
private def recompute_collapse_dirs
|
|
343
362
|
collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
|
|
344
363
|
end
|
|
345
364
|
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
|
-
|
|
16
|
-
actual_eager_load_dir(root_dir,
|
|
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,13 +40,15 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
37
40
|
|
|
38
41
|
root_namespace = nil
|
|
39
42
|
walk_up(abspath) do |dir|
|
|
40
|
-
return if
|
|
43
|
+
return if ignored_path?(dir)
|
|
41
44
|
return if eager_load_exclusions.member?(dir)
|
|
42
45
|
|
|
43
|
-
break if root_namespace =
|
|
46
|
+
break if root_namespace = roots[dir]
|
|
47
|
+
|
|
48
|
+
basename = File.basename(dir)
|
|
49
|
+
return if hidden?(basename)
|
|
44
50
|
|
|
45
51
|
unless collapse?(dir)
|
|
46
|
-
basename = File.basename(dir)
|
|
47
52
|
cnames << inflector.camelize(basename, dir).to_sym
|
|
48
53
|
end
|
|
49
54
|
end
|
|
@@ -67,29 +72,32 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
67
72
|
|
|
68
73
|
# @sig (Module) -> void
|
|
69
74
|
def eager_load_namespace(mod)
|
|
75
|
+
raise Zeitwerk::SetupRequired unless @setup
|
|
76
|
+
|
|
70
77
|
unless mod.is_a?(Module)
|
|
71
78
|
raise Zeitwerk::Error, "#{mod.inspect} is not a class or module object"
|
|
72
79
|
end
|
|
73
80
|
|
|
74
81
|
return if @eager_loaded
|
|
75
82
|
|
|
76
|
-
|
|
83
|
+
mod_name = real_mod_name(mod)
|
|
84
|
+
return unless mod_name
|
|
85
|
+
|
|
86
|
+
actual_roots.each do |root_dir, root_namespace|
|
|
77
87
|
if mod.equal?(Object)
|
|
78
88
|
# A shortcircuiting test depends on the invocation of this method.
|
|
79
89
|
# Please keep them in sync if refactored.
|
|
80
90
|
actual_eager_load_dir(root_dir, root_namespace)
|
|
81
91
|
elsif root_namespace.equal?(Object)
|
|
82
|
-
eager_load_child_namespace(mod, root_dir, root_namespace)
|
|
92
|
+
eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
|
|
83
93
|
else
|
|
84
|
-
mod_name = real_mod_name(mod)
|
|
85
94
|
root_namespace_name = real_mod_name(root_namespace)
|
|
86
|
-
|
|
87
95
|
if root_namespace_name.start_with?(mod_name + "::")
|
|
88
96
|
actual_eager_load_dir(root_dir, root_namespace)
|
|
89
97
|
elsif mod_name == root_namespace_name
|
|
90
98
|
actual_eager_load_dir(root_dir, root_namespace)
|
|
91
99
|
elsif mod_name.start_with?(root_namespace_name + "::")
|
|
92
|
-
eager_load_child_namespace(mod, root_dir, root_namespace)
|
|
100
|
+
eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
|
|
93
101
|
else
|
|
94
102
|
# Unrelated constant hierarchies, do nothing.
|
|
95
103
|
end
|
|
@@ -110,21 +118,25 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
110
118
|
|
|
111
119
|
raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
|
|
112
120
|
raise Zeitwerk::Error.new("#{abspath} is not a Ruby file") if dir?(abspath) || !ruby?(abspath)
|
|
113
|
-
raise Zeitwerk::Error.new("#{abspath} is ignored") if
|
|
121
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)
|
|
114
122
|
|
|
115
123
|
basename = File.basename(abspath, ".rb")
|
|
124
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
|
|
125
|
+
|
|
116
126
|
base_cname = inflector.camelize(basename, abspath).to_sym
|
|
117
127
|
|
|
118
128
|
root_namespace = nil
|
|
119
129
|
cnames = []
|
|
120
130
|
|
|
121
131
|
walk_up(File.dirname(abspath)) do |dir|
|
|
122
|
-
raise Zeitwerk::Error.new("#{abspath} is ignored") if
|
|
132
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(dir)
|
|
133
|
+
|
|
134
|
+
break if root_namespace = roots[dir]
|
|
123
135
|
|
|
124
|
-
|
|
136
|
+
basename = File.basename(dir)
|
|
137
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
|
|
125
138
|
|
|
126
139
|
unless collapse?(dir)
|
|
127
|
-
basename = File.basename(dir)
|
|
128
140
|
cnames << inflector.camelize(basename, dir).to_sym
|
|
129
141
|
end
|
|
130
142
|
end
|
|
@@ -177,11 +189,11 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
177
189
|
end
|
|
178
190
|
|
|
179
191
|
# In order to invoke this method, the caller has to ensure `child` is a
|
|
180
|
-
# strict namespace
|
|
192
|
+
# strict namespace descendant of `root_namespace`.
|
|
181
193
|
#
|
|
182
194
|
# @sig (Module, String, Module, Boolean) -> void
|
|
183
|
-
private def eager_load_child_namespace(child, root_dir, root_namespace)
|
|
184
|
-
suffix =
|
|
195
|
+
private def eager_load_child_namespace(child, child_name, root_dir, root_namespace)
|
|
196
|
+
suffix = child_name
|
|
185
197
|
unless root_namespace.equal?(Object)
|
|
186
198
|
suffix = suffix.delete_prefix(real_mod_name(root_namespace) + "::")
|
|
187
199
|
end
|
|
@@ -202,7 +214,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
202
214
|
next unless dir?(abspath)
|
|
203
215
|
|
|
204
216
|
if collapse?(abspath)
|
|
205
|
-
|
|
217
|
+
dirs << abspath
|
|
206
218
|
elsif segment == inflector.camelize(basename, abspath)
|
|
207
219
|
next_dirs << abspath
|
|
208
220
|
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
|
|
29
|
+
next if ignored_path?(abspath)
|
|
32
30
|
|
|
33
31
|
if dir?(abspath)
|
|
34
|
-
next if
|
|
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,73 @@ 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
|
|
137
|
+
|
|
138
|
+
# @raise [NameError]
|
|
139
|
+
# @sig (Module, Symbol) -> Object
|
|
140
|
+
private def crem(parent, cname)
|
|
141
|
+
parent.__send__(:remove_const, cname)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
CNAME_VALIDATOR = Module.new
|
|
145
|
+
private_constant :CNAME_VALIDATOR
|
|
146
|
+
|
|
147
|
+
# @raise [Zeitwerk::NameError]
|
|
148
|
+
# @sig (String, String) -> Symbol
|
|
149
|
+
private def cname_for(basename, abspath)
|
|
150
|
+
cname = inflector.camelize(basename, abspath)
|
|
151
|
+
|
|
152
|
+
unless cname.is_a?(String)
|
|
153
|
+
raise TypeError, "#{inflector.class}#camelize must return a String, received #{cname.inspect}"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if cname.include?("::")
|
|
157
|
+
raise Zeitwerk::NameError.new(<<~MESSAGE, cname)
|
|
158
|
+
wrong constant name #{cname} inferred by #{inflector.class} from
|
|
159
|
+
|
|
160
|
+
#{abspath}
|
|
161
|
+
|
|
162
|
+
#{inflector.class}#camelize should return a simple constant name without "::"
|
|
163
|
+
MESSAGE
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
begin
|
|
167
|
+
CNAME_VALIDATOR.const_defined?(cname, false)
|
|
168
|
+
rescue ::NameError => error
|
|
169
|
+
path_type = ruby?(abspath) ? "file" : "directory"
|
|
170
|
+
|
|
171
|
+
raise Zeitwerk::NameError.new(<<~MESSAGE, error.name)
|
|
172
|
+
#{error.message} inferred by #{inflector.class} from #{path_type}
|
|
173
|
+
|
|
174
|
+
#{abspath}
|
|
175
|
+
|
|
176
|
+
Possible ways to address this:
|
|
177
|
+
|
|
178
|
+
* Tell Zeitwerk to ignore this particular #{path_type}.
|
|
179
|
+
* Tell Zeitwerk to ignore one of its parent directories.
|
|
180
|
+
* Rename the #{path_type} to comply with the naming conventions.
|
|
181
|
+
* Modify the inflector to handle this case.
|
|
182
|
+
MESSAGE
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
cname.to_sym
|
|
186
|
+
end
|
|
139
187
|
end
|