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.
- checksums.yaml +4 -4
- data/README.md +233 -63
- data/lib/zeitwerk/{kernel.rb → core_ext/kernel.rb} +6 -10
- data/lib/zeitwerk/core_ext/module.rb +20 -0
- data/lib/zeitwerk/cref/map.rb +159 -0
- data/lib/zeitwerk/cref.rb +69 -0
- data/lib/zeitwerk/error.rb +2 -0
- data/lib/zeitwerk/gem_inflector.rb +4 -4
- data/lib/zeitwerk/gem_loader.rb +15 -12
- data/lib/zeitwerk/inflector.rb +3 -3
- data/lib/zeitwerk/internal.rb +1 -0
- data/lib/zeitwerk/loader/callbacks.rb +43 -39
- data/lib/zeitwerk/loader/config.rb +44 -52
- data/lib/zeitwerk/loader/eager_load.rb +29 -25
- data/lib/zeitwerk/loader/helpers.rb +65 -63
- data/lib/zeitwerk/loader.rb +294 -162
- data/lib/zeitwerk/null_inflector.rb +6 -0
- data/lib/zeitwerk/real_mod_name.rb +9 -12
- data/lib/zeitwerk/registry/autoloads.rb +38 -0
- data/lib/zeitwerk/registry/explicit_namespaces.rb +61 -0
- data/lib/zeitwerk/registry/inceptions.rb +31 -0
- data/lib/zeitwerk/registry/loaders.rb +33 -0
- data/lib/zeitwerk/registry.rb +20 -95
- data/lib/zeitwerk/version.rb +2 -1
- data/lib/zeitwerk.rb +6 -3
- metadata +13 -9
- data/lib/zeitwerk/explicit_namespace.rb +0 -93
@@ -7,10 +7,10 @@ module Zeitwerk::Loader::Config
|
|
7
7
|
extend Zeitwerk::Internal
|
8
8
|
include Zeitwerk::RealModName
|
9
9
|
|
10
|
-
|
10
|
+
#: camelize(String, String) -> String
|
11
11
|
attr_accessor :inflector
|
12
12
|
|
13
|
-
|
13
|
+
#: call(String) -> void | debug(String) -> void | nil
|
14
14
|
attr_accessor :logger
|
15
15
|
|
16
16
|
# Absolute paths of the root directories, mapped to their respective root namespaces:
|
@@ -25,14 +25,13 @@ module Zeitwerk::Loader::Config
|
|
25
25
|
# This is a private collection maintained by the loader. The public
|
26
26
|
# interface for it is `push_dir` and `dirs`.
|
27
27
|
#
|
28
|
-
|
28
|
+
#: Hash[String, Module]
|
29
29
|
attr_reader :roots
|
30
30
|
internal :roots
|
31
31
|
|
32
|
-
# Absolute paths of files, directories, or glob patterns to be
|
33
|
-
# ignored.
|
32
|
+
# Absolute paths of files, directories, or glob patterns to be ignored.
|
34
33
|
#
|
35
|
-
|
34
|
+
#: Set[String]
|
36
35
|
attr_reader :ignored_glob_patterns
|
37
36
|
private :ignored_glob_patterns
|
38
37
|
|
@@ -40,46 +39,46 @@ module Zeitwerk::Loader::Config
|
|
40
39
|
# ignored glob patterns were expanded. Computed on setup, and recomputed on
|
41
40
|
# reload.
|
42
41
|
#
|
43
|
-
|
42
|
+
#: Set[String]
|
44
43
|
attr_reader :ignored_paths
|
45
44
|
private :ignored_paths
|
46
45
|
|
47
46
|
# Absolute paths of directories or glob patterns to be collapsed.
|
48
47
|
#
|
49
|
-
|
48
|
+
#: Set[String]
|
50
49
|
attr_reader :collapse_glob_patterns
|
51
50
|
private :collapse_glob_patterns
|
52
51
|
|
53
52
|
# The actual collection of absolute directory names at the time the collapse
|
54
53
|
# glob patterns were expanded. Computed on setup, and recomputed on reload.
|
55
54
|
#
|
56
|
-
|
55
|
+
#: Set[String]
|
57
56
|
attr_reader :collapse_dirs
|
58
57
|
private :collapse_dirs
|
59
58
|
|
60
59
|
# Absolute paths of files or directories not to be eager loaded.
|
61
60
|
#
|
62
|
-
|
61
|
+
#: Set[String]
|
63
62
|
attr_reader :eager_load_exclusions
|
64
63
|
private :eager_load_exclusions
|
65
64
|
|
66
65
|
# User-oriented callbacks to be fired on setup and on reload.
|
67
66
|
#
|
68
|
-
|
67
|
+
#: Array[{ () -> void }]
|
69
68
|
attr_reader :on_setup_callbacks
|
70
69
|
private :on_setup_callbacks
|
71
70
|
|
72
71
|
# User-oriented callbacks to be fired when a constant is loaded.
|
73
72
|
#
|
74
|
-
|
75
|
-
|
73
|
+
#: Hash[String, Array[{ (top, String) -> void }]]
|
74
|
+
#| Hash[Symbol, Array[{ (String, top, String) -> void }]]
|
76
75
|
attr_reader :on_load_callbacks
|
77
76
|
private :on_load_callbacks
|
78
77
|
|
79
78
|
# User-oriented callbacks to be fired before constants are removed.
|
80
79
|
#
|
81
|
-
|
82
|
-
|
80
|
+
#: Hash[String, Array[{ (top, String) -> void }]]
|
81
|
+
#| Hash[Symbol, Array[{ (String, top, String) -> void }]]
|
83
82
|
attr_reader :on_unload_callbacks
|
84
83
|
private :on_unload_callbacks
|
85
84
|
|
@@ -106,11 +105,9 @@ module Zeitwerk::Loader::Config
|
|
106
105
|
# the same process already manages that directory or one of its ascendants or
|
107
106
|
# descendants.
|
108
107
|
#
|
109
|
-
|
110
|
-
# @sig (String | Pathname, Module) -> void
|
108
|
+
#: (String | Pathname, namespace: Module) -> void ! Zeitwerk::Error
|
111
109
|
def push_dir(path, namespace: Object)
|
112
|
-
# Note that Class < Module.
|
113
|
-
unless namespace.is_a?(Module)
|
110
|
+
unless namespace.is_a?(Module) # Note that Class < Module.
|
114
111
|
raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
|
115
112
|
end
|
116
113
|
|
@@ -132,14 +129,14 @@ module Zeitwerk::Loader::Config
|
|
132
129
|
# Implemented as a method instead of via attr_reader for symmetry with the
|
133
130
|
# writer below.
|
134
131
|
#
|
135
|
-
|
132
|
+
#: () -> String
|
136
133
|
def tag
|
137
134
|
@tag
|
138
135
|
end
|
139
136
|
|
140
137
|
# Sets a tag for the loader, useful for logging.
|
141
138
|
#
|
142
|
-
|
139
|
+
#: (to_s() -> String) -> void
|
143
140
|
def tag=(tag)
|
144
141
|
@tag = tag.to_s
|
145
142
|
end
|
@@ -153,7 +150,7 @@ module Zeitwerk::Loader::Config
|
|
153
150
|
#
|
154
151
|
# These are read-only collections, please add to them with `push_dir`.
|
155
152
|
#
|
156
|
-
|
153
|
+
#: (?namespaces: boolish, ?ignored: boolish) -> Array[String] | Hash[String, Module]
|
157
154
|
def dirs(namespaces: false, ignored: false)
|
158
155
|
if namespaces
|
159
156
|
if ignored || ignored_paths.empty?
|
@@ -173,8 +170,7 @@ module Zeitwerk::Loader::Config
|
|
173
170
|
# You need to call this method before setup in order to be able to reload.
|
174
171
|
# There is no way to undo this, either you want to reload or you don't.
|
175
172
|
#
|
176
|
-
|
177
|
-
# @sig () -> void
|
173
|
+
#: () -> void ! Zeitwerk::Error
|
178
174
|
def enable_reloading
|
179
175
|
mutex.synchronize do
|
180
176
|
break if @reloading_enabled
|
@@ -187,7 +183,7 @@ module Zeitwerk::Loader::Config
|
|
187
183
|
end
|
188
184
|
end
|
189
185
|
|
190
|
-
|
186
|
+
#: () -> bool
|
191
187
|
def reloading_enabled?
|
192
188
|
@reloading_enabled
|
193
189
|
end
|
@@ -195,14 +191,14 @@ module Zeitwerk::Loader::Config
|
|
195
191
|
# Let eager load ignore the given files or directories. The constants defined
|
196
192
|
# in those files are still autoloadable.
|
197
193
|
#
|
198
|
-
|
194
|
+
#: (*(String | Pathname | Array[String | Pathname])) -> void
|
199
195
|
def do_not_eager_load(*paths)
|
200
196
|
mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
|
201
197
|
end
|
202
198
|
|
203
199
|
# Configure files, directories, or glob patterns to be totally ignored.
|
204
200
|
#
|
205
|
-
|
201
|
+
#: (*(String | Pathname | Array[String | Pathname])) -> void
|
206
202
|
def ignore(*glob_patterns)
|
207
203
|
glob_patterns = expand_paths(glob_patterns)
|
208
204
|
mutex.synchronize do
|
@@ -213,7 +209,7 @@ module Zeitwerk::Loader::Config
|
|
213
209
|
|
214
210
|
# Configure directories or glob patterns to be collapsed.
|
215
211
|
#
|
216
|
-
|
212
|
+
#: (*(String | Pathname | Array[String | Pathname])) -> void
|
217
213
|
def collapse(*glob_patterns)
|
218
214
|
glob_patterns = expand_paths(glob_patterns)
|
219
215
|
mutex.synchronize do
|
@@ -225,7 +221,7 @@ module Zeitwerk::Loader::Config
|
|
225
221
|
# Configure a block to be called after setup and on each reload.
|
226
222
|
# If setup was already done, the block runs immediately.
|
227
223
|
#
|
228
|
-
|
224
|
+
#: () { () -> void } -> void
|
229
225
|
def on_setup(&block)
|
230
226
|
mutex.synchronize do
|
231
227
|
on_setup_callbacks << block
|
@@ -247,9 +243,7 @@ module Zeitwerk::Loader::Config
|
|
247
243
|
# # ...
|
248
244
|
# end
|
249
245
|
#
|
250
|
-
|
251
|
-
# @sig (String) { (Object, String) -> void } -> void
|
252
|
-
# (:ANY) { (String, Object, String) -> void } -> void
|
246
|
+
#: (String?) { (top, String) -> void } -> void ! TypeError
|
253
247
|
def on_load(cpath = :ANY, &block)
|
254
248
|
raise TypeError, "on_load only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
|
255
249
|
|
@@ -272,9 +266,7 @@ module Zeitwerk::Loader::Config
|
|
272
266
|
# # ...
|
273
267
|
# end
|
274
268
|
#
|
275
|
-
|
276
|
-
# @sig (String) { (Object) -> void } -> void
|
277
|
-
# (:ANY) { (String, Object) -> void } -> void
|
269
|
+
#: (String?) { (top, String) -> void } -> void ! TypeError
|
278
270
|
def on_unload(cpath = :ANY, &block)
|
279
271
|
raise TypeError, "on_unload only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
|
280
272
|
|
@@ -285,7 +277,7 @@ module Zeitwerk::Loader::Config
|
|
285
277
|
|
286
278
|
# Logs to `$stdout`, handy shortcut for debugging.
|
287
279
|
#
|
288
|
-
|
280
|
+
#: () -> void
|
289
281
|
def log!
|
290
282
|
@logger = ->(msg) { puts msg }
|
291
283
|
end
|
@@ -293,72 +285,72 @@ module Zeitwerk::Loader::Config
|
|
293
285
|
# Returns true if the argument has been configured to be ignored, or is a
|
294
286
|
# descendant of an ignored directory.
|
295
287
|
#
|
296
|
-
|
288
|
+
#: (String) -> bool
|
297
289
|
internal def ignores?(abspath)
|
298
290
|
# Common use case.
|
299
291
|
return false if ignored_paths.empty?
|
300
292
|
|
301
|
-
walk_up(abspath) do |
|
302
|
-
return true if ignored_path?(
|
303
|
-
return false if roots.key?(
|
293
|
+
walk_up(abspath) do |path|
|
294
|
+
return true if ignored_path?(path)
|
295
|
+
return false if roots.key?(path)
|
304
296
|
end
|
305
297
|
|
306
298
|
false
|
307
299
|
end
|
308
300
|
|
309
|
-
|
301
|
+
#: (String) -> bool
|
310
302
|
private def ignored_path?(abspath)
|
311
303
|
ignored_paths.member?(abspath)
|
312
304
|
end
|
313
305
|
|
314
|
-
|
306
|
+
#: () -> Array[String]
|
315
307
|
private def actual_roots
|
316
308
|
roots.reject do |root_dir, _root_namespace|
|
317
309
|
!dir?(root_dir) || ignored_path?(root_dir)
|
318
310
|
end
|
319
311
|
end
|
320
312
|
|
321
|
-
|
313
|
+
#: (String) -> bool
|
322
314
|
private def root_dir?(dir)
|
323
315
|
roots.key?(dir)
|
324
316
|
end
|
325
317
|
|
326
|
-
|
318
|
+
#: (String) -> bool
|
327
319
|
private def excluded_from_eager_load?(abspath)
|
328
320
|
# Optimize this common use case.
|
329
321
|
return false if eager_load_exclusions.empty?
|
330
322
|
|
331
|
-
walk_up(abspath) do |
|
332
|
-
return true if eager_load_exclusions.member?(
|
333
|
-
return false if roots.key?(
|
323
|
+
walk_up(abspath) do |path|
|
324
|
+
return true if eager_load_exclusions.member?(path)
|
325
|
+
return false if roots.key?(path)
|
334
326
|
end
|
335
327
|
|
336
328
|
false
|
337
329
|
end
|
338
330
|
|
339
|
-
|
331
|
+
#: (String) -> bool
|
340
332
|
private def collapse?(dir)
|
341
333
|
collapse_dirs.member?(dir)
|
342
334
|
end
|
343
335
|
|
344
|
-
|
336
|
+
#: (String | Pathname | Array[String | Pathname]) -> Array[String]
|
345
337
|
private def expand_paths(paths)
|
346
338
|
paths.flatten.map! { |path| File.expand_path(path) }
|
347
339
|
end
|
348
340
|
|
349
|
-
|
341
|
+
#: (Array[String]) -> Array[String]
|
350
342
|
private def expand_glob_patterns(glob_patterns)
|
351
343
|
# Note that Dir.glob works with regular file names just fine. That is,
|
352
344
|
# glob patterns technically need no wildcards.
|
353
345
|
glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
|
354
346
|
end
|
355
347
|
|
356
|
-
|
348
|
+
#: () -> void
|
357
349
|
private def recompute_ignored_paths
|
358
350
|
ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
|
359
351
|
end
|
360
352
|
|
361
|
-
|
353
|
+
#: () -> void
|
362
354
|
private def recompute_collapse_dirs
|
363
355
|
collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
|
364
356
|
end
|
@@ -5,7 +5,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
5
5
|
# specific files and directories with `do_not_eager_load`, and that can be
|
6
6
|
# overridden passing `force: true`.
|
7
7
|
#
|
8
|
-
|
8
|
+
#: (?force: boolish) -> void
|
9
9
|
def eager_load(force: false)
|
10
10
|
mutex.synchronize do
|
11
11
|
break if @eager_loaded
|
@@ -18,7 +18,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
18
18
|
end
|
19
19
|
|
20
20
|
autoloaded_dirs.each do |autoloaded_dir|
|
21
|
-
Zeitwerk::Registry.
|
21
|
+
Zeitwerk::Registry.autoloads.unregister(autoloaded_dir)
|
22
22
|
end
|
23
23
|
autoloaded_dirs.clear
|
24
24
|
|
@@ -28,7 +28,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
|
31
|
+
#: (String | Pathname) -> void
|
32
32
|
def eager_load_dir(path)
|
33
33
|
raise Zeitwerk::SetupRequired unless @setup
|
34
34
|
|
@@ -45,8 +45,10 @@ module Zeitwerk::Loader::EagerLoad
|
|
45
45
|
|
46
46
|
break if root_namespace = roots[dir]
|
47
47
|
|
48
|
+
basename = File.basename(dir)
|
49
|
+
return if hidden?(basename)
|
50
|
+
|
48
51
|
unless collapse?(dir)
|
49
|
-
basename = File.basename(dir)
|
50
52
|
cnames << inflector.camelize(basename, dir).to_sym
|
51
53
|
end
|
52
54
|
end
|
@@ -59,8 +61,8 @@ module Zeitwerk::Loader::EagerLoad
|
|
59
61
|
cnames.reverse_each do |cname|
|
60
62
|
# Can happen if there are no Ruby files. This is not an error condition,
|
61
63
|
# the directory is actually managed. Could have Ruby files later.
|
62
|
-
return unless
|
63
|
-
namespace =
|
64
|
+
return unless namespace.const_defined?(cname, false)
|
65
|
+
namespace = namespace.const_get(cname, false)
|
64
66
|
end
|
65
67
|
|
66
68
|
# A shortcircuiting test depends on the invocation of this method. Please
|
@@ -68,7 +70,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
68
70
|
actual_eager_load_dir(abspath, namespace)
|
69
71
|
end
|
70
72
|
|
71
|
-
|
73
|
+
#: (Module) -> void
|
72
74
|
def eager_load_namespace(mod)
|
73
75
|
raise Zeitwerk::SetupRequired unless @setup
|
74
76
|
|
@@ -82,7 +84,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
82
84
|
return unless mod_name
|
83
85
|
|
84
86
|
actual_roots.each do |root_dir, root_namespace|
|
85
|
-
if
|
87
|
+
if Object.equal?(mod)
|
86
88
|
# A shortcircuiting test depends on the invocation of this method.
|
87
89
|
# Please keep them in sync if refactored.
|
88
90
|
actual_eager_load_dir(root_dir, root_namespace)
|
@@ -110,7 +112,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
110
112
|
# The method is implemented as `constantize` for files, in a sense, to be able
|
111
113
|
# to descend orderly and make sure the file is loadable.
|
112
114
|
#
|
113
|
-
|
115
|
+
#: (String | Pathname) -> void
|
114
116
|
def load_file(path)
|
115
117
|
abspath = File.expand_path(path)
|
116
118
|
|
@@ -119,6 +121,8 @@ module Zeitwerk::Loader::EagerLoad
|
|
119
121
|
raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)
|
120
122
|
|
121
123
|
basename = File.basename(abspath, ".rb")
|
124
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
|
125
|
+
|
122
126
|
base_cname = inflector.camelize(basename, abspath).to_sym
|
123
127
|
|
124
128
|
root_namespace = nil
|
@@ -129,8 +133,10 @@ module Zeitwerk::Loader::EagerLoad
|
|
129
133
|
|
130
134
|
break if root_namespace = roots[dir]
|
131
135
|
|
136
|
+
basename = File.basename(dir)
|
137
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
|
138
|
+
|
132
139
|
unless collapse?(dir)
|
133
|
-
basename = File.basename(dir)
|
134
140
|
cnames << inflector.camelize(basename, dir).to_sym
|
135
141
|
end
|
136
142
|
end
|
@@ -139,18 +145,18 @@ module Zeitwerk::Loader::EagerLoad
|
|
139
145
|
|
140
146
|
namespace = root_namespace
|
141
147
|
cnames.reverse_each do |cname|
|
142
|
-
namespace =
|
148
|
+
namespace = namespace.const_get(cname, false)
|
143
149
|
end
|
144
150
|
|
145
151
|
raise Zeitwerk::Error.new("#{abspath} is shadowed") if shadowed_file?(abspath)
|
146
152
|
|
147
|
-
|
153
|
+
namespace.const_get(base_cname, false)
|
148
154
|
end
|
149
155
|
|
150
156
|
# The caller is responsible for making sure `namespace` is the namespace that
|
151
157
|
# corresponds to `dir`.
|
152
158
|
#
|
153
|
-
|
159
|
+
#: (String, Module, ?force: boolish) -> void
|
154
160
|
private def actual_eager_load_dir(dir, namespace, force: false)
|
155
161
|
honour_exclusions = !force
|
156
162
|
return if honour_exclusions && excluded_from_eager_load?(dir)
|
@@ -158,22 +164,20 @@ module Zeitwerk::Loader::EagerLoad
|
|
158
164
|
log("eager load directory #{dir} start") if logger
|
159
165
|
|
160
166
|
queue = [[dir, namespace]]
|
161
|
-
while
|
162
|
-
|
163
|
-
|
164
|
-
ls(dir) do |basename, abspath|
|
167
|
+
while (current_dir, namespace = queue.shift)
|
168
|
+
ls(current_dir) do |basename, abspath, ftype|
|
165
169
|
next if honour_exclusions && eager_load_exclusions.member?(abspath)
|
166
170
|
|
167
|
-
if
|
168
|
-
if (cref = autoloads[abspath])
|
169
|
-
|
171
|
+
if ftype == :file
|
172
|
+
if (cref = autoloads[abspath])
|
173
|
+
cref.get
|
170
174
|
end
|
171
175
|
else
|
172
176
|
if collapse?(abspath)
|
173
177
|
queue << [abspath, namespace]
|
174
178
|
else
|
175
179
|
cname = inflector.camelize(basename, abspath).to_sym
|
176
|
-
queue << [abspath,
|
180
|
+
queue << [abspath, namespace.const_get(cname, false)]
|
177
181
|
end
|
178
182
|
end
|
179
183
|
end
|
@@ -185,7 +189,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
185
189
|
# In order to invoke this method, the caller has to ensure `child` is a
|
186
190
|
# strict namespace descendant of `root_namespace`.
|
187
191
|
#
|
188
|
-
|
192
|
+
#: (Module, String, String, Module) -> void
|
189
193
|
private def eager_load_child_namespace(child, child_name, root_dir, root_namespace)
|
190
194
|
suffix = child_name
|
191
195
|
unless root_namespace.equal?(Object)
|
@@ -203,9 +207,9 @@ module Zeitwerk::Loader::EagerLoad
|
|
203
207
|
next_dirs = []
|
204
208
|
|
205
209
|
suffix.split("::").each do |segment|
|
206
|
-
while dir = dirs.shift
|
207
|
-
ls(dir) do |basename, abspath|
|
208
|
-
next unless
|
210
|
+
while (dir = dirs.shift)
|
211
|
+
ls(dir) do |basename, abspath, ftype|
|
212
|
+
next unless ftype == :directory
|
209
213
|
|
210
214
|
if collapse?(abspath)
|
211
215
|
dirs << abspath
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Zeitwerk::Loader::Helpers
|
4
4
|
# --- Logging -----------------------------------------------------------------------------------
|
5
5
|
|
6
|
-
|
6
|
+
#: (to_s() -> String) -> void
|
7
7
|
private def log(message)
|
8
8
|
method_name = logger.respond_to?(:debug) ? :debug : :call
|
9
9
|
logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
|
@@ -11,7 +11,7 @@ module Zeitwerk::Loader::Helpers
|
|
11
11
|
|
12
12
|
# --- Files and directories ---------------------------------------------------------------------
|
13
13
|
|
14
|
-
|
14
|
+
#: (String) { (String, String, Symbol) -> void } -> void
|
15
15
|
private def ls(dir)
|
16
16
|
children = Dir.children(dir)
|
17
17
|
|
@@ -30,27 +30,43 @@ module Zeitwerk::Loader::Helpers
|
|
30
30
|
|
31
31
|
if dir?(abspath)
|
32
32
|
next if roots.key?(abspath)
|
33
|
-
|
33
|
+
|
34
|
+
if !has_at_least_one_ruby_file?(abspath)
|
35
|
+
log("directory #{abspath} is ignored because it has no Ruby files") if logger
|
36
|
+
next
|
37
|
+
end
|
38
|
+
|
39
|
+
ftype = :directory
|
34
40
|
else
|
35
41
|
next unless ruby?(abspath)
|
42
|
+
ftype = :file
|
36
43
|
end
|
37
44
|
|
38
45
|
# We freeze abspath because that saves allocations when passed later to
|
39
46
|
# File methods. See #125.
|
40
|
-
yield basename, abspath.freeze
|
47
|
+
yield basename, abspath.freeze, ftype
|
41
48
|
end
|
42
49
|
end
|
43
50
|
|
44
|
-
#
|
51
|
+
# Looks for a Ruby file using breadth-first search. This type of search is
|
52
|
+
# important to list as less directories as possible and return fast in the
|
53
|
+
# common case in which there are Ruby files.
|
54
|
+
#
|
55
|
+
#: (String) -> bool
|
45
56
|
private def has_at_least_one_ruby_file?(dir)
|
46
57
|
to_visit = [dir]
|
47
58
|
|
48
|
-
while dir = to_visit.shift
|
49
|
-
|
59
|
+
while (dir = to_visit.shift)
|
60
|
+
Dir.each_child(dir) do |basename|
|
61
|
+
next if hidden?(basename)
|
62
|
+
|
63
|
+
abspath = File.join(dir, basename)
|
64
|
+
next if ignored_path?(abspath)
|
65
|
+
|
50
66
|
if dir?(abspath)
|
51
|
-
to_visit << abspath
|
67
|
+
to_visit << abspath unless roots.key?(abspath)
|
52
68
|
else
|
53
|
-
return true
|
69
|
+
return true if ruby?(abspath)
|
54
70
|
end
|
55
71
|
end
|
56
72
|
end
|
@@ -58,22 +74,22 @@ module Zeitwerk::Loader::Helpers
|
|
58
74
|
false
|
59
75
|
end
|
60
76
|
|
61
|
-
|
77
|
+
#: (String) -> bool
|
62
78
|
private def ruby?(path)
|
63
79
|
path.end_with?(".rb")
|
64
80
|
end
|
65
81
|
|
66
|
-
|
82
|
+
#: (String) -> bool
|
67
83
|
private def dir?(path)
|
68
84
|
File.directory?(path)
|
69
85
|
end
|
70
86
|
|
71
|
-
|
87
|
+
#: (String) -> bool
|
72
88
|
private def hidden?(basename)
|
73
89
|
basename.start_with?(".")
|
74
90
|
end
|
75
91
|
|
76
|
-
|
92
|
+
#: (String) { (String) -> void } -> void
|
77
93
|
private def walk_up(abspath)
|
78
94
|
loop do
|
79
95
|
yield abspath
|
@@ -82,62 +98,48 @@ module Zeitwerk::Loader::Helpers
|
|
82
98
|
end
|
83
99
|
end
|
84
100
|
|
85
|
-
# ---
|
101
|
+
# --- Inflection --------------------------------------------------------------------------------
|
86
102
|
|
87
|
-
|
88
|
-
|
89
|
-
#
|
90
|
-
# For example, given
|
91
|
-
#
|
92
|
-
# class A
|
93
|
-
# autoload :X, "x.rb"
|
94
|
-
# end
|
95
|
-
#
|
96
|
-
# class B < A
|
97
|
-
# end
|
98
|
-
#
|
99
|
-
# B.autoload?(:X) returns "x.rb".
|
100
|
-
#
|
101
|
-
# We need a way to strictly check in parent ignoring ancestors.
|
102
|
-
#
|
103
|
-
# @sig (Module, Symbol) -> String?
|
104
|
-
if method(:autoload?).arity == 1
|
105
|
-
private def strict_autoload_path(parent, cname)
|
106
|
-
parent.autoload?(cname) if cdef?(parent, cname)
|
107
|
-
end
|
108
|
-
else
|
109
|
-
private def strict_autoload_path(parent, cname)
|
110
|
-
parent.autoload?(cname, false)
|
111
|
-
end
|
112
|
-
end
|
103
|
+
CNAME_VALIDATOR = Module.new #: Module
|
104
|
+
private_constant :CNAME_VALIDATOR
|
113
105
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
106
|
+
#: (String, String) -> Symbol ! Zeitwerk::NameError
|
107
|
+
private def cname_for(basename, abspath)
|
108
|
+
cname = inflector.camelize(basename, abspath)
|
109
|
+
|
110
|
+
unless cname.is_a?(String)
|
111
|
+
raise TypeError, "#{inflector.class}#camelize must return a String, received #{cname.inspect}"
|
120
112
|
end
|
121
|
-
|
122
|
-
|
123
|
-
|
113
|
+
|
114
|
+
if cname.include?("::")
|
115
|
+
raise Zeitwerk::NameError.new(<<~MESSAGE, cname)
|
116
|
+
wrong constant name #{cname} inferred by #{inflector.class} from
|
117
|
+
|
118
|
+
#{abspath}
|
119
|
+
|
120
|
+
#{inflector.class}#camelize should return a simple constant name without "::"
|
121
|
+
MESSAGE
|
124
122
|
end
|
125
|
-
end
|
126
123
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
124
|
+
begin
|
125
|
+
CNAME_VALIDATOR.const_defined?(cname, false)
|
126
|
+
rescue ::NameError => error
|
127
|
+
path_type = ruby?(abspath) ? "file" : "directory"
|
131
128
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
129
|
+
raise Zeitwerk::NameError.new(<<~MESSAGE, error.name)
|
130
|
+
#{error.message} inferred by #{inflector.class} from #{path_type}
|
131
|
+
|
132
|
+
#{abspath}
|
133
|
+
|
134
|
+
Possible ways to address this:
|
135
|
+
|
136
|
+
* Tell Zeitwerk to ignore this particular #{path_type}.
|
137
|
+
* Tell Zeitwerk to ignore one of its parent directories.
|
138
|
+
* Rename the #{path_type} to comply with the naming conventions.
|
139
|
+
* Modify the inflector to handle this case.
|
140
|
+
MESSAGE
|
141
|
+
end
|
137
142
|
|
138
|
-
|
139
|
-
# @sig (Module, Symbol) -> Object
|
140
|
-
private def crem(parent, cname)
|
141
|
-
parent.__send__(:remove_const, cname)
|
143
|
+
cname.to_sym
|
142
144
|
end
|
143
145
|
end
|