zeitwerk 1.3.1 → 1.3.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 172e4bb51ab526d5f4f606f0c4099cd9d4b8bdfe15a2ec851ed58e7095018f07
4
- data.tar.gz: 74b2d3f010de6374d919e7cae4d759a86490d91bfb7cd3f743b314e10569d2e9
3
+ metadata.gz: 40e2919df324c67a85e00ac31349b96349b1a74a39e97e3f41c513258f108542
4
+ data.tar.gz: 17c8c31d6a89e4ead544d1bc35143f548331923106decb05be67ebf57daf1bce
5
5
  SHA512:
6
- metadata.gz: 57531ede94526489ffb47235b0113ff6cb290c5d21c4b03dbbdea79a55b0e6b74d685b8f5d57f5b0bf9ec85986e731368bd57b9f5dd0ccfdc40033e5b570bbaa
7
- data.tar.gz: d50a101226b04214af4bcc6961422501044a4b9bede6d18bc06a622736bedd7d19fa9b8b9191b8f08042382d0bebcfb8e0162aa7aef37eddd2e17dbcdc6ee8a6
6
+ metadata.gz: 455ff00c99726bb3003cebfcd4d264a4288a84d3d6d5bc7e8005ed4f0e7d66eff2abf3670b474bbeb5ca0a5517e1e7e316df64ccc065775b60e9b1822e5664cc
7
+ data.tar.gz: 4971b4baec41fcffa1075248dbb714e456b2a0c37e193bd4ad47f81c658751dcf9de687ed023d4b0e00a67e57c4fa219ff7db6fe82437901774c6a0e59861dbf
data/README.md CHANGED
@@ -60,7 +60,9 @@ Main interface for gems:
60
60
  # lib/my_gem.rb (main file)
61
61
 
62
62
  require "zeitwerk"
63
- Zeitwerk::Loader.for_gem.setup # ready!
63
+ loader = Zeitwerk::Loader.for_gem
64
+ loader.setup # ready!
65
+ # loader.eager_load, optionally
64
66
 
65
67
  module MyGem
66
68
  # ...
@@ -150,6 +152,8 @@ class Hotel < ApplicationRecord
150
152
  end
151
153
  ```
152
154
 
155
+ An explicit namespace must be managed by one single loader. Loaders that reopen namespaces owned by other projects are responsible for loading their constants before setup.
156
+
153
157
  ### Nested root directories
154
158
 
155
159
  Root directories should not be ideally nested, but Zeitwerk supports them because in Rails, for example, both `app/models` and `app/models/concerns` belong to the autoload paths.
@@ -172,6 +176,8 @@ Loaders are ready to load code right after calling `setup` on them:
172
176
  loader.setup
173
177
  ```
174
178
 
179
+ This method is synchronized and idempotent.
180
+
175
181
  Customization should generally be done before that call. In particular, in the generic interface you may set the root directories from which you want to load files:
176
182
 
177
183
  ```ruby
@@ -212,6 +218,8 @@ loader.eager_load
212
218
 
213
219
  That skips ignored files and directories.
214
220
 
221
+ Eager loading is synchronized and idempotent.
222
+
215
223
  If you want to eager load yourself and all dependencies using Zeitwerk, you can broadcast the `eager_load` call to all instances:
216
224
 
217
225
  ```ruby
@@ -220,6 +228,8 @@ Zeitwerk::Loader.eager_load_all
220
228
 
221
229
  This may be handy in top-level services, like web applications.
222
230
 
231
+ Note that thanks to idempotence `Zeitwerk::Loader.eager_load_all` won't eager load twice if any of the instances already eager loaded.
232
+
223
233
  ### Preloading
224
234
 
225
235
  Zeitwerk instances are able to preload files and directories.
@@ -3,6 +3,7 @@
3
3
  module Zeitwerk
4
4
  require_relative "zeitwerk/loader"
5
5
  require_relative "zeitwerk/registry"
6
+ require_relative "zeitwerk/explicit_namespace"
6
7
  require_relative "zeitwerk/inflector"
7
8
  require_relative "zeitwerk/gem_inflector"
8
9
  require_relative "zeitwerk/kernel"
@@ -0,0 +1,77 @@
1
+ module Zeitwerk
2
+ # Centralizes the logic for the trace point used to detect the creation of
3
+ # explicit namespaces, needed to descend into matching subdirectories right
4
+ # after the constant has been defined.
5
+ #
6
+ # The implementation assumes an explicit namespace is managed by one loader.
7
+ # Loaders that reopen namespaces owned by other projects are responsible for
8
+ # loading their constant before setup. This is documented.
9
+ module ExplicitNamespace # :nodoc: all
10
+ class << self
11
+ # Maps constant paths that correspond to explicit namespaces according to
12
+ # the file system, to the loader responsible for them.
13
+ #
14
+ # @private
15
+ # @return [{String => Zeitwerk::Loader}]
16
+ attr_reader :cpaths
17
+
18
+ # @private
19
+ # @return [Mutex]
20
+ attr_reader :mutex
21
+
22
+ # @private
23
+ # @return [TracePoint]
24
+ attr_reader :tracer
25
+
26
+ # Asserts `cpath` corresponds to an explicit namespace for which `loader`
27
+ # is responsible.
28
+ #
29
+ # @private
30
+ # @param cpath [String]
31
+ # @param loader [Zeitwerk::Loader]
32
+ # @return [void]
33
+ def register(cpath, loader)
34
+ mutex.synchronize do
35
+ cpaths[cpath] = loader
36
+ # We check enabled? because, looking at the C source code, enabling an
37
+ # enabled tracer does not seem to be a simple no-op.
38
+ tracer.enable unless tracer.enabled?
39
+ end
40
+ end
41
+
42
+ # @private
43
+ # @param loader [Zeitwerk::Loader]
44
+ # @return [void]
45
+ def unregister(loader)
46
+ cpaths.delete_if { |_cpath, l| l == loader }
47
+ disable_tracer_if_unneeded
48
+ end
49
+
50
+ # Utility for the test suite
51
+ #
52
+ # @private
53
+ # @return [void]
54
+ def teardown
55
+ cpaths.clear
56
+ tracer.disable
57
+ end
58
+
59
+ def disable_tracer_if_unneeded
60
+ mutex.synchronize do
61
+ tracer.disable if cpaths.empty?
62
+ end
63
+ end
64
+ end
65
+
66
+ @cpaths = {}
67
+ @mutex = Mutex.new
68
+ @tracer = TracePoint.new(:class) do |event|
69
+ # Note that it makes sense to compute the hash code unconditionally,
70
+ # because if cpaths was empty the trace point would be disabled.
71
+ if loader = cpaths.delete(event.self.name)
72
+ loader.on_namespace_loaded(event.self)
73
+ disable_tracer_if_unneeded
74
+ end
75
+ end
76
+ end
77
+ end
@@ -92,14 +92,6 @@ module Zeitwerk
92
92
  # @return [Mutex]
93
93
  attr_reader :mutex
94
94
 
95
- # This tracer listens to `:class` events, and it is used to support explicit
96
- # namespaces. Benchmarks have shown the tracer does not impact performance
97
- # in any measurable way.
98
- #
99
- # @private
100
- # @return [TracePoint]
101
- attr_reader :tracer
102
-
103
95
  def initialize
104
96
  @initialized_at = Time.now
105
97
 
@@ -119,10 +111,6 @@ module Zeitwerk
119
111
  @setup = false
120
112
  @eager_loaded = false
121
113
 
122
- @tracer = TracePoint.new(:class) do |tp|
123
- on_namespace_loaded(tp.self)
124
- end
125
-
126
114
  Registry.register_loader(self)
127
115
  end
128
116
 
@@ -230,8 +218,8 @@ module Zeitwerk
230
218
  lazy_subdirs.clear
231
219
 
232
220
  Registry.on_unload(self)
221
+ ExplicitNamespace.unregister(self)
233
222
 
234
- disable_tracer
235
223
  @setup = false
236
224
  end
237
225
  end
@@ -268,7 +256,6 @@ module Zeitwerk
268
256
  end
269
257
  end
270
258
 
271
- disable_tracer
272
259
  @eager_loaded = true
273
260
  end
274
261
  end
@@ -359,11 +346,12 @@ module Zeitwerk
359
346
  # @return [void]
360
347
  def autoload_subdir(parent, cname, subdir)
361
348
  if autoload_path = autoload_for?(parent, cname)
362
- enable_tracer if ruby?(autoload_path)
349
+ cpath = cpath(parent, cname)
350
+ register_explicit_namespace(cpath) if ruby?(autoload_path)
363
351
  # We do not need to issue another autoload, the existing one is enough
364
352
  # no matter if it is for a file or a directory. Just remember the
365
353
  # subdirectory has to be visited if the namespace is used.
366
- (lazy_subdirs[cpath(parent, cname)] ||= []) << subdir
354
+ (lazy_subdirs[cpath] ||= []) << subdir
367
355
  elsif !cdef?(parent, cname)
368
356
  # First time we find this namespace, set an autoload for it.
369
357
  (lazy_subdirs[cpath(parent, cname)] ||= []) << subdir
@@ -390,7 +378,7 @@ module Zeitwerk
390
378
  Registry.unregister_autoload(autoload_path)
391
379
 
392
380
  set_autoload(parent, cname, file)
393
- enable_tracer
381
+ register_explicit_namespace(cpath(parent, cname))
394
382
  elsif !cdef?(parent, cname)
395
383
  set_autoload(parent, cname, file)
396
384
  end
@@ -508,20 +496,14 @@ module Zeitwerk
508
496
  logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
509
497
  end
510
498
 
511
- def enable_tracer
512
- # We check enabled? because, looking at the C source code, enabling an
513
- # enabled tracer does not seem to be a simple no-op.
514
- tracer.enable if !tracer.enabled?
515
- end
516
-
517
- def disable_tracer
518
- tracer.disable if tracer.enabled?
519
- end
520
-
521
499
  def cdef?(parent, cname)
522
500
  parent.const_defined?(cname, false)
523
501
  end
524
502
 
503
+ def register_explicit_namespace(cpath)
504
+ ExplicitNamespace.register(cpath, self)
505
+ end
506
+
525
507
  def raise_if_conflicting_directory(dir)
526
508
  self.class.mutex.synchronize do
527
509
  Registry.loaders.each do |loader|
@@ -0,0 +1,21 @@
1
+ module Zeitwerk
2
+ class OrderedSet
3
+ def initialize
4
+ @set = {}
5
+ end
6
+
7
+ def add(object)
8
+ @set[object] = true
9
+ end
10
+
11
+ def del(object)
12
+ @set.delete(object)
13
+ end
14
+
15
+ def each
16
+ @set.each_key do |key|
17
+ yield key
18
+ end
19
+ end
20
+ end
21
+ end
@@ -137,6 +137,16 @@ module Zeitwerk
137
137
  autoloads.delete_if { |_path, object| object == loader }
138
138
  inceptions.delete_if { |_cpath, (_path, object)| object == loader }
139
139
  end
140
+
141
+ # Utility for the test suite
142
+ #
143
+ # @private
144
+ # @return [void]
145
+ def teardown
146
+ loaders.each(&:unload)
147
+ loaders.clear
148
+ loaders_managing_gems.clear
149
+ end
140
150
  end
141
151
 
142
152
  @loaders = []
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "1.3.1"
4
+ VERSION = "1.3.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zeitwerk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-23 00:00:00.000000000 Z
11
+ date: 2019-03-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -23,11 +23,13 @@ files:
23
23
  - README.md
24
24
  - lib/zeitwerk.rb
25
25
  - lib/zeitwerk/conflicting_directory.rb
26
+ - lib/zeitwerk/explicit_namespace.rb
26
27
  - lib/zeitwerk/gem_inflector.rb
27
28
  - lib/zeitwerk/inflector.rb
28
29
  - lib/zeitwerk/kernel.rb
29
30
  - lib/zeitwerk/loader.rb
30
31
  - lib/zeitwerk/loader/callbacks.rb
32
+ - lib/zeitwerk/ordered_set.rb
31
33
  - lib/zeitwerk/registry.rb
32
34
  - lib/zeitwerk/version.rb
33
35
  homepage: https://github.com/fxn/zeitwerk