zeitwerk 2.1.10 → 2.2.0

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: 3dbf7502d4126651ab645926f072af5f5164fcb495c06ae32e9748aa2807670e
4
- data.tar.gz: e124bca82ed054d1ca304e95605ad011a2c6411792653c3c86b785a3cc956e06
3
+ metadata.gz: 2af50b4d74832ccd3c08e3445c09a941ed56758c57acd20e8d88436f79e16ea2
4
+ data.tar.gz: 23f693ca2b9fc44870c26991cd91c587031825e9d29c90dd5285f2c7a3731f6a
5
5
  SHA512:
6
- metadata.gz: 7efe14aeb7e16563d6a1b69b2aac82d75dd1c4e79dae2fb4654e7c93742e15018f16fad4591f8763bf03b2f9d9d71a6e95209907a0765952dafa8dedc957d2ef
7
- data.tar.gz: 8dea5ad3b1979cacb619b13385b337e81cc08efc39e0411f890229f0a9d17035fdfb17cf2ac84f6dcd3eeec4b4daa732e796e51fc1fdf707c210d9943481edcf
6
+ metadata.gz: 1d47e1a735c5314ec56f9c16d66695222209109dddb7da3a663969b852295eed82d8eee56fc48a48cb6b0604e508940f474533e98c7d824175c344420a64baec
7
+ data.tar.gz: be275743889771c745fc90d300a4cbd01911b5b4547c41ea1df6429af1357b9328fb69293a03a282677295f27528220f180f1ca943f43b5567677b856baa3e06
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2019–ω Xavier Noria
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -3,7 +3,7 @@
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/travis/com/fxn/zeitwerk.svg?style=for-the-badge&branch=master)](https://travis-ci.com/fxn/zeitwerk)
6
+ [![Build Status](https://img.shields.io/travis/com/fxn/zeitwerk/master?style=for-the-badge)](https://travis-ci.com/fxn/zeitwerk)
7
7
 
8
8
  <!-- TOC -->
9
9
 
@@ -29,6 +29,7 @@
29
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
  - [Rules of thumb](#rules-of-thumb)
32
+ - [Autoloading, explicit namespaces, and debuggers](#autoloading-explicit-namespaces-and-debuggers)
32
33
  - [Pronunciation](#pronunciation)
33
34
  - [Supported Ruby versions](#supported-ruby-versions)
34
35
  - [Motivation](#motivation)
@@ -199,6 +200,22 @@ loader.setup
199
200
 
200
201
  The loader returned by `Zeitwerk::Loader.for_gem` has the directory of the caller pushed, normally that is the absolute path of `lib`. In that sense, `for_gem` can be used also by projects with a gem structure, even if they are not technically gems. That is, you don't need a gemspec or anything.
201
202
 
203
+ If the main module of a library references project constants at the top-level, Zeitwerk has to be ready to load them. Their definitions, in turn, may reference other project constants. And this is recursive. Therefore, it is important that the `setup` call happens above the main module definition:
204
+
205
+ ```ruby
206
+ # lib/my_gem.rb (main file)
207
+
208
+ require "zeitwerk"
209
+ loader = Zeitwerk::Loader.for_gem
210
+ loader.setup
211
+
212
+ module MyGem
213
+ # Since the setup has been performed, at this point we are already able
214
+ # to reference project constants, in this case MyGem::MyLogger.
215
+ include MyLogger
216
+ end
217
+ ```
218
+
202
219
  Zeitwerk works internally only with absolute paths to avoid costly file searches in `$LOAD_PATH`. Indeed, the root directories do not even need to belong to `$LOAD_PATH`, everything just works by design if they don't.
203
220
 
204
221
  <a id="markdown-reloading" name="reloading"></a>
@@ -247,6 +264,8 @@ loader.setup
247
264
  loader.eager_load # won't eager load the database adapters
248
265
  ```
249
266
 
267
+ In gems, the method needs to be invoked after the main namespace has been defined, as shown in [Synopsis](https://github.com/fxn/zeitwerk#synopsis).
268
+
250
269
  Eager loading is synchronized and idempotent.
251
270
 
252
271
  If you want to eager load yourself and all dependencies using Zeitwerk, you can broadcast the `eager_load` call to all instances:
@@ -275,14 +294,34 @@ users_controller -> UsersController
275
294
  html_parser -> HtmlParser
276
295
  ```
277
296
 
297
+ The camelize logic can be overridden easily for individual basenames:
298
+
299
+ ```ruby
300
+ loader.inflector.inflect(
301
+ "html_parser" => "HTMLParser",
302
+ "mysql_adapter" => "MySQLAdapter"
303
+ )
304
+ ```
305
+
306
+ The `inflect` method can be invoked several times if you prefer this other style:
307
+
308
+ ```ruby
309
+ loader.inflector.inflect "html_parser" => "HTMLParser"
310
+ loader.inflector.inflect "mysql_adapter" => "MySQLAdapter"
311
+ ```
312
+
313
+ Overrides need to be configured before calling `setup`.
314
+
278
315
  There are no inflection rules or global configuration that can affect this inflector. It is deterministic.
279
316
 
280
- This is the default inflector.
317
+ Loaders instantiated with `Zeitwerk::Loader.new` have an inflector of this type, independent of each other.
281
318
 
282
319
  <a id="markdown-zeitwerkgeminflector" name="zeitwerkgeminflector"></a>
283
320
  #### Zeitwerk::GemInflector
284
321
 
285
- The loader instantiated behind the scenes by `Zeitwerk::Loader.for_gem` gets assigned by default an inflector that is like the basic one, except it expects `lib/my_gem/version.rb` to define `MyGem::VERSION`.
322
+ This inflector is like the basic one, except it expects `lib/my_gem/version.rb` to define `MyGem::VERSION`.
323
+
324
+ Loaders instantiated with `Zeitwerk::Loader.for_gem` have an inflector of this type, independent of each other.
286
325
 
287
326
  <a id="markdown-custom-inflector" name="custom-inflector"></a>
288
327
  #### Custom inflector
@@ -293,12 +332,9 @@ The inflectors that ship with Zeitwerk are deterministic and simple. But you can
293
332
  # frozen_string_literal: true
294
333
 
295
334
  class MyInflector < Zeitwerk::Inflector
296
- def camelize(basename, _abspath)
297
- case basename
298
- when "api"
299
- "API"
300
- when "mysql_adapter"
301
- "MySQLAdapter"
335
+ def camelize(basename, abspath)
336
+ if basename =~ /\Ahtml_(.*)/
337
+ "HTML" + super($1, abspath)
302
338
  else
303
339
  super
304
340
  end
@@ -318,6 +354,49 @@ loader.inflector = MyInflector.new
318
354
 
319
355
  This needs to be done before calling `setup`.
320
356
 
357
+ If a custom inflector definition in a gem takes too much space in the main file, you can extract it. For example, this is a simple pattern:
358
+
359
+ ```ruby
360
+ # lib/my_gem/inflector.rb
361
+ module MyGem
362
+ class Inflector < Zeitwerk::GemInflector
363
+ ...
364
+ end
365
+ end
366
+
367
+ # lib/my_gem.rb
368
+ require "zeitwerk"
369
+ require_relative "my_gem/inflector"
370
+
371
+ loader = Zeitwerk::Loader.for_gem
372
+ loader.inflector = MyGem::Inflector.new(__FILE__)
373
+ loader.setup
374
+
375
+ module MyGem
376
+ # ...
377
+ end
378
+ ```
379
+
380
+ Since `MyGem` is referenced before the namespace is defined in the main file, it is important to use this style:
381
+
382
+ ```ruby
383
+ # Correct, effectively defines MyGem.
384
+ module MyGem
385
+ class Inflector < Zeitwerk::GemInflector
386
+ # ...
387
+ end
388
+ end
389
+ ```
390
+
391
+ instead of:
392
+
393
+ ```ruby
394
+ # Raises uninitialized constant MyGem (NameError).
395
+ class MyGem::Inflector < Zeitwerk::GemInflector
396
+ # ...
397
+ end
398
+ ```
399
+
321
400
  <a id="markdown-logging" name="logging"></a>
322
401
  ### Logging
323
402
 
@@ -501,6 +580,15 @@ This only affects explicit namespaces, those idioms work well for any other ordi
501
580
 
502
581
  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.
503
582
 
583
+ <a id="markdown-autoloading-explicit-namespaces-and-debuggers" name="autoloading-explicit-namespaces-and-debuggers"></a>
584
+ ### Autoloading, explicit namespaces, and debuggers
585
+
586
+ As of this writing, Zeitwerk is unable to autoload classes or modules that belong to [explicit namespaces](#explicit-namespaces) inside debugger sessions. You'll get a `NameError`.
587
+
588
+ The root cause is that debuggers set trace points, and Zeitwerk does too to support explicit namespaces. A debugger session happens inside a trace point handler, and Ruby does not invoke other handlers from within a running handler. Therefore, the code that manages explicit namespaces in Zeitwerk does not get called by the interpreter. See [this issue](https://github.com/deivid-rodriguez/byebug/issues/564#issuecomment-499413606) for further details.
589
+
590
+ As a workaround, you can eager load. Zeitwerk tries hard to succeed or fail consistently both autoloading and eager loading, so switching to eager loading should not introduce any interference in your debugging logic, generally speaking.
591
+
504
592
  <a id="markdown-pronunciation" name="pronunciation"></a>
505
593
  ## Pronunciation
506
594
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- class GemInflector < Inflector # :nodoc:
4
+ class GemInflector < Inflector
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 == "version" && abspath == @version_file) ? "VERSION" : super
16
+ abspath == @version_file ? "VERSION" : super
17
17
  end
18
18
  end
19
19
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- class Inflector # :nodoc:
4
+ class Inflector
5
5
  # Very basic snake case -> camel case conversion.
6
6
  #
7
7
  # inflector = Zeitwerk::Inflector.new
@@ -9,11 +9,41 @@ module Zeitwerk
9
9
  # inflector.camelize("users_controller", ...) # => "UsersController"
10
10
  # inflector.camelize("api", ...) # => "Api"
11
11
  #
12
+ # Takes into account hard-coded mappings configured with `inflect`.
13
+ #
12
14
  # @param basename [String]
13
15
  # @param _abspath [String]
14
16
  # @return [String]
15
17
  def camelize(basename, _abspath)
16
- basename.split('_').map!(&:capitalize).join
18
+ overrides[basename] || basename.split('_').map!(&:capitalize).join
19
+ end
20
+
21
+ # Configures hard-coded inflections:
22
+ #
23
+ # inflector = Zeitwerk::Inflector.new
24
+ # inflector.inflect(
25
+ # "html_parser" => "HTMLParser",
26
+ # "mysql_adapter" => "MySQLAdapter"
27
+ # )
28
+ #
29
+ # inflector.camelize("html_parser", abspath) # => "HTMLParser"
30
+ # inflector.camelize("mysql_adapter", abspath) # => "MySQLAdapter"
31
+ # inflector.camelize("users_controller", abspath) # => "PostsController"
32
+ #
33
+ # @param inflections [{String => String}]
34
+ # @return [void]
35
+ def inflect(inflections)
36
+ overrides.merge!(inflections)
37
+ end
38
+
39
+ private
40
+
41
+ # Hard-coded basename to constant name user maps that override the default
42
+ # inflection logic.
43
+ #
44
+ # @return [{String => String}]
45
+ def overrides
46
+ @overrides ||= {}
17
47
  end
18
48
  end
19
49
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.1.10"
4
+ VERSION = "2.2.0"
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.10
4
+ version: 2.2.0
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-09-06 00:00:00.000000000 Z
11
+ date: 2019-10-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -20,6 +20,7 @@ executables: []
20
20
  extensions: []
21
21
  extra_rdoc_files: []
22
22
  files:
23
+ - MIT-LICENSE
23
24
  - README.md
24
25
  - lib/zeitwerk.rb
25
26
  - lib/zeitwerk/error.rb