# 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) next if !has_at_least_one_ruby_file?(abspath) else next unless ruby?(abspath) end # We freeze abspath because that saves allocations when passed later to # File methods. See #125. yield basename, abspath.freeze end end # @sig (String) -> bool private def has_at_least_one_ruby_file?(dir) to_visit = [dir] while dir = to_visit.shift ls(dir) do |_basename, abspath| if dir?(abspath) to_visit << abspath else return true 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 # --- Constants --------------------------------------------------------------------------------- # The autoload? predicate takes into account the ancestor chain of the # receiver, like const_defined? and other methods in the constants API do. # # For example, given # # class A # autoload :X, "x.rb" # end # # class B < A # end # # B.autoload?(:X) returns "x.rb". # # We need a way to strictly check in parent ignoring ancestors. # # @sig (Module, Symbol) -> String? if method(:autoload?).arity == 1 private def strict_autoload_path(parent, cname) parent.autoload?(cname) if cdef?(parent, cname) end else private def strict_autoload_path(parent, cname) parent.autoload?(cname, false) end end # @sig (Module, Symbol) -> String if Symbol.method_defined?(:name) # Symbol#name was introduced in Ruby 3.0. It returns always the same # frozen object, so we may save a few string allocations. private def cpath(parent, cname) Object == parent ? cname.name : "#{real_mod_name(parent)}::#{cname.name}" end else private def cpath(parent, cname) Object == parent ? cname.to_s : "#{real_mod_name(parent)}::#{cname}" end end # @sig (Module, Symbol) -> bool private def cdef?(parent, cname) parent.const_defined?(cname, false) end # @raise [NameError] # @sig (Module, Symbol) -> Object private def cget(parent, cname) parent.const_get(cname, false) end end