zeitwerk 2.6.1 → 2.6.7
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 +184 -36
- data/lib/zeitwerk/error.rb +6 -0
- data/lib/zeitwerk/explicit_namespace.rb +14 -10
- data/lib/zeitwerk/gem_loader.rb +1 -1
- data/lib/zeitwerk/internal.rb +12 -0
- data/lib/zeitwerk/kernel.rb +3 -0
- data/lib/zeitwerk/loader/callbacks.rb +10 -10
- data/lib/zeitwerk/loader/config.rb +82 -46
- data/lib/zeitwerk/loader/eager_load.rb +228 -0
- data/lib/zeitwerk/loader/helpers.rb +30 -16
- data/lib/zeitwerk/loader.rb +112 -113
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +1 -0
- metadata +5 -3
@@ -4,86 +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
|
-
# "/Users/fxn/blog/app/assets" => Object,
|
12
18
|
# "/Users/fxn/blog/app/channels" => Object,
|
13
|
-
# "/Users/fxn/blog/adapters"
|
19
|
+
# "/Users/fxn/blog/app/adapters" => ActiveJob::QueueAdapters,
|
14
20
|
# ...
|
15
21
|
#
|
22
|
+
# Stored in a hash to preserve order, easily handle duplicates, and have a
|
23
|
+
# fast lookup by directory.
|
24
|
+
#
|
16
25
|
# This is a private collection maintained by the loader. The public
|
17
26
|
# interface for it is `push_dir` and `dirs`.
|
18
27
|
#
|
19
|
-
# @private
|
20
28
|
# @sig Hash[String, Module]
|
21
|
-
attr_reader :
|
22
|
-
|
23
|
-
# @sig #camelize
|
24
|
-
attr_accessor :inflector
|
29
|
+
attr_reader :roots
|
30
|
+
internal :roots
|
25
31
|
|
26
32
|
# Absolute paths of files, directories, or glob patterns to be totally
|
27
33
|
# ignored.
|
28
34
|
#
|
29
|
-
# @private
|
30
35
|
# @sig Set[String]
|
31
36
|
attr_reader :ignored_glob_patterns
|
37
|
+
private :ignored_glob_patterns
|
32
38
|
|
33
39
|
# The actual collection of absolute file and directory names at the time the
|
34
40
|
# ignored glob patterns were expanded. Computed on setup, and recomputed on
|
35
41
|
# reload.
|
36
42
|
#
|
37
|
-
# @private
|
38
43
|
# @sig Set[String]
|
39
44
|
attr_reader :ignored_paths
|
45
|
+
private :ignored_paths
|
40
46
|
|
41
47
|
# Absolute paths of directories or glob patterns to be collapsed.
|
42
48
|
#
|
43
|
-
# @private
|
44
49
|
# @sig Set[String]
|
45
50
|
attr_reader :collapse_glob_patterns
|
51
|
+
private :collapse_glob_patterns
|
46
52
|
|
47
53
|
# The actual collection of absolute directory names at the time the collapse
|
48
54
|
# glob patterns were expanded. Computed on setup, and recomputed on reload.
|
49
55
|
#
|
50
|
-
# @private
|
51
56
|
# @sig Set[String]
|
52
57
|
attr_reader :collapse_dirs
|
58
|
+
private :collapse_dirs
|
53
59
|
|
54
60
|
# Absolute paths of files or directories not to be eager loaded.
|
55
61
|
#
|
56
|
-
# @private
|
57
62
|
# @sig Set[String]
|
58
63
|
attr_reader :eager_load_exclusions
|
64
|
+
private :eager_load_exclusions
|
59
65
|
|
60
66
|
# User-oriented callbacks to be fired on setup and on reload.
|
61
67
|
#
|
62
|
-
# @private
|
63
68
|
# @sig Array[{ () -> void }]
|
64
69
|
attr_reader :on_setup_callbacks
|
70
|
+
private :on_setup_callbacks
|
65
71
|
|
66
72
|
# User-oriented callbacks to be fired when a constant is loaded.
|
67
73
|
#
|
68
|
-
# @private
|
69
74
|
# @sig Hash[String, Array[{ (Object, String) -> void }]]
|
70
75
|
# Hash[Symbol, Array[{ (String, Object, String) -> void }]]
|
71
76
|
attr_reader :on_load_callbacks
|
77
|
+
private :on_load_callbacks
|
72
78
|
|
73
79
|
# User-oriented callbacks to be fired before constants are removed.
|
74
80
|
#
|
75
|
-
# @private
|
76
81
|
# @sig Hash[String, Array[{ (Object, String) -> void }]]
|
77
82
|
# Hash[Symbol, Array[{ (String, Object, String) -> void }]]
|
78
83
|
attr_reader :on_unload_callbacks
|
79
|
-
|
80
|
-
# @sig #call | #debug | nil
|
81
|
-
attr_accessor :logger
|
84
|
+
private :on_unload_callbacks
|
82
85
|
|
83
86
|
def initialize
|
84
|
-
@initialized_at = Time.now
|
85
|
-
@root_dirs = {}
|
86
87
|
@inflector = Zeitwerk::Inflector.new
|
88
|
+
@logger = self.class.default_logger
|
89
|
+
@tag = SecureRandom.hex(3)
|
90
|
+
@initialized_at = Time.now
|
91
|
+
@roots = {}
|
87
92
|
@ignored_glob_patterns = Set.new
|
88
93
|
@ignored_paths = Set.new
|
89
94
|
@collapse_glob_patterns = Set.new
|
@@ -93,8 +98,6 @@ module Zeitwerk::Loader::Config
|
|
93
98
|
@on_setup_callbacks = []
|
94
99
|
@on_load_callbacks = {}
|
95
100
|
@on_unload_callbacks = {}
|
96
|
-
@logger = self.class.default_logger
|
97
|
-
@tag = SecureRandom.hex(3)
|
98
101
|
end
|
99
102
|
|
100
103
|
# Pushes `path` to the list of root directories.
|
@@ -111,10 +114,14 @@ module Zeitwerk::Loader::Config
|
|
111
114
|
raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
|
112
115
|
end
|
113
116
|
|
117
|
+
unless real_mod_name(namespace)
|
118
|
+
raise Zeitwerk::Error, "root namespaces cannot be anonymous"
|
119
|
+
end
|
120
|
+
|
114
121
|
abspath = File.expand_path(path)
|
115
122
|
if dir?(abspath)
|
116
123
|
raise_if_conflicting_directory(abspath)
|
117
|
-
|
124
|
+
roots[abspath] = namespace
|
118
125
|
else
|
119
126
|
raise Zeitwerk::Error, "the root directory #{abspath} does not exist"
|
120
127
|
end
|
@@ -142,14 +149,24 @@ module Zeitwerk::Loader::Config
|
|
142
149
|
# instead. Keys are the absolute paths of the root directories as strings,
|
143
150
|
# values are their corresponding namespaces, class or module objects.
|
144
151
|
#
|
152
|
+
# If `ignored` is falsey (default), ignored root directories are filtered out.
|
153
|
+
#
|
145
154
|
# These are read-only collections, please add to them with `push_dir`.
|
146
155
|
#
|
147
156
|
# @sig () -> Array[String] | Hash[String, Module]
|
148
|
-
def dirs(namespaces: false)
|
157
|
+
def dirs(namespaces: false, ignored: false)
|
149
158
|
if namespaces
|
150
|
-
|
159
|
+
if ignored || ignored_paths.empty?
|
160
|
+
roots.clone
|
161
|
+
else
|
162
|
+
roots.reject { |root_dir, _namespace| ignored_path?(root_dir) }
|
163
|
+
end
|
151
164
|
else
|
152
|
-
|
165
|
+
if ignored || ignored_paths.empty?
|
166
|
+
roots.keys
|
167
|
+
else
|
168
|
+
roots.keys.reject { |root_dir| ignored_path?(root_dir) }
|
169
|
+
end
|
153
170
|
end.freeze
|
154
171
|
end
|
155
172
|
|
@@ -273,57 +290,76 @@ module Zeitwerk::Loader::Config
|
|
273
290
|
@logger = ->(msg) { puts msg }
|
274
291
|
end
|
275
292
|
|
276
|
-
#
|
293
|
+
# Returns true if the argument has been configured to be ignored, or is a
|
294
|
+
# descendant of an ignored directory.
|
295
|
+
#
|
277
296
|
# @sig (String) -> bool
|
278
|
-
def ignores?(abspath)
|
279
|
-
|
280
|
-
|
297
|
+
internal def ignores?(abspath)
|
298
|
+
# Common use case.
|
299
|
+
return false if ignored_paths.empty?
|
300
|
+
|
301
|
+
walk_up(abspath) do |abspath|
|
302
|
+
return true if ignored_path?(abspath)
|
303
|
+
return false if roots.key?(abspath)
|
281
304
|
end
|
305
|
+
|
306
|
+
false
|
282
307
|
end
|
283
308
|
|
284
|
-
|
309
|
+
# @sig (String) -> bool
|
310
|
+
private def ignored_path?(abspath)
|
311
|
+
ignored_paths.member?(abspath)
|
312
|
+
end
|
285
313
|
|
286
314
|
# @sig () -> Array[String]
|
287
|
-
def
|
288
|
-
|
289
|
-
!dir?(root_dir) ||
|
315
|
+
private def actual_roots
|
316
|
+
roots.reject do |root_dir, _root_namespace|
|
317
|
+
!dir?(root_dir) || ignored_path?(root_dir)
|
290
318
|
end
|
291
319
|
end
|
292
320
|
|
293
321
|
# @sig (String) -> bool
|
294
|
-
def root_dir?(dir)
|
295
|
-
|
322
|
+
private def root_dir?(dir)
|
323
|
+
roots.key?(dir)
|
296
324
|
end
|
297
325
|
|
298
326
|
# @sig (String) -> bool
|
299
|
-
def excluded_from_eager_load?(abspath)
|
300
|
-
|
327
|
+
private def excluded_from_eager_load?(abspath)
|
328
|
+
# Optimize this common use case.
|
329
|
+
return false if eager_load_exclusions.empty?
|
330
|
+
|
331
|
+
walk_up(abspath) do |abspath|
|
332
|
+
return true if eager_load_exclusions.member?(abspath)
|
333
|
+
return false if roots.key?(abspath)
|
334
|
+
end
|
335
|
+
|
336
|
+
false
|
301
337
|
end
|
302
338
|
|
303
339
|
# @sig (String) -> bool
|
304
|
-
def collapse?(dir)
|
340
|
+
private def collapse?(dir)
|
305
341
|
collapse_dirs.member?(dir)
|
306
342
|
end
|
307
343
|
|
308
344
|
# @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
|
309
|
-
def expand_paths(paths)
|
345
|
+
private def expand_paths(paths)
|
310
346
|
paths.flatten.map! { |path| File.expand_path(path) }
|
311
347
|
end
|
312
348
|
|
313
349
|
# @sig (Array[String]) -> Array[String]
|
314
|
-
def expand_glob_patterns(glob_patterns)
|
350
|
+
private def expand_glob_patterns(glob_patterns)
|
315
351
|
# Note that Dir.glob works with regular file names just fine. That is,
|
316
352
|
# glob patterns technically need no wildcards.
|
317
353
|
glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
|
318
354
|
end
|
319
355
|
|
320
356
|
# @sig () -> void
|
321
|
-
def recompute_ignored_paths
|
357
|
+
private def recompute_ignored_paths
|
322
358
|
ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
|
323
359
|
end
|
324
360
|
|
325
361
|
# @sig () -> void
|
326
|
-
def recompute_collapse_dirs
|
362
|
+
private def recompute_collapse_dirs
|
327
363
|
collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
|
328
364
|
end
|
329
365
|
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
module Zeitwerk::Loader::EagerLoad
|
2
|
+
# Eager loads all files in the root directories, recursively. Files do not
|
3
|
+
# need to be in `$LOAD_PATH`, absolute file names are used. Ignored and
|
4
|
+
# shadowed files are not eager loaded. You can opt-out specifically in
|
5
|
+
# specific files and directories with `do_not_eager_load`, and that can be
|
6
|
+
# overridden passing `force: true`.
|
7
|
+
#
|
8
|
+
# @sig (true | false) -> void
|
9
|
+
def eager_load(force: false)
|
10
|
+
mutex.synchronize do
|
11
|
+
break if @eager_loaded
|
12
|
+
raise Zeitwerk::SetupRequired unless @setup
|
13
|
+
|
14
|
+
log("eager load start") if logger
|
15
|
+
|
16
|
+
actual_roots.each do |root_dir, root_namespace|
|
17
|
+
actual_eager_load_dir(root_dir, root_namespace, force: force)
|
18
|
+
end
|
19
|
+
|
20
|
+
autoloaded_dirs.each do |autoloaded_dir|
|
21
|
+
Zeitwerk::Registry.unregister_autoload(autoloaded_dir)
|
22
|
+
end
|
23
|
+
autoloaded_dirs.clear
|
24
|
+
|
25
|
+
@eager_loaded = true
|
26
|
+
|
27
|
+
log("eager load end") if logger
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @sig (String | Pathname) -> void
|
32
|
+
def eager_load_dir(path)
|
33
|
+
raise Zeitwerk::SetupRequired unless @setup
|
34
|
+
|
35
|
+
abspath = File.expand_path(path)
|
36
|
+
|
37
|
+
raise Zeitwerk::Error.new("#{abspath} is not a directory") unless dir?(abspath)
|
38
|
+
|
39
|
+
cnames = []
|
40
|
+
|
41
|
+
root_namespace = nil
|
42
|
+
walk_up(abspath) do |dir|
|
43
|
+
return if ignored_path?(dir)
|
44
|
+
return if eager_load_exclusions.member?(dir)
|
45
|
+
|
46
|
+
break if root_namespace = roots[dir]
|
47
|
+
|
48
|
+
unless collapse?(dir)
|
49
|
+
basename = File.basename(dir)
|
50
|
+
cnames << inflector.camelize(basename, dir).to_sym
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
raise Zeitwerk::Error.new("I do not manage #{abspath}") unless root_namespace
|
55
|
+
|
56
|
+
return if @eager_loaded
|
57
|
+
|
58
|
+
namespace = root_namespace
|
59
|
+
cnames.reverse_each do |cname|
|
60
|
+
# Can happen if there are no Ruby files. This is not an error condition,
|
61
|
+
# the directory is actually managed. Could have Ruby files later.
|
62
|
+
return unless cdef?(namespace, cname)
|
63
|
+
namespace = cget(namespace, cname)
|
64
|
+
end
|
65
|
+
|
66
|
+
# A shortcircuiting test depends on the invocation of this method. Please
|
67
|
+
# keep them in sync if refactored.
|
68
|
+
actual_eager_load_dir(abspath, namespace)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @sig (Module) -> void
|
72
|
+
def eager_load_namespace(mod)
|
73
|
+
raise Zeitwerk::SetupRequired unless @setup
|
74
|
+
|
75
|
+
unless mod.is_a?(Module)
|
76
|
+
raise Zeitwerk::Error, "#{mod.inspect} is not a class or module object"
|
77
|
+
end
|
78
|
+
|
79
|
+
return if @eager_loaded
|
80
|
+
|
81
|
+
mod_name = real_mod_name(mod)
|
82
|
+
return unless mod_name
|
83
|
+
|
84
|
+
actual_roots.each do |root_dir, root_namespace|
|
85
|
+
if mod.equal?(Object)
|
86
|
+
# A shortcircuiting test depends on the invocation of this method.
|
87
|
+
# Please keep them in sync if refactored.
|
88
|
+
actual_eager_load_dir(root_dir, root_namespace)
|
89
|
+
elsif root_namespace.equal?(Object)
|
90
|
+
eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
|
91
|
+
else
|
92
|
+
root_namespace_name = real_mod_name(root_namespace)
|
93
|
+
if root_namespace_name.start_with?(mod_name + "::")
|
94
|
+
actual_eager_load_dir(root_dir, root_namespace)
|
95
|
+
elsif mod_name == root_namespace_name
|
96
|
+
actual_eager_load_dir(root_dir, root_namespace)
|
97
|
+
elsif mod_name.start_with?(root_namespace_name + "::")
|
98
|
+
eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
|
99
|
+
else
|
100
|
+
# Unrelated constant hierarchies, do nothing.
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Loads the given Ruby file.
|
107
|
+
#
|
108
|
+
# Raises if the argument is ignored, shadowed, or not managed by the receiver.
|
109
|
+
#
|
110
|
+
# The method is implemented as `constantize` for files, in a sense, to be able
|
111
|
+
# to descend orderly and make sure the file is loadable.
|
112
|
+
#
|
113
|
+
# @sig (String | Pathname) -> void
|
114
|
+
def load_file(path)
|
115
|
+
abspath = File.expand_path(path)
|
116
|
+
|
117
|
+
raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
|
118
|
+
raise Zeitwerk::Error.new("#{abspath} is not a Ruby file") if dir?(abspath) || !ruby?(abspath)
|
119
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)
|
120
|
+
|
121
|
+
basename = File.basename(abspath, ".rb")
|
122
|
+
base_cname = inflector.camelize(basename, abspath).to_sym
|
123
|
+
|
124
|
+
root_namespace = nil
|
125
|
+
cnames = []
|
126
|
+
|
127
|
+
walk_up(File.dirname(abspath)) do |dir|
|
128
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(dir)
|
129
|
+
|
130
|
+
break if root_namespace = roots[dir]
|
131
|
+
|
132
|
+
unless collapse?(dir)
|
133
|
+
basename = File.basename(dir)
|
134
|
+
cnames << inflector.camelize(basename, dir).to_sym
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
raise Zeitwerk::Error.new("I do not manage #{abspath}") unless root_namespace
|
139
|
+
|
140
|
+
namespace = root_namespace
|
141
|
+
cnames.reverse_each do |cname|
|
142
|
+
namespace = cget(namespace, cname)
|
143
|
+
end
|
144
|
+
|
145
|
+
raise Zeitwerk::Error.new("#{abspath} is shadowed") if shadowed_file?(abspath)
|
146
|
+
|
147
|
+
cget(namespace, base_cname)
|
148
|
+
end
|
149
|
+
|
150
|
+
# The caller is responsible for making sure `namespace` is the namespace that
|
151
|
+
# corresponds to `dir`.
|
152
|
+
#
|
153
|
+
# @sig (String, Module, Boolean) -> void
|
154
|
+
private def actual_eager_load_dir(dir, namespace, force: false)
|
155
|
+
honour_exclusions = !force
|
156
|
+
return if honour_exclusions && excluded_from_eager_load?(dir)
|
157
|
+
|
158
|
+
log("eager load directory #{dir} start") if logger
|
159
|
+
|
160
|
+
queue = [[dir, namespace]]
|
161
|
+
while to_eager_load = queue.shift
|
162
|
+
dir, namespace = to_eager_load
|
163
|
+
|
164
|
+
ls(dir) do |basename, abspath|
|
165
|
+
next if honour_exclusions && eager_load_exclusions.member?(abspath)
|
166
|
+
|
167
|
+
if ruby?(abspath)
|
168
|
+
if (cref = autoloads[abspath]) && !shadowed_file?(abspath)
|
169
|
+
cget(*cref)
|
170
|
+
end
|
171
|
+
else
|
172
|
+
if collapse?(abspath)
|
173
|
+
queue << [abspath, namespace]
|
174
|
+
else
|
175
|
+
cname = inflector.camelize(basename, abspath).to_sym
|
176
|
+
queue << [abspath, cget(namespace, cname)]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
log("eager load directory #{dir} end") if logger
|
183
|
+
end
|
184
|
+
|
185
|
+
# In order to invoke this method, the caller has to ensure `child` is a
|
186
|
+
# strict namespace descendant of `root_namespace`.
|
187
|
+
#
|
188
|
+
# @sig (Module, String, Module, Boolean) -> void
|
189
|
+
private def eager_load_child_namespace(child, child_name, root_dir, root_namespace)
|
190
|
+
suffix = child_name
|
191
|
+
unless root_namespace.equal?(Object)
|
192
|
+
suffix = suffix.delete_prefix(real_mod_name(root_namespace) + "::")
|
193
|
+
end
|
194
|
+
|
195
|
+
# These directories are at the same namespace level, there may be more if
|
196
|
+
# we find collapsed ones. As we scan, we look for matches for the first
|
197
|
+
# segment, and store them in `next_dirs`. If there are any, we look for
|
198
|
+
# the next segments in those matches. Repeat.
|
199
|
+
#
|
200
|
+
# If we exhaust the search locating directories that match all segments,
|
201
|
+
# we just need to eager load those ones.
|
202
|
+
dirs = [root_dir]
|
203
|
+
next_dirs = []
|
204
|
+
|
205
|
+
suffix.split("::").each do |segment|
|
206
|
+
while dir = dirs.shift
|
207
|
+
ls(dir) do |basename, abspath|
|
208
|
+
next unless dir?(abspath)
|
209
|
+
|
210
|
+
if collapse?(abspath)
|
211
|
+
dirs << abspath
|
212
|
+
elsif segment == inflector.camelize(basename, abspath)
|
213
|
+
next_dirs << abspath
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
return if next_dirs.empty?
|
219
|
+
|
220
|
+
dirs.replace(next_dirs)
|
221
|
+
next_dirs.clear
|
222
|
+
end
|
223
|
+
|
224
|
+
dirs.each do |dir|
|
225
|
+
actual_eager_load_dir(dir, child)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
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,11 @@ 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
|
32
|
+
next if roots.key?(abspath)
|
33
|
+
next if !has_at_least_one_ruby_file?(abspath)
|
35
34
|
else
|
36
35
|
next unless ruby?(abspath)
|
37
36
|
end
|
@@ -43,7 +42,7 @@ module Zeitwerk::Loader::Helpers
|
|
43
42
|
end
|
44
43
|
|
45
44
|
# @sig (String) -> bool
|
46
|
-
def has_at_least_one_ruby_file?(dir)
|
45
|
+
private def has_at_least_one_ruby_file?(dir)
|
47
46
|
to_visit = [dir]
|
48
47
|
|
49
48
|
while dir = to_visit.shift
|
@@ -60,20 +59,29 @@ module Zeitwerk::Loader::Helpers
|
|
60
59
|
end
|
61
60
|
|
62
61
|
# @sig (String) -> bool
|
63
|
-
def ruby?(path)
|
62
|
+
private def ruby?(path)
|
64
63
|
path.end_with?(".rb")
|
65
64
|
end
|
66
65
|
|
67
66
|
# @sig (String) -> bool
|
68
|
-
def dir?(path)
|
67
|
+
private def dir?(path)
|
69
68
|
File.directory?(path)
|
70
69
|
end
|
71
70
|
|
72
71
|
# @sig (String) -> bool
|
73
|
-
def hidden?(basename)
|
72
|
+
private def hidden?(basename)
|
74
73
|
basename.start_with?(".")
|
75
74
|
end
|
76
75
|
|
76
|
+
# @sig (String) { (String) -> void } -> void
|
77
|
+
private def walk_up(abspath)
|
78
|
+
loop do
|
79
|
+
yield abspath
|
80
|
+
abspath, basename = File.split(abspath)
|
81
|
+
break if basename == "/"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
77
85
|
# --- Constants ---------------------------------------------------------------------------------
|
78
86
|
|
79
87
|
# The autoload? predicate takes into account the ancestor chain of the
|
@@ -94,11 +102,11 @@ module Zeitwerk::Loader::Helpers
|
|
94
102
|
#
|
95
103
|
# @sig (Module, Symbol) -> String?
|
96
104
|
if method(:autoload?).arity == 1
|
97
|
-
def strict_autoload_path(parent, cname)
|
105
|
+
private def strict_autoload_path(parent, cname)
|
98
106
|
parent.autoload?(cname) if cdef?(parent, cname)
|
99
107
|
end
|
100
108
|
else
|
101
|
-
def strict_autoload_path(parent, cname)
|
109
|
+
private def strict_autoload_path(parent, cname)
|
102
110
|
parent.autoload?(cname, false)
|
103
111
|
end
|
104
112
|
end
|
@@ -107,23 +115,29 @@ module Zeitwerk::Loader::Helpers
|
|
107
115
|
if Symbol.method_defined?(:name)
|
108
116
|
# Symbol#name was introduced in Ruby 3.0. It returns always the same
|
109
117
|
# frozen object, so we may save a few string allocations.
|
110
|
-
def cpath(parent, cname)
|
118
|
+
private def cpath(parent, cname)
|
111
119
|
Object == parent ? cname.name : "#{real_mod_name(parent)}::#{cname.name}"
|
112
120
|
end
|
113
121
|
else
|
114
|
-
def cpath(parent, cname)
|
122
|
+
private def cpath(parent, cname)
|
115
123
|
Object == parent ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
|
116
124
|
end
|
117
125
|
end
|
118
126
|
|
119
127
|
# @sig (Module, Symbol) -> bool
|
120
|
-
def cdef?(parent, cname)
|
128
|
+
private def cdef?(parent, cname)
|
121
129
|
parent.const_defined?(cname, false)
|
122
130
|
end
|
123
131
|
|
124
132
|
# @raise [NameError]
|
125
133
|
# @sig (Module, Symbol) -> Object
|
126
|
-
def cget(parent, cname)
|
134
|
+
private def cget(parent, cname)
|
127
135
|
parent.const_get(cname, false)
|
128
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
|
129
143
|
end
|