zeitwerk 2.5.0 → 2.6.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +406 -46
- data/lib/zeitwerk/error.rb +9 -0
- data/lib/zeitwerk/explicit_namespace.rb +14 -10
- data/lib/zeitwerk/gem_inflector.rb +2 -2
- data/lib/zeitwerk/gem_loader.rb +69 -0
- data/lib/zeitwerk/internal.rb +12 -0
- data/lib/zeitwerk/kernel.rb +18 -15
- data/lib/zeitwerk/loader/callbacks.rb +25 -17
- data/lib/zeitwerk/loader/config.rb +95 -52
- data/lib/zeitwerk/loader/eager_load.rb +234 -0
- data/lib/zeitwerk/loader/helpers.rb +106 -16
- data/lib/zeitwerk/loader.rb +241 -172
- data/lib/zeitwerk/registry.rb +9 -16
- data/lib/zeitwerk/version.rb +1 -1
- data/lib/zeitwerk.rb +2 -1
- metadata +6 -4
- data/lib/zeitwerk/autoloads.rb +0 -71
data/lib/zeitwerk/error.rb
CHANGED
@@ -5,8 +5,17 @@ module Zeitwerk
|
|
5
5
|
end
|
6
6
|
|
7
7
|
class ReloadingDisabledError < Error
|
8
|
+
def initialize
|
9
|
+
super("can't reload, please call loader.enable_reloading before setup")
|
10
|
+
end
|
8
11
|
end
|
9
12
|
|
10
13
|
class NameError < ::NameError
|
11
14
|
end
|
15
|
+
|
16
|
+
class SetupRequired < Error
|
17
|
+
def initialize
|
18
|
+
super("please, finish your configuration and call Zeitwerk::Loader#setup once all is ready")
|
19
|
+
end
|
20
|
+
end
|
12
21
|
end
|
@@ -11,28 +11,28 @@ module Zeitwerk
|
|
11
11
|
module ExplicitNamespace # :nodoc: all
|
12
12
|
class << self
|
13
13
|
include RealModName
|
14
|
+
extend Internal
|
14
15
|
|
15
16
|
# Maps constant paths that correspond to explicit namespaces according to
|
16
17
|
# the file system, to the loader responsible for them.
|
17
18
|
#
|
18
|
-
# @private
|
19
19
|
# @sig Hash[String, Zeitwerk::Loader]
|
20
20
|
attr_reader :cpaths
|
21
|
+
private :cpaths
|
21
22
|
|
22
|
-
# @private
|
23
23
|
# @sig Mutex
|
24
24
|
attr_reader :mutex
|
25
|
+
private :mutex
|
25
26
|
|
26
|
-
# @private
|
27
27
|
# @sig TracePoint
|
28
28
|
attr_reader :tracer
|
29
|
+
private :tracer
|
29
30
|
|
30
31
|
# Asserts `cpath` corresponds to an explicit namespace for which `loader`
|
31
32
|
# is responsible.
|
32
33
|
#
|
33
|
-
# @private
|
34
34
|
# @sig (String, Zeitwerk::Loader) -> void
|
35
|
-
def register(cpath, loader)
|
35
|
+
internal def register(cpath, loader)
|
36
36
|
mutex.synchronize do
|
37
37
|
cpaths[cpath] = loader
|
38
38
|
# We check enabled? because, looking at the C source code, enabling an
|
@@ -41,24 +41,28 @@ module Zeitwerk
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
# @private
|
45
44
|
# @sig (Zeitwerk::Loader) -> void
|
46
|
-
def unregister_loader(loader)
|
45
|
+
internal def unregister_loader(loader)
|
47
46
|
cpaths.delete_if { |_cpath, l| l == loader }
|
48
47
|
disable_tracer_if_unneeded
|
49
48
|
end
|
50
49
|
|
51
|
-
|
50
|
+
# This is an internal method only used by the test suite.
|
51
|
+
#
|
52
|
+
# @sig (String) -> bool
|
53
|
+
internal def registered?(cpath)
|
54
|
+
cpaths.key?(cpath)
|
55
|
+
end
|
52
56
|
|
53
57
|
# @sig () -> void
|
54
|
-
def disable_tracer_if_unneeded
|
58
|
+
private def disable_tracer_if_unneeded
|
55
59
|
mutex.synchronize do
|
56
60
|
tracer.disable if cpaths.empty?
|
57
61
|
end
|
58
62
|
end
|
59
63
|
|
60
64
|
# @sig (TracePoint) -> void
|
61
|
-
def tracepoint_class_callback(event)
|
65
|
+
private def tracepoint_class_callback(event)
|
62
66
|
# If the class is a singleton class, we won't do anything with it so we
|
63
67
|
# can bail out immediately. This is several orders of magnitude faster
|
64
68
|
# than accessing its name.
|
@@ -5,8 +5,8 @@ module Zeitwerk
|
|
5
5
|
# @sig (String) -> void
|
6
6
|
def initialize(root_file)
|
7
7
|
namespace = File.basename(root_file, ".rb")
|
8
|
-
|
9
|
-
@version_file = File.join(
|
8
|
+
root_dir = File.dirname(root_file)
|
9
|
+
@version_file = File.join(root_dir, namespace, "version.rb")
|
10
10
|
end
|
11
11
|
|
12
12
|
# @sig (String, String) -> String
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zeitwerk
|
4
|
+
# @private
|
5
|
+
class GemLoader < Loader
|
6
|
+
include RealModName
|
7
|
+
|
8
|
+
# Users should not create instances directly, the public interface is
|
9
|
+
# `Zeitwerk::Loader.for_gem`.
|
10
|
+
private_class_method :new
|
11
|
+
|
12
|
+
# @private
|
13
|
+
# @sig (String, bool) -> Zeitwerk::GemLoader
|
14
|
+
def self.__new(root_file, namespace:, warn_on_extra_files:)
|
15
|
+
new(root_file, namespace: namespace, warn_on_extra_files: warn_on_extra_files)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @sig (String, bool) -> void
|
19
|
+
def initialize(root_file, namespace:, warn_on_extra_files:)
|
20
|
+
super()
|
21
|
+
|
22
|
+
@tag = File.basename(root_file, ".rb")
|
23
|
+
@tag = real_mod_name(namespace) + "-" + @tag unless namespace.equal?(Object)
|
24
|
+
|
25
|
+
@inflector = GemInflector.new(root_file)
|
26
|
+
@root_file = File.expand_path(root_file)
|
27
|
+
@root_dir = File.dirname(root_file)
|
28
|
+
@warn_on_extra_files = warn_on_extra_files
|
29
|
+
|
30
|
+
push_dir(@root_dir, namespace: namespace)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @sig () -> void
|
34
|
+
def setup
|
35
|
+
warn_on_extra_files if @warn_on_extra_files
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @sig () -> void
|
42
|
+
def warn_on_extra_files
|
43
|
+
expected_namespace_dir = @root_file.delete_suffix(".rb")
|
44
|
+
|
45
|
+
ls(@root_dir) do |basename, abspath|
|
46
|
+
next if abspath == @root_file
|
47
|
+
next if abspath == expected_namespace_dir
|
48
|
+
|
49
|
+
basename_without_ext = basename.delete_suffix(".rb")
|
50
|
+
cname = inflector.camelize(basename_without_ext, abspath).to_sym
|
51
|
+
ftype = dir?(abspath) ? "directory" : "file"
|
52
|
+
|
53
|
+
warn(<<~EOS)
|
54
|
+
WARNING: Zeitwerk defines the constant #{cname} after the #{ftype}
|
55
|
+
|
56
|
+
#{abspath}
|
57
|
+
|
58
|
+
To prevent that, please configure the loader to ignore it:
|
59
|
+
|
60
|
+
loader.ignore("\#{__dir__}/#{basename}")
|
61
|
+
|
62
|
+
Otherwise, there is a flag to silence this warning:
|
63
|
+
|
64
|
+
Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
65
|
+
EOS
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/zeitwerk/kernel.rb
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
module Kernel
|
4
4
|
module_function
|
5
5
|
|
6
|
-
#
|
6
|
+
# Zeitwerk's main idea is to define autoloads for project constants, and then
|
7
|
+
# intercept them when triggered in this thin `Kernel#require` wrapper.
|
7
8
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# managed files are loaded.
|
9
|
+
# That allows us to complete the circle, invoke callbacks, autovivify modules,
|
10
|
+
# define autoloads for just autoloaded namespaces, update internal state, etc.
|
11
11
|
#
|
12
12
|
# On the other hand, if you publish a new version of a gem that is now managed
|
13
13
|
# by Zeitwerk, client code can reference directly your classes and modules and
|
@@ -17,29 +17,32 @@ module Kernel
|
|
17
17
|
#
|
18
18
|
# We cannot decorate with prepend + super because Kernel has already been
|
19
19
|
# included in Object, and changes in ancestors don't get propagated into
|
20
|
-
# already existing ancestor chains.
|
20
|
+
# already existing ancestor chains on Ruby < 3.0.
|
21
21
|
alias_method :zeitwerk_original_require, :require
|
22
|
+
class << self
|
23
|
+
alias_method :zeitwerk_original_require, :require
|
24
|
+
end
|
22
25
|
|
23
26
|
# @sig (String) -> true | false
|
24
27
|
def require(path)
|
25
28
|
if loader = Zeitwerk::Registry.loader_for(path)
|
26
29
|
if path.end_with?(".rb")
|
27
|
-
zeitwerk_original_require(path)
|
28
|
-
|
29
|
-
|
30
|
+
required = zeitwerk_original_require(path)
|
31
|
+
loader.__on_file_autoloaded(path) if required
|
32
|
+
required
|
30
33
|
else
|
31
|
-
loader.
|
34
|
+
loader.__on_dir_autoloaded(path)
|
32
35
|
true
|
33
36
|
end
|
34
37
|
else
|
35
|
-
zeitwerk_original_require(path)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
38
|
+
required = zeitwerk_original_require(path)
|
39
|
+
if required
|
40
|
+
abspath = $LOADED_FEATURES.last
|
41
|
+
if loader = Zeitwerk::Registry.loader_for(abspath)
|
42
|
+
loader.__on_file_autoloaded(abspath)
|
41
43
|
end
|
42
44
|
end
|
45
|
+
required
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
@@ -2,38 +2,46 @@
|
|
2
2
|
|
3
3
|
module Zeitwerk::Loader::Callbacks
|
4
4
|
include Zeitwerk::RealModName
|
5
|
+
extend Zeitwerk::Internal
|
5
6
|
|
6
7
|
# Invoked from our decorated Kernel#require when a managed file is autoloaded.
|
7
8
|
#
|
8
|
-
# @private
|
9
9
|
# @sig (String) -> void
|
10
|
-
def on_file_autoloaded(file)
|
10
|
+
internal def on_file_autoloaded(file)
|
11
11
|
cref = autoloads.delete(file)
|
12
12
|
cpath = cpath(*cref)
|
13
13
|
|
14
|
-
# If reloading is enabled, we need to put this constant for unloading
|
15
|
-
# regardless of what cdef? says. In Ruby < 3.1 the internal state is not
|
16
|
-
# fully cleared. Module#constants still includes it, and you need to
|
17
|
-
# remove_const. See https://github.com/ruby/ruby/pull/4715.
|
18
|
-
to_unload[cpath] = [file, cref] if reloading_enabled?
|
19
14
|
Zeitwerk::Registry.unregister_autoload(file)
|
20
15
|
|
21
16
|
if cdef?(*cref)
|
22
17
|
log("constant #{cpath} loaded from file #{file}") if logger
|
18
|
+
to_unload[cpath] = [file, cref] if reloading_enabled?
|
23
19
|
run_on_load_callbacks(cpath, cget(*cref), file) unless on_load_callbacks.empty?
|
24
20
|
else
|
25
|
-
|
21
|
+
msg = "expected file #{file} to define constant #{cpath}, but didn't"
|
22
|
+
log(msg) if logger
|
23
|
+
|
24
|
+
# Ruby still keeps the autoload defined, but we remove it because the
|
25
|
+
# contract in Zeitwerk is more strict.
|
26
|
+
crem(*cref)
|
27
|
+
|
28
|
+
# Since the expected constant was not defined, there is nothing to unload.
|
29
|
+
# However, if the exception is rescued and reloading is enabled, we still
|
30
|
+
# need to deleted the file from $LOADED_FEATURES.
|
31
|
+
to_unload[cpath] = [file, cref] if reloading_enabled?
|
32
|
+
|
33
|
+
raise Zeitwerk::NameError.new(msg, cref.last)
|
26
34
|
end
|
27
35
|
end
|
28
36
|
|
29
37
|
# Invoked from our decorated Kernel#require when a managed directory is
|
30
38
|
# autoloaded.
|
31
39
|
#
|
32
|
-
# @private
|
33
40
|
# @sig (String) -> void
|
34
|
-
def on_dir_autoloaded(dir)
|
35
|
-
# Module#autoload does not serialize concurrent requires, and
|
36
|
-
# directories ourselves
|
41
|
+
internal def on_dir_autoloaded(dir)
|
42
|
+
# Module#autoload does not serialize concurrent requires in CRuby < 3.2, and
|
43
|
+
# we handle directories ourselves without going through Kernel#require, so
|
44
|
+
# the callback needs to account for concurrency.
|
37
45
|
#
|
38
46
|
# Multi-threading would introduce a race condition here in which thread t1
|
39
47
|
# autovivifies the module, and while autoloads for its children are being
|
@@ -42,8 +50,8 @@ module Zeitwerk::Loader::Callbacks
|
|
42
50
|
# Without the mutex and subsequent delete call, t2 would reset the module.
|
43
51
|
# That not only would reassign the constant (undesirable per se) but, worse,
|
44
52
|
# the module object created by t2 wouldn't have any of the autoloads for its
|
45
|
-
# children, since t1 would have correctly deleted its
|
46
|
-
|
53
|
+
# children, since t1 would have correctly deleted its namespace_dirs entry.
|
54
|
+
dirs_autoload_monitor.synchronize do
|
47
55
|
if cref = autoloads.delete(dir)
|
48
56
|
autovivified_module = cref[0].const_set(cref[1], Module.new)
|
49
57
|
cpath = autovivified_module.name
|
@@ -71,9 +79,9 @@ module Zeitwerk::Loader::Callbacks
|
|
71
79
|
# @private
|
72
80
|
# @sig (Module) -> void
|
73
81
|
def on_namespace_loaded(namespace)
|
74
|
-
if
|
75
|
-
|
76
|
-
|
82
|
+
if dirs = namespace_dirs.delete(real_mod_name(namespace))
|
83
|
+
dirs.each do |dir|
|
84
|
+
define_autoloads_for_dir(dir, namespace)
|
77
85
|
end
|
78
86
|
end
|
79
87
|
end
|
@@ -4,85 +4,91 @@ require "set"
|
|
4
4
|
require "securerandom"
|
5
5
|
|
6
6
|
module Zeitwerk::Loader::Config
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
extend Zeitwerk::Internal
|
8
|
+
include Zeitwerk::RealModName
|
9
|
+
|
10
|
+
# @sig #camelize
|
11
|
+
attr_accessor :inflector
|
12
|
+
|
13
|
+
# @sig #call | #debug | nil
|
14
|
+
attr_accessor :logger
|
15
|
+
|
16
|
+
# Absolute paths of the root directories, mapped to their respective root namespaces:
|
10
17
|
#
|
11
|
-
# "/Users/fxn/blog/app/
|
12
|
-
# "/Users/fxn/blog/app/
|
18
|
+
# "/Users/fxn/blog/app/channels" => Object,
|
19
|
+
# "/Users/fxn/blog/app/adapters" => ActiveJob::QueueAdapters,
|
13
20
|
# ...
|
14
21
|
#
|
22
|
+
# Stored in a hash to preserve order, easily handle duplicates, and have a
|
23
|
+
# fast lookup by directory.
|
24
|
+
#
|
15
25
|
# This is a private collection maintained by the loader. The public
|
16
26
|
# interface for it is `push_dir` and `dirs`.
|
17
27
|
#
|
18
|
-
# @
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# @sig #camelize
|
23
|
-
attr_accessor :inflector
|
28
|
+
# @sig Hash[String, Module]
|
29
|
+
attr_reader :roots
|
30
|
+
internal :roots
|
24
31
|
|
25
32
|
# Absolute paths of files, directories, or glob patterns to be totally
|
26
33
|
# ignored.
|
27
34
|
#
|
28
|
-
# @private
|
29
35
|
# @sig Set[String]
|
30
36
|
attr_reader :ignored_glob_patterns
|
37
|
+
private :ignored_glob_patterns
|
31
38
|
|
32
39
|
# The actual collection of absolute file and directory names at the time the
|
33
40
|
# ignored glob patterns were expanded. Computed on setup, and recomputed on
|
34
41
|
# reload.
|
35
42
|
#
|
36
|
-
# @private
|
37
43
|
# @sig Set[String]
|
38
44
|
attr_reader :ignored_paths
|
45
|
+
private :ignored_paths
|
39
46
|
|
40
47
|
# Absolute paths of directories or glob patterns to be collapsed.
|
41
48
|
#
|
42
|
-
# @private
|
43
49
|
# @sig Set[String]
|
44
50
|
attr_reader :collapse_glob_patterns
|
51
|
+
private :collapse_glob_patterns
|
45
52
|
|
46
53
|
# The actual collection of absolute directory names at the time the collapse
|
47
54
|
# glob patterns were expanded. Computed on setup, and recomputed on reload.
|
48
55
|
#
|
49
|
-
# @private
|
50
56
|
# @sig Set[String]
|
51
57
|
attr_reader :collapse_dirs
|
58
|
+
private :collapse_dirs
|
52
59
|
|
53
60
|
# Absolute paths of files or directories not to be eager loaded.
|
54
61
|
#
|
55
|
-
# @private
|
56
62
|
# @sig Set[String]
|
57
63
|
attr_reader :eager_load_exclusions
|
64
|
+
private :eager_load_exclusions
|
58
65
|
|
59
66
|
# User-oriented callbacks to be fired on setup and on reload.
|
60
67
|
#
|
61
|
-
# @private
|
62
68
|
# @sig Array[{ () -> void }]
|
63
69
|
attr_reader :on_setup_callbacks
|
70
|
+
private :on_setup_callbacks
|
64
71
|
|
65
72
|
# User-oriented callbacks to be fired when a constant is loaded.
|
66
73
|
#
|
67
|
-
# @private
|
68
74
|
# @sig Hash[String, Array[{ (Object, String) -> void }]]
|
69
75
|
# Hash[Symbol, Array[{ (String, Object, String) -> void }]]
|
70
76
|
attr_reader :on_load_callbacks
|
77
|
+
private :on_load_callbacks
|
71
78
|
|
72
79
|
# User-oriented callbacks to be fired before constants are removed.
|
73
80
|
#
|
74
|
-
# @private
|
75
81
|
# @sig Hash[String, Array[{ (Object, String) -> void }]]
|
76
82
|
# Hash[Symbol, Array[{ (String, Object, String) -> void }]]
|
77
83
|
attr_reader :on_unload_callbacks
|
78
|
-
|
79
|
-
# @sig #call | #debug | nil
|
80
|
-
attr_accessor :logger
|
84
|
+
private :on_unload_callbacks
|
81
85
|
|
82
86
|
def initialize
|
83
|
-
@initialized_at = Time.now
|
84
|
-
@root_dirs = {}
|
85
87
|
@inflector = Zeitwerk::Inflector.new
|
88
|
+
@logger = self.class.default_logger
|
89
|
+
@tag = SecureRandom.hex(3)
|
90
|
+
@initialized_at = Time.now
|
91
|
+
@roots = {}
|
86
92
|
@ignored_glob_patterns = Set.new
|
87
93
|
@ignored_paths = Set.new
|
88
94
|
@collapse_glob_patterns = Set.new
|
@@ -92,8 +98,6 @@ module Zeitwerk::Loader::Config
|
|
92
98
|
@on_setup_callbacks = []
|
93
99
|
@on_load_callbacks = {}
|
94
100
|
@on_unload_callbacks = {}
|
95
|
-
@logger = self.class.default_logger
|
96
|
-
@tag = SecureRandom.hex(3)
|
97
101
|
end
|
98
102
|
|
99
103
|
# Pushes `path` to the list of root directories.
|
@@ -105,15 +109,18 @@ module Zeitwerk::Loader::Config
|
|
105
109
|
# @raise [Zeitwerk::Error]
|
106
110
|
# @sig (String | Pathname, Module) -> void
|
107
111
|
def push_dir(path, namespace: Object)
|
108
|
-
# Note that Class < Module.
|
109
|
-
unless namespace.is_a?(Module)
|
112
|
+
unless namespace.is_a?(Module) # Note that Class < Module.
|
110
113
|
raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
|
111
114
|
end
|
112
115
|
|
116
|
+
unless real_mod_name(namespace)
|
117
|
+
raise Zeitwerk::Error, "root namespaces cannot be anonymous"
|
118
|
+
end
|
119
|
+
|
113
120
|
abspath = File.expand_path(path)
|
114
121
|
if dir?(abspath)
|
115
122
|
raise_if_conflicting_directory(abspath)
|
116
|
-
|
123
|
+
roots[abspath] = namespace
|
117
124
|
else
|
118
125
|
raise Zeitwerk::Error, "the root directory #{abspath} does not exist"
|
119
126
|
end
|
@@ -131,18 +138,35 @@ module Zeitwerk::Loader::Config
|
|
131
138
|
|
132
139
|
# Sets a tag for the loader, useful for logging.
|
133
140
|
#
|
134
|
-
# @param tag [#to_s]
|
135
141
|
# @sig (#to_s) -> void
|
136
142
|
def tag=(tag)
|
137
143
|
@tag = tag.to_s
|
138
144
|
end
|
139
145
|
|
140
|
-
#
|
141
|
-
#
|
146
|
+
# If `namespaces` is falsey (default), returns an array with the absolute
|
147
|
+
# paths of the root directories as strings. If truthy, returns a hash table
|
148
|
+
# instead. Keys are the absolute paths of the root directories as strings,
|
149
|
+
# values are their corresponding namespaces, class or module objects.
|
142
150
|
#
|
143
|
-
#
|
144
|
-
|
145
|
-
|
151
|
+
# If `ignored` is falsey (default), ignored root directories are filtered out.
|
152
|
+
#
|
153
|
+
# These are read-only collections, please add to them with `push_dir`.
|
154
|
+
#
|
155
|
+
# @sig () -> Array[String] | Hash[String, Module]
|
156
|
+
def dirs(namespaces: false, ignored: false)
|
157
|
+
if namespaces
|
158
|
+
if ignored || ignored_paths.empty?
|
159
|
+
roots.clone
|
160
|
+
else
|
161
|
+
roots.reject { |root_dir, _namespace| ignored_path?(root_dir) }
|
162
|
+
end
|
163
|
+
else
|
164
|
+
if ignored || ignored_paths.empty?
|
165
|
+
roots.keys
|
166
|
+
else
|
167
|
+
roots.keys.reject { |root_dir| ignored_path?(root_dir) }
|
168
|
+
end
|
169
|
+
end.freeze
|
146
170
|
end
|
147
171
|
|
148
172
|
# You need to call this method before setup in order to be able to reload.
|
@@ -265,57 +289,76 @@ module Zeitwerk::Loader::Config
|
|
265
289
|
@logger = ->(msg) { puts msg }
|
266
290
|
end
|
267
291
|
|
268
|
-
#
|
292
|
+
# Returns true if the argument has been configured to be ignored, or is a
|
293
|
+
# descendant of an ignored directory.
|
294
|
+
#
|
269
295
|
# @sig (String) -> bool
|
270
|
-
def ignores?(abspath)
|
271
|
-
|
272
|
-
|
296
|
+
internal def ignores?(abspath)
|
297
|
+
# Common use case.
|
298
|
+
return false if ignored_paths.empty?
|
299
|
+
|
300
|
+
walk_up(abspath) do |path|
|
301
|
+
return true if ignored_path?(path)
|
302
|
+
return false if roots.key?(path)
|
273
303
|
end
|
304
|
+
|
305
|
+
false
|
274
306
|
end
|
275
307
|
|
276
|
-
|
308
|
+
# @sig (String) -> bool
|
309
|
+
private def ignored_path?(abspath)
|
310
|
+
ignored_paths.member?(abspath)
|
311
|
+
end
|
277
312
|
|
278
313
|
# @sig () -> Array[String]
|
279
|
-
def
|
280
|
-
|
281
|
-
!dir?(root_dir) ||
|
314
|
+
private def actual_roots
|
315
|
+
roots.reject do |root_dir, _root_namespace|
|
316
|
+
!dir?(root_dir) || ignored_path?(root_dir)
|
282
317
|
end
|
283
318
|
end
|
284
319
|
|
285
320
|
# @sig (String) -> bool
|
286
|
-
def root_dir?(dir)
|
287
|
-
|
321
|
+
private def root_dir?(dir)
|
322
|
+
roots.key?(dir)
|
288
323
|
end
|
289
324
|
|
290
325
|
# @sig (String) -> bool
|
291
|
-
def excluded_from_eager_load?(abspath)
|
292
|
-
|
326
|
+
private def excluded_from_eager_load?(abspath)
|
327
|
+
# Optimize this common use case.
|
328
|
+
return false if eager_load_exclusions.empty?
|
329
|
+
|
330
|
+
walk_up(abspath) do |path|
|
331
|
+
return true if eager_load_exclusions.member?(path)
|
332
|
+
return false if roots.key?(path)
|
333
|
+
end
|
334
|
+
|
335
|
+
false
|
293
336
|
end
|
294
337
|
|
295
338
|
# @sig (String) -> bool
|
296
|
-
def collapse?(dir)
|
339
|
+
private def collapse?(dir)
|
297
340
|
collapse_dirs.member?(dir)
|
298
341
|
end
|
299
342
|
|
300
343
|
# @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
|
301
|
-
def expand_paths(paths)
|
344
|
+
private def expand_paths(paths)
|
302
345
|
paths.flatten.map! { |path| File.expand_path(path) }
|
303
346
|
end
|
304
347
|
|
305
348
|
# @sig (Array[String]) -> Array[String]
|
306
|
-
def expand_glob_patterns(glob_patterns)
|
349
|
+
private def expand_glob_patterns(glob_patterns)
|
307
350
|
# Note that Dir.glob works with regular file names just fine. That is,
|
308
351
|
# glob patterns technically need no wildcards.
|
309
352
|
glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
|
310
353
|
end
|
311
354
|
|
312
355
|
# @sig () -> void
|
313
|
-
def recompute_ignored_paths
|
356
|
+
private def recompute_ignored_paths
|
314
357
|
ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
|
315
358
|
end
|
316
359
|
|
317
360
|
# @sig () -> void
|
318
|
-
def recompute_collapse_dirs
|
361
|
+
private def recompute_collapse_dirs
|
319
362
|
collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
|
320
363
|
end
|
321
364
|
end
|