module Zeitwerk

Constants

VERSION

Public Class Methods

with_loader() { |loader| ... } click to toggle source

This is a dangerous method.

@experimental @sig () -> void

# File lib/zeitwerk.rb, line 20
def self.with_loader
  loader = Zeitwerk::Loader.new
  yield loader
ensure
  loader.unregister
end

Public Instance Methods

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

@sig (Module, Symbol, String) -> void

# File lib/zeitwerk/loader.rb, line 385
def autoload_file(parent, cname, file)
  if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
    # First autoload for a Ruby file wins, just ignore subsequent ones.
    if ruby?(autoload_path)
      shadowed_files << file
      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)
    shadowed_files << file
    log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
  else
    set_autoload(parent, cname, file)
  end
end
autoload_path_set_by_me_for?(parent, cname) click to toggle source

@sig (Module, Symbol) -> String?

# File lib/zeitwerk/loader.rb, line 443
def autoload_path_set_by_me_for?(parent, cname)
  if autoload_path = strict_autoload_path(parent, cname)
    autoload_path if autoloads.key?(autoload_path)
  else
    Registry.inception?(cpath(parent, cname))
  end
end
autoload_subdir(parent, cname, subdir) click to toggle source

@sig (Module, Symbol, String) -> void

# File lib/zeitwerk/loader.rb, line 364
def autoload_subdir(parent, cname, subdir)
  if autoload_path = autoload_path_set_by_me_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.
    namespace_dirs[cpath] << subdir
  elsif !cdef?(parent, cname)
    # First time we find this namespace, set an autoload for it.
    namespace_dirs[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.
    log("the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
    set_autoloads_in_dir(subdir, cget(parent, cname))
  end
end
promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:) click to toggle source

`dir` is the directory that would have autovivified a namespace. `file` is the file where we've found the namespace is explicitly defined.

@sig (dir: String, file: String, parent: Module, cname: Symbol) -> void

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

  log("earlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger

  set_autoload(parent, cname, file)
  register_explicit_namespace(cpath(parent, cname))
end
raise_if_conflicting_directory(dir) click to toggle source

@sig (String) -> void

# File lib/zeitwerk/loader.rb, line 457
def raise_if_conflicting_directory(dir)
  MUTEX.synchronize do
    dir_slash = dir + "/"

    Registry.loaders.each do |loader|
      next if loader == self
      next if loader.__ignores?(dir)

      loader.__roots.each_key do |root_dir|
        next if ignores?(root_dir)

        root_dir_slash = root_dir + "/"
        if dir_slash.start_with?(root_dir_slash) || root_dir_slash.start_with?(dir_slash)
          require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
          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
end
register_explicit_namespace(cpath) click to toggle source

@sig (String) -> void

# File lib/zeitwerk/loader.rb, line 452
def register_explicit_namespace(cpath)
  ExplicitNamespace.__register(cpath, self)
end
run_on_unload_callbacks(cpath, value, abspath) click to toggle source

@sig (String, Object, String) -> void

# File lib/zeitwerk/loader.rb, line 482
def run_on_unload_callbacks(cpath, value, abspath)
  # Order matters. If present, run the most specific one.
  on_unload_callbacks[cpath]&.each { |c| c.call(value, abspath) }
  on_unload_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
end
set_autoload(parent, cname, abspath) click to toggle source

@sig (Module, Symbol, String) -> void

# File lib/zeitwerk/loader.rb, line 422
def set_autoload(parent, cname, abspath)
  parent.autoload(cname, abspath)

  if logger
    if ruby?(abspath)
      log("autoload set for #{cpath(parent, cname)}, to be loaded from #{abspath}")
    else
      log("autoload set for #{cpath(parent, cname)}, to be autovivified from #{abspath}")
    end
  end

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

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

@sig (Module, Symbol) -> void

# File lib/zeitwerk/loader.rb, line 489
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

@sig (Module, Symbol) -> void

# File lib/zeitwerk/loader.rb, line 495
def unload_cref(parent, cname)
  # Let's optimistically remove_const. The way we use it, this is going to
  # succeed always if all is good.
  parent.__send__(:remove_const, cname)
rescue ::NameError
  # There are a few edge scenarios in which this may happen. If the constant
  # is gone, that is OK, anyway.
else
  log("#{cpath(parent, cname)} unloaded") if logger
end