zeitwerk 2.7.5 → 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 +131 -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 +4 -4
- data/lib/zeitwerk/inflector.rb +8 -8
- data/lib/zeitwerk/loader/callbacks.rb +1 -1
- data/lib/zeitwerk/loader/config.rb +64 -18
- data/lib/zeitwerk/loader/eager_load.rb +23 -27
- data/lib/zeitwerk/loader/file_system.rb +72 -25
- data/lib/zeitwerk/loader/helpers.rb +2 -2
- data/lib/zeitwerk/loader.rb +164 -126
- data/lib/zeitwerk/registry.rb +6 -6
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +13 -13
- metadata +1 -1
|
@@ -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,7 +129,7 @@ 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)
|
|
@@ -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
|
|
@@ -315,6 +348,16 @@ module Zeitwerk::Loader::Config
|
|
|
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.
|
|
@@ -328,11 +371,6 @@ module Zeitwerk::Loader::Config
|
|
|
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
|
|
|
@@ -91,11 +92,11 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
91
92
|
eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
|
|
92
93
|
else
|
|
93
94
|
root_namespace_name = real_mod_name(root_namespace)
|
|
94
|
-
if root_namespace_name.start_with?(mod_name +
|
|
95
|
+
if root_namespace_name.start_with?(mod_name + '::')
|
|
95
96
|
actual_eager_load_dir(root_dir, root_namespace)
|
|
96
97
|
elsif mod_name == root_namespace_name
|
|
97
98
|
actual_eager_load_dir(root_dir, root_namespace)
|
|
98
|
-
elsif mod_name.start_with?(root_namespace_name +
|
|
99
|
+
elsif mod_name.start_with?(root_namespace_name + '::')
|
|
99
100
|
eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
|
|
100
101
|
else
|
|
101
102
|
# Unrelated constant hierarchies, do nothing.
|
|
@@ -119,7 +120,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
119
120
|
raise Zeitwerk::Error.new("#{abspath} is not a Ruby file") if !@fs.rb_extension?(abspath)
|
|
120
121
|
raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)
|
|
121
122
|
|
|
122
|
-
file_basename = File.basename(abspath
|
|
123
|
+
file_basename = File.basename(abspath)
|
|
123
124
|
raise Zeitwerk::Error.new("#{abspath} is ignored") if @fs.hidden?(file_basename)
|
|
124
125
|
|
|
125
126
|
root_namespace = nil
|
|
@@ -138,17 +139,20 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
138
139
|
|
|
139
140
|
raise Zeitwerk::Error.new("I do not manage #{abspath}") unless root_namespace
|
|
140
141
|
|
|
141
|
-
base_cname = cname_for(file_basename, abspath)
|
|
142
|
-
|
|
143
142
|
namespace = root_namespace
|
|
144
143
|
paths.reverse_each do |basename, dir|
|
|
145
144
|
cname = cname_for(basename, dir)
|
|
146
145
|
namespace = namespace.const_get(cname, false)
|
|
147
146
|
end
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
152
156
|
end
|
|
153
157
|
|
|
154
158
|
# The caller is responsible for making sure `namespace` is the namespace that
|
|
@@ -171,12 +175,8 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
171
175
|
cref.get
|
|
172
176
|
end
|
|
173
177
|
else
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
else
|
|
177
|
-
cname = cname_for(basename, abspath)
|
|
178
|
-
queue << [abspath, namespace.const_get(cname, false)]
|
|
179
|
-
end
|
|
178
|
+
cname = cname_for(basename, abspath)
|
|
179
|
+
queue << [abspath, namespace.const_get(cname, false)]
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
182
|
end
|
|
@@ -191,7 +191,7 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
191
191
|
private def eager_load_child_namespace(child, child_name, root_dir, root_namespace)
|
|
192
192
|
suffix = child_name
|
|
193
193
|
unless root_namespace.equal?(Object)
|
|
194
|
-
suffix = suffix.delete_prefix(real_mod_name(root_namespace) +
|
|
194
|
+
suffix = suffix.delete_prefix(real_mod_name(root_namespace) + '::')
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
# These directories are at the same namespace level, there may be more if
|
|
@@ -204,14 +204,10 @@ module Zeitwerk::Loader::EagerLoad
|
|
|
204
204
|
dirs = [root_dir]
|
|
205
205
|
next_dirs = []
|
|
206
206
|
|
|
207
|
-
suffix.split(
|
|
207
|
+
suffix.split('::').each do |segment|
|
|
208
208
|
while (dir = dirs.shift)
|
|
209
209
|
@fs.ls(dir) do |basename, abspath, ftype|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if collapse?(abspath)
|
|
213
|
-
dirs << abspath
|
|
214
|
-
elsif segment == cname_for(basename, abspath).to_s
|
|
210
|
+
if ftype == :directory && segment == cname_for(basename, abspath).to_s
|
|
215
211
|
next_dirs << abspath
|
|
216
212
|
end
|
|
217
213
|
end
|
|
@@ -12,8 +12,22 @@ class Zeitwerk::Loader::FileSystem # :nodoc:
|
|
|
12
12
|
@loader = loader
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# This method lists directories, filtering out the following:
|
|
16
|
+
#
|
|
17
|
+
# - Hidden entries.
|
|
18
|
+
# - Ignored entries.
|
|
19
|
+
# - Files whose extension is not `.rb`.
|
|
20
|
+
# - Nested root directories, since they represent separate trees.
|
|
21
|
+
# - Subdirectories that (recursively) contain no Ruby files.
|
|
22
|
+
#
|
|
23
|
+
# If `collapse` is true, collapsed directories are not yielded, instead, the
|
|
24
|
+
# method recurses so that the caller gets a conceptually flat listing.
|
|
25
|
+
#
|
|
26
|
+
# For every entry that is not excluded, `ls` yields its basename, absolute
|
|
27
|
+
# path, and file type, which can only be :file or :directory.
|
|
28
|
+
#
|
|
15
29
|
#: (String) { (String, String, Symbol) -> void } -> void
|
|
16
|
-
def ls(dir)
|
|
30
|
+
def ls(dir, collapse: true, &)
|
|
17
31
|
children = relevant_dir_entries(dir)
|
|
18
32
|
|
|
19
33
|
# The order in which a directory is listed depends on the file system.
|
|
@@ -24,9 +38,14 @@ class Zeitwerk::Loader::FileSystem # :nodoc:
|
|
|
24
38
|
children.sort_by!(&:first)
|
|
25
39
|
|
|
26
40
|
children.each do |basename, abspath, ftype|
|
|
27
|
-
if
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
if ftype == :directory
|
|
42
|
+
if !has_at_least_one_ruby_file?(abspath)
|
|
43
|
+
@loader.__log { "directory #{abspath} is ignored because it has no Ruby files" }
|
|
44
|
+
next
|
|
45
|
+
elsif collapse && @loader.__collapse?(abspath)
|
|
46
|
+
ls(abspath, collapse: collapse, &)
|
|
47
|
+
next
|
|
48
|
+
end
|
|
30
49
|
end
|
|
31
50
|
|
|
32
51
|
yield basename, abspath, ftype
|
|
@@ -38,8 +57,47 @@ class Zeitwerk::Loader::FileSystem # :nodoc:
|
|
|
38
57
|
loop do
|
|
39
58
|
yield abspath
|
|
40
59
|
abspath, basename = File.split(abspath)
|
|
41
|
-
break if basename ==
|
|
60
|
+
break if basename == '/'
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns the absolute path to an nsfile in `dir`, if there is exactly one. If
|
|
65
|
+
# there is none, it returns `nil`.
|
|
66
|
+
#
|
|
67
|
+
# This method accounts for collapsed directories, which conceptually allow for
|
|
68
|
+
# multiple nsfiles. If two are found, Zeitwerk::ConflictingNamespaceDefinitionError is raised.
|
|
69
|
+
#
|
|
70
|
+
#: (Zeitwerk::Cref, String) -> String? ! Zeitwerk::ConflictingNamespaceDefinitionError
|
|
71
|
+
def has_exactly_one_nsfile?(cref, dir)
|
|
72
|
+
return unless @loader.nsfile
|
|
73
|
+
|
|
74
|
+
# When `dir` does not have any collapsed directories a simple lookup
|
|
75
|
+
# suffices. This is a common case worth optimizing.
|
|
76
|
+
unless @loader.__collapse_parent?(dir)
|
|
77
|
+
nsfile_abspath = File.join(dir, @loader.nsfile)
|
|
78
|
+
if File.exist?(nsfile_abspath) && !@loader.__ignored_path?(nsfile_abspath)
|
|
79
|
+
return nsfile_abspath
|
|
80
|
+
end
|
|
81
|
+
return
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
nsfile = nil
|
|
85
|
+
|
|
86
|
+
to_visit = [dir]
|
|
87
|
+
while (dir = to_visit.shift)
|
|
88
|
+
relevant_dir_entries(dir) do |basename, abspath, ftype|
|
|
89
|
+
if ftype == :file && basename == @loader.nsfile
|
|
90
|
+
if nsfile
|
|
91
|
+
raise Zeitwerk::ConflictingNamespaceDefinitionError.new(cref.path, location: nsfile, conflicting_file: abspath)
|
|
92
|
+
end
|
|
93
|
+
nsfile = abspath
|
|
94
|
+
elsif ftype == :directory && @loader.__collapse?(abspath)
|
|
95
|
+
to_visit << abspath
|
|
96
|
+
end
|
|
97
|
+
end
|
|
42
98
|
end
|
|
99
|
+
|
|
100
|
+
nsfile
|
|
43
101
|
end
|
|
44
102
|
|
|
45
103
|
# Encodes the documented conventions.
|
|
@@ -55,7 +113,7 @@ class Zeitwerk::Loader::FileSystem # :nodoc:
|
|
|
55
113
|
|
|
56
114
|
#: (String) -> bool
|
|
57
115
|
def rb_extension?(path)
|
|
58
|
-
path.end_with?(
|
|
116
|
+
path.end_with?('.rb')
|
|
59
117
|
end
|
|
60
118
|
|
|
61
119
|
#: (String) -> bool
|
|
@@ -65,7 +123,7 @@ class Zeitwerk::Loader::FileSystem # :nodoc:
|
|
|
65
123
|
|
|
66
124
|
#: (String) -> bool
|
|
67
125
|
def hidden?(basename)
|
|
68
|
-
basename.start_with?(
|
|
126
|
+
basename.start_with?('.')
|
|
69
127
|
end
|
|
70
128
|
|
|
71
129
|
private
|
|
@@ -80,7 +138,7 @@ class Zeitwerk::Loader::FileSystem # :nodoc:
|
|
|
80
138
|
|
|
81
139
|
while (dir = to_visit.shift)
|
|
82
140
|
relevant_dir_entries(dir) do |_, abspath, ftype|
|
|
83
|
-
return true if
|
|
141
|
+
return true if ftype == :file
|
|
84
142
|
to_visit << abspath
|
|
85
143
|
end
|
|
86
144
|
end
|
|
@@ -96,18 +154,9 @@ class Zeitwerk::Loader::FileSystem # :nodoc:
|
|
|
96
154
|
each_ruby_file_or_directory(dir) do |basename, abspath, ftype|
|
|
97
155
|
next if @loader.__ignored_path?(abspath)
|
|
98
156
|
|
|
99
|
-
if
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
rescue Errno::ENOENT
|
|
103
|
-
warn "ignoring broken symlink #{abspath}"
|
|
104
|
-
next
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
if :file == ftype
|
|
109
|
-
yield basename, abspath, ftype if rb_extension?(basename)
|
|
110
|
-
elsif :directory == ftype
|
|
157
|
+
if ftype == :file
|
|
158
|
+
yield basename, abspath, ftype
|
|
159
|
+
else
|
|
111
160
|
# Conceptually, root directories represent a separate project tree.
|
|
112
161
|
yield basename, abspath, ftype unless @loader.__root_dir?(abspath)
|
|
113
162
|
end
|
|
@@ -135,10 +184,10 @@ class Zeitwerk::Loader::FileSystem # :nodoc:
|
|
|
135
184
|
if rb_extension?(basename)
|
|
136
185
|
abspath = File.join(dir, basename).freeze
|
|
137
186
|
yield basename, abspath, :file # By convention.
|
|
138
|
-
elsif
|
|
187
|
+
elsif ftype == :directory
|
|
139
188
|
abspath = File.join(dir, basename).freeze
|
|
140
189
|
yield basename, abspath, :directory
|
|
141
|
-
elsif
|
|
190
|
+
elsif ftype == :link
|
|
142
191
|
abspath = File.join(dir, basename).freeze
|
|
143
192
|
yield basename, abspath, :directory if dir?(abspath)
|
|
144
193
|
end
|
|
@@ -155,9 +204,7 @@ class Zeitwerk::Loader::FileSystem # :nodoc:
|
|
|
155
204
|
yield basename, abspath, :file # By convention.
|
|
156
205
|
else
|
|
157
206
|
abspath = File.join(dir, basename).freeze
|
|
158
|
-
if dir?(abspath)
|
|
159
|
-
yield basename, abspath, :directory
|
|
160
|
-
end
|
|
207
|
+
yield basename, abspath, :directory if dir?(abspath)
|
|
161
208
|
end
|
|
162
209
|
end
|
|
163
210
|
end
|
|
@@ -12,7 +12,7 @@ module Zeitwerk::Loader::Helpers
|
|
|
12
12
|
raise TypeError, "#{inflector.class}#camelize must return a String, received #{cname.inspect}"
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
if cname.include?(
|
|
15
|
+
if cname.include?('::')
|
|
16
16
|
raise Zeitwerk::NameError.new(<<~MESSAGE, cname)
|
|
17
17
|
wrong constant name #{cname} inferred by #{inflector.class} from
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ module Zeitwerk::Loader::Helpers
|
|
|
25
25
|
begin
|
|
26
26
|
CNAME_VALIDATOR.const_defined?(cname, false)
|
|
27
27
|
rescue ::NameError => error
|
|
28
|
-
path_type = @fs.rb_extension?(abspath) ?
|
|
28
|
+
path_type = @fs.rb_extension?(abspath) ? 'file' : 'directory'
|
|
29
29
|
|
|
30
30
|
raise Zeitwerk::NameError.new(<<~MESSAGE, error.name)
|
|
31
31
|
#{error.message} inferred by #{inflector.class} from #{path_type}
|