module Zeitwerk::Loader::Config

Attributes

collapse_dirs[R]
The actual collection of absolute directory names at the time the collapse
glob patterns were expanded. Computed on setup and recomputed on reload.

: Set

collapse_glob_patterns[R]
Absolute paths of directories or glob patterns to be collapsed.

: Set

collapse_parents[R]
Absolute paths of directories that are parents of collapsed directories.
This is a cache to optimize some tree walks. Computed on setup and
recomputed on reload.

: Set

eager_load_exclusions[R]
Absolute paths of files or directories not to be eager loaded.

: Set

ignored_glob_patterns[R]
Absolute paths of files, directories, or glob patterns to be ignored.

: Set

ignored_paths[R]
The actual collection of absolute file and directory names at the time the
ignored glob patterns were expanded. Computed on setup, and recomputed on
reload.

: Set

inflector[RW]

: camelize(String, String) -> String

logger[RW]

: call(String) -> void | debug(String) -> void | nil

nsfile[R]
Basename of files that define namespaces. For example, if `nsfile` is
'ns.rb', then `foo/ns.rb` defines the `Foo` namespace.

: String?

on_load_callbacks[R]
User-oriented callbacks to be fired when a constant is loaded.

: Hash[String, Array[{ (top, String) -> void }]] | Hash[Symbol, Array[{ (String, top, String) -> void }]]

on_setup_callbacks[R]
User-oriented callbacks to be fired on setup and on reload.

: Array[{ () -> void }]

on_unload_callbacks[R]
User-oriented callbacks to be fired before constants are removed.

: Hash[String, Array[{ (top, String) -> void }]] | Hash[Symbol, Array[{ (String, top, String) -> void }]]

roots[R]
Absolute paths of the root directories, mapped to their respective root namespaces:

  '/Users/fxn/blog/app/channels' => Object,
  '/Users/fxn/blog/app/adapters' => ActiveJob::QueueAdapters,
  ...

Stored in a hash to preserve order, easily handle duplicates, and have a
fast lookup by directory.

This is a private collection maintained by the loader. The public
interface for it is `push_dir` and `dirs`.

: Hash[String, Module]

Public Class Methods

new() click to toggle source

: () -> void

# File lib/zeitwerk/loader/config.rb, line 100
def initialize
  @inflector              = Zeitwerk::Inflector.new
  @logger                 = self.class.default_logger
  @tag                    = SecureRandom.hex(3)
  @initialized_at         = Time.now
  @roots                  = {}
  @nsfile                 = nil
  @ignored_glob_patterns  = Set.new
  @ignored_paths          = Set.new
  @collapse_glob_patterns = Set.new
  @collapse_dirs          = Set.new
  @collapse_parents       = Set.new
  @eager_load_exclusions  = Set.new
  @reloading_enabled      = false
  @on_setup_callbacks     = []
  @on_load_callbacks      = {}
  @on_unload_callbacks    = {}
end

Public Instance Methods

collapse(*glob_patterns) click to toggle source
Configure directories or glob patterns to be collapsed.

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

# File lib/zeitwerk/loader/config.rb, line 242
def collapse(*glob_patterns)
  glob_patterns = expand_paths(glob_patterns)
  mutex.synchronize do
    collapse_glob_patterns.merge(glob_patterns)
    new_collapse_dirs = expand_glob_patterns(glob_patterns)
    collapse_dirs.merge(new_collapse_dirs)
    new_collapse_dirs.each do |dir|
      collapse_parents << File.dirname(dir)
    end
  end
end
collapse?(dir) click to toggle source
# File lib/zeitwerk/loader/config.rb, line 352
         def collapse?(dir)
  collapse_dirs.member?(dir)
end
collapse_parent?(dir) click to toggle source
# File lib/zeitwerk/loader/config.rb, line 357
         def collapse_parent?(dir)
  collapse_parents.member?(dir)
end
dirs(namespaces: false, ignored: false) click to toggle source
If `namespaces` is falsey (default), returns an array with the absolute
paths of the root directories as strings. If truthy, returns a hash table
instead. Keys are the absolute paths of the root directories as strings,
values are their corresponding namespaces, class or module objects.

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

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

: (?namespaces: boolish, ?ignored: boolish) -> Array | Hash[String, Module]

# File lib/zeitwerk/loader/config.rb, line 183
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
do_not_eager_load(*paths) click to toggle source
Let eager load ignore the given files or directories. The constants defined
in those files are still autoloadable.

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

# File lib/zeitwerk/loader/config.rb, line 224
def do_not_eager_load(*paths)
  mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
end
enable_reloading() click to toggle source
You need to call this method before setup in order to be able to reload.
There is no way to undo this, either you want to reload or you don't.

: () -> void ! Zeitwerk::Error

# File lib/zeitwerk/loader/config.rb, line 203
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
ignore(*glob_patterns) click to toggle source
Configure files, directories, or glob patterns to be totally ignored.

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

# File lib/zeitwerk/loader/config.rb, line 231
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
ignored_path?(abspath) click to toggle source
# File lib/zeitwerk/loader/config.rb, line 335
         def ignored_path?(abspath)
  ignored_paths.member?(abspath)
end
ignores?(abspath) click to toggle source
# File lib/zeitwerk/loader/config.rb, line 322
         def ignores?(abspath)
  # Common use case.
  return false if ignored_paths.empty?

  @fs.walk_up(abspath) do |path|
    return true  if ignored_path?(path)
    return false if root_dir?(path)
  end

  false
end
log!() click to toggle source
Logs to `$stdout`, handy shortcut for debugging.

: () -> void

# File lib/zeitwerk/loader/config.rb, line 314
def log!
  @logger = ->(msg) { puts msg }
end
nsfile=(nsfile) click to toggle source

: (String?) -> void ! TypeError, ArgumentError

# File lib/zeitwerk/loader/config.rb, line 162
def nsfile=(nsfile)
  unless nsfile.nil?
    raise TypeError,     'nsfiles must be strings'              unless nsfile.is_a?(String)
    raise ArgumentError, 'nsfiles must have .rb extension'      unless @fs.rb_extension?(nsfile)
    raise ArgumentError, 'nsfiles must be basenames, not paths' unless File.basename(nsfile) == nsfile
    raise ArgumentError, 'nsfiles cannot be hidden'             if @fs.hidden?(nsfile)
  end

  @nsfile = nsfile
end
on_load(cpath = :ANY, &block) click to toggle source
Configure a block to be invoked once a certain constant path is loaded.
Supports multiple callbacks, and if there are many, they are executed in
the order in which they were defined.

  loader.on_load('SomeApiClient') do |klass, _abspath|
    klass.endpoint = 'https://api.dev'
  end

Can also be configured for any constant loaded:

  loader.on_load do |cpath, value, abspath|
    # ...
  end

: (String?) { (top, String) -> void } -> void ! TypeError

# File lib/zeitwerk/loader/config.rb, line 280
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
on_setup(&block) click to toggle source
Configure a block to be called after setup and on each reload.
If setup was already done, the block runs immediately.

: () { () -> void } -> void

# File lib/zeitwerk/loader/config.rb, line 258
def on_setup(&block)
  mutex.synchronize do
    on_setup_callbacks << block
    block.call if @setup
  end
end
on_unload(cpath = :ANY, &block) click to toggle source
Configure a block to be invoked right before a certain constant is removed.
Supports multiple callbacks, and if there are many, they are executed in the
order in which they were defined.

  loader.on_unload('Country') do |klass, _abspath|
    klass.clear_cache
  end

Can also be configured for any removed constant:

  loader.on_unload do |cpath, value, abspath|
    # ...
  end

: (String?) { (top, String) -> void } -> void ! TypeError

# File lib/zeitwerk/loader/config.rb, line 303
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
push_dir(path, namespace: Object) click to toggle source
Pushes `path` to the list of root directories.

Raises `Zeitwerk::Error` if `path` does not exist, or if another loader in
the same process already manages that directory or one of its ascendants or
descendants.

: (String | Pathname, namespace: Module) -> void ! Zeitwerk::Error

# File lib/zeitwerk/loader/config.rb, line 126
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 @fs.dir?(abspath)
    raise_if_conflicting_root_dir(abspath)
    roots[abspath] = namespace
  else
    raise Zeitwerk::Error, "the root directory #{abspath} does not exist"
  end
end
reloading_enabled?() click to toggle source

: () -> bool

# File lib/zeitwerk/loader/config.rb, line 216
def reloading_enabled?
  @reloading_enabled
end
root_dir?(dir) click to toggle source
# File lib/zeitwerk/loader/config.rb, line 347
         def root_dir?(dir)
  roots.key?(dir)
end
tag() click to toggle source
Returns the loader's tag.

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

: () -> String

# File lib/zeitwerk/loader/config.rb, line 150
def tag
  @tag
end
tag=(tag) click to toggle source
Sets a tag for the loader, useful for logging.

: (to_s() -> String) -> void

# File lib/zeitwerk/loader/config.rb, line 157
def tag=(tag)
  @tag = tag.to_s
end

Private Instance Methods

actual_roots() click to toggle source

: () -> Array

# File lib/zeitwerk/loader/config.rb, line 340
        def actual_roots
  roots.reject do |root_dir, _root_namespace|
    !@fs.dir?(root_dir) || ignored_path?(root_dir)
  end
end
excluded_from_eager_load?(abspath) click to toggle source

: (String) -> bool

# File lib/zeitwerk/loader/config.rb, line 362
        def excluded_from_eager_load?(abspath)
  # Optimize this common use case.
  return false if eager_load_exclusions.empty?

  @fs.walk_up(abspath) do |path|
    return true  if eager_load_exclusions.member?(path)
    return false if root_dir?(path)
  end

  false
end
expand_glob_patterns(glob_patterns) click to toggle source

: (Array) -> Array

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

: (String | Pathname | Array[String | Pathname]) -> Array

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

: () -> void

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

: () -> void

# File lib/zeitwerk/loader/config.rb, line 397
        def recompute_collapse_parents
  collapse_parents.clear
  collapse_dirs.each do |dir|
    collapse_parents << File.dirname(dir)
  end
end
recompute_ignored_paths() click to toggle source

: () -> void

# File lib/zeitwerk/loader/config.rb, line 387
        def recompute_ignored_paths
  ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
end