zeitwerk 2.1.5 → 2.1.7

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: e89de8ad7897a95ccc14f71e1688eb826c7e01f2c0c9381e09de5dd8815d1974
4
- data.tar.gz: 11bf19ab1b396399afc3110e8122cb783d2a2d64a889fa5ac7e506e65a8cc9be
3
+ metadata.gz: 2cf7265c8e1a167cdf0107bdef2643d219317855b0f5a06b77048672e3a192a3
4
+ data.tar.gz: d1429158609c03b57f85b261290e61d88c7762f02693232336a0f4eb94bc7417
5
5
  SHA512:
6
- metadata.gz: b9a3e74bde3eec64186834b010f3c047d0b1c849a95c89747c6195b3a5fef974d4b1018cf4fca64a46e27aa5768c543d73b6203af80b170c20dab108544833e0
7
- data.tar.gz: 2055af7e3b74fa7c6c3e0c1e7dc6bb1888736c5d8204106ed0504b83a3cba74f7db2a24a72a0e2afc995cf780d4ebcd5965c74262150bb9c90fc3abf4ae97310
6
+ metadata.gz: 3457b0e4afeb1e134db7815e538c8251aab11bda9b9feb8eec07257506786693f9f9ec0c0eb7a11c0663502f48778b37939cb47483d84b369ed82830518ff78a
7
+ data.tar.gz: 4996a053629f48004cb4c86f24a649295baaf1afbde0a2a5b5be0c3163205376659382ac3a9322586e2b86fee200c8bf97c5bb8faa2ae17c19aa0b169457af09
data/README.md CHANGED
@@ -29,6 +29,7 @@
29
29
  - [Use case: The adapter pattern](#use-case-the-adapter-pattern)
30
30
  - [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
31
31
  - [Edge cases](#edge-cases)
32
+ - [Rules of thumb](#rules-of-thumb)
32
33
  - [Pronunciation](#pronunciation)
33
34
  - [Supported Ruby versions](#supported-ruby-versions)
34
35
  - [Motivation](#motivation)
@@ -42,15 +43,13 @@
42
43
 
43
44
  Zeitwerk is an efficient and thread-safe code loader for Ruby.
44
45
 
45
- Given a conventional file structure, Zeitwerk loads your project's classes and modules on demand. You don't need to write `require` calls for your own files, rather, you can streamline your programming knowing that your classes and modules are available everywhere. This feature is efficient, thread-safe, and matches Ruby's semantics for constants.
46
+ Given a [conventional file structure](#file-structure), Zeitwerk is able to load your project's classes and modules on demand (autoloading), or upfront (eager loading). You don't need to write `require` calls for your own files, rather, you can streamline your programming knowing that your classes and modules are available everywhere. This feature is efficient, thread-safe, and matches Ruby's semantics for constants.
46
47
 
47
- Zeitwerk issues `require` calls exclusively using absolute file names, so there are no costly file system lookups in `$LOAD_PATH`. Technically, the directories managed by Zeitwerk do not even need to be in `$LOAD_PATH`. Furthermore, by design, Zeitwerk does only one single scan of the project tree, and it descends into subdirectories lazily, only if their namespaces are used.
48
+ Zeitwerk is also able to reload code, which may be handy while developing web applications. Coordination is needed to reload in a thread-safe manner. The documentation below explains how to do this.
48
49
 
49
- The library is designed so that each gem and application can have their own loader, independent of each other. Each loader has its own configuration, inflector, and optional logger.
50
+ The gem is designed so that any project, gem dependency, application, etc. can have their own independent loader, coexisting in the same process, managing their own project trees, and independent of each other. Each loader has its own configuration, inflector, and optional logger.
50
51
 
51
- Zeitwerk is also able to reload code, which may be handy for web applications. Coordination is needed to reload in a thread-safe manner. The documentation below explains how to do this.
52
-
53
- Finally, in some production setups it may be optimal to eager load all code upfront. Zeitwerk is able to do that too.
52
+ Internally, Zeitwerk issues `require` calls exclusively using absolute file names, so there are no costly file system lookups in `$LOAD_PATH`. Technically, the directories managed by Zeitwerk do not even need to be in `$LOAD_PATH`. Furthermore, Zeitwerk does only one single scan of the project tree, and it descends into subdirectories lazily, only if their namespaces are used.
54
53
 
55
54
  <a id="markdown-synopsis" name="synopsis"></a>
56
55
  ## Synopsis
@@ -62,8 +61,8 @@ Main interface for gems:
62
61
 
63
62
  require "zeitwerk"
64
63
  loader = Zeitwerk::Loader.for_gem
65
- loader.setup # ready!
66
- # loader.eager_load, optionally
64
+ loader.setup # ready!
65
+ loader.eager_load # optionally
67
66
 
68
67
  module MyGem
69
68
  # ...
@@ -205,7 +204,7 @@ Zeitwerk works internally only with absolute paths to avoid costly file searches
205
204
  <a id="markdown-reloading" name="reloading"></a>
206
205
  ### Reloading
207
206
 
208
- Zeitwer is able to reload code, but you need to enable this feature:
207
+ Zeitwerk is able to reload code, but you need to enable this feature:
209
208
 
210
209
  ```ruby
211
210
  loader = Zeitwerk::Loader.new
@@ -338,7 +337,15 @@ This needs to be done before calling `setup`.
338
337
  <a id="markdown-logging" name="logging"></a>
339
338
  ### Logging
340
339
 
341
- Zeitwerk is silent by default, but you can configure a callable as logger:
340
+ Zeitwerk is silent by default, but you can ask loaders to trace their activity. Logging is meant just for troubleshooting, shouldn't normally be enabled.
341
+
342
+ The `log!` method is a quick shortcut to let the loader log to `$stdout`:
343
+
344
+ ```
345
+ loader.log!
346
+ ```
347
+
348
+ If you want more control, a logger can be configured as a callable
342
349
 
343
350
  ```ruby
344
351
  loader.logger = method(:puts)
@@ -493,6 +500,21 @@ Trip = Struct.new { ... } # NOT SUPPORTED
493
500
 
494
501
  This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
495
502
 
503
+ <a id="markdown-rules-of-thumb" name="rules-of-thumb"></a>
504
+ ### Rules of thumb
505
+
506
+ 1. Different loaders should manage different directory trees. It is an error condition to configure overlapping root directories in different loaders.
507
+
508
+ 2. Think the mere existence of a file is effectively like writing a `require` call for them, which is executed on demand (autoload) or upfront (eager load).
509
+
510
+ 3. In that line, if two loaders manage files that translate to the same constant in the same namespace, the first one wins, the rest are ignored. Similar to what happens with `require` and `$LOAD_PATH`, only the first occurrence matters.
511
+
512
+ 4. Projects that reopen a namespace defined by some dependency have to ensure said namespace is loaded before setup. That is, the project has to make sure it reopens, rather than define. This is often accomplished just loading the dependency.
513
+
514
+ 5. Objects stored in reloadable constants should not be cached in places that are not reloaded. For example, non-reloadable classes should not subclass a reloadable class, or mixin a reloadable module. Otherwise, after reloading, those classes or module objects would become stale. Referring to constants in dynamic places like method calls or lambdas is fine.
515
+
516
+ 6. In a given process, ideally, there should be at most one loader with reloading enabled. Technically, you can have more, but it may get tricky if one refers to constants managed by the other one. Do that only if you know what you are doing.
517
+
496
518
  <a id="markdown-pronunciation" name="pronunciation"></a>
497
519
  ## Pronunciation
498
520
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- class GemInflector < Inflector
4
+ class GemInflector < Inflector # :nodoc:
5
5
  # @param root_file [String]
6
6
  def initialize(root_file)
7
7
  namespace = File.basename(root_file, ".rb")
@@ -13,7 +13,7 @@ module Zeitwerk
13
13
  # @param _abspath [String]
14
14
  # @return [String]
15
15
  def camelize(basename, _abspath)
16
- basename.split('_').map!(&:capitalize!).join
16
+ basename.split('_').map!(&:capitalize).join
17
17
  end
18
18
  end
19
19
  end
@@ -342,16 +342,20 @@ module Zeitwerk
342
342
  break if @eager_loaded
343
343
 
344
344
  queue = actual_root_dirs.reject { |dir| eager_load_exclusions.member?(dir) }
345
- while dir = queue.shift
346
- const_get_if_autoload(dir)
345
+ queue.map! { |dir| [Object, dir] }
346
+ while to_eager_load = queue.shift
347
+ namespace, dir = to_eager_load
347
348
 
348
- each_abspath(dir) do |abspath|
349
+ ls(dir) do |basename, abspath|
349
350
  next if eager_load_exclusions.member?(abspath)
350
351
 
351
352
  if ruby?(abspath)
352
- const_get_if_autoload(abspath)
353
+ if cref = autoloads[File.realpath(abspath)]
354
+ cref[0].const_get(cref[1], false)
355
+ end
353
356
  elsif dir?(abspath)
354
- queue << abspath
357
+ cname = inflector.camelize(basename, abspath)
358
+ queue << [namespace.const_get(cname, false), abspath]
355
359
  end
356
360
  end
357
361
  end
@@ -391,6 +395,13 @@ module Zeitwerk
391
395
  to_unload.keys.freeze
392
396
  end
393
397
 
398
+ # Logs to `$stdout`, handy shortcut for debugging.
399
+ #
400
+ # @return [void]
401
+ def log!
402
+ @logger = ->(msg) { puts msg }
403
+ end
404
+
394
405
  # --- Class methods ---------------------------------------------------------------------------
395
406
 
396
407
  class << self
@@ -414,7 +425,7 @@ module Zeitwerk
414
425
  #
415
426
  # @return [Zeitwerk::Loader]
416
427
  def for_gem
417
- called_from = caller_locations.first.path
428
+ called_from = caller_locations(1, 1).first.path
418
429
  Registry.loader_for_gem(called_from)
419
430
  end
420
431
 
@@ -449,9 +460,10 @@ module Zeitwerk
449
460
  # @param parent [Module]
450
461
  # @return [void]
451
462
  def set_autoloads_in_dir(dir, parent)
452
- each_abspath(dir) do |abspath|
453
- cname = inflector.camelize(File.basename(abspath, ".rb"), abspath).to_sym
454
- if ruby?(abspath)
463
+ ls(dir) do |basename, abspath|
464
+ if ruby?(basename)
465
+ basename.slice!(-3, 3)
466
+ cname = inflector.camelize(basename, abspath).to_sym
455
467
  autoload_file(parent, cname, abspath)
456
468
  elsif dir?(abspath)
457
469
  # In a Rails application, `app/models/concerns` is a subdirectory of
@@ -460,7 +472,10 @@ module Zeitwerk
460
472
  # To resolve the ambiguity file name -> constant path this introduces,
461
473
  # the `app/models/concerns` directory is totally ignored as a namespace,
462
474
  # it counts only as root. The guard checks that.
463
- autoload_subdir(parent, cname, abspath) unless root_dirs.key?(abspath)
475
+ unless root_dirs.key?(abspath)
476
+ cname = inflector.camelize(basename, abspath).to_sym
477
+ autoload_subdir(parent, cname, abspath)
478
+ end
464
479
  end
465
480
  end
466
481
  end
@@ -605,7 +620,7 @@ module Zeitwerk
605
620
  # @param dir [String]
606
621
  # @return [void]
607
622
  def do_preload_dir(dir)
608
- each_abspath(dir) do |abspath|
623
+ ls(dir) do |_basename, abspath|
609
624
  do_preload_abspath(abspath)
610
625
  end
611
626
  end
@@ -625,13 +640,13 @@ module Zeitwerk
625
640
  end
626
641
 
627
642
  # @param dir [String]
628
- # @yieldparam path [String]
643
+ # @yieldparam path [String, String]
629
644
  # @return [void]
630
- def each_abspath(dir)
631
- Dir.foreach(dir) do |entry|
632
- next if entry.start_with?(".")
633
- abspath = File.join(dir, entry)
634
- yield abspath unless ignored_paths.member?(abspath)
645
+ def ls(dir)
646
+ Dir.foreach(dir) do |basename|
647
+ next if basename.start_with?(".")
648
+ abspath = File.join(dir, basename)
649
+ yield basename, abspath unless ignored_paths.member?(abspath)
635
650
  end
636
651
  end
637
652
 
@@ -664,12 +679,6 @@ module Zeitwerk
664
679
  parent.const_defined?(cname, false)
665
680
  end
666
681
 
667
- def const_get_if_autoload(abspath)
668
- if cref = autoloads[File.realpath(abspath)]
669
- cref[0].const_get(cref[1], false)
670
- end
671
- end
672
-
673
682
  def register_explicit_namespace(cpath)
674
683
  ExplicitNamespace.register(cpath, self)
675
684
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.1.5"
4
+ VERSION = "2.1.7"
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.5
4
+ version: 2.1.7
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-04-24 00:00:00.000000000 Z
11
+ date: 2019-06-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -50,7 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
50
50
  - !ruby/object:Gem::Version
51
51
  version: '0'
52
52
  requirements: []
53
- rubygems_version: 3.0.1
53
+ rubygems_version: 3.0.3
54
54
  signing_key:
55
55
  specification_version: 4
56
56
  summary: Efficient and thread-safe constant autoloader