This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Zeitwerk::Loader::Helpers
|
||||
# --- Logging -----------------------------------------------------------------------------------
|
||||
|
||||
# @sig (String) -> void
|
||||
private def log(message)
|
||||
method_name = logger.respond_to?(:debug) ? :debug : :call
|
||||
logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
|
||||
end
|
||||
|
||||
# --- Files and directories ---------------------------------------------------------------------
|
||||
|
||||
# @sig (String) { (String, String) -> void } -> void
|
||||
private def ls(dir)
|
||||
children = Dir.children(dir)
|
||||
|
||||
# The order in which a directory is listed depends on the file system.
|
||||
#
|
||||
# Since client code may run in different platforms, it seems convenient to
|
||||
# order directory entries. This provides consistent eager loading across
|
||||
# platforms, for example.
|
||||
children.sort!
|
||||
|
||||
children.each do |basename|
|
||||
next if hidden?(basename)
|
||||
|
||||
abspath = File.join(dir, basename)
|
||||
next if ignored_path?(abspath)
|
||||
|
||||
if dir?(abspath)
|
||||
next if roots.key?(abspath)
|
||||
|
||||
if !has_at_least_one_ruby_file?(abspath)
|
||||
log("directory #{abspath} is ignored because it has no Ruby files") if logger
|
||||
next
|
||||
end
|
||||
|
||||
ftype = :directory
|
||||
else
|
||||
next unless ruby?(abspath)
|
||||
ftype = :file
|
||||
end
|
||||
|
||||
# We freeze abspath because that saves allocations when passed later to
|
||||
# File methods. See #125.
|
||||
yield basename, abspath.freeze, ftype
|
||||
end
|
||||
end
|
||||
|
||||
# Looks for a Ruby file using breadth-first search. This type of search is
|
||||
# important to list as less directories as possible and return fast in the
|
||||
# common case in which there are Ruby files.
|
||||
#
|
||||
# @sig (String) -> bool
|
||||
private def has_at_least_one_ruby_file?(dir)
|
||||
to_visit = [dir]
|
||||
|
||||
while (dir = to_visit.shift)
|
||||
Dir.each_child(dir) do |basename|
|
||||
next if hidden?(basename)
|
||||
|
||||
abspath = File.join(dir, basename)
|
||||
next if ignored_path?(abspath)
|
||||
|
||||
if dir?(abspath)
|
||||
to_visit << abspath unless roots.key?(abspath)
|
||||
else
|
||||
return true if ruby?(abspath)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# @sig (String) -> bool
|
||||
private def ruby?(path)
|
||||
path.end_with?(".rb")
|
||||
end
|
||||
|
||||
# @sig (String) -> bool
|
||||
private def dir?(path)
|
||||
File.directory?(path)
|
||||
end
|
||||
|
||||
# @sig (String) -> bool
|
||||
private def hidden?(basename)
|
||||
basename.start_with?(".")
|
||||
end
|
||||
|
||||
# @sig (String) { (String) -> void } -> void
|
||||
private def walk_up(abspath)
|
||||
loop do
|
||||
yield abspath
|
||||
abspath, basename = File.split(abspath)
|
||||
break if basename == "/"
|
||||
end
|
||||
end
|
||||
|
||||
# --- Inflection --------------------------------------------------------------------------------
|
||||
|
||||
CNAME_VALIDATOR = Module.new
|
||||
private_constant :CNAME_VALIDATOR
|
||||
|
||||
# @raise [Zeitwerk::NameError]
|
||||
# @sig (String, String) -> Symbol
|
||||
private def cname_for(basename, abspath)
|
||||
cname = inflector.camelize(basename, abspath)
|
||||
|
||||
unless cname.is_a?(String)
|
||||
raise TypeError, "#{inflector.class}#camelize must return a String, received #{cname.inspect}"
|
||||
end
|
||||
|
||||
if cname.include?("::")
|
||||
raise Zeitwerk::NameError.new(<<~MESSAGE, cname)
|
||||
wrong constant name #{cname} inferred by #{inflector.class} from
|
||||
|
||||
#{abspath}
|
||||
|
||||
#{inflector.class}#camelize should return a simple constant name without "::"
|
||||
MESSAGE
|
||||
end
|
||||
|
||||
begin
|
||||
CNAME_VALIDATOR.const_defined?(cname, false)
|
||||
rescue ::NameError => error
|
||||
path_type = ruby?(abspath) ? "file" : "directory"
|
||||
|
||||
raise Zeitwerk::NameError.new(<<~MESSAGE, error.name)
|
||||
#{error.message} inferred by #{inflector.class} from #{path_type}
|
||||
|
||||
#{abspath}
|
||||
|
||||
Possible ways to address this:
|
||||
|
||||
* Tell Zeitwerk to ignore this particular #{path_type}.
|
||||
* Tell Zeitwerk to ignore one of its parent directories.
|
||||
* Rename the #{path_type} to comply with the naming conventions.
|
||||
* Modify the inflector to handle this case.
|
||||
MESSAGE
|
||||
end
|
||||
|
||||
cname.to_sym
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user