module Zeitwerk
Constants
- VERSION
Public Instance Methods
@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
@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
@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
# File lib/zeitwerk/loader.rb, line 770 def cdef?(parent, cname) parent.const_defined?(cname, false) end
@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
@param path [String] @return [Boolean]
# File lib/zeitwerk/loader.rb, line 735 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.
@return [void]
# File lib/zeitwerk/loader.rb, line 678 def do_preload preloads.each do |abspath| do_preload_abspath(abspath) end end
@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
@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
@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
@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
@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
@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
@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
@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
# 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
@return [void]
# File lib/zeitwerk/loader.rb, line 759 def recompute_collapse_dirs collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns)) end
@return [void]
# File lib/zeitwerk/loader.rb, line 754 def recompute_ignored_paths ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns)) end
# File lib/zeitwerk/loader.rb, line 774 def register_explicit_namespace(cpath) ExplicitNamespace.register(cpath, self) end
@param path [String] @return [Boolean]
# File lib/zeitwerk/loader.rb, line 729 def ruby?(path) path.end_with?(".rb") end
@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
@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
# File lib/zeitwerk/loader.rb, line 665 def strict_autoload_path(parent, cname) parent.autoload?(cname) if cdef?(parent, cname) end
@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
@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