zeitwerk 2.6.1 → 2.6.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|