opal-zeitwerk 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,300 +1,301 @@
1
- require "set"
2
- require "securerandom"
3
-
4
- module Zeitwerk::Loader::Config
5
- # Absolute paths of the root directories. Stored in a hash to preserve
6
- # order, easily handle duplicates, and also be able to have a fast lookup,
7
- # needed for detecting nested paths.
8
- #
9
- # "/Users/fxn/blog/app/assets" => true,
10
- # "/Users/fxn/blog/app/channels" => true,
11
- # ...
12
- #
13
- # This is a private collection maintained by the loader. The public
14
- # interface for it is `push_dir` and `dirs`.
15
- #
16
- # @private
17
- # @sig Hash[String, true]
18
- attr_reader :root_dirs
19
-
20
- # @sig #camelize
21
- attr_accessor :inflector
22
-
23
- # Absolute paths of files, directories, or glob patterns to be totally
24
- # ignored.
25
- #
26
- # @private
27
- # @sig Set[String]
28
- attr_reader :ignored_glob_patterns
29
-
30
- # The actual collection of absolute file and directory names at the time the
31
- # ignored glob patterns were expanded. Computed on setup, and recomputed on
32
- # reload.
33
- #
34
- # @private
35
- # @sig Set[String]
36
- attr_reader :ignored_paths
37
-
38
- # Absolute paths of directories or glob patterns to be collapsed.
39
- #
40
- # @private
41
- # @sig Set[String]
42
- attr_reader :collapse_glob_patterns
43
-
44
- # The actual collection of absolute directory names at the time the collapse
45
- # glob patterns were expanded. Computed on setup, and recomputed on reload.
46
- #
47
- # @private
48
- # @sig Set[String]
49
- attr_reader :collapse_dirs
50
-
51
- # Absolute paths of files or directories not to be eager loaded.
52
- #
53
- # @private
54
- # @sig Set[String]
55
- attr_reader :eager_load_exclusions
56
-
57
- # User-oriented callbacks to be fired on setup and on reload.
58
- #
59
- # @private
60
- # @sig Array[{ () -> void }]
61
- attr_reader :on_setup_callbacks
62
-
63
- # User-oriented callbacks to be fired when a constant is loaded.
64
- #
65
- # @private
66
- # @sig Hash[String, Array[{ (Object, String) -> void }]]
67
- # Hash[Symbol, Array[{ (String, Object, String) -> void }]]
68
- attr_reader :on_load_callbacks
69
-
70
- # User-oriented callbacks to be fired before constants are removed.
71
- #
72
- # @private
73
- # @sig Hash[String, Array[{ (Object, String) -> void }]]
74
- # Hash[Symbol, Array[{ (String, Object, String) -> void }]]
75
- attr_reader :on_unload_callbacks
76
-
77
- # @sig #call | #debug | nil
78
- attr_accessor :logger
79
-
80
- def initialize
81
- @initialized_at = Time.now
82
- @root_dirs = {}
83
- @inflector = Zeitwerk::Inflector.new
84
- @ignored_glob_patterns = Set.new
85
- @ignored_paths = Set.new
86
- @collapse_glob_patterns = Set.new
87
- @collapse_dirs = Set.new
88
- @eager_load_exclusions = Set.new
89
- @reloading_enabled = false
90
- @on_setup_callbacks = []
91
- @on_load_callbacks = {}
92
- @on_unload_callbacks = {}
93
- @logger = self.class.default_logger
94
- @tag = SecureRandom.hex(3)
95
- end
96
-
97
- # Pushes `path` to the list of root directories.
98
- #
99
- # Raises `Zeitwerk::Error` if `path` does not exist, or if another loader in
100
- # the same process already manages that directory or one of its ascendants or
101
- # descendants.
102
- #
103
- # @raise [Zeitwerk::Error]
104
- # @sig (String | Pathname, Module) -> void
105
- def push_dir(path, namespace: Object)
106
- # Note that Class < Module.
107
- unless namespace.is_a?(Module)
108
- raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
109
- end
110
-
111
- abspath = File.expand_path(path)
112
- if dir?(abspath)
113
- raise_if_conflicting_directory(abspath)
114
- root_dirs[abspath] = namespace
115
- else
116
- STDERR.puts "Zeitwerk: the root path #{abspath} does not exist, not added"
117
- end
118
- end
119
-
120
- # Returns the loader's tag.
121
- #
122
- # Implemented as a method instead of via attr_reader for symmetry with the
123
- # writer below.
124
- #
125
- # @sig () -> String
126
- def tag
127
- @tag
128
- end
129
-
130
- # Sets a tag for the loader, useful for logging.
131
- #
132
- # @param tag [#to_s]
133
- # @sig (#to_s) -> void
134
- def tag=(tag)
135
- @tag = tag.to_s
136
- end
137
-
138
- # Absolute paths of the root directories. This is a read-only collection,
139
- # please push here via `push_dir`.
140
- #
141
- # @sig () -> Array[String]
142
- def dirs
143
- root_dirs.keys
144
- end
145
-
146
- # You need to call this method before setup in order to be able to reload.
147
- # There is no way to undo this, either you want to reload or you don't.
148
- #
149
- # @raise [Zeitwerk::Error]
150
- # @sig () -> void
151
- def enable_reloading
152
- return if @reloading_enabled
153
-
154
- if @setup
155
- raise Zeitwerk::Error, "cannot enable reloading after setup"
156
- else
157
- @reloading_enabled = true
158
- end
159
- end
160
-
161
- # @sig () -> bool
162
- def reloading_enabled?
163
- @reloading_enabled
164
- end
165
-
166
- # Let eager load ignore the given files or directories. The constants defined
167
- # in those files are still autoloadable.
168
- #
169
- # @sig (*(String | Pathname | Array[String | Pathname])) -> void
170
- def do_not_eager_load(*paths)
171
- eager_load_exclusions.merge(expand_paths(paths))
172
- end
173
-
174
- # Configure files, directories, or glob patterns to be totally ignored.
175
- #
176
- # @sig (*(String | Pathname | Array[String | Pathname])) -> void
177
- def ignore(*glob_patterns)
178
- glob_patterns = expand_paths(glob_patterns)
179
- ignored_glob_patterns.merge(glob_patterns)
180
- ignored_paths.merge(expand_glob_patterns(glob_patterns))
181
- end
182
-
183
- # Configure directories or glob patterns to be collapsed.
184
- #
185
- # @sig (*(String | Pathname | Array[String | Pathname])) -> void
186
- def collapse(*glob_patterns)
187
- glob_patterns = expand_paths(glob_patterns)
188
- collapse_glob_patterns.merge(glob_patterns)
189
- collapse_dirs.merge(expand_glob_patterns(glob_patterns))
190
- end
191
-
192
- # Configure a block to be called after setup and on each reload.
193
- # If setup was already done, the block runs immediately.
194
- #
195
- # @sig () { () -> void } -> void
196
- def on_setup(&block)
197
- on_setup_callbacks << block
198
- block.call if @setup
199
- end
200
-
201
- # Configure a block to be invoked once a certain constant path is loaded.
202
- # Supports multiple callbacks, and if there are many, they are executed in
203
- # the order in which they were defined.
204
- #
205
- # loader.on_load("SomeApiClient") do |klass, _abspath|
206
- # klass.endpoint = "https://api.dev"
207
- # end
208
- #
209
- # Can also be configured for any constant loaded:
210
- #
211
- # loader.on_load do |cpath, value, abspath|
212
- # # ...
213
- # end
214
- #
215
- # @raise [TypeError]
216
- # @sig (String) { (Object, String) -> void } -> void
217
- # (:ANY) { (String, Object, String) -> void } -> void
218
- def on_load(cpath = :ANY, &block)
219
- raise TypeError, "on_load only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
220
-
221
- (on_load_callbacks[cpath] ||= []) << block
222
- end
223
-
224
- # Configure a block to be invoked right before a certain constant is removed.
225
- # Supports multiple callbacks, and if there are many, they are executed in the
226
- # order in which they were defined.
227
- #
228
- # loader.on_unload("Country") do |klass, _abspath|
229
- # klass.clear_cache
230
- # end
231
- #
232
- # Can also be configured for any removed constant:
233
- #
234
- # loader.on_unload do |cpath, value, abspath|
235
- # # ...
236
- # end
237
- #
238
- # @raise [TypeError]
239
- # @sig (String) { (Object) -> void } -> void
240
- # (:ANY) { (String, Object) -> void } -> void
241
- def on_unload(cpath = :ANY, &block)
242
- raise TypeError, "on_unload only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
243
-
244
- (on_unload_callbacks[cpath] ||= []) << block
245
- end
246
-
247
- # @private
248
- # @sig (String) -> bool
249
- def ignores?(abspath)
250
- ignored_paths.any? do |ignored_path|
251
- ignored_path == abspath || (dir?(ignored_path) && abspath.start_with?(ignored_path + "/"))
252
- end
253
- end
254
-
255
- private
256
-
257
- # @sig () -> Array[String]
258
- def actual_root_dirs
259
- root_dirs.reject do |root_dir, _namespace|
260
- !dir?(root_dir) || ignored_paths.member?(root_dir)
261
- end
262
- end
263
-
264
- # @sig (String) -> bool
265
- def root_dir?(dir)
266
- root_dirs.key?(dir)
267
- end
268
-
269
- # @sig (String) -> bool
270
- def excluded_from_eager_load?(abspath)
271
- eager_load_exclusions.member?(abspath)
272
- end
273
-
274
- # @sig (String) -> bool
275
- def collapse?(dir)
276
- collapse_dirs.member?(dir)
277
- end
278
-
279
- # @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
280
- def expand_paths(paths)
281
- paths.flatten.map! { |path| File.expand_path(path) }
282
- end
283
-
284
- # @sig (Array[String]) -> Array[String]
285
- def expand_glob_patterns(glob_patterns)
286
- # Note that Dir.glob works with regular file names just fine. That is,
287
- # glob patterns technically need no wildcards.
288
- glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
289
- end
290
-
291
- # @sig () -> void
292
- def recompute_ignored_paths
293
- ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
294
- end
295
-
296
- # @sig () -> void
297
- def recompute_collapse_dirs
298
- collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
299
- end
300
- end
1
+ require "set"
2
+ require "securerandom"
3
+
4
+ module Zeitwerk::Loader::Config
5
+ # Absolute paths of the root directories. Stored in a hash to preserve
6
+ # order, easily handle duplicates, and also be able to have a fast lookup,
7
+ # needed for detecting nested paths.
8
+ #
9
+ # "/Users/fxn/blog/app/assets" => true,
10
+ # "/Users/fxn/blog/app/channels" => true,
11
+ # ...
12
+ #
13
+ # This is a private collection maintained by the loader. The public
14
+ # interface for it is `push_dir` and `dirs`.
15
+ #
16
+ # @private
17
+ # @sig Hash[String, true]
18
+ attr_reader :root_dirs
19
+
20
+ # @sig #camelize
21
+ attr_accessor :inflector
22
+
23
+ # Absolute paths of files, directories, or glob patterns to be totally
24
+ # ignored.
25
+ #
26
+ # @private
27
+ # @sig Set[String]
28
+ attr_reader :ignored_glob_patterns
29
+
30
+ # The actual collection of absolute file and directory names at the time the
31
+ # ignored glob patterns were expanded. Computed on setup, and recomputed on
32
+ # reload.
33
+ #
34
+ # @private
35
+ # @sig Set[String]
36
+ attr_reader :ignored_paths
37
+
38
+ # Absolute paths of directories or glob patterns to be collapsed.
39
+ #
40
+ # @private
41
+ # @sig Set[String]
42
+ attr_reader :collapse_glob_patterns
43
+
44
+ # The actual collection of absolute directory names at the time the collapse
45
+ # glob patterns were expanded. Computed on setup, and recomputed on reload.
46
+ #
47
+ # @private
48
+ # @sig Set[String]
49
+ attr_reader :collapse_dirs
50
+
51
+ # Absolute paths of files or directories not to be eager loaded.
52
+ #
53
+ # @private
54
+ # @sig Set[String]
55
+ attr_reader :eager_load_exclusions
56
+
57
+ # User-oriented callbacks to be fired on setup and on reload.
58
+ #
59
+ # @private
60
+ # @sig Array[{ () -> void }]
61
+ attr_reader :on_setup_callbacks
62
+
63
+ # User-oriented callbacks to be fired when a constant is loaded.
64
+ #
65
+ # @private
66
+ # @sig Hash[String, Array[{ (Object, String) -> void }]]
67
+ # Hash[Symbol, Array[{ (String, Object, String) -> void }]]
68
+ attr_reader :on_load_callbacks
69
+
70
+ # User-oriented callbacks to be fired before constants are removed.
71
+ #
72
+ # @private
73
+ # @sig Hash[String, Array[{ (Object, String) -> void }]]
74
+ # Hash[Symbol, Array[{ (String, Object, String) -> void }]]
75
+ attr_reader :on_unload_callbacks
76
+
77
+ # @sig #call | #debug | nil
78
+ attr_accessor :logger
79
+
80
+ def initialize
81
+ @initialized_at = Time.now
82
+ @root_dirs = {}
83
+ @inflector = Zeitwerk::Inflector.new
84
+ @ignored_glob_patterns = Set.new
85
+ @ignored_paths = Set.new
86
+ @collapse_glob_patterns = Set.new
87
+ @collapse_dirs = Set.new
88
+ @eager_load_exclusions = Set.new
89
+ @reloading_enabled = false
90
+ @on_setup_callbacks = []
91
+ @on_load_callbacks = {}
92
+ @on_unload_callbacks = {}
93
+ @logger = self.class.default_logger
94
+ @tag = SecureRandom.hex(3)
95
+ end
96
+
97
+ # Pushes `path` to the list of root directories.
98
+ #
99
+ # Raises `Zeitwerk::Error` if `path` does not exist, or if another loader in
100
+ # the same process already manages that directory or one of its ascendants or
101
+ # descendants.
102
+ #
103
+ # @raise [Zeitwerk::Error]
104
+ # @sig (String | Pathname, Module) -> void
105
+ def push_dir(path, namespace: Object)
106
+ # Note that Class < Module.
107
+ unless namespace.is_a?(Module)
108
+ raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
109
+ end
110
+
111
+ abspath = File.expand_path(path)
112
+ abspath = abspath[1..] if abspath.start_with?('/')
113
+ if dir?(abspath)
114
+ raise_if_conflicting_directory(abspath)
115
+ root_dirs[abspath] = namespace
116
+ else
117
+ STDERR.puts "Zeitwerk: the root path #{abspath} does not exist, not added"
118
+ end
119
+ end
120
+
121
+ # Returns the loader's tag.
122
+ #
123
+ # Implemented as a method instead of via attr_reader for symmetry with the
124
+ # writer below.
125
+ #
126
+ # @sig () -> String
127
+ def tag
128
+ @tag
129
+ end
130
+
131
+ # Sets a tag for the loader, useful for logging.
132
+ #
133
+ # @param tag [#to_s]
134
+ # @sig (#to_s) -> void
135
+ def tag=(tag)
136
+ @tag = tag.to_s
137
+ end
138
+
139
+ # Absolute paths of the root directories. This is a read-only collection,
140
+ # please push here via `push_dir`.
141
+ #
142
+ # @sig () -> Array[String]
143
+ def dirs
144
+ root_dirs.keys
145
+ end
146
+
147
+ # You need to call this method before setup in order to be able to reload.
148
+ # There is no way to undo this, either you want to reload or you don't.
149
+ #
150
+ # @raise [Zeitwerk::Error]
151
+ # @sig () -> void
152
+ def enable_reloading
153
+ return if @reloading_enabled
154
+
155
+ if @setup
156
+ raise Zeitwerk::Error, "cannot enable reloading after setup"
157
+ else
158
+ @reloading_enabled = true
159
+ end
160
+ end
161
+
162
+ # @sig () -> bool
163
+ def reloading_enabled?
164
+ @reloading_enabled
165
+ end
166
+
167
+ # Let eager load ignore the given files or directories. The constants defined
168
+ # in those files are still autoloadable.
169
+ #
170
+ # @sig (*(String | Pathname | Array[String | Pathname])) -> void
171
+ def do_not_eager_load(*paths)
172
+ eager_load_exclusions.merge(expand_paths(paths))
173
+ end
174
+
175
+ # Configure files, directories, or glob patterns to be totally ignored.
176
+ #
177
+ # @sig (*(String | Pathname | Array[String | Pathname])) -> void
178
+ def ignore(*glob_patterns)
179
+ glob_patterns = expand_paths(glob_patterns)
180
+ ignored_glob_patterns.merge(glob_patterns)
181
+ ignored_paths.merge(expand_glob_patterns(glob_patterns))
182
+ end
183
+
184
+ # Configure directories or glob patterns to be collapsed.
185
+ #
186
+ # @sig (*(String | Pathname | Array[String | Pathname])) -> void
187
+ def collapse(*glob_patterns)
188
+ glob_patterns = expand_paths(glob_patterns)
189
+ collapse_glob_patterns.merge(glob_patterns)
190
+ collapse_dirs.merge(expand_glob_patterns(glob_patterns))
191
+ end
192
+
193
+ # Configure a block to be called after setup and on each reload.
194
+ # If setup was already done, the block runs immediately.
195
+ #
196
+ # @sig () { () -> void } -> void
197
+ def on_setup(&block)
198
+ on_setup_callbacks << block
199
+ block.call if @setup
200
+ end
201
+
202
+ # Configure a block to be invoked once a certain constant path is loaded.
203
+ # Supports multiple callbacks, and if there are many, they are executed in
204
+ # the order in which they were defined.
205
+ #
206
+ # loader.on_load("SomeApiClient") do |klass, _abspath|
207
+ # klass.endpoint = "https://api.dev"
208
+ # end
209
+ #
210
+ # Can also be configured for any constant loaded:
211
+ #
212
+ # loader.on_load do |cpath, value, abspath|
213
+ # # ...
214
+ # end
215
+ #
216
+ # @raise [TypeError]
217
+ # @sig (String) { (Object, String) -> void } -> void
218
+ # (:ANY) { (String, Object, String) -> void } -> void
219
+ def on_load(cpath = :ANY, &block)
220
+ raise TypeError, "on_load only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
221
+
222
+ (on_load_callbacks[cpath] ||= []) << block
223
+ end
224
+
225
+ # Configure a block to be invoked right before a certain constant is removed.
226
+ # Supports multiple callbacks, and if there are many, they are executed in the
227
+ # order in which they were defined.
228
+ #
229
+ # loader.on_unload("Country") do |klass, _abspath|
230
+ # klass.clear_cache
231
+ # end
232
+ #
233
+ # Can also be configured for any removed constant:
234
+ #
235
+ # loader.on_unload do |cpath, value, abspath|
236
+ # # ...
237
+ # end
238
+ #
239
+ # @raise [TypeError]
240
+ # @sig (String) { (Object) -> void } -> void
241
+ # (:ANY) { (String, Object) -> void } -> void
242
+ def on_unload(cpath = :ANY, &block)
243
+ raise TypeError, "on_unload only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
244
+
245
+ (on_unload_callbacks[cpath] ||= []) << block
246
+ end
247
+
248
+ # @private
249
+ # @sig (String) -> bool
250
+ def ignores?(abspath)
251
+ ignored_paths.any? do |ignored_path|
252
+ ignored_path == abspath || (dir?(ignored_path) && abspath.start_with?(ignored_path + "/"))
253
+ end
254
+ end
255
+
256
+ private
257
+
258
+ # @sig () -> Array[String]
259
+ def actual_root_dirs
260
+ root_dirs.reject do |root_dir, _namespace|
261
+ !dir?(root_dir) || ignored_paths.member?(root_dir)
262
+ end
263
+ end
264
+
265
+ # @sig (String) -> bool
266
+ def root_dir?(dir)
267
+ root_dirs.key?(dir)
268
+ end
269
+
270
+ # @sig (String) -> bool
271
+ def excluded_from_eager_load?(abspath)
272
+ eager_load_exclusions.member?(abspath)
273
+ end
274
+
275
+ # @sig (String) -> bool
276
+ def collapse?(dir)
277
+ collapse_dirs.member?(dir)
278
+ end
279
+
280
+ # @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
281
+ def expand_paths(paths)
282
+ paths.flatten.map! { |path| File.expand_path(path) }
283
+ end
284
+
285
+ # @sig (Array[String]) -> Array[String]
286
+ def expand_glob_patterns(glob_patterns)
287
+ # Note that Dir.glob works with regular file names just fine. That is,
288
+ # glob patterns technically need no wildcards.
289
+ glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
290
+ end
291
+
292
+ # @sig () -> void
293
+ def recompute_ignored_paths
294
+ ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
295
+ end
296
+
297
+ # @sig () -> void
298
+ def recompute_collapse_dirs
299
+ collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
300
+ end
301
+ end