module Zeitwerk
Constants
- VERSION
Public Instance Methods
@param parent [Module] @param cname [Symbol] @param file [String] @return [void]
# File lib/zeitwerk/loader.rb, line 540 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 603 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 517 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 727 def cdef?(parent, cname) parent.const_defined?(cname, false) end
@param parent [Module] @param cname [Symbol] @return [String]
# File lib/zeitwerk/loader.rb, line 674 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 697 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 640 def do_preload preloads.each do |abspath| do_preload_abspath(abspath) end end
@param abspath [String] @return [void]
# File lib/zeitwerk/loader.rb, line 648 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 658 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 666 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 709 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 703 def expand_paths(paths) paths.flatten.map! { |path| File.expand_path(path) } end
@param message [String] @return [void]
# File lib/zeitwerk/loader.rb, line 722 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 681 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 565 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 735 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 716 def recompute_ignored_paths ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns)) end
# File lib/zeitwerk/loader.rb, line 731 def register_explicit_namespace(cpath) ExplicitNamespace.register(cpath, self) end
@param path [String] @return [Boolean]
# File lib/zeitwerk/loader.rb, line 691 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 577 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 475 def set_autoloads_in_dir(dir, parent) ls(dir) do |basename, abspath| begin if ruby?(basename) basename.slice!(-3, 3) 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 autoload_subdir(parent, cname, abspath) 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 627 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 752 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 760 def unload_cref(parent, cname) parent.send(:remove_const, cname) log("#{cpath(parent, cname)} unloaded") if logger end