zeitwerk 2.1.6 → 2.1.8

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: 43750607caaaa5d4fff5bd52204cafb98fbd5109b51a24d9099ba83c6a2a4990
4
- data.tar.gz: 8ad3251fd93af9e66a9102c13556beff5805a38660d69e4c5de37e2808e76d8f
3
+ metadata.gz: 0a89545605d519214e873572adc81662ece65e5d3aa6ffaa19f04877beb2d576
4
+ data.tar.gz: ac086c60c9391fbdaed5875b88038e6a977bf63110e39c38357b160056997b07
5
5
  SHA512:
6
- metadata.gz: e6e87877104078f564686a15b81882a8e20a560a36999e0f8b9916aa520dd46fe1a2b7f65cacdb7b6c62d252472b6ce1370939054f026a335e21652881820814
7
- data.tar.gz: 3641d412ab4b9176861b9ed9b28ea7964b6a814ebb72804c96967e511562fb87f424c48d7780345be8ccd2c53bdacfe095e3e5173e166a4185bf389740258ca6
6
+ metadata.gz: 58326a65a566e5c72e8e2a588eb2dd4dda97efc485ebe422dcc63d9e9b3e83395f9dac5a3e35fe0cb9b17efc75cca3a41f73e3d3ac99b9b49a9d999d9e73b551
7
+ data.tar.gz: 24475fe5d47dadf250ce4a658bc44ecd72d29f6e0cd87ce8502d62eaa2e4ba7dbecb2bea2a28cb3279a3fb19bb5aa82b5676b7151b9f301e3bea607b2d75854c
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
@@ -340,7 +339,7 @@ This needs to be done before calling `setup`.
340
339
 
341
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.
342
341
 
343
- The `log!` mehtod is a quick shortcut to let the loader log to `$stdout`:
342
+ The `log!` method is a quick shortcut to let the loader log to `$stdout`:
344
343
 
345
344
  ```
346
345
  loader.log!
@@ -501,6 +500,21 @@ Trip = Struct.new { ... } # NOT SUPPORTED
501
500
 
502
501
  This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
503
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
+
504
518
  <a id="markdown-pronunciation" name="pronunciation"></a>
505
519
  ## Pronunciation
506
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
@@ -53,23 +53,6 @@ module Zeitwerk
53
53
  # @return [Set<String>]
54
54
  attr_reader :ignored_paths
55
55
 
56
- # A _shadowed file_ is a file managed by this loader that is ignored when
57
- # setting autoloads because its matching constant is taken. Either the
58
- # constant is already defined, or there exists an autoload for it.
59
- #
60
- # Think $LOAD_PATH and require, only the first occurrence of a given
61
- # relative name is loaded.
62
- #
63
- # This set keeps track of the absolute path of shadowed files, to be able to
64
- # skip them while eager loading.
65
- #
66
- # Note this cannot be implemented with do_not_eager_load because these
67
- # files are not autoloadable.
68
- #
69
- # @private
70
- # @return [Set<String>]
71
- attr_reader :shadowed_files
72
-
73
56
  # Maps real absolute paths for which an autoload has been set ---and not
74
57
  # executed--- to their corresponding parent class or module and constant
75
58
  # name.
@@ -155,7 +138,6 @@ module Zeitwerk
155
138
  @autoloaded_dirs = []
156
139
  @to_unload = {}
157
140
  @lazy_subdirs = {}
158
- @shadowed_files = Set.new
159
141
  @eager_load_exclusions = Set.new
160
142
 
161
143
  # TODO: find a better name for these mutexes.
@@ -324,7 +306,6 @@ module Zeitwerk
324
306
  autoloaded_dirs.clear
325
307
  to_unload.clear
326
308
  lazy_subdirs.clear
327
- shadowed_files.clear
328
309
 
329
310
  Registry.on_unload(self)
330
311
  ExplicitNamespace.unregister(self)
@@ -361,20 +342,24 @@ module Zeitwerk
361
342
  break if @eager_loaded
362
343
 
363
344
  queue = actual_root_dirs.reject { |dir| eager_load_exclusions.member?(dir) }
364
- while dir = queue.shift
365
- ls(dir) do |_basename, abspath|
345
+ queue.map! { |dir| [Object, dir] }
346
+ while to_eager_load = queue.shift
347
+ namespace, dir = to_eager_load
348
+
349
+ ls(dir) do |basename, abspath|
366
350
  next if eager_load_exclusions.member?(abspath)
367
351
 
368
352
  if ruby?(abspath)
369
- require abspath unless shadowed_files.member?(abspath)
370
- elsif dir?(abspath)
371
- queue << abspath
353
+ if cref = autoloads[File.realpath(abspath)]
354
+ cref[0].const_get(cref[1], false)
355
+ end
356
+ elsif dir?(abspath) && !root_dirs.key?(abspath)
357
+ cname = inflector.camelize(basename, abspath)
358
+ queue << [namespace.const_get(cname, false), abspath]
372
359
  end
373
360
  end
374
361
  end
375
362
 
376
- shadowed_files.clear
377
-
378
363
  autoloaded_dirs.each do |autoloaded_dir|
379
364
  Registry.unregister_autoload(autoloaded_dir)
380
365
  end
@@ -526,7 +511,6 @@ module Zeitwerk
526
511
  if autoload_path = autoload_for?(parent, cname)
527
512
  # First autoload for a Ruby file wins, just ignore subsequent ones.
528
513
  if ruby?(autoload_path)
529
- shadowed_files.add(file)
530
514
  log("file #{file} is ignored because #{autoload_path} has precedence") if logger
531
515
  else
532
516
  promote_namespace_from_implicit_to_explicit(
@@ -537,7 +521,6 @@ module Zeitwerk
537
521
  )
538
522
  end
539
523
  elsif cdef?(parent, cname)
540
- shadowed_files.add(file)
541
524
  log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
542
525
  else
543
526
  set_autoload(parent, cname, file)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.1.6"
4
+ VERSION = "2.1.8"
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.6
4
+ version: 2.1.8
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-30 00:00:00.000000000 Z
11
+ date: 2019-06-29 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