zeitwerk 2.1.8 → 2.1.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a89545605d519214e873572adc81662ece65e5d3aa6ffaa19f04877beb2d576
4
- data.tar.gz: ac086c60c9391fbdaed5875b88038e6a977bf63110e39c38357b160056997b07
3
+ metadata.gz: e58e19aad2055cd64f4ac3018cd6425e8f29f83d765b2410a28bef02f3fee381
4
+ data.tar.gz: e23bb3b21e15667fb8be922b0ced718ba135a5610cc0c6965304701ff580e404
5
5
  SHA512:
6
- metadata.gz: 58326a65a566e5c72e8e2a588eb2dd4dda97efc485ebe422dcc63d9e9b3e83395f9dac5a3e35fe0cb9b17efc75cca3a41f73e3d3ac99b9b49a9d999d9e73b551
7
- data.tar.gz: 24475fe5d47dadf250ce4a658bc44ecd72d29f6e0cd87ce8502d62eaa2e4ba7dbecb2bea2a28cb3279a3fb19bb5aa82b5676b7151b9f301e3bea607b2d75854c
6
+ metadata.gz: 3e6e6271f1f9cce47fb81b50d495aeb1159819ee68b0675752bb329494d77524ac624cb067d96f638a1580c2ad342832e3eab9f8cf8ad6b296d83524ef590f03
7
+ data.tar.gz: 7b4c4bae3b1fdf0214eeb7881ab855ad1babd5a28d1ab640105405b073bc1fcc54b08df1d6d0c416584363804268d7428b082a9822d8a0870f59224462ded513
data/README.md CHANGED
@@ -17,7 +17,6 @@
17
17
  - [Setup](#setup)
18
18
  - [Reloading](#reloading)
19
19
  - [Eager loading](#eager-loading)
20
- - [Preloading](#preloading)
21
20
  - [Inflection](#inflection)
22
21
  - [Zeitwerk::Inflector](#zeitwerkinflector)
23
22
  - [Zeitwerk::GemInflector](#zeitwerkgeminflector)
@@ -61,12 +60,13 @@ Main interface for gems:
61
60
 
62
61
  require "zeitwerk"
63
62
  loader = Zeitwerk::Loader.for_gem
64
- loader.setup # ready!
65
- loader.eager_load # optionally
63
+ loader.setup # ready!
66
64
 
67
65
  module MyGem
68
66
  # ...
69
67
  end
68
+
69
+ loader.eager_load # optionally
70
70
  ```
71
71
 
72
72
  Main generic interface:
@@ -259,22 +259,6 @@ This may be handy in top-level services, like web applications.
259
259
 
260
260
  Note that thanks to idempotence `Zeitwerk::Loader.eager_load_all` won't eager load twice if any of the instances already eager loaded.
261
261
 
262
- <a id="markdown-preloading" name="preloading"></a>
263
- ### Preloading
264
-
265
- Zeitwerk instances are able to preload files and directories.
266
-
267
- ```ruby
268
- loader.preload("app/models/videogame.rb")
269
- loader.preload("app/models/book.rb")
270
- ```
271
-
272
- The call can happen before `setup` (preloads during setup), or after `setup` (preloads on the spot). Each reload preloads too.
273
-
274
- This is a feature specifically thought for STIs in Rails, preloading the leafs of a STI tree ensures all classes are known when doing a query.
275
-
276
- The example above depicts several calls are supported, but `preload` accepts multiple arguments and arrays of strings as well.
277
-
278
262
  <a id="markdown-inflection" name="inflection"></a>
279
263
  ### Inflection
280
264
 
@@ -399,7 +383,7 @@ Zeitwerk ignores automatically any file or directory whose name starts with a do
399
383
 
400
384
  However, sometimes it might still be convenient to tell Zeitwerk to completely ignore some particular Ruby file or directory. That is possible with `ignore`, which accepts an arbitrary number of strings or `Pathname` objects, and also an array of them.
401
385
 
402
- You can ignore file names, directory names, and glob patterns. Glob patterns are expanded on setup and on reload.
386
+ You can ignore file names, directory names, and glob patterns. Glob patterns are expanded when they are added and again on each reload.
403
387
 
404
388
  Let's see some use cases.
405
389
 
@@ -460,6 +444,8 @@ The chosen adapter, then, has to be loaded by hand somehow:
460
444
  require "my_gem/db_adapters/#{config[:db_adapter]}"
461
445
  ```
462
446
 
447
+ Note that since the directory is ignored, the required adapter can instantiate another loader to manage its subtree, if desired. Such loader would coexist with the main one just fine.
448
+
463
449
  <a id="markdown-use-case-test-files-mixed-with-implementation-files" name="use-case-test-files-mixed-with-implementation-files"></a>
464
450
  #### Use case: Test files mixed with implementation files
465
451
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
+ require_relative "zeitwerk/real_mod_name"
4
5
  require_relative "zeitwerk/loader"
5
6
  require_relative "zeitwerk/registry"
6
7
  require_relative "zeitwerk/explicit_namespace"
@@ -8,6 +8,8 @@ module Zeitwerk
8
8
  # loading their constant before setup. This is documented.
9
9
  module ExplicitNamespace # :nodoc: all
10
10
  class << self
11
+ include RealModName
12
+
11
13
  # Maps constant paths that correspond to explicit namespaces according to
12
14
  # the file system, to the loader responsible for them.
13
15
  #
@@ -52,21 +54,27 @@ module Zeitwerk
52
54
  tracer.disable if cpaths.empty?
53
55
  end
54
56
  end
57
+
58
+ def tracepoint_class_callback(event)
59
+ # If the class is a singleton class, we won't do anything with it so we
60
+ # can bail out immediately. This is several orders of magnitude faster
61
+ # than accessing its name.
62
+ return if event.self.singleton_class?
63
+
64
+ # Note that it makes sense to compute the hash code unconditionally,
65
+ # because the trace point is disabled if cpaths is empty.
66
+ if loader = cpaths.delete(real_mod_name(event.self))
67
+ loader.on_namespace_loaded(event.self)
68
+ disable_tracer_if_unneeded
69
+ end
70
+ end
55
71
  end
56
72
 
57
73
  @cpaths = {}
58
74
  @mutex = Mutex.new
59
- @tracer = TracePoint.new(:class) do |event|
60
- # If the class is a singleton class, we won't do anything with it so we can bail out immediately.
61
- # This is several orders of magnitude faster than accessing `Module#name`.
62
- next if event.self.singleton_class?
63
75
 
64
- # Note that it makes sense to compute the hash code unconditionally,
65
- # because the trace point is disabled if cpaths is empty.
66
- if loader = cpaths.delete(event.self.name)
67
- loader.on_namespace_loaded(event.self)
68
- disable_tracer_if_unneeded
69
- end
70
- end
76
+ # We go through a method instead of defining a block mainly to have a better
77
+ # label when profiling.
78
+ @tracer = TracePoint.new(:class, &method(:tracepoint_class_callback))
71
79
  end
72
80
  end
@@ -7,6 +7,7 @@ module Zeitwerk
7
7
  class Loader
8
8
  require_relative "loader/callbacks"
9
9
  include Callbacks
10
+ include RealModName
10
11
 
11
12
  # @return [String]
12
13
  attr_reader :tag
@@ -43,7 +44,7 @@ module Zeitwerk
43
44
  #
44
45
  # @private
45
46
  # @return [Set<String>]
46
- attr_reader :ignored
47
+ attr_reader :ignored_glob_patterns
47
48
 
48
49
  # The actual collection of absolute file and directory names at the time the
49
50
  # ignored glob patterns were expanded. Computed on setup, and recomputed on
@@ -132,7 +133,7 @@ module Zeitwerk
132
133
 
133
134
  @root_dirs = {}
134
135
  @preloads = []
135
- @ignored = Set.new
136
+ @ignored_glob_patterns = Set.new
136
137
  @ignored_paths = Set.new
137
138
  @autoloads = {}
138
139
  @autoloaded_dirs = []
@@ -224,16 +225,12 @@ module Zeitwerk
224
225
  #
225
226
  # @param paths [<String, Pathname, <String, Pathname>>]
226
227
  # @return [void]
227
- def ignore(*paths)
228
- mutex.synchronize { ignored.merge(expand_paths(paths)) }
229
- end
230
-
231
- # @private
232
- # @return [void]
233
- def expand_ignored_glob_patterns
234
- # Note that Dir.glob works with regular file names just fine. That is,
235
- # glob patterns technically need no wildcards.
236
- ignored_paths.replace(ignored.flat_map { |path| Dir.glob(path) })
228
+ def ignore(*glob_patterns)
229
+ glob_patterns = expand_paths(glob_patterns)
230
+ mutex.synchronize do
231
+ ignored_glob_patterns.merge(glob_patterns)
232
+ ignored_paths.merge(expand_glob_patterns(glob_patterns))
233
+ end
237
234
  end
238
235
 
239
236
  # Sets autoloads in the root namespace and preloads files, if any.
@@ -243,7 +240,6 @@ module Zeitwerk
243
240
  mutex.synchronize do
244
241
  break if @setup
245
242
 
246
- expand_ignored_glob_patterns
247
243
  actual_root_dirs.each { |root_dir| set_autoloads_in_dir(root_dir, Object) }
248
244
  do_preload
249
245
 
@@ -325,6 +321,7 @@ module Zeitwerk
325
321
  def reload
326
322
  if reloading_enabled?
327
323
  unload
324
+ recompute_ignored_paths
328
325
  setup
329
326
  else
330
327
  raise ReloadingDisabledError, "can't reload, please call loader.enable_reloading before setup"
@@ -402,6 +399,22 @@ module Zeitwerk
402
399
  @logger = ->(msg) { puts msg }
403
400
  end
404
401
 
402
+ # @private
403
+ # @param dir [String]
404
+ # @return [Boolean]
405
+ def manages?(dir)
406
+ dir = dir + "/"
407
+ ignored_paths.each do |ignored_path|
408
+ return false if dir.start_with?(ignored_path + "/")
409
+ end
410
+
411
+ root_dirs.each_key do |root_dir|
412
+ return true if root_dir.start_with?(dir) || dir.start_with?(root_dir + "/")
413
+ end
414
+
415
+ false
416
+ end
417
+
405
418
  # --- Class methods ---------------------------------------------------------------------------
406
419
 
407
420
  class << self
@@ -636,7 +649,7 @@ module Zeitwerk
636
649
  # @param cname [Symbol]
637
650
  # @return [String]
638
651
  def cpath(parent, cname)
639
- parent.equal?(Object) ? cname.to_s : "#{parent.name}::#{cname}"
652
+ parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
640
653
  end
641
654
 
642
655
  # @param dir [String]
@@ -665,7 +678,20 @@ module Zeitwerk
665
678
  # @param paths [<String, Pathname, <String, Pathname>>]
666
679
  # @return [<String>]
667
680
  def expand_paths(paths)
668
- Array(paths).flatten.map! { |path| File.expand_path(path) }
681
+ paths.flatten.map! { |path| File.expand_path(path) }
682
+ end
683
+
684
+ # @param glob_patterns [<String>]
685
+ # @return [<String>]
686
+ def expand_glob_patterns(glob_patterns)
687
+ # Note that Dir.glob works with regular file names just fine. That is,
688
+ # glob patterns technically need no wildcards.
689
+ glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
690
+ end
691
+
692
+ # @return [void]
693
+ def recompute_ignored_paths
694
+ ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
669
695
  end
670
696
 
671
697
  # @param message [String]
@@ -686,16 +712,12 @@ module Zeitwerk
686
712
  def raise_if_conflicting_directory(dir)
687
713
  self.class.mutex.synchronize do
688
714
  Registry.loaders.each do |loader|
689
- next if loader == self
690
-
691
- loader.dirs.each do |already_managed_dir|
692
- if dir.start_with?(already_managed_dir) || already_managed_dir.start_with?(dir)
693
- require "pp"
694
- raise Error,
695
- "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
696
- " which is already managed by\n\n#{loader.pretty_inspect}\n"
697
- EOS
698
- end
715
+ if loader != self && loader.manages?(dir)
716
+ require "pp"
717
+ raise Error,
718
+ "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
719
+ " which is already managed by\n\n#{loader.pretty_inspect}\n"
720
+ EOS
699
721
  end
700
722
  end
701
723
  end
@@ -1,4 +1,6 @@
1
1
  module Zeitwerk::Loader::Callbacks
2
+ include Zeitwerk::RealModName
3
+
2
4
  # Invoked from our decorated Kernel#require when a managed file is autoloaded.
3
5
  #
4
6
  # @private
@@ -60,7 +62,7 @@ module Zeitwerk::Loader::Callbacks
60
62
  # @param namespace [Module]
61
63
  # @return [void]
62
64
  def on_namespace_loaded(namespace)
63
- if subdirs = lazy_subdirs.delete(namespace.name)
65
+ if subdirs = lazy_subdirs.delete(real_mod_name(namespace))
64
66
  subdirs.each do |subdir|
65
67
  set_autoloads_in_dir(subdir, namespace)
66
68
  end
@@ -0,0 +1,15 @@
1
+ module Zeitwerk::RealModName
2
+ UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
3
+ private_constant :UNBOUND_METHOD_MODULE_NAME
4
+
5
+ # Returns the real name of the class or module, as set after the first
6
+ # constant to which it was assigned (or nil).
7
+ #
8
+ # The name method can be overridden, hence the indirection in this method.
9
+ #
10
+ # @param mod [Class, Module]
11
+ # @return [String]
12
+ def real_mod_name(mod)
13
+ UNBOUND_METHOD_MODULE_NAME.bind(mod).call
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.1.8"
4
+ VERSION = "2.1.9"
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: 2.1.8
4
+ version: 2.1.9
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-06-29 00:00:00.000000000 Z
11
+ date: 2019-07-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -29,6 +29,7 @@ files:
29
29
  - lib/zeitwerk/kernel.rb
30
30
  - lib/zeitwerk/loader.rb
31
31
  - lib/zeitwerk/loader/callbacks.rb
32
+ - lib/zeitwerk/real_mod_name.rb
32
33
  - lib/zeitwerk/registry.rb
33
34
  - lib/zeitwerk/version.rb
34
35
  homepage: https://github.com/fxn/zeitwerk