module Zeitwerk
Constants
- VERSION
Public Instance Methods
@sig (Module, Symbol, String) -> void
# File lib/zeitwerk/loader.rb, line 595 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
@sig (Module, Symbol) -> String?
# File lib/zeitwerk/loader.rb, line 655 def autoload_for?(parent, cname) strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname)) end
@sig (Module, Symbol, String) -> void
# File lib/zeitwerk/loader.rb, line 575 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
@sig (Module, Symbol) -> bool
# File lib/zeitwerk/loader.rb, line 776 def cdef?(parent, cname) parent.const_defined?(cname, false) end
@sig (Module, Symbol) -> String
# File lib/zeitwerk/loader.rb, line 719 def cpath(parent, cname) parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}" end
@sig (String) -> bool
# File lib/zeitwerk/loader.rb, line 743 def dir?(path) File.directory?(path) end
This method is called this way because I prefer `preload` to be the method name to configure preloads in the public interface.
@sig () -> void
# File lib/zeitwerk/loader.rb, line 690 def do_preload preloads.each do |abspath| do_preload_abspath(abspath) end end
@sig (String) -> void
# File lib/zeitwerk/loader.rb, line 697 def do_preload_abspath(abspath) if ruby?(abspath) do_preload_file(abspath) elsif dir?(abspath) do_preload_dir(abspath) end end
@sig (String) -> void
# File lib/zeitwerk/loader.rb, line 706 def do_preload_dir(dir) ls(dir) do |_basename, abspath| do_preload_abspath(abspath) end end
@sig (String) -> bool
# File lib/zeitwerk/loader.rb, line 713 def do_preload_file(file) log("preloading #{file}") if logger require file end
@sig (String | Pathname | Array[String | Pathname]) -> Array
# File lib/zeitwerk/loader.rb, line 748 def expand_paths(paths) paths.flatten.map! { |path| File.expand_path(path) } end
@sig (String) -> void
# File lib/zeitwerk/loader.rb, line 770 def log(message) method_name = logger.respond_to?(:debug) ? :debug : :call logger.send(method_name, "Zeitwerk@#{tag}: #{message}") end
@sig (String) { (String, String) -> void } -> void
# File lib/zeitwerk/loader.rb, line 724 def ls(dir) Dir.foreach(dir) do |basename| next if basename.start_with?(".") abspath = File.join(dir, basename) next if ignored_paths.member?(abspath) # We freeze abspath because that saves allocations when passed later to # File methods. See #125. yield basename, abspath.freeze end end
`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 619 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
@sig (String) -> void
# File lib/zeitwerk/loader.rb, line 786 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
@sig () -> void
# File lib/zeitwerk/loader.rb, line 765 def recompute_collapse_dirs collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns)) end
@sig () -> void
# File lib/zeitwerk/loader.rb, line 760 def recompute_ignored_paths ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns)) end
@sig (String) -> void
# File lib/zeitwerk/loader.rb, line 781 def register_explicit_namespace(cpath) ExplicitNamespace.register(cpath, self) end
@sig (String) -> bool
# File lib/zeitwerk/loader.rb, line 738 def ruby?(path) path.end_with?(".rb") end
@sig (Module, Symbol, String) -> void
# File lib/zeitwerk/loader.rb, line 628 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. # # We freeze realpath because that saves allocations in Module#autoload. # See #125. realpath = File.realpath(abspath).freeze 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
@sig (String, Module) -> void
# File lib/zeitwerk/loader.rb, line 532 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
# File lib/zeitwerk/loader.rb, line 677 def strict_autoload_path(parent, cname) parent.autoload?(cname) if cdef?(parent, cname) end
@sig (Module, Symbol) -> void
# File lib/zeitwerk/loader.rb, line 801 def unload_autoload(parent, cname) parent.__send__(:remove_const, cname) log("autoload for #{cpath(parent, cname)} removed") if logger end
@sig (Module, Symbol) -> void
# File lib/zeitwerk/loader.rb, line 807 def unload_cref(parent, cname) parent.__send__(:remove_const, cname) log("#{cpath(parent, cname)} unloaded") if logger end