zeitwerk 2.7.4 → 2.8.0
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 +150 -71
- data/lib/zeitwerk/core_ext/kernel.rb +1 -1
- data/lib/zeitwerk/cref/map.rb +1 -1
- data/lib/zeitwerk/cref.rb +8 -1
- data/lib/zeitwerk/error.rb +12 -1
- data/lib/zeitwerk/gem_inflector.rb +3 -3
- data/lib/zeitwerk/gem_loader.rb +6 -6
- data/lib/zeitwerk/inflector.rb +8 -8
- data/lib/zeitwerk/loader/callbacks.rb +4 -4
- data/lib/zeitwerk/loader/config.rb +73 -27
- data/lib/zeitwerk/loader/eager_load.rb +42 -48
- data/lib/zeitwerk/loader/file_system.rb +212 -0
- data/lib/zeitwerk/loader/helpers.rb +2 -101
- data/lib/zeitwerk/loader.rb +195 -162
- data/lib/zeitwerk/real_mod_name.rb +1 -1
- data/lib/zeitwerk/registry/loaders.rb +2 -2
- data/lib/zeitwerk/registry.rb +30 -4
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +13 -13
- metadata +4 -6
data/lib/zeitwerk/inflector.rb
CHANGED
|
@@ -5,9 +5,9 @@ module Zeitwerk
|
|
|
5
5
|
# Very basic snake case -> camel case conversion.
|
|
6
6
|
#
|
|
7
7
|
# inflector = Zeitwerk::Inflector.new
|
|
8
|
-
# inflector.camelize(
|
|
9
|
-
# inflector.camelize(
|
|
10
|
-
# inflector.camelize(
|
|
8
|
+
# inflector.camelize('post', ...) # => 'Post'
|
|
9
|
+
# inflector.camelize('users_controller', ...) # => 'UsersController'
|
|
10
|
+
# inflector.camelize('api', ...) # => 'Api'
|
|
11
11
|
#
|
|
12
12
|
# Takes into account hard-coded mappings configured with `inflect`.
|
|
13
13
|
#
|
|
@@ -20,13 +20,13 @@ module Zeitwerk
|
|
|
20
20
|
#
|
|
21
21
|
# inflector = Zeitwerk::Inflector.new
|
|
22
22
|
# inflector.inflect(
|
|
23
|
-
#
|
|
24
|
-
#
|
|
23
|
+
# 'html_parser' => 'HTMLParser',
|
|
24
|
+
# 'mysql_adapter' => 'MySQLAdapter'
|
|
25
25
|
# )
|
|
26
26
|
#
|
|
27
|
-
# inflector.camelize(
|
|
28
|
-
# inflector.camelize(
|
|
29
|
-
# inflector.camelize(
|
|
27
|
+
# inflector.camelize('html_parser', abspath) # => 'HTMLParser'
|
|
28
|
+
# inflector.camelize('mysql_adapter', abspath) # => 'MySQLAdapter'
|
|
29
|
+
# inflector.camelize('users_controller', abspath) # => 'UsersController'
|
|
30
30
|
#
|
|
31
31
|
#: (Hash[String, String]) -> void
|
|
32
32
|
def inflect(inflections)
|
|
@@ -12,12 +12,12 @@ module Zeitwerk::Loader::Callbacks # :nodoc: all
|
|
|
12
12
|
Zeitwerk::Registry.autoloads.unregister(file)
|
|
13
13
|
|
|
14
14
|
if cref.defined?
|
|
15
|
-
log
|
|
15
|
+
log { "constant #{cref} loaded from file #{file}" }
|
|
16
16
|
to_unload[file] = cref if reloading_enabled?
|
|
17
17
|
run_on_load_callbacks(cref.path, cref.get, file) unless on_load_callbacks.empty?
|
|
18
18
|
else
|
|
19
19
|
msg = "expected file #{file} to define constant #{cref}, but didn't"
|
|
20
|
-
log
|
|
20
|
+
log { msg }
|
|
21
21
|
|
|
22
22
|
# Ruby still keeps the autoload defined, but we remove it because the
|
|
23
23
|
# contract in Zeitwerk is more strict.
|
|
@@ -52,7 +52,7 @@ module Zeitwerk::Loader::Callbacks # :nodoc: all
|
|
|
52
52
|
dirs_autoload_monitor.synchronize do
|
|
53
53
|
if cref = autoloads.delete(dir)
|
|
54
54
|
implicit_namespace = cref.set(Module.new)
|
|
55
|
-
log
|
|
55
|
+
log { "module #{cref} autovivified from directory #{dir}" }
|
|
56
56
|
|
|
57
57
|
to_unload[dir] = cref if reloading_enabled?
|
|
58
58
|
|
|
@@ -77,7 +77,7 @@ module Zeitwerk::Loader::Callbacks # :nodoc: all
|
|
|
77
77
|
internal def on_namespace_loaded(cref, namespace)
|
|
78
78
|
if dirs = namespace_dirs.delete(cref)
|
|
79
79
|
dirs.each do |dir|
|
|
80
|
-
define_autoloads_for_dir(dir, namespace)
|
|
80
|
+
define_autoloads_for_dir(dir, namespace, external: false)
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'set'
|
|
4
|
+
require 'securerandom'
|
|
5
5
|
|
|
6
6
|
module Zeitwerk::Loader::Config
|
|
7
7
|
extend Zeitwerk::Internal
|
|
@@ -15,8 +15,8 @@ module Zeitwerk::Loader::Config
|
|
|
15
15
|
|
|
16
16
|
# Absolute paths of the root directories, mapped to their respective root namespaces:
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
18
|
+
# '/Users/fxn/blog/app/channels' => Object,
|
|
19
|
+
# '/Users/fxn/blog/app/adapters' => ActiveJob::QueueAdapters,
|
|
20
20
|
# ...
|
|
21
21
|
#
|
|
22
22
|
# Stored in a hash to preserve order, easily handle duplicates, and have a
|
|
@@ -29,6 +29,12 @@ module Zeitwerk::Loader::Config
|
|
|
29
29
|
attr_reader :roots
|
|
30
30
|
internal :roots
|
|
31
31
|
|
|
32
|
+
# Basename of files that define namespaces. For example, if `nsfile` is
|
|
33
|
+
# 'ns.rb', then `foo/ns.rb` defines the `Foo` namespace.
|
|
34
|
+
#
|
|
35
|
+
#: String?
|
|
36
|
+
attr_reader :nsfile
|
|
37
|
+
|
|
32
38
|
# Absolute paths of files, directories, or glob patterns to be ignored.
|
|
33
39
|
#
|
|
34
40
|
#: Set[String]
|
|
@@ -50,12 +56,20 @@ module Zeitwerk::Loader::Config
|
|
|
50
56
|
private :collapse_glob_patterns
|
|
51
57
|
|
|
52
58
|
# The actual collection of absolute directory names at the time the collapse
|
|
53
|
-
# glob patterns were expanded. Computed on setup
|
|
59
|
+
# glob patterns were expanded. Computed on setup and recomputed on reload.
|
|
54
60
|
#
|
|
55
61
|
#: Set[String]
|
|
56
62
|
attr_reader :collapse_dirs
|
|
57
63
|
private :collapse_dirs
|
|
58
64
|
|
|
65
|
+
# Absolute paths of directories that are parents of collapsed directories.
|
|
66
|
+
# This is a cache to optimize some tree walks. Computed on setup and
|
|
67
|
+
# recomputed on reload.
|
|
68
|
+
#
|
|
69
|
+
#: Set[String]
|
|
70
|
+
attr_reader :collapse_parents
|
|
71
|
+
private :collapse_parents
|
|
72
|
+
|
|
59
73
|
# Absolute paths of files or directories not to be eager loaded.
|
|
60
74
|
#
|
|
61
75
|
#: Set[String]
|
|
@@ -82,16 +96,19 @@ module Zeitwerk::Loader::Config
|
|
|
82
96
|
attr_reader :on_unload_callbacks
|
|
83
97
|
private :on_unload_callbacks
|
|
84
98
|
|
|
99
|
+
#: () -> void
|
|
85
100
|
def initialize
|
|
86
101
|
@inflector = Zeitwerk::Inflector.new
|
|
87
102
|
@logger = self.class.default_logger
|
|
88
103
|
@tag = SecureRandom.hex(3)
|
|
89
104
|
@initialized_at = Time.now
|
|
90
105
|
@roots = {}
|
|
106
|
+
@nsfile = nil
|
|
91
107
|
@ignored_glob_patterns = Set.new
|
|
92
108
|
@ignored_paths = Set.new
|
|
93
109
|
@collapse_glob_patterns = Set.new
|
|
94
110
|
@collapse_dirs = Set.new
|
|
111
|
+
@collapse_parents = Set.new
|
|
95
112
|
@eager_load_exclusions = Set.new
|
|
96
113
|
@reloading_enabled = false
|
|
97
114
|
@on_setup_callbacks = []
|
|
@@ -112,12 +129,12 @@ module Zeitwerk::Loader::Config
|
|
|
112
129
|
end
|
|
113
130
|
|
|
114
131
|
unless real_mod_name(namespace)
|
|
115
|
-
raise Zeitwerk::Error,
|
|
132
|
+
raise Zeitwerk::Error, 'root namespaces cannot be anonymous'
|
|
116
133
|
end
|
|
117
134
|
|
|
118
135
|
abspath = File.expand_path(path)
|
|
119
|
-
if dir?(abspath)
|
|
120
|
-
|
|
136
|
+
if @fs.dir?(abspath)
|
|
137
|
+
raise_if_conflicting_root_dir(abspath)
|
|
121
138
|
roots[abspath] = namespace
|
|
122
139
|
else
|
|
123
140
|
raise Zeitwerk::Error, "the root directory #{abspath} does not exist"
|
|
@@ -141,6 +158,18 @@ module Zeitwerk::Loader::Config
|
|
|
141
158
|
@tag = tag.to_s
|
|
142
159
|
end
|
|
143
160
|
|
|
161
|
+
#: (String?) -> void ! TypeError, ArgumentError
|
|
162
|
+
def nsfile=(nsfile)
|
|
163
|
+
unless nsfile.nil?
|
|
164
|
+
raise TypeError, 'nsfiles must be strings' unless nsfile.is_a?(String)
|
|
165
|
+
raise ArgumentError, 'nsfiles must have .rb extension' unless @fs.rb_extension?(nsfile)
|
|
166
|
+
raise ArgumentError, 'nsfiles must be basenames, not paths' unless File.basename(nsfile) == nsfile
|
|
167
|
+
raise ArgumentError, 'nsfiles cannot be hidden' if @fs.hidden?(nsfile)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
@nsfile = nsfile
|
|
171
|
+
end
|
|
172
|
+
|
|
144
173
|
# If `namespaces` is falsey (default), returns an array with the absolute
|
|
145
174
|
# paths of the root directories as strings. If truthy, returns a hash table
|
|
146
175
|
# instead. Keys are the absolute paths of the root directories as strings,
|
|
@@ -176,7 +205,7 @@ module Zeitwerk::Loader::Config
|
|
|
176
205
|
break if @reloading_enabled
|
|
177
206
|
|
|
178
207
|
if @setup
|
|
179
|
-
raise Zeitwerk::Error,
|
|
208
|
+
raise Zeitwerk::Error, 'cannot enable reloading after setup'
|
|
180
209
|
else
|
|
181
210
|
@reloading_enabled = true
|
|
182
211
|
end
|
|
@@ -214,7 +243,11 @@ module Zeitwerk::Loader::Config
|
|
|
214
243
|
glob_patterns = expand_paths(glob_patterns)
|
|
215
244
|
mutex.synchronize do
|
|
216
245
|
collapse_glob_patterns.merge(glob_patterns)
|
|
217
|
-
|
|
246
|
+
new_collapse_dirs = expand_glob_patterns(glob_patterns)
|
|
247
|
+
collapse_dirs.merge(new_collapse_dirs)
|
|
248
|
+
new_collapse_dirs.each do |dir|
|
|
249
|
+
collapse_parents << File.dirname(dir)
|
|
250
|
+
end
|
|
218
251
|
end
|
|
219
252
|
end
|
|
220
253
|
|
|
@@ -233,8 +266,8 @@ module Zeitwerk::Loader::Config
|
|
|
233
266
|
# Supports multiple callbacks, and if there are many, they are executed in
|
|
234
267
|
# the order in which they were defined.
|
|
235
268
|
#
|
|
236
|
-
# loader.on_load(
|
|
237
|
-
# klass.endpoint =
|
|
269
|
+
# loader.on_load('SomeApiClient') do |klass, _abspath|
|
|
270
|
+
# klass.endpoint = 'https://api.dev'
|
|
238
271
|
# end
|
|
239
272
|
#
|
|
240
273
|
# Can also be configured for any constant loaded:
|
|
@@ -245,7 +278,7 @@ module Zeitwerk::Loader::Config
|
|
|
245
278
|
#
|
|
246
279
|
#: (String?) { (top, String) -> void } -> void ! TypeError
|
|
247
280
|
def on_load(cpath = :ANY, &block)
|
|
248
|
-
raise TypeError,
|
|
281
|
+
raise TypeError, 'on_load only accepts strings' unless cpath.is_a?(String) || cpath == :ANY
|
|
249
282
|
|
|
250
283
|
mutex.synchronize do
|
|
251
284
|
(on_load_callbacks[cpath] ||= []) << block
|
|
@@ -256,7 +289,7 @@ module Zeitwerk::Loader::Config
|
|
|
256
289
|
# Supports multiple callbacks, and if there are many, they are executed in the
|
|
257
290
|
# order in which they were defined.
|
|
258
291
|
#
|
|
259
|
-
# loader.on_unload(
|
|
292
|
+
# loader.on_unload('Country') do |klass, _abspath|
|
|
260
293
|
# klass.clear_cache
|
|
261
294
|
# end
|
|
262
295
|
#
|
|
@@ -268,7 +301,7 @@ module Zeitwerk::Loader::Config
|
|
|
268
301
|
#
|
|
269
302
|
#: (String?) { (top, String) -> void } -> void ! TypeError
|
|
270
303
|
def on_unload(cpath = :ANY, &block)
|
|
271
|
-
raise TypeError,
|
|
304
|
+
raise TypeError, 'on_unload only accepts strings' unless cpath.is_a?(String) || cpath == :ANY
|
|
272
305
|
|
|
273
306
|
mutex.synchronize do
|
|
274
307
|
(on_unload_callbacks[cpath] ||= []) << block
|
|
@@ -290,49 +323,54 @@ module Zeitwerk::Loader::Config
|
|
|
290
323
|
# Common use case.
|
|
291
324
|
return false if ignored_paths.empty?
|
|
292
325
|
|
|
293
|
-
walk_up(abspath) do |path|
|
|
326
|
+
@fs.walk_up(abspath) do |path|
|
|
294
327
|
return true if ignored_path?(path)
|
|
295
|
-
return false if
|
|
328
|
+
return false if root_dir?(path)
|
|
296
329
|
end
|
|
297
330
|
|
|
298
331
|
false
|
|
299
332
|
end
|
|
300
333
|
|
|
301
334
|
#: (String) -> bool
|
|
302
|
-
|
|
335
|
+
internal def ignored_path?(abspath)
|
|
303
336
|
ignored_paths.member?(abspath)
|
|
304
337
|
end
|
|
305
338
|
|
|
306
339
|
#: () -> Array[String]
|
|
307
340
|
private def actual_roots
|
|
308
341
|
roots.reject do |root_dir, _root_namespace|
|
|
309
|
-
|
|
342
|
+
!@fs.dir?(root_dir) || ignored_path?(root_dir)
|
|
310
343
|
end
|
|
311
344
|
end
|
|
312
345
|
|
|
313
346
|
#: (String) -> bool
|
|
314
|
-
|
|
347
|
+
internal def root_dir?(dir)
|
|
315
348
|
roots.key?(dir)
|
|
316
349
|
end
|
|
317
350
|
|
|
351
|
+
#: (String) -> bool
|
|
352
|
+
internal def collapse?(dir)
|
|
353
|
+
collapse_dirs.member?(dir)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
#: (String) -> bool
|
|
357
|
+
internal def collapse_parent?(dir)
|
|
358
|
+
collapse_parents.member?(dir)
|
|
359
|
+
end
|
|
360
|
+
|
|
318
361
|
#: (String) -> bool
|
|
319
362
|
private def excluded_from_eager_load?(abspath)
|
|
320
363
|
# Optimize this common use case.
|
|
321
364
|
return false if eager_load_exclusions.empty?
|
|
322
365
|
|
|
323
|
-
walk_up(abspath) do |path|
|
|
366
|
+
@fs.walk_up(abspath) do |path|
|
|
324
367
|
return true if eager_load_exclusions.member?(path)
|
|
325
|
-
return false if
|
|
368
|
+
return false if root_dir?(path)
|
|
326
369
|
end
|
|
327
370
|
|
|
328
371
|
false
|
|
329
372
|
end
|
|
330
373
|
|
|
331
|
-
#: (String) -> bool
|
|
332
|
-
private def collapse?(dir)
|
|
333
|
-
collapse_dirs.member?(dir)
|
|
334
|
-
end
|
|
335
|
-
|
|
336
374
|
#: (String | Pathname | Array[String | Pathname]) -> Array[String]
|
|
337
375
|
private def expand_paths(paths)
|
|
338
376
|
paths.flatten.map! { |path| File.expand_path(path) }
|
|
@@ -354,4 +392,12 @@ module Zeitwerk::Loader::Config
|
|
|
354
392
|
private def recompute_collapse_dirs
|
|
355
393
|
collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
|
|
356
394
|
end
|
|
395
|
+
|
|
396
|
+
#: () -> void
|
|
397
|
+
private def recompute_collapse_parents
|
|
398
|
+
collapse_parents.clear
|
|
399
|
+
collapse_dirs.each do |dir|
|
|
400
|
+
collapse_parents << File.dirname(dir)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
357
403
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
module Zeitwerk::Loader::EagerLoad
|
|
2
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.
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# need to be in `$LOAD_PATH`, absolute file names are used.
|
|
4
|
+
#
|
|
5
|
+
# Ignored files are not eager loaded. You can opt-out specifically in specific
|
|
6
|
+
# files and directories with `do_not_eager_load`, and that can be overridden
|
|
7
|
+
# passing `force: true`.
|
|
7
8
|
#
|
|
8
9
|
#: (?force: boolish) -> void
|
|
9
10
|
def eager_load(force: false)
|
|
@@ -11,7 +12,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
11
12
|
break if @eager_loaded
|
|
12
13
|
raise Zeitwerk::SetupRequired unless @setup
|
|
13
14
|
|
|
14
|
-
log
|
|
15
|
+
log { 'eager load start' }
|
|
15
16
|
|
|
16
17
|
actual_roots.each do |root_dir, root_namespace|
|
|
17
18
|
actual_eager_load_dir(root_dir, root_namespace, force: force)
|
|
@@ -24,7 +25,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
24
25
|
|
|
25
26
|
@eager_loaded = true
|
|
26
27
|
|
|
27
|
-
log
|
|
28
|
+
log { 'eager load end' }
|
|
28
29
|
end
|
|
29
30
|
end
|
|
30
31
|
|
|
@@ -34,23 +35,21 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
34
35
|
|
|
35
36
|
abspath = File.expand_path(path)
|
|
36
37
|
|
|
37
|
-
raise Zeitwerk::Error.new("#{abspath} is not a directory") unless dir?(abspath)
|
|
38
|
+
raise Zeitwerk::Error.new("#{abspath} is not a directory") unless @fs.dir?(abspath)
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
paths = []
|
|
40
41
|
|
|
41
42
|
root_namespace = nil
|
|
42
|
-
walk_up(abspath) do |dir|
|
|
43
|
+
@fs.walk_up(abspath) do |dir|
|
|
43
44
|
return if ignored_path?(dir)
|
|
44
45
|
return if eager_load_exclusions.member?(dir)
|
|
45
46
|
|
|
46
47
|
break if root_namespace = roots[dir]
|
|
47
48
|
|
|
48
49
|
basename = File.basename(dir)
|
|
49
|
-
return if hidden?(basename)
|
|
50
|
+
return if @fs.hidden?(basename)
|
|
50
51
|
|
|
51
|
-
unless collapse?(dir)
|
|
52
|
-
cnames << inflector.camelize(basename, dir).to_sym
|
|
53
|
-
end
|
|
52
|
+
paths << [basename, dir] unless collapse?(dir)
|
|
54
53
|
end
|
|
55
54
|
|
|
56
55
|
raise Zeitwerk::Error.new("I do not manage #{abspath}") unless root_namespace
|
|
@@ -58,7 +57,8 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
58
57
|
return if @eager_loaded
|
|
59
58
|
|
|
60
59
|
namespace = root_namespace
|
|
61
|
-
|
|
60
|
+
paths.reverse_each do |basename, dir|
|
|
61
|
+
cname = cname_for(basename, dir)
|
|
62
62
|
# Can happen if there are no Ruby files. This is not an error condition,
|
|
63
63
|
# the directory is actually managed. Could have Ruby files later.
|
|
64
64
|
return unless namespace.const_defined?(cname, false)
|
|
@@ -92,11 +92,11 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
92
92
|
eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
|
|
93
93
|
else
|
|
94
94
|
root_namespace_name = real_mod_name(root_namespace)
|
|
95
|
-
if root_namespace_name.start_with?(mod_name +
|
|
95
|
+
if root_namespace_name.start_with?(mod_name + '::')
|
|
96
96
|
actual_eager_load_dir(root_dir, root_namespace)
|
|
97
97
|
elsif mod_name == root_namespace_name
|
|
98
98
|
actual_eager_load_dir(root_dir, root_namespace)
|
|
99
|
-
elsif mod_name.start_with?(root_namespace_name +
|
|
99
|
+
elsif mod_name.start_with?(root_namespace_name + '::')
|
|
100
100
|
eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
|
|
101
101
|
else
|
|
102
102
|
# Unrelated constant hierarchies, do nothing.
|
|
@@ -117,40 +117,42 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
117
117
|
abspath = File.expand_path(path)
|
|
118
118
|
|
|
119
119
|
raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
|
|
120
|
-
raise Zeitwerk::Error.new("#{abspath} is not a Ruby file") if
|
|
120
|
+
raise Zeitwerk::Error.new("#{abspath} is not a Ruby file") if !@fs.rb_extension?(abspath)
|
|
121
121
|
raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(
|
|
125
|
-
|
|
126
|
-
base_cname = inflector.camelize(basename, abspath).to_sym
|
|
123
|
+
file_basename = File.basename(abspath)
|
|
124
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if @fs.hidden?(file_basename)
|
|
127
125
|
|
|
128
126
|
root_namespace = nil
|
|
129
|
-
|
|
127
|
+
paths = []
|
|
130
128
|
|
|
131
|
-
walk_up(File.dirname(abspath)) do |dir|
|
|
129
|
+
@fs.walk_up(File.dirname(abspath)) do |dir|
|
|
132
130
|
raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(dir)
|
|
133
131
|
|
|
134
132
|
break if root_namespace = roots[dir]
|
|
135
133
|
|
|
136
134
|
basename = File.basename(dir)
|
|
137
|
-
raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
|
|
135
|
+
raise Zeitwerk::Error.new("#{abspath} is ignored") if @fs.hidden?(basename)
|
|
138
136
|
|
|
139
|
-
unless collapse?(dir)
|
|
140
|
-
cnames << inflector.camelize(basename, dir).to_sym
|
|
141
|
-
end
|
|
137
|
+
paths << [basename, dir] unless collapse?(dir)
|
|
142
138
|
end
|
|
143
139
|
|
|
144
140
|
raise Zeitwerk::Error.new("I do not manage #{abspath}") unless root_namespace
|
|
145
141
|
|
|
146
142
|
namespace = root_namespace
|
|
147
|
-
|
|
143
|
+
paths.reverse_each do |basename, dir|
|
|
144
|
+
cname = cname_for(basename, dir)
|
|
148
145
|
namespace = namespace.const_get(cname, false)
|
|
149
146
|
end
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
if file_basename == @nsfile
|
|
149
|
+
namespace
|
|
150
|
+
elsif shadowed_file?(abspath)
|
|
151
|
+
raise Zeitwerk::Error.new("#{abspath} is shadowed")
|
|
152
|
+
else
|
|
153
|
+
cname = cname_for(file_basename.delete_suffix('.rb'), abspath)
|
|
154
|
+
namespace.const_get(cname, false)
|
|
155
|
+
end
|
|
154
156
|
end
|
|
155
157
|
|
|
156
158
|
# The caller is responsible for making sure `namespace` is the namespace that
|
|
@@ -161,11 +163,11 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
161
163
|
honour_exclusions = !force
|
|
162
164
|
return if honour_exclusions && excluded_from_eager_load?(dir)
|
|
163
165
|
|
|
164
|
-
log
|
|
166
|
+
log { "eager load directory #{dir} start" }
|
|
165
167
|
|
|
166
168
|
queue = [[dir, namespace]]
|
|
167
169
|
while (current_dir, namespace = queue.shift)
|
|
168
|
-
ls(current_dir) do |basename, abspath, ftype|
|
|
170
|
+
@fs.ls(current_dir) do |basename, abspath, ftype|
|
|
169
171
|
next if honour_exclusions && eager_load_exclusions.member?(abspath)
|
|
170
172
|
|
|
171
173
|
if ftype == :file
|
|
@@ -173,17 +175,13 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
173
175
|
cref.get
|
|
174
176
|
end
|
|
175
177
|
else
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
else
|
|
179
|
-
cname = inflector.camelize(basename, abspath).to_sym
|
|
180
|
-
queue << [abspath, namespace.const_get(cname, false)]
|
|
181
|
-
end
|
|
178
|
+
cname = cname_for(basename, abspath)
|
|
179
|
+
queue << [abspath, namespace.const_get(cname, false)]
|
|
182
180
|
end
|
|
183
181
|
end
|
|
184
182
|
end
|
|
185
183
|
|
|
186
|
-
log
|
|
184
|
+
log { "eager load directory #{dir} end" }
|
|
187
185
|
end
|
|
188
186
|
|
|
189
187
|
# In order to invoke this method, the caller has to ensure `child` is a
|
|
@@ -193,7 +191,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
193
191
|
private def eager_load_child_namespace(child, child_name, root_dir, root_namespace)
|
|
194
192
|
suffix = child_name
|
|
195
193
|
unless root_namespace.equal?(Object)
|
|
196
|
-
suffix = suffix.delete_prefix(real_mod_name(root_namespace) +
|
|
194
|
+
suffix = suffix.delete_prefix(real_mod_name(root_namespace) + '::')
|
|
197
195
|
end
|
|
198
196
|
|
|
199
197
|
# These directories are at the same namespace level, there may be more if
|
|
@@ -206,14 +204,10 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
206
204
|
dirs = [root_dir]
|
|
207
205
|
next_dirs = []
|
|
208
206
|
|
|
209
|
-
suffix.split(
|
|
207
|
+
suffix.split('::').each do |segment|
|
|
210
208
|
while (dir = dirs.shift)
|
|
211
|
-
ls(dir) do |basename, abspath, ftype|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if collapse?(abspath)
|
|
215
|
-
dirs << abspath
|
|
216
|
-
elsif segment == inflector.camelize(basename, abspath)
|
|
209
|
+
@fs.ls(dir) do |basename, abspath, ftype|
|
|
210
|
+
if ftype == :directory && segment == cname_for(basename, abspath).to_s
|
|
217
211
|
next_dirs << abspath
|
|
218
212
|
end
|
|
219
213
|
end
|