zeitwerk 2.6.8 → 2.6.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: 418f16f6224359c716a990f72ae916c980a3f388b09ab018c0972b8558530620
4
- data.tar.gz: '04847c6c8f8f894fe3c2b3c39d175b5854b583d3a02ef5cca27a53cb1b8dafd4'
3
+ metadata.gz: b1de024fb1ddb2f4bc64ced5aacf898f01b6336d5d8b85701d0757e256d062fb
4
+ data.tar.gz: d3f06cb22aad419d5c1778fc4a13e1cc036cd062b5ac1fcece5eb4fa3ba1ce00
5
5
  SHA512:
6
- metadata.gz: f86272e84721cf9b8b1e07182b1b60cfe732ef2c266dbe076225d32af52fc9a30c278a14651ddbb3d435636646dd23c948d3fe0f00ed9b8393abdeb34dd40028
7
- data.tar.gz: 562738b53779310e899c2065c7046844d27daabb5f354972e4c9b65bec3e5c0cdade92d19ed815fe7b0dc01538603daa3e4dd2f0049fe9a7a2e74313c0f040f6
6
+ metadata.gz: 3b49724afa7c8097eb6014264faa50f6320c7686760fc5677f6c648f6f4c482b2aa6efb8f78621721c6b00ed424a0a77864cdf8fafb2a481a0ddd1c333115d04
7
+ data.tar.gz: 01b7782fea2affe16c5241f9325649e90f3beb3150f168cecf6d710fdfffb66a9bfa853ca4ce562ddbdca3133718b7b2bdb2b1260cf997724d668d6639a5199b
data/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
 
4
4
 
5
5
  [![Gem Version](https://img.shields.io/gem/v/zeitwerk.svg?style=for-the-badge)](https://rubygems.org/gems/zeitwerk)
6
- [![Build Status](https://img.shields.io/github/actions/workflow/status/fxn/zeitwerk/ci.yml?branch=main&event=push&style=for-the-badge)](https://github.com/fxn/zeitwerk/actions/workflows/ci.yml?query=branch%3main)
6
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/fxn/zeitwerk/ci.yml?branch=main&event=push&style=for-the-badge)](https://github.com/fxn/zeitwerk/actions/workflows/ci.yml?query=branch%3Amain)
7
+
7
8
 
8
9
  <!-- TOC -->
9
10
 
@@ -56,6 +57,8 @@
56
57
  - [Beware of circular dependencies](#beware-of-circular-dependencies)
57
58
  - [Reopening third-party namespaces](#reopening-third-party-namespaces)
58
59
  - [Introspection](#introspection)
60
+ - [`Zeitwerk::Loader#dirs`](#zeitwerkloaderdirs)
61
+ - [`Zeitwerk::Loader#cpath_expected_at`](#zeitwerkloadercpath_expected_at)
59
62
  - [Encodings](#encodings)
60
63
  - [Rules of thumb](#rules-of-thumb)
61
64
  - [Debuggers](#debuggers)
@@ -76,15 +79,15 @@
76
79
 
77
80
  Zeitwerk is an efficient and thread-safe code loader for Ruby.
78
81
 
79
- 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.
82
+ Given a [conventional file structure](#file-structure), Zeitwerk is capable of loading 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; instead, you can streamline your programming by knowing that your classes and modules are available everywhere. This feature is efficient, thread-safe, and aligns with Ruby's semantics for constants.
80
83
 
81
- 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.
84
+ Zeitwerk also supports code reloading, which can be useful during web application development. However, coordination is required to reload in a thread-safe manner. The documentation below explains how to achieve this.
82
85
 
83
- 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.
86
+ The gem is designed to allow any project, gem dependency, or application to have its own independent loader. Multiple loaders can coexist in the same process, each managing its own project tree and operating independently of each other. Each loader has its own configuration, inflector, and optional logger.
84
87
 
85
- 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`.
88
+ Internally, Zeitwerk exclusively uses absolute file names when issuing `require` calls, eliminating the need for costly file system lookups in `$LOAD_PATH`. Technically, the directories managed by Zeitwerk don't even need to be in `$LOAD_PATH`.
86
89
 
87
- Furthermore, Zeitwerk does at most one single scan of the project tree, and it descends into subdirectories lazily, only if their namespaces are used.
90
+ Furthermore, Zeitwerk performs a single scan of the project tree at most, lazily descending into subdirectories only when their namespaces are used.
88
91
 
89
92
  <a id="markdown-synopsis" name="synopsis"></a>
90
93
  ## Synopsis
@@ -144,7 +147,7 @@ Zeitwerk::Loader.eager_load_all
144
147
  <a id="markdown-the-idea-file-paths-match-constant-paths" name="the-idea-file-paths-match-constant-paths"></a>
145
148
  ### The idea: File paths match constant paths
146
149
 
147
- To have a file structure Zeitwerk can work with, just name files and directories after the name of the classes and modules they define:
150
+ For Zeitwerk to work with your file structure, simply name files and directories after the classes and modules they define:
148
151
 
149
152
  ```
150
153
  lib/my_gem.rb -> MyGem
@@ -153,7 +156,7 @@ lib/my_gem/bar_baz.rb -> MyGem::BarBaz
153
156
  lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo
154
157
  ```
155
158
 
156
- You can tune that a bit by [collapsing directories](#collapsing-directories), or by [ignoring parts of the project](#ignoring-parts-of-the-project), but that is the main idea.
159
+ You can fine-tune this behavior by [collapsing directories](#collapsing-directories) or [ignoring specific parts of the project](#ignoring-parts-of-the-project), but that is the main idea.
157
160
 
158
161
  <a id="markdown-inner-simple-constants" name="inner-simple-constants"></a>
159
162
  ### Inner simple constants
@@ -211,7 +214,7 @@ serializers/user_serializer.rb -> UserSerializer
211
214
  <a id="markdown-custom-root-namespaces" name="custom-root-namespaces"></a>
212
215
  #### Custom root namespaces
213
216
 
214
- While `Object` is by far the most common root namespace, you can associate a different one to a particular root directory. The method `push_dir` accepts a non-anonymous class or module object in the optional `namespace` keyword argument.
217
+ Although `Object` is the most common root namespace, you have the flexibility to associate a different one with a specific root directory. The `push_dir` method accepts a non-anonymous class or module object as the optional `namespace` keyword argument.
215
218
 
216
219
  For example, given:
217
220
 
@@ -227,14 +230,14 @@ a file defining `ActiveJob::QueueAdapters::MyQueueAdapter` does not need the con
227
230
  adapters/my_queue_adapter.rb -> ActiveJob::QueueAdapters::MyQueueAdapter
228
231
  ```
229
232
 
230
- Please, note that the given root namespace must be non-reloadable, though autoloaded constants in that namespace can be. That is, if you associate `app/api` with an existing `Api` module, that module should not be reloadable. However, if the project defines and autoloads the class `Api::Deliveries`, that one can be reloaded.
233
+ Please note that the provided root namespace must be non-reloadable, while allowing autoloaded constants within that namespace to be reloadable. This means that if you associate the `app/api` directory with an existing `Api` module, the module itself should not be reloadable. However, if the project defines and autoloads the `Api::Deliveries` class, that class can be reloaded.
231
234
 
232
235
  <a id="markdown-nested-root-directories" name="nested-root-directories"></a>
233
236
  #### Nested root directories
234
237
 
235
- 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.
238
+ Root directories are recommended not to be nested; however, Zeitwerk provides support for nested root directories since in frameworks like Rails, both `app/models` and `app/models/concerns` belong to the autoload paths.
236
239
 
237
- Zeitwerk detects nested root directories, and treats them as roots only. In the example above, `concerns` is not considered to be a namespace below `app/models`. For example, the file:
240
+ Zeitwerk identifies nested root directories and treats them as independent roots. In the given example, `concerns` is not considered a namespace within `app/models`. For instance, consider the following file:
238
241
 
239
242
  ```
240
243
  app/models/concerns/geolocatable.rb
@@ -245,9 +248,9 @@ should define `Geolocatable`, not `Concerns::Geolocatable`.
245
248
  <a id="markdown-implicit-namespaces" name="implicit-namespaces"></a>
246
249
  ### Implicit namespaces
247
250
 
248
- If a namespace is just a simple module with no code, you do not need to define it in a file: Directories without a matching Ruby file get modules created automatically on your behalf.
251
+ If a namespace consists only of a simple module without any code, there is no need to explicitly define it in a separate file. Zeitwerk automatically creates modules on your behalf for directories without a corresponding Ruby file.
249
252
 
250
- For example, if a project has an `admin` directory:
253
+ For instance, suppose a project includes an `admin` directory:
251
254
 
252
255
  ```
253
256
  app/controllers/admin/users_controller.rb -> Admin::UsersController
@@ -255,7 +258,7 @@ app/controllers/admin/users_controller.rb -> Admin::UsersController
255
258
 
256
259
  and does not have a file called `admin.rb`, Zeitwerk automatically creates an `Admin` module on your behalf the first time `Admin` is used.
257
260
 
258
- For this to happen, the directory has to contain non-ignored Ruby files with extension `.rb`, directly or recursively, otherwise it is ignored. This condition is evaluated again on reloads.
261
+ To trigger this behavior, the directory must contain non-ignored Ruby files with the `.rb` extension, either directly or recursively. Otherwise, the directory is ignored. This condition is reevaluated during reloads.
259
262
 
260
263
  <a id="markdown-explicit-namespaces" name="explicit-namespaces"></a>
261
264
  ### Explicit namespaces
@@ -733,9 +736,34 @@ loader.inflector.inflect "html_parser" => "HTMLParser"
733
736
  loader.inflector.inflect "mysql_adapter" => "MySQLAdapter"
734
737
  ```
735
738
 
739
+ Overrides have to match exactly directory or file (without extension) _basenames_. For example, if you configure
740
+
741
+ ```ruby
742
+ loader.inflector.inflect("xml" => "XML")
743
+ ```
744
+
745
+ then the following constants are expected:
746
+
747
+ ```
748
+ xml.rb -> XML
749
+ foo/xml -> Foo::XML
750
+ foo/bar/xml.rb -> Foo::Bar::XML
751
+ ```
752
+
753
+ As you see, any directory whose basename is exactly `xml`, and any file whose basename is exactly `xml.rb` are expected to define the constant `XML` in the corresponding namespace. On the other hand, partial matches are ignored. For example, `xml_parser.rb` would be inflected as `XmlParser` because `xml_parser` is not equal to `xml`. You'd need an additional override:
754
+
755
+ ```ruby
756
+ loader.inflector.inflect(
757
+ "xml" => "XML",
758
+ "xml_parser" => "XMLParser"
759
+ )
760
+ ```
761
+
762
+ If you need more flexibility, you can define a custom inflector, as explained down below.
763
+
736
764
  Overrides need to be configured before calling `setup`.
737
765
 
738
- The inflectors of different loaders are independent of each other. There are no global inflection rules or global configuration that can affect this inflector. It is deterministic.
766
+ The inflectors of different loaders are independent of each other. There are no global inflection rules or global configuration that can affect this inflector. It is deterministic.
739
767
 
740
768
  <a id="markdown-zeitwerkgeminflector" name="zeitwerkgeminflector"></a>
741
769
  #### Zeitwerk::GemInflector
@@ -1214,6 +1242,9 @@ With that, when Zeitwerk scans the file system and reaches the gem directories `
1214
1242
  <a id="markdown-introspection" name="introspection"></a>
1215
1243
  ### Introspection
1216
1244
 
1245
+ <a id="markdown-zeitwerkloaderdirs" name="zeitwerkloaderdirs"></a>
1246
+ #### `Zeitwerk::Loader#dirs`
1247
+
1217
1248
  The method `Zeitwerk::Loader#dirs` returns an array with the absolute paths of the root directories as strings:
1218
1249
 
1219
1250
  ```ruby
@@ -1235,6 +1266,44 @@ By default, ignored root directories are filtered out. If you want them included
1235
1266
 
1236
1267
  These collections are read-only. Please add to them with `Zeitwerk::Loader#push_dir`.
1237
1268
 
1269
+ <a id="markdown-zeitwerkloadercpath_expected_at" name="zeitwerkloadercpath_expected_at"></a>
1270
+ #### `Zeitwerk::Loader#cpath_expected_at`
1271
+
1272
+ Given a path as a string or `Pathname` object, `Zeitwerk::Loader#cpath_expected_at` returns a string with the corresponding expected constant path.
1273
+
1274
+ Some examples, assuming that `app/models` is a root directory:
1275
+
1276
+ ```ruby
1277
+ loader.cpath_expected_at("app/models") # => "Object"
1278
+ loader.cpath_expected_at("app/models/user.rb") # => "User"
1279
+ loader.cpath_expected_at("app/models/hotel") # => "Hotel"
1280
+ loader.cpath_expected_at("app/models/hotel/billing.rb") # => "Hotel::Billing"
1281
+ ```
1282
+
1283
+ If `collapsed` is a collapsed directory:
1284
+
1285
+ ```ruby
1286
+ loader.cpath_expected_at("a/b/collapsed/c") # => "A::B::C"
1287
+ loader.cpath_expected_at("a/b/collapsed") # => "A::B", edge case
1288
+ loader.cpath_expected_at("a/b") # => "A::B"
1289
+ ```
1290
+
1291
+ If the argument corresponds to an [ignored file or directory](#ignoring-parts-of-the-project), the method returns `nil`. Same if the argument is not managed by the loader.
1292
+
1293
+ `Zeitwerk::Error` is raised if the given path does not exist:
1294
+
1295
+ ```ruby
1296
+ loader.cpath_expected_at("non_existing_file.rb") # => Zeitwerk::Error
1297
+ ```
1298
+
1299
+ `Zeitwer::NameError` is raised if a constant path cannot be derived from it:
1300
+
1301
+ ```ruby
1302
+ loader.cpath_expected_at("8.rb") # => Zeitwerk::NameError
1303
+ ```
1304
+
1305
+ This method does not parse file contents and does not guarantee files define the returned constant path. It just says which is the _expected_ one.
1306
+
1238
1307
  <a id="markdown-encodings" name="encodings"></a>
1239
1308
  ### Encodings
1240
1309
 
@@ -1265,9 +1334,11 @@ The test suite passes on Windows with codepage `Windows-1252` if all the involve
1265
1334
  <a id="markdown-debuggers" name="debuggers"></a>
1266
1335
  ### Debuggers
1267
1336
 
1268
- Zeitwerk works fine with [debug.rb](https://github.com/ruby/debug) and [Break](https://github.com/gsamokovarov/break).
1337
+ Zeitwerk and [debug.rb](https://github.com/ruby/debug) are fully compatible if CRuby is ≥ 3.1 (see [ruby/debug#558](https://github.com/ruby/debug/pull/558)).
1338
+
1339
+ [Byebug](https://github.com/deivid-rodriguez/byebug) is compatible except for an edge case explained in [deivid-rodriguez/byebug#564](https://github.com/deivid-rodriguez/byebug/issues/564). Prior to CRuby 3.1, `debug.rb` has a similar edge incompatibility.
1269
1340
 
1270
- [Byebug](https://github.com/deivid-rodriguez/byebug) is compatible except for an edge case explained in [deivid-rodriguez/byebug#564](https://github.com/deivid-rodriguez/byebug/issues/564).
1341
+ [Break](https://github.com/gsamokovarov/break) is fully compatible.
1271
1342
 
1272
1343
  <a id="markdown-pronunciation" name="pronunciation"></a>
1273
1344
  ## Pronunciation
@@ -45,8 +45,10 @@ module Zeitwerk::Loader::EagerLoad
45
45
 
46
46
  break if root_namespace = roots[dir]
47
47
 
48
+ basename = File.basename(dir)
49
+ return if hidden?(basename)
50
+
48
51
  unless collapse?(dir)
49
- basename = File.basename(dir)
50
52
  cnames << inflector.camelize(basename, dir).to_sym
51
53
  end
52
54
  end
@@ -119,6 +121,8 @@ module Zeitwerk::Loader::EagerLoad
119
121
  raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)
120
122
 
121
123
  basename = File.basename(abspath, ".rb")
124
+ raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
125
+
122
126
  base_cname = inflector.camelize(basename, abspath).to_sym
123
127
 
124
128
  root_namespace = nil
@@ -129,8 +133,10 @@ module Zeitwerk::Loader::EagerLoad
129
133
 
130
134
  break if root_namespace = roots[dir]
131
135
 
136
+ basename = File.basename(dir)
137
+ raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
138
+
132
139
  unless collapse?(dir)
133
- basename = File.basename(dir)
134
140
  cnames << inflector.camelize(basename, dir).to_sym
135
141
  end
136
142
  end
@@ -228,6 +228,69 @@ module Zeitwerk
228
228
  setup
229
229
  end
230
230
 
231
+ # @sig (String | Pathname) -> String?
232
+ def cpath_expected_at(path)
233
+ abspath = File.expand_path(path)
234
+
235
+ raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
236
+
237
+ return unless dir?(abspath) || ruby?(abspath)
238
+ return if ignored_path?(abspath)
239
+
240
+ cnames = []
241
+ abspaths = []
242
+
243
+ if ruby?(abspath)
244
+ basename = File.basename(abspath, ".rb")
245
+ return if hidden?(basename)
246
+
247
+ cnames << inflector.camelize(basename, abspath).to_sym
248
+ abspaths << abspath
249
+ walk_up_from = File.dirname(abspath)
250
+ else
251
+ walk_up_from = abspath
252
+ end
253
+
254
+ root_namespace = nil
255
+
256
+ walk_up(walk_up_from) do |dir|
257
+ break if root_namespace = roots[dir]
258
+ return if ignored_path?(dir)
259
+
260
+ basename = File.basename(dir)
261
+ return if hidden?(basename)
262
+
263
+ unless collapse?(dir)
264
+ cnames << inflector.camelize(basename, dir).to_sym
265
+ abspaths << dir
266
+ end
267
+ end
268
+
269
+ return unless root_namespace
270
+
271
+ if cnames.empty?
272
+ real_mod_name(root_namespace)
273
+ else
274
+ # We reverse before validating the segments to report the leftmost
275
+ # problematic one, if any.
276
+ cnames.reverse!
277
+
278
+ cname_validator = Module.new
279
+ cnames.each_with_index do |cname, i|
280
+ cname_validator.const_defined?(cname, false)
281
+ rescue ::NameError
282
+ j = -(i + 1)
283
+ raise Zeitwerk::NameError.new("cannot derive a constant name from #{abspaths[j]}")
284
+ end
285
+
286
+ if root_namespace == Object
287
+ cnames.join("::")
288
+ else
289
+ "#{real_mod_name(root_namespace)}::#{cnames.join("::")}"
290
+ end
291
+ end
292
+ end
293
+
231
294
  # Says if the given constant path would be unloaded on reload. This
232
295
  # predicate returns `false` if reloading is disabled.
233
296
  #
@@ -511,7 +574,6 @@ module Zeitwerk
511
574
  raise Error,
512
575
  "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
513
576
  " which is already managed by\n\n#{loader.pretty_inspect}\n"
514
- EOS
515
577
  end
516
578
  end
517
579
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.6.8"
4
+ VERSION = "2.6.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.6.8
4
+ version: 2.6.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: 2023-04-28 00:00:00.000000000 Z
11
+ date: 2023-07-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -61,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0'
63
63
  requirements: []
64
- rubygems_version: 3.4.11
64
+ rubygems_version: 3.4.16
65
65
  signing_key:
66
66
  specification_version: 4
67
67
  summary: Efficient and thread-safe constant autoloader