module Zeitwerk

Constants

VERSION

Public Instance Methods

autoload_file(parent, cname, file) click to toggle source

@param parent [Module] @param cname [Symbol] @param file [String] @return [void]

# File lib/zeitwerk/loader.rb, line 578
def autoload_file(parent, cname, file)
  if autoload_path = autoload_for?(parent, cname)
    # First autoload for a Ruby file wins, just ignore subsequent ones.
    if ruby?(autoload_path)
      log("file #{file} is ignored because #{autoload_path} has precedence") if logger
    else
      promote_namespace_from_implicit_to_explicit(
        dir:    autoload_path,
        file:   file,
        parent: parent,
        cname:  cname
      )
    end
  elsif cdef?(parent, cname)
    log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
  else
    set_autoload(parent, cname, file)
  end
end
autoload_for?(parent, cname) click to toggle source

@param parent [Module] @param cname [Symbol] @return [String, nil]

# File lib/zeitwerk/loader.rb, line 641
def autoload_for?(parent, cname)
  strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
end
autoload_subdir(parent, cname, subdir) click to toggle source

@param parent [Module] @param cname [Symbol] @param subdir [String] @return [void]

# File lib/zeitwerk/loader.rb, line 555
def autoload_subdir(parent, cname, subdir)
  if autoload_path = autoload_for?(parent, cname)
    cpath = cpath(parent, cname)
    register_explicit_namespace(cpath) if ruby?(autoload_path)
    # We do not need to issue another autoload, the existing one is enough
    # no matter if it is for a file or a directory. Just remember the
    # subdirectory has to be visited if the namespace is used.
    (lazy_subdirs[cpath] ||= []) << subdir
  elsif !cdef?(parent, cname)
    # First time we find this namespace, set an autoload for it.
    (lazy_subdirs[cpath(parent, cname)] ||= []) << subdir
    set_autoload(parent, cname, subdir)
  else
    # For whatever reason the constant that corresponds to this namespace has
    # already been defined, we have to recurse.
    set_autoloads_in_dir(subdir, parent.const_get(cname))
  end
end
cdef?(parent, cname) click to toggle source
# File lib/zeitwerk/loader.rb, line 770
def cdef?(parent, cname)
  parent.const_defined?(cname, false)
end
cpath(parent, cname) click to toggle source

@param parent [Module] @param cname [Symbol] @return [String]

# File lib/zeitwerk/loader.rb, line 712
def cpath(parent, cname)
  parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
end
dir?(path) click to toggle source

@param path [String] @return [Boolean]

# File lib/zeitwerk/loader.rb, line 735
def dir?(path)
  File.directory?(path)
end
do_preload() click to toggle source

This method is called this way because I prefer `preload` to be the method name to configure preloads in the public interface.

@return [void]

# File lib/zeitwerk/loader.rb, line 678
def do_preload
  preloads.each do |abspath|
    do_preload_abspath(abspath)
  end
end
do_preload_abspath(abspath) click to toggle source

@param abspath [String] @return [void]

# File lib/zeitwerk/loader.rb, line 686
def do_preload_abspath(abspath)
  if ruby?(abspath)
    do_preload_file(abspath)
  elsif dir?(abspath)
    do_preload_dir(abspath)
  end
end
do_preload_dir(dir) click to toggle source

@param dir [String] @return [void]

# File lib/zeitwerk/loader.rb, line 696
def do_preload_dir(dir)
  ls(dir) do |_basename, abspath|
    do_preload_abspath(abspath)
  end
end
do_preload_file(file) click to toggle source

@param file [String] @return [Boolean]

# File lib/zeitwerk/loader.rb, line 704
def do_preload_file(file)
  log("preloading #{file}") if logger
  require file
end
expand_glob_patterns(glob_patterns) click to toggle source

@param glob_patterns [<String>] @return [<String>]

# File lib/zeitwerk/loader.rb, line 747
def expand_glob_patterns(glob_patterns)
  # Note that Dir.glob works with regular file names just fine. That is,
  # glob patterns technically need no wildcards.
  glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
end
expand_paths(paths) click to toggle source

@param paths [<String, Pathname, <String, Pathname>>] @return [<String>]

# File lib/zeitwerk/loader.rb, line 741
def expand_paths(paths)
  paths.flatten.map! { |path| File.expand_path(path) }
end
log(message) click to toggle source

@param message [String] @return [void]

# File lib/zeitwerk/loader.rb, line 765
def log(message)
  method_name = logger.respond_to?(:debug) ? :debug : :call
  logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
end
ls(dir) { |basename, abspath| ... } click to toggle source

@param dir [String] @yieldparam path [String, String] @return [void]

# File lib/zeitwerk/loader.rb, line 719
def ls(dir)
  Dir.foreach(dir) do |basename|
    next if basename.start_with?(".")
    abspath = File.join(dir, basename)
    yield basename, abspath unless ignored_paths.member?(abspath)
  end
end
promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:) click to toggle source

@param dir [String] directory that would have autovivified a module @param file [String] the file where the namespace is explicitly defined @param parent [Module] @param cname [Symbol] @return [void]

# File lib/zeitwerk/loader.rb, line 603
def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
  autoloads.delete(dir)
  Registry.unregister_autoload(dir)

  set_autoload(parent, cname, file)
  register_explicit_namespace(cpath(parent, cname))
end
raise_if_conflicting_directory(dir) click to toggle source
# File lib/zeitwerk/loader.rb, line 778
def raise_if_conflicting_directory(dir)
  self.class.mutex.synchronize do
    Registry.loaders.each do |loader|
      if loader != self && loader.manages?(dir)
        require "pp"
        raise Error,
          "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
          " which is already managed by\n\n#{loader.pretty_inspect}\n"
        EOS
      end
    end
  end
end
recompute_collapse_dirs() click to toggle source

@return [void]

# File lib/zeitwerk/loader.rb, line 759
def recompute_collapse_dirs
  collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
end
recompute_ignored_paths() click to toggle source

@return [void]

# File lib/zeitwerk/loader.rb, line 754
def recompute_ignored_paths
  ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
end
register_explicit_namespace(cpath) click to toggle source
# File lib/zeitwerk/loader.rb, line 774
def register_explicit_namespace(cpath)
  ExplicitNamespace.register(cpath, self)
end
ruby?(path) click to toggle source

@param path [String] @return [Boolean]

# File lib/zeitwerk/loader.rb, line 729
def ruby?(path)
  path.end_with?(".rb")
end
set_autoload(parent, cname, abspath) click to toggle source

@param parent [Module] @param cname [Symbol] @param abspath [String] @return [void]

# File lib/zeitwerk/loader.rb, line 615
def set_autoload(parent, cname, abspath)
  # $LOADED_FEATURES stores real paths since Ruby 2.4.4. We set and save the
  # real path to be able to delete it from $LOADED_FEATURES on unload, and to
  # be able to do a lookup later in Kernel#require for manual require calls.
  realpath = File.realpath(abspath)
  parent.autoload(cname, realpath)
  if logger
    if ruby?(realpath)
      log("autoload set for #{cpath(parent, cname)}, to be loaded from #{realpath}")
    else
      log("autoload set for #{cpath(parent, cname)}, to be autovivified from #{realpath}")
    end
  end

  autoloads[realpath] = [parent, cname]
  Registry.register_autoload(self, realpath)

  # See why in the documentation of Zeitwerk::Registry.inceptions.
  unless parent.autoload?(cname)
    Registry.register_inception(cpath(parent, cname), realpath, self)
  end
end
set_autoloads_in_dir(dir, parent) click to toggle source

@param dir [String] @param parent [Module] @return [void]

# File lib/zeitwerk/loader.rb, line 509
    def set_autoloads_in_dir(dir, parent)
      ls(dir) do |basename, abspath|
        begin
          if ruby?(basename)
            basename[-3..-1] = ''
            cname = inflector.camelize(basename, abspath).to_sym
            autoload_file(parent, cname, abspath)
          elsif dir?(abspath)
            # In a Rails application, `app/models/concerns` is a subdirectory of
            # `app/models`, but both of them are root directories.
            #
            # To resolve the ambiguity file name -> constant path this introduces,
            # the `app/models/concerns` directory is totally ignored as a namespace,
            # it counts only as root. The guard checks that.
            unless root_dirs.key?(abspath)
              cname = inflector.camelize(basename, abspath).to_sym
              if collapse_dirs.member?(abspath)
                set_autoloads_in_dir(abspath, parent)
              else
                autoload_subdir(parent, cname, abspath)
              end
            end
          end
        rescue ::NameError => error
          path_type = ruby?(abspath) ? "file" : "directory"

          raise 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
      end
    end
strict_autoload_path(parent, cname) click to toggle source
# File lib/zeitwerk/loader.rb, line 665
def strict_autoload_path(parent, cname)
  parent.autoload?(cname) if cdef?(parent, cname)
end
unload_autoload(parent, cname) click to toggle source

@param parent [Module] @param cname [Symbol] @return [void]

# File lib/zeitwerk/loader.rb, line 795
def unload_autoload(parent, cname)
  parent.send(:remove_const, cname)
  log("autoload for #{cpath(parent, cname)} removed") if logger
end
unload_cref(parent, cname) click to toggle source

@param parent [Module] @param cname [Symbol] @return [void]

# File lib/zeitwerk/loader.rb, line 803
def unload_cref(parent, cname)
  parent.send(:remove_const, cname)
  log("#{cpath(parent, cname)} unloaded") if logger
end