zeitwerk 2.5.4 → 2.6.18
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 +473 -50
- data/lib/zeitwerk/cref.rb +99 -0
- data/lib/zeitwerk/error.rb +9 -0
- data/lib/zeitwerk/explicit_namespace.rb +14 -10
- data/lib/zeitwerk/gem_inflector.rb +2 -2
- data/lib/zeitwerk/gem_loader.rb +68 -0
- data/lib/zeitwerk/internal.rb +12 -0
- data/lib/zeitwerk/kernel.rb +6 -7
- data/lib/zeitwerk/loader/callbacks.rb +34 -27
- data/lib/zeitwerk/loader/config.rb +95 -52
- data/lib/zeitwerk/loader/eager_load.rb +232 -0
- data/lib/zeitwerk/loader/helpers.rb +106 -55
- data/lib/zeitwerk/loader.rb +287 -197
- data/lib/zeitwerk/null_inflector.rb +5 -0
- data/lib/zeitwerk/registry.rb +14 -18
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +4 -0
- metadata +8 -3
@@ -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,84 +12,137 @@ module Zeitwerk::Loader::Helpers
|
|
14
12
|
# --- Files and directories ---------------------------------------------------------------------
|
15
13
|
|
16
14
|
# @sig (String) { (String, String) -> void } -> void
|
17
|
-
def ls(dir)
|
18
|
-
Dir.
|
15
|
+
private def ls(dir)
|
16
|
+
children = Dir.children(dir)
|
17
|
+
|
18
|
+
# The order in which a directory is listed depends on the file system.
|
19
|
+
#
|
20
|
+
# Since client code may run in different platforms, it seems convenient to
|
21
|
+
# order directory entries. This provides consistent eager loading across
|
22
|
+
# platforms, for example.
|
23
|
+
children.sort!
|
24
|
+
|
25
|
+
children.each do |basename|
|
19
26
|
next if hidden?(basename)
|
20
27
|
|
21
28
|
abspath = File.join(dir, basename)
|
22
|
-
next if
|
29
|
+
next if ignored_path?(abspath)
|
30
|
+
|
31
|
+
if dir?(abspath)
|
32
|
+
next if roots.key?(abspath)
|
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
|
40
|
+
else
|
41
|
+
next unless ruby?(abspath)
|
42
|
+
ftype = :file
|
43
|
+
end
|
23
44
|
|
24
45
|
# We freeze abspath because that saves allocations when passed later to
|
25
46
|
# File methods. See #125.
|
26
|
-
yield basename, abspath.freeze
|
47
|
+
yield basename, abspath.freeze, ftype
|
27
48
|
end
|
28
49
|
end
|
29
50
|
|
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
|
+
#
|
30
55
|
# @sig (String) -> bool
|
31
|
-
def
|
56
|
+
private def has_at_least_one_ruby_file?(dir)
|
57
|
+
to_visit = [dir]
|
58
|
+
|
59
|
+
while (dir = to_visit.shift)
|
60
|
+
children = Dir.children(dir)
|
61
|
+
|
62
|
+
children.each do |basename|
|
63
|
+
next if hidden?(basename)
|
64
|
+
|
65
|
+
abspath = File.join(dir, basename)
|
66
|
+
next if ignored_path?(abspath)
|
67
|
+
|
68
|
+
if dir?(abspath)
|
69
|
+
to_visit << abspath unless roots.key?(abspath)
|
70
|
+
else
|
71
|
+
return true if ruby?(abspath)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
# @sig (String) -> bool
|
80
|
+
private def ruby?(path)
|
32
81
|
path.end_with?(".rb")
|
33
82
|
end
|
34
83
|
|
35
84
|
# @sig (String) -> bool
|
36
|
-
def dir?(path)
|
85
|
+
private def dir?(path)
|
37
86
|
File.directory?(path)
|
38
87
|
end
|
39
88
|
|
40
|
-
# @sig String -> bool
|
41
|
-
def hidden?(basename)
|
89
|
+
# @sig (String) -> bool
|
90
|
+
private def hidden?(basename)
|
42
91
|
basename.start_with?(".")
|
43
92
|
end
|
44
93
|
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
# class A
|
53
|
-
# autoload :X, "x.rb"
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
# class B < A
|
57
|
-
# end
|
58
|
-
#
|
59
|
-
# B.autoload?(:X) returns "x.rb".
|
60
|
-
#
|
61
|
-
# We need a way to strictly check in parent ignoring ancestors.
|
62
|
-
#
|
63
|
-
# @sig (Module, Symbol) -> String?
|
64
|
-
if method(:autoload?).arity == 1
|
65
|
-
def strict_autoload_path(parent, cname)
|
66
|
-
parent.autoload?(cname) if cdef?(parent, cname)
|
67
|
-
end
|
68
|
-
else
|
69
|
-
def strict_autoload_path(parent, cname)
|
70
|
-
parent.autoload?(cname, false)
|
94
|
+
# @sig (String) { (String) -> void } -> void
|
95
|
+
private def walk_up(abspath)
|
96
|
+
loop do
|
97
|
+
yield abspath
|
98
|
+
abspath, basename = File.split(abspath)
|
99
|
+
break if basename == "/"
|
71
100
|
end
|
72
101
|
end
|
73
102
|
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
103
|
+
# --- Inflection --------------------------------------------------------------------------------
|
104
|
+
|
105
|
+
CNAME_VALIDATOR = Module.new
|
106
|
+
private_constant :CNAME_VALIDATOR
|
107
|
+
|
108
|
+
# @raise [Zeitwerk::NameError]
|
109
|
+
# @sig (String, String) -> Symbol
|
110
|
+
private def cname_for(basename, abspath)
|
111
|
+
cname = inflector.camelize(basename, abspath)
|
112
|
+
|
113
|
+
unless cname.is_a?(String)
|
114
|
+
raise TypeError, "#{inflector.class}#camelize must return a String, received #{cname.inspect}"
|
80
115
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
116
|
+
|
117
|
+
if cname.include?("::")
|
118
|
+
raise Zeitwerk::NameError.new(<<~MESSAGE, cname)
|
119
|
+
wrong constant name #{cname} inferred by #{inflector.class} from
|
120
|
+
|
121
|
+
#{abspath}
|
122
|
+
|
123
|
+
#{inflector.class}#camelize should return a simple constant name without "::"
|
124
|
+
MESSAGE
|
84
125
|
end
|
85
|
-
end
|
86
126
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
127
|
+
begin
|
128
|
+
CNAME_VALIDATOR.const_defined?(cname, false)
|
129
|
+
rescue ::NameError => error
|
130
|
+
path_type = ruby?(abspath) ? "file" : "directory"
|
131
|
+
|
132
|
+
raise Zeitwerk::NameError.new(<<~MESSAGE, error.name)
|
133
|
+
#{error.message} inferred by #{inflector.class} from #{path_type}
|
134
|
+
|
135
|
+
#{abspath}
|
136
|
+
|
137
|
+
Possible ways to address this:
|
138
|
+
|
139
|
+
* Tell Zeitwerk to ignore this particular #{path_type}.
|
140
|
+
* Tell Zeitwerk to ignore one of its parent directories.
|
141
|
+
* Rename the #{path_type} to comply with the naming conventions.
|
142
|
+
* Modify the inflector to handle this case.
|
143
|
+
MESSAGE
|
144
|
+
end
|
91
145
|
|
92
|
-
|
93
|
-
# @sig (Module, Symbol) -> Object
|
94
|
-
def cget(parent, cname)
|
95
|
-
parent.const_get(cname, false)
|
146
|
+
cname.to_sym
|
96
147
|
end
|
97
148
|
end
|