module Zeitwerk::Loader::Config

def actual_roots

@sig () -> Array[String]
def actual_roots
reject do |root_dir, _root_namespace|
?(root_dir) || ignored_path?(root_dir)

def collapse(*glob_patterns)

@sig (*(String | Pathname | Array[String | Pathname])) -> void

Configure directories or glob patterns to be collapsed.
def collapse(*glob_patterns)
  glob_patterns = expand_paths(glob_patterns)
  mutex.synchronize do
    collapse_glob_patterns.merge(glob_patterns)
    collapse_dirs.merge(expand_glob_patterns(glob_patterns))
  end
end

def collapse?(dir)

@sig (String) -> bool
def collapse?(dir)
se_dirs.member?(dir)

def dirs(namespaces: false, ignored: false)

@sig () -> Array[String] | Hash[String, Module]

These are read-only collections, please add to them with `push_dir`.

If `ignored` is falsey (default), ignored root directories are filtered out.

values are their corresponding namespaces, class or module objects.
instead. Keys are the absolute paths of the root directories as strings,
paths of the root directories as strings. If truthy, returns a hash table
If `namespaces` is falsey (default), returns an array with the absolute
def dirs(namespaces: false, ignored: false)
  if namespaces
    if ignored || ignored_paths.empty?
      roots.clone
    else
      roots.reject { |root_dir, _namespace| ignored_path?(root_dir) }
    end
  else
    if ignored || ignored_paths.empty?
      roots.keys
    else
      roots.keys.reject { |root_dir| ignored_path?(root_dir) }
    end
  end.freeze
end

def do_not_eager_load(*paths)

@sig (*(String | Pathname | Array[String | Pathname])) -> void

in those files are still autoloadable.
Let eager load ignore the given files or directories. The constants defined
def do_not_eager_load(*paths)
  mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
end

def enable_reloading

Raises:
  • (Zeitwerk::Error) -
def enable_reloading
  mutex.synchronize do
    break if @reloading_enabled
    if @setup
      raise Zeitwerk::Error, "cannot enable reloading after setup"
    else
      @reloading_enabled = true
    end
  end
end

def excluded_from_eager_load?(abspath)

@sig (String) -> bool
def excluded_from_eager_load?(abspath)
mize this common use case.
 false if eager_load_exclusions.empty?
p(abspath) do |path|
rn true  if eager_load_exclusions.member?(path)
rn false if roots.key?(path)

def expand_glob_patterns(glob_patterns)

@sig (Array[String]) -> Array[String]
def expand_glob_patterns(glob_patterns)
 that Dir.glob works with regular file names just fine. That is,
 patterns technically need no wildcards.
atterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }

def expand_paths(paths)

@sig (String | Pathname | Array[String | Pathname]) -> Array[String]
def expand_paths(paths)
flatten.map! { |path| File.expand_path(path) }

def ignore(*glob_patterns)

@sig (*(String | Pathname | Array[String | Pathname])) -> void

Configure files, directories, or glob patterns to be totally ignored.
def ignore(*glob_patterns)
  glob_patterns = expand_paths(glob_patterns)
  mutex.synchronize do
    ignored_glob_patterns.merge(glob_patterns)
    ignored_paths.merge(expand_glob_patterns(glob_patterns))
  end
end

def ignored_path?(abspath)

@sig (String) -> bool
def ignored_path?(abspath)
d_paths.member?(abspath)

def ignores?(abspath)

@sig (String) -> bool

descendant of an ignored directory.
Returns true if the argument has been configured to be ignored, or is a
def ignores?(abspath)
n use case.
false if ignored_paths.empty?
(abspath) do |path|
n true  if ignored_path?(path)
n false if roots.key?(path)

def initialize

def initialize
  @inflector              = Zeitwerk::Inflector.new
  @logger                 = self.class.default_logger
  @tag                    = SecureRandom.hex(3)
  @initialized_at         = Time.now
  @roots                  = {}
  @ignored_glob_patterns  = Set.new
  @ignored_paths          = Set.new
  @collapse_glob_patterns = Set.new
  @collapse_dirs          = Set.new
  @eager_load_exclusions  = Set.new
  @reloading_enabled      = false
  @on_setup_callbacks     = []
  @on_load_callbacks      = {}
  @on_unload_callbacks    = {}
end

def log!

@sig () -> void

Logs to `$stdout`, handy shortcut for debugging.
def log!
  @logger = ->(msg) { puts msg }
end

def on_load(cpath = :ANY, &block)

Raises:
  • (TypeError) -
def on_load(cpath = :ANY, &block)
  raise TypeError, "on_load only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
  mutex.synchronize do
    (on_load_callbacks[cpath] ||= []) << block
  end
end

def on_setup(&block)

@sig () { () -> void } -> void

If setup was already done, the block runs immediately.
Configure a block to be called after setup and on each reload.
def on_setup(&block)
  mutex.synchronize do
    on_setup_callbacks << block
    block.call if @setup
  end
end

def on_unload(cpath = :ANY, &block)

Raises:
  • (TypeError) -
def on_unload(cpath = :ANY, &block)
  raise TypeError, "on_unload only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
  mutex.synchronize do
    (on_unload_callbacks[cpath] ||= []) << block
  end
end

def push_dir(path, namespace: Object)

Raises:
  • (Zeitwerk::Error) -
def push_dir(path, namespace: Object)
  unless namespace.is_a?(Module) # Note that Class < Module.
    raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
  end
  unless real_mod_name(namespace)
    raise Zeitwerk::Error, "root namespaces cannot be anonymous"
  end
  abspath = File.expand_path(path)
  if dir?(abspath)
    raise_if_conflicting_directory(abspath)
    roots[abspath] = namespace
  else
    raise Zeitwerk::Error, "the root directory #{abspath} does not exist"
  end
end

def recompute_collapse_dirs

@sig () -> void
def recompute_collapse_dirs
se_dirs.replace(expand_glob_patterns(collapse_glob_patterns))

def recompute_ignored_paths

@sig () -> void
def recompute_ignored_paths
d_paths.replace(expand_glob_patterns(ignored_glob_patterns))

def reloading_enabled?

@sig () -> bool
def reloading_enabled?
  @reloading_enabled
end

def root_dir?(dir)

@sig (String) -> bool
def root_dir?(dir)
key?(dir)

def tag

@sig () -> String

writer below.
Implemented as a method instead of via attr_reader for symmetry with the

Returns the loader's tag.
def tag
  @tag
end

def tag=(tag)

@sig (#to_s) -> void

Sets a tag for the loader, useful for logging.
def tag=(tag)
  @tag = tag.to_s
end