module Zeitwerk::Loader::EagerLoad
Public Instance Methods
eager_load(force: false)
click to toggle source
Eager loads all files in the root directories, recursively. Files do not need to be in `$LOAD_PATH`, absolute file names are used. Ignored and shadowed files are not eager loaded. You can opt-out specifically in specific files and directories with `do_not_eager_load`, and that can be overridden passing `force: true`.
: (?force: boolish) -> void
# File lib/zeitwerk/loader/eager_load.rb, line 9 def eager_load(force: false) mutex.synchronize do break if @eager_loaded raise Zeitwerk::SetupRequired unless @setup log { "eager load start" } actual_roots.each do |root_dir, root_namespace| actual_eager_load_dir(root_dir, root_namespace, force: force) end autoloaded_dirs.each do |autoloaded_dir| Zeitwerk::Registry.autoloads.unregister(autoloaded_dir) end autoloaded_dirs.clear @eager_loaded = true log { "eager load end" } end end
eager_load_dir(path)
click to toggle source
: (String | Pathname) -> void
# File lib/zeitwerk/loader/eager_load.rb, line 32 def eager_load_dir(path) raise Zeitwerk::SetupRequired unless @setup abspath = File.expand_path(path) raise Zeitwerk::Error.new("#{abspath} is not a directory") unless @fs.dir?(abspath) paths = [] root_namespace = nil @fs.walk_up(abspath) do |dir| return if ignored_path?(dir) return if eager_load_exclusions.member?(dir) break if root_namespace = roots[dir] basename = File.basename(dir) return if @fs.hidden?(basename) paths << [basename, dir] unless collapse?(dir) end raise Zeitwerk::Error.new("I do not manage #{abspath}") unless root_namespace return if @eager_loaded namespace = root_namespace paths.reverse_each do |basename, dir| cname = cname_for(basename, dir) # Can happen if there are no Ruby files. This is not an error condition, # the directory is actually managed. Could have Ruby files later. return unless namespace.const_defined?(cname, false) namespace = namespace.const_get(cname, false) end # A shortcircuiting test depends on the invocation of this method. Please # keep them in sync if refactored. actual_eager_load_dir(abspath, namespace) end
eager_load_namespace(mod)
click to toggle source
: (Module) -> void
# File lib/zeitwerk/loader/eager_load.rb, line 73 def eager_load_namespace(mod) raise Zeitwerk::SetupRequired unless @setup unless mod.is_a?(Module) raise Zeitwerk::Error, "#{mod.inspect} is not a class or module object" end return if @eager_loaded mod_name = real_mod_name(mod) return unless mod_name actual_roots.each do |root_dir, root_namespace| if Object.equal?(mod) # A shortcircuiting test depends on the invocation of this method. # Please keep them in sync if refactored. actual_eager_load_dir(root_dir, root_namespace) elsif root_namespace.equal?(Object) eager_load_child_namespace(mod, mod_name, root_dir, root_namespace) else root_namespace_name = real_mod_name(root_namespace) if root_namespace_name.start_with?(mod_name + "::") actual_eager_load_dir(root_dir, root_namespace) elsif mod_name == root_namespace_name actual_eager_load_dir(root_dir, root_namespace) elsif mod_name.start_with?(root_namespace_name + "::") eager_load_child_namespace(mod, mod_name, root_dir, root_namespace) else # Unrelated constant hierarchies, do nothing. end end end end
load_file(path)
click to toggle source
Loads the given Ruby file. Raises if the argument is ignored, shadowed, or not managed by the receiver. The method is implemented as `constantize` for files, in a sense, to be able to descend orderly and make sure the file is loadable.
: (String | Pathname) -> void
# File lib/zeitwerk/loader/eager_load.rb, line 115 def load_file(path) abspath = File.expand_path(path) raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath) raise Zeitwerk::Error.new("#{abspath} is not a Ruby file") if !@fs.rb_extension?(abspath) raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath) file_basename = File.basename(abspath, ".rb") raise Zeitwerk::Error.new("#{abspath} is ignored") if @fs.hidden?(file_basename) root_namespace = nil paths = [] @fs.walk_up(File.dirname(abspath)) do |dir| raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(dir) break if root_namespace = roots[dir] basename = File.basename(dir) raise Zeitwerk::Error.new("#{abspath} is ignored") if @fs.hidden?(basename) paths << [basename, dir] unless collapse?(dir) end raise Zeitwerk::Error.new("I do not manage #{abspath}") unless root_namespace base_cname = cname_for(file_basename, abspath) namespace = root_namespace paths.reverse_each do |basename, dir| cname = cname_for(basename, dir) namespace = namespace.const_get(cname, false) end raise Zeitwerk::Error.new("#{abspath} is shadowed") if shadowed_file?(abspath) namespace.const_get(base_cname, false) end
Private Instance Methods
actual_eager_load_dir(dir, namespace, force: false)
click to toggle source
The caller is responsible for making sure `namespace` is the namespace that corresponds to `dir`.
: (String, Module, ?force: boolish) -> void
# File lib/zeitwerk/loader/eager_load.rb, line 158 def actual_eager_load_dir(dir, namespace, force: false) honour_exclusions = !force return if honour_exclusions && excluded_from_eager_load?(dir) log { "eager load directory #{dir} start" } queue = [[dir, namespace]] while (current_dir, namespace = queue.shift) @fs.ls(current_dir) do |basename, abspath, ftype| next if honour_exclusions && eager_load_exclusions.member?(abspath) if ftype == :file if (cref = autoloads[abspath]) cref.get end else if collapse?(abspath) queue << [abspath, namespace] else cname = cname_for(basename, abspath) queue << [abspath, namespace.const_get(cname, false)] end end end end log { "eager load directory #{dir} end" } end
eager_load_child_namespace(child, child_name, root_dir, root_namespace)
click to toggle source
In order to invoke this method, the caller has to ensure `child` is a strict namespace descendant of `root_namespace`.
: (Module, String, String, Module) -> void
# File lib/zeitwerk/loader/eager_load.rb, line 191 def eager_load_child_namespace(child, child_name, root_dir, root_namespace) suffix = child_name unless root_namespace.equal?(Object) suffix = suffix.delete_prefix(real_mod_name(root_namespace) + "::") end # These directories are at the same namespace level, there may be more if # we find collapsed ones. As we scan, we look for matches for the first # segment, and store them in `next_dirs`. If there are any, we look for # the next segments in those matches. Repeat. # # If we exhaust the search locating directories that match all segments, # we just need to eager load those ones. dirs = [root_dir] next_dirs = [] suffix.split("::").each do |segment| while (dir = dirs.shift) @fs.ls(dir) do |basename, abspath, ftype| next unless ftype == :directory if collapse?(abspath) dirs << abspath elsif segment == cname_for(basename, abspath).to_s next_dirs << abspath end end end return if next_dirs.empty? dirs.replace(next_dirs) next_dirs.clear end dirs.each do |dir| actual_eager_load_dir(dir, child) end end