Class: Zeitwerk::Loader::FileSystem

Inherits:
Object
  • Object
show all
Defined in:
lib/zeitwerk/loader/file_system.rb

Overview

This private class encapsulates interactions with the file system.

It is used to list directories and check file types, and it encodes the conventions documented in the README.

Instance Method Summary collapse

Constructor Details

#initialize(loader) ⇒ FileSystem

: (Zeitwerk::Loader) -> void



11
12
13
# File 'lib/zeitwerk/loader/file_system.rb', line 11

def initialize(loader)
  @loader = loader
end

Instance Method Details

#dir?(path) ⇒ Boolean

: (String) -> bool

Returns:

  • (Boolean)


120
121
122
# File 'lib/zeitwerk/loader/file_system.rb', line 120

def dir?(path)
  File.directory?(path)
end

#has_exactly_one_nsfile?(cref, dir) ⇒ Boolean

Returns the absolute path to an nsfile in ‘dir`, if there is exactly one. If there is none, it returns `nil`.

This method accounts for collapsed directories, which conceptually allow for multiple nsfiles. If two are found, Zeitwerk::ConflictingNamespaceDefinitionError is raised.

: (Zeitwerk::Cref, String) -> String? ! Zeitwerk::ConflictingNamespaceDefinitionError

Returns:

  • (Boolean)


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/zeitwerk/loader/file_system.rb', line 71

def has_exactly_one_nsfile?(cref, dir)
  return unless @loader.nsfile

  # When `dir` does not have any collapsed directories a simple lookup
  # suffices. This is a common case worth optimizing.
  unless @loader.__collapse_parent?(dir)
    nsfile_abspath = File.join(dir, @loader.nsfile)
    if File.exist?(nsfile_abspath) && !@loader.__ignored_path?(nsfile_abspath)
      return nsfile_abspath
    end
    return
  end

  nsfile = nil

  to_visit = [dir]
  while (dir = to_visit.shift)
    relevant_dir_entries(dir) do |basename, abspath, ftype|
      if ftype == :file && basename == @loader.nsfile
        if nsfile
          raise Zeitwerk::ConflictingNamespaceDefinitionError.new(cref.path, location: nsfile, conflicting_file: abspath)
        end
        nsfile = abspath
      elsif ftype == :directory && @loader.__collapse?(abspath)
        to_visit << abspath
      end
    end
  end

  nsfile
end

#hidden?(basename) ⇒ Boolean

: (String) -> bool

Returns:

  • (Boolean)


125
126
127
# File 'lib/zeitwerk/loader/file_system.rb', line 125

def hidden?(basename)
  basename.start_with?('.')
end

#ls(dir, collapse: true, &block) ⇒ Object

This method lists directories, filtering out the following:

  • Hidden entries.

  • Ignored entries.

  • Files whose extension is not ‘.rb`.

  • Nested root directories, since they represent separate trees.

  • Subdirectories that (recursively) contain no Ruby files.

If ‘collapse` is true, collapsed directories are not yielded, instead, the method recurses so that the caller gets a conceptually flat listing.

For every entry that is not excluded, ‘ls` yields its basename, absolute path, and file type, which can only be :file or :directory.

: (String) { (String, String, Symbol) -> void } -> void



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/zeitwerk/loader/file_system.rb', line 30

def ls(dir, collapse: true, &block)
  children = relevant_dir_entries(dir)

  # The order in which a directory is listed depends on the file system.
  #
  # Since client code may run on different platforms, it seems convenient to
  # sort directory entries. This provides more deterministic behavior, with
  # consistent eager loading in particular.
  children.sort_by!(&:first)

  children.each do |basename, abspath, ftype|
    if ftype == :directory
      if !has_at_least_one_ruby_file?(abspath)
        @loader.__log { "directory #{abspath} is ignored because it has no Ruby files" }
        next
      elsif collapse && @loader.__collapse?(abspath)
        ls(abspath, collapse: collapse, &block)
        next
      end
    end

    yield basename, abspath, ftype
  end
end

#rb_extension?(path) ⇒ Boolean

: (String) -> bool

Returns:

  • (Boolean)


115
116
117
# File 'lib/zeitwerk/loader/file_system.rb', line 115

def rb_extension?(path)
  path.end_with?('.rb')
end

#supported_ftype?(abspath) ⇒ Boolean

Encodes the documented conventions.

: (String) -> Symbol?

Returns:

  • (Boolean)


106
107
108
109
110
111
112
# File 'lib/zeitwerk/loader/file_system.rb', line 106

def supported_ftype?(abspath)
  if rb_extension?(abspath)
    :file # By convention, we can avoid a syscall here.
  elsif dir?(abspath)
    :directory
  end
end

#walk_up(abspath) ⇒ Object

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



56
57
58
59
60
61
62
# File 'lib/zeitwerk/loader/file_system.rb', line 56

def walk_up(abspath)
  loop do
    yield abspath
    abspath, basename = File.split(abspath)
    break if basename == '/'
  end
end