147 lines
4.0 KiB
Ruby
147 lines
4.0 KiB
Ruby
# 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
|