zeitwerk 1.1.0 → 1.2.0.beta

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: ce0c3d755356cd98361bd89b27925a3083632a8e2f25191808e987d0f5a0c40e
4
- data.tar.gz: fb21b9d8e59e12b672a464e748157814c265898da26a71225229f29d9ea9023f
3
+ metadata.gz: '08f2b427ddbb6d8f7b995820ebafc52933ae2682fd19779097b6ab8c95205644'
4
+ data.tar.gz: 16b8e508f9026e87f2a66ecfe587a4b19dcc213e3393743b00d82e560ea601fb
5
5
  SHA512:
6
- metadata.gz: b3fa972c877ed3c36efc98224088869ec383370bbf002761e0f379550ce31e50cf387a729890fd36e4768d99929a566ef3587a717208aa7d6e8e7abb1c29df1b
7
- data.tar.gz: ab0b2b3e11d46e21ce3605f8dcf8dcb6a579ca6ce7438c58d6b87d07f1431f4c346bdc07d5c1ae9601a577d5c592e938f07a56fe583b8fa27a308371afca4fea
6
+ metadata.gz: 1a41b015721362e40fdc5ae4402e388dfd38814d642a87e89aa4a90f36dca863cb562be68b11269817269a58862bf89761f1b671ff9be8b24dd220114a85298e
7
+ data.tar.gz: b2f7d5b6f61ee132a0608550f1e18bc58c8b2eff7eba154534742945373c4409e82eb7556a09a4179aca903d9b0c2136d2833902e2a3ea1743f4ade792902c91
data/README.md CHANGED
@@ -24,9 +24,9 @@
24
24
  - [Logging](#logging)
25
25
  - [Loader tag](#loader-tag)
26
26
  - [Ignoring parts of the project](#ignoring-parts-of-the-project)
27
- - [Files that do not follow the conventions](#files-that-do-not-follow-the-conventions)
28
- - [The adapter pattern](#the-adapter-pattern)
29
- - [Test files mixed with implementation files](#test-files-mixed-with-implementation-files)
27
+ - [Use case: Files that do not follow the conventions](#use-case-files-that-do-not-follow-the-conventions)
28
+ - [Use case: The adapter pattern](#use-case-the-adapter-pattern)
29
+ - [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
30
30
  - [Edge cases](#edge-cases)
31
31
  - [Pronunciation](#pronunciation)
32
32
  - [Supported Ruby versions](#supported-ruby-versions)
@@ -336,7 +336,7 @@ You can ignore file names, directory names, and glob patterns. Glob patterns are
336
336
 
337
337
  Let's see some use cases.
338
338
 
339
- #### Files that do not follow the conventions
339
+ #### Use case: Files that do not follow the conventions
340
340
 
341
341
  Let's suppose that your gem decorates something in `Kernel`:
342
342
 
@@ -356,9 +356,28 @@ loader.ignore(kernel_ext)
356
356
  loader.setup
357
357
  ```
358
358
 
359
- #### The adapter pattern
359
+ You can also ignore the whole directory:
360
360
 
361
- Another use case for ignoring files is the adapter pattern. Let's imagine your project talks to databases, supports several, and has adapters for each one of them. Those adapters may have top-level `require` calls that load their respective drivers, but you don't want your users to install them all, only the one they are going to use. On the other hand, if your code is eager loaded by you or a parent project (with `Zeitwerk::Loader.eager_load_all`), those `require` calls are going to be executed. Ignoring the adapters prevents that:
361
+ ```ruby
362
+ core_ext = "#{__dir__}/my_gem/core_ext"
363
+ loader.ignore(core_ext)
364
+ loader.setup
365
+ ```
366
+
367
+ #### Use case: The adapter pattern
368
+
369
+ Another use case for ignoring files is the adapter pattern.
370
+
371
+ Let's imagine your project talks to databases, supports several, and has adapters for each one of them. Those adapters may have top-level `require` calls that load their respective drivers:
372
+
373
+ ```ruby
374
+ # my_gem/db_adapters/postgresql.rb
375
+ require "pg"
376
+ ```
377
+
378
+ but you don't want your users to install them all, only the one they are going to use.
379
+
380
+ On the other hand, if your code is eager loaded by you or a parent project (with `Zeitwerk::Loader.eager_load_all`), those `require` calls are going to be executed. Ignoring the adapters prevents that:
362
381
 
363
382
  ```ruby
364
383
  db_adapters = "#{__dir__}/my_gem/db_adapters"
@@ -372,7 +391,7 @@ The chosen adapter, then, has to be loaded by hand somehow:
372
391
  require "my_gem/db_adapters/#{config[:db_adapter]}"
373
392
  ```
374
393
 
375
- #### Test files mixed with implementation files
394
+ #### Use case: Test files mixed with implementation files
376
395
 
377
396
  There are project layouts that put implementation files and test files together. To ignore the test files, you can use a glob pattern like this:
378
397
 
@@ -6,4 +6,5 @@ module Zeitwerk
6
6
  require_relative "zeitwerk/inflector"
7
7
  require_relative "zeitwerk/gem_inflector"
8
8
  require_relative "zeitwerk/kernel"
9
+ require_relative "zeitwerk/conflicting_directory"
9
10
  end
@@ -0,0 +1,2 @@
1
+ class Zeitwerk::ConflictingDirectory < StandardError
2
+ end
@@ -145,9 +145,10 @@ module Zeitwerk
145
145
  # @param path [<String, Pathname>]
146
146
  # @return [void]
147
147
  def push_dir(path)
148
- abspath = File.expand_path(path)
149
- mutex.synchronize do
148
+ self.class.mutex.synchronize do
149
+ abspath = File.expand_path(path)
150
150
  if dir?(abspath)
151
+ raise_if_conflicting_directory(abspath)
151
152
  root_dirs[abspath] = true
152
153
  else
153
154
  raise ArgumentError, "the root directory #{abspath} does not exist"
@@ -191,7 +192,7 @@ module Zeitwerk
191
192
  mutex.synchronize do
192
193
  unless @setup
193
194
  expand_ignored_glob_patterns
194
- non_ignored_root_dirs.each { |dir| set_autoloads_in_dir(dir, Object) }
195
+ non_ignored_root_dirs.each { |root_dir| set_autoloads_in_dir(root_dir, Object) }
195
196
  do_preload
196
197
  @setup = true
197
198
  end
@@ -253,11 +254,21 @@ module Zeitwerk
253
254
  # @return [void]
254
255
  def eager_load
255
256
  mutex.synchronize do
256
- unless @eager_loaded
257
- non_ignored_root_dirs.each { |dir| eager_load_dir(dir) }
258
- disable_tracer
259
- @eager_loaded = true
257
+ break if @eager_loaded
258
+
259
+ queue = non_ignored_root_dirs
260
+ while dir = queue.shift
261
+ each_abspath(dir) do |abspath|
262
+ if ruby?(abspath)
263
+ require abspath
264
+ elsif dir?(abspath)
265
+ queue << abspath
266
+ end
267
+ end
260
268
  end
269
+
270
+ disable_tracer
271
+ @eager_loaded = true
261
272
  end
262
273
  end
263
274
 
@@ -275,6 +286,10 @@ module Zeitwerk
275
286
  # @return [#call, nil]
276
287
  attr_accessor :default_logger
277
288
 
289
+ # @private
290
+ # @return [Mutex]
291
+ attr_accessor :mutex
292
+
278
293
  # This is a shortcut for
279
294
  #
280
295
  # require "zeitwerk"
@@ -307,6 +322,8 @@ module Zeitwerk
307
322
  end
308
323
  end
309
324
 
325
+ self.mutex = Mutex.new
326
+
310
327
  # --- Callbacks -------------------------------------------------------------------------------
311
328
 
312
329
  # Callback invoked from Kernel when a managed file is loaded.
@@ -327,12 +344,14 @@ module Zeitwerk
327
344
  # @return [void]
328
345
  def on_dir_loaded(dir)
329
346
  parent, cname = autoloads[dir]
330
- loaded.add(cpath(parent, cname))
331
- autovivified = parent.const_set(cname, Module.new)
332
- log("module #{cpath(parent, cname)} autovivified from directory #{dir}") if logger
333
347
 
334
- if subdirs = lazy_subdirs[cpath(parent, cname)]
335
- subdirs.each { |subdir| set_autoloads_in_dir(subdir, autovivified) }
348
+ autovivified_module = parent.const_set(cname, Module.new)
349
+ log("module #{autovivified_module.name} autovivified from directory #{dir}") if logger
350
+
351
+ loaded.add(autovivified_module.name)
352
+
353
+ if subdirs = lazy_subdirs[autovivified_module.name]
354
+ subdirs.each { |subdir| set_autoloads_in_dir(subdir, autovivified_module) }
336
355
  end
337
356
  end
338
357
 
@@ -340,7 +359,7 @@ module Zeitwerk
340
359
 
341
360
  # @return [<String>]
342
361
  def non_ignored_root_dirs
343
- root_dirs.keys.delete_if { |dir| ignored_paths.member?(dir) }
362
+ root_dirs.keys.delete_if { |root_dir| ignored_paths.member?(root_dir) }
344
363
  end
345
364
 
346
365
  # @param dir [String]
@@ -440,21 +459,6 @@ module Zeitwerk
440
459
  parent.autoload?(cname) || Registry.inception?(cpath(parent, cname))
441
460
  end
442
461
 
443
- # @param dir [String]
444
- # @return [void]
445
- def eager_load_dir(dir)
446
- queue = [dir]
447
- while dir = queue.shift
448
- each_abspath(dir) do |abspath|
449
- if ruby?(abspath)
450
- require abspath
451
- elsif dir?(abspath)
452
- queue << abspath
453
- end
454
- end
455
- end
456
- end
457
-
458
462
  # This method is called this way because I prefer `preload` to be the method
459
463
  # name to configure preloads in the public interface.
460
464
  #
@@ -545,5 +549,20 @@ module Zeitwerk
545
549
  def cdef?(parent, cname)
546
550
  parent.const_defined?(cname, false)
547
551
  end
552
+
553
+ def raise_if_conflicting_directory(dir)
554
+ Registry.loaders.each do |loader|
555
+ next if loader == self
556
+
557
+ loader.dirs.each do |already_managed_dir|
558
+ if dir.start_with?(already_managed_dir) || already_managed_dir.start_with?(dir)
559
+ raise ConflictingDirectory,
560
+ "loader\n\n\t#{inspect}\n\nwants to manage directory #{dir}," \
561
+ " which is already managed by\n\n\t#{loader.inspect}"
562
+ EOS
563
+ end
564
+ end
565
+ end
566
+ end
548
567
  end
549
568
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0.beta"
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.1.0
4
+ version: 1.2.0.beta
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-14 00:00:00.000000000 Z
11
+ date: 2019-02-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -22,6 +22,7 @@ extra_rdoc_files: []
22
22
  files:
23
23
  - README.md
24
24
  - lib/zeitwerk.rb
25
+ - lib/zeitwerk/conflicting_directory.rb
25
26
  - lib/zeitwerk/gem_inflector.rb
26
27
  - lib/zeitwerk/inflector.rb
27
28
  - lib/zeitwerk/kernel.rb
@@ -43,9 +44,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
43
44
  version: 2.4.4
44
45
  required_rubygems_version: !ruby/object:Gem::Requirement
45
46
  requirements:
46
- - - ">="
47
+ - - ">"
47
48
  - !ruby/object:Gem::Version
48
- version: '0'
49
+ version: 1.3.1
49
50
  requirements: []
50
51
  rubygems_version: 3.0.1
51
52
  signing_key: