Hello community,
here is the log from the commit of package rubygem-zeitwerk for
openSUSE:Factory checked in at 2019-11-13 13:25:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rubygem-zeitwerk (Old)
and /work/SRC/openSUSE:Factory/.rubygem-zeitwerk.new.2990 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-zeitwerk"
Wed Nov 13 13:25:22 2019 rev:2 rq:747690 version:2.2.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/rubygem-zeitwerk/rubygem-zeitwerk.changes
2019-08-19 23:02:21.663448257 +0200
+++
/work/SRC/openSUSE:Factory/.rubygem-zeitwerk.new.2990/rubygem-zeitwerk.changes
2019-11-13 13:25:23.899514628 +0100
@@ -1,0 +2,25 @@
+Tue Nov 12 14:02:42 UTC 2019 - Manuel Schnitzer <[email protected]>
+
+- updated to version 2.2.1
+
+ * Zeitwerk raised `NameError` when a managed file did not define its
expected constant. Now, it raises `Zeitwerk::NameError` instead, so it is
possible for client code to distinguish that mismatch from a regular
`NameError`.
+
+ Regarding backwards compatibility, `Zeitwerk::NameError` is a subclass
of `NameError`.
+
+ ## 2.2.0 (9 October 2019)
+
+ * The default inflectors have API to override how to camelize selected
basenames:
+
+ ```ruby
+ loader.inflector.inflect "mysql_adapter" => "MySQLAdapter"
+ ```
+
+ This addresses a common pattern, which is to use the basic inflectors
with a few straightforward exceptions typically configured in a hash table or
`case` expression. You no longer have to define a custom inflector if that is
all you need.
+
+ * Documentation improvements.
+
+ ## 2.1.10 (6 September 2019)
+
+ * Raises `Zeitwerk::NameError` with a better error message when a managed
file or directory has a name that yields an invalid constant name when
inflected. `Zeitwerk::NameError` is a subclass of `NameError`.
+
+-------------------------------------------------------------------
Old:
----
zeitwerk-2.1.9.gem
New:
----
zeitwerk-2.2.1.gem
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ rubygem-zeitwerk.spec ++++++
--- /var/tmp/diff_new_pack.rxIvxr/_old 2019-11-13 13:25:24.559515314 +0100
+++ /var/tmp/diff_new_pack.rxIvxr/_new 2019-11-13 13:25:24.563515318 +0100
@@ -24,7 +24,7 @@
#
Name: rubygem-zeitwerk
-Version: 2.1.9
+Version: 2.2.1
Release: 0
%define mod_name zeitwerk
%define mod_full_name %{mod_name}-%{version}
@@ -51,7 +51,7 @@
%install
%gem_install \
- --doc-files="README.md" \
+ --doc-files="MIT-LICENSE README.md" \
-f
%gem_packages
++++++ zeitwerk-2.1.9.gem -> zeitwerk-2.2.1.gem ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/MIT-LICENSE new/MIT-LICENSE
--- old/MIT-LICENSE 1970-01-01 01:00:00.000000000 +0100
+++ new/MIT-LICENSE 2019-11-01 12:53:07.000000000 +0100
@@ -0,0 +1,20 @@
+Copyright (c) 2019–ω Xavier Noria
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/README.md new/README.md
--- old/README.md 2019-07-16 23:21:48.000000000 +0200
+++ new/README.md 2019-11-01 12:53:07.000000000 +0100
@@ -3,7 +3,7 @@
[](https://rubygems.org/gems/zeitwerk)
-[](https://travis-ci.com/fxn/zeitwerk)
+[](https://travis-ci.com/fxn/zeitwerk)
<!-- TOC -->
@@ -29,8 +29,10 @@
- [Use case: Test files mixed with implementation
files](#use-case-test-files-mixed-with-implementation-files)
- [Edge cases](#edge-cases)
- [Rules of thumb](#rules-of-thumb)
+ - [Autoloading, explicit namespaces, and
debuggers](#autoloading-explicit-namespaces-and-debuggers)
- [Pronunciation](#pronunciation)
- [Supported Ruby versions](#supported-ruby-versions)
+- [Testing](#testing)
- [Motivation](#motivation)
- [Thanks](#thanks)
- [License](#license)
@@ -199,6 +201,22 @@
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.
+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:
+
+```ruby
+# lib/my_gem.rb (main file)
+
+require "zeitwerk"
+loader = Zeitwerk::Loader.for_gem
+loader.setup
+
+module MyGem
+ # Since the setup has been performed, at this point we are already able
+ # to reference project constants, in this case MyGem::MyLogger.
+ include MyLogger
+end
+```
+
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.
<a id="markdown-reloading" name="reloading"></a>
@@ -221,7 +239,7 @@
Generally speaking, reloading is useful while developing running services like
web applications. Gems that implement regular libraries, so to speak, or
services running in testing or production environments, won't normally have a
use case for reloading. If reloading is not enabled, Zeitwerk is able to use
less memory.
-Reloading removes the currently loaded classes and modules, resets the loader
so that it will pick whatever is in the file system now, and runs preloads if
there are any.
+Reloading removes the currently loaded classes and modules and resets the
loader so that it will pick whatever is in the file system now.
It is important to highlight that this is an instance method. Don't worry
about project dependencies managed by Zeitwerk, their loaders are independent.
@@ -247,6 +265,8 @@
loader.eager_load # won't eager load the database adapters
```
+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).
+
Eager loading is synchronized and idempotent.
If you want to eager load yourself and all dependencies using Zeitwerk, you
can broadcast the `eager_load` call to all instances:
@@ -275,14 +295,34 @@
html_parser -> HtmlParser
```
+The camelize logic can be overridden easily for individual basenames:
+
+```ruby
+loader.inflector.inflect(
+ "html_parser" => "HTMLParser",
+ "mysql_adapter" => "MySQLAdapter"
+)
+```
+
+The `inflect` method can be invoked several times if you prefer this other
style:
+
+```ruby
+loader.inflector.inflect "html_parser" => "HTMLParser"
+loader.inflector.inflect "mysql_adapter" => "MySQLAdapter"
+```
+
+Overrides need to be configured before calling `setup`.
+
There are no inflection rules or global configuration that can affect this
inflector. It is deterministic.
-This is the default inflector.
+Loaders instantiated with `Zeitwerk::Loader.new` have an inflector of this
type, independent of each other.
<a id="markdown-zeitwerkgeminflector" name="zeitwerkgeminflector"></a>
#### Zeitwerk::GemInflector
-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`.
+This inflector is like the basic one, except it expects
`lib/my_gem/version.rb` to define `MyGem::VERSION`.
+
+Loaders instantiated with `Zeitwerk::Loader.for_gem` have an inflector of this
type, independent of each other.
<a id="markdown-custom-inflector" name="custom-inflector"></a>
#### Custom inflector
@@ -293,12 +333,9 @@
# frozen_string_literal: true
class MyInflector < Zeitwerk::Inflector
- def camelize(basename, _abspath)
- case basename
- when "api"
- "API"
- when "mysql_adapter"
- "MySQLAdapter"
+ def camelize(basename, abspath)
+ if basename =~ /\Ahtml_(.*)/
+ "HTML" + super($1, abspath)
else
super
end
@@ -318,6 +355,49 @@
This needs to be done before calling `setup`.
+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:
+
+```ruby
+# lib/my_gem/inflector.rb
+module MyGem
+ class Inflector < Zeitwerk::GemInflector
+ ...
+ end
+end
+
+# lib/my_gem.rb
+require "zeitwerk"
+require_relative "my_gem/inflector"
+
+loader = Zeitwerk::Loader.for_gem
+loader.inflector = MyGem::Inflector.new(__FILE__)
+loader.setup
+
+module MyGem
+ # ...
+end
+```
+
+Since `MyGem` is referenced before the namespace is defined in the main file,
it is important to use this style:
+
+```ruby
+# Correct, effectively defines MyGem.
+module MyGem
+ class Inflector < Zeitwerk::GemInflector
+ # ...
+ end
+end
+```
+
+instead of:
+
+```ruby
+# Raises uninitialized constant MyGem (NameError).
+class MyGem::Inflector < Zeitwerk::GemInflector
+ # ...
+end
+```
+
<a id="markdown-logging" name="logging"></a>
### Logging
@@ -501,6 +581,15 @@
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.
+<a id="markdown-autoloading-explicit-namespaces-and-debuggers"
name="autoloading-explicit-namespaces-and-debuggers"></a>
+### Autoloading, explicit namespaces, and debuggers
+
+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`.
+
+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.
+
+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.
+
<a id="markdown-pronunciation" name="pronunciation"></a>
## Pronunciation
@@ -511,6 +600,32 @@
Zeitwerk works with MRI 2.4.4 and above.
+<a id="markdown-testing" name="testing"></a>
+## Testing
+
+In order to run the test suite of Zeitwerk, `cd` into the project root and
execute
+
+```
+bin/test
+```
+
+To run one particular suite, pass its file name as an argument:
+
+```
+bin/test test/lib/zeitwerk/test_eager_load.rb
+```
+
+Furthermore, the project has a development dependency on
[`minitest-focus`](https://github.com/seattlerb/minitest-focus). To run an
individual test mark it with `focus`:
+
+```ruby
+focus
+test "capitalizes the first letter" do
+ assert_equal "User", camelize("user")
+end
+```
+
+and run `bin/test`.
+
<a id="markdown-motivation" name="motivation"></a>
## Motivation
@@ -525,6 +640,8 @@
Also, would like to thank [@Shopify](https://github.com/Shopify),
[@rafaelfranca](https://github.com/rafaelfranca), and
[@dylanahsmith](https://github.com/dylanahsmith), for sharing [this
PoC](https://github.com/Shopify/autoload_reloader). The technique Zeitwerk uses
to support explicit namespaces was copied from that project.
+Jean Boussier ([@casperisfine](https://github.com/casperisfine),
[@byroot](https://github.com/byroot)) deserves special mention. Jean migrated
autoloading in Shopify when Zeitwerk integration in Rails was yet unreleased.
His work and positive attitude have been outstanding, and thanks to his
feedback the interface and performance of Zeitwerk are way, way better. Kudos
man ❤️.
+
Finally, many thanks to [@schurig](https://github.com/schurig) for recording
an [audio file](http://share.hashref.com/zeitwerk/zeitwerk_pronunciation.mp3)
with the pronunciation of "Zeitwerk" in perfect German. 💯
<a id="markdown-license" name="license"></a>
Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/lib/zeitwerk/error.rb new/lib/zeitwerk/error.rb
--- old/lib/zeitwerk/error.rb 2019-07-16 23:21:48.000000000 +0200
+++ new/lib/zeitwerk/error.rb 2019-11-01 12:53:07.000000000 +0100
@@ -4,4 +4,7 @@
class ReloadingDisabledError < Error
end
+
+ class NameError < ::NameError
+ end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/lib/zeitwerk/gem_inflector.rb
new/lib/zeitwerk/gem_inflector.rb
--- old/lib/zeitwerk/gem_inflector.rb 2019-07-16 23:21:48.000000000 +0200
+++ new/lib/zeitwerk/gem_inflector.rb 2019-11-01 12:53:07.000000000 +0100
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Zeitwerk
- class GemInflector < Inflector # :nodoc:
+ class GemInflector < Inflector
# @param root_file [String]
def initialize(root_file)
namespace = File.basename(root_file, ".rb")
@@ -13,7 +13,7 @@
# @param abspath [String]
# @return [String]
def camelize(basename, abspath)
- (basename == "version" && abspath == @version_file) ? "VERSION" : super
+ abspath == @version_file ? "VERSION" : super
end
end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/lib/zeitwerk/inflector.rb
new/lib/zeitwerk/inflector.rb
--- old/lib/zeitwerk/inflector.rb 2019-07-16 23:21:48.000000000 +0200
+++ new/lib/zeitwerk/inflector.rb 2019-11-01 12:53:07.000000000 +0100
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Zeitwerk
- class Inflector # :nodoc:
+ class Inflector
# Very basic snake case -> camel case conversion.
#
# inflector = Zeitwerk::Inflector.new
@@ -9,11 +9,41 @@
# inflector.camelize("users_controller", ...) # => "UsersController"
# inflector.camelize("api", ...) # => "Api"
#
+ # Takes into account hard-coded mappings configured with `inflect`.
+ #
# @param basename [String]
# @param _abspath [String]
# @return [String]
def camelize(basename, _abspath)
- basename.split('_').map!(&:capitalize).join
+ overrides[basename] || basename.split('_').map!(&:capitalize).join
+ end
+
+ # Configures hard-coded inflections:
+ #
+ # inflector = Zeitwerk::Inflector.new
+ # inflector.inflect(
+ # "html_parser" => "HTMLParser",
+ # "mysql_adapter" => "MySQLAdapter"
+ # )
+ #
+ # inflector.camelize("html_parser", abspath) # => "HTMLParser"
+ # inflector.camelize("mysql_adapter", abspath) # => "MySQLAdapter"
+ # inflector.camelize("users_controller", abspath) # => "UsersController"
+ #
+ # @param inflections [{String => String}]
+ # @return [void]
+ def inflect(inflections)
+ overrides.merge!(inflections)
+ end
+
+ private
+
+ # Hard-coded basename to constant name user maps that override the default
+ # inflection logic.
+ #
+ # @return [{String => String}]
+ def overrides
+ @overrides ||= {}
end
end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/lib/zeitwerk/loader/callbacks.rb
new/lib/zeitwerk/loader/callbacks.rb
--- old/lib/zeitwerk/loader/callbacks.rb 2019-07-16 23:21:48.000000000
+0200
+++ new/lib/zeitwerk/loader/callbacks.rb 2019-11-01 12:53:07.000000000
+0100
@@ -14,7 +14,7 @@
if logger && cdef?(*cref)
log("constant #{cpath(*cref)} loaded from file #{file}")
elsif !cdef?(*cref)
- raise NameError, "expected file #{file} to define constant
#{cpath(*cref)}, but didn't"
+ raise Zeitwerk::NameError, "expected file #{file} to define constant
#{cpath(*cref)}, but didn't"
end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/lib/zeitwerk/loader.rb new/lib/zeitwerk/loader.rb
--- old/lib/zeitwerk/loader.rb 2019-07-16 23:21:48.000000000 +0200
+++ new/lib/zeitwerk/loader.rb 2019-11-01 12:53:07.000000000 +0100
@@ -474,21 +474,38 @@
# @return [void]
def set_autoloads_in_dir(dir, parent)
ls(dir) do |basename, abspath|
- if ruby?(basename)
- basename.slice!(-3, 3)
- cname = inflector.camelize(basename, abspath).to_sym
- autoload_file(parent, cname, abspath)
- elsif dir?(abspath)
- # In a Rails application, `app/models/concerns` is a subdirectory of
- # `app/models`, but both of them are root directories.
- #
- # To resolve the ambiguity file name -> constant path this
introduces,
- # the `app/models/concerns` directory is totally ignored as a
namespace,
- # it counts only as root. The guard checks that.
- unless root_dirs.key?(abspath)
+ begin
+ if ruby?(basename)
+ basename.slice!(-3, 3)
cname = inflector.camelize(basename, abspath).to_sym
- autoload_subdir(parent, cname, abspath)
+ autoload_file(parent, cname, abspath)
+ elsif dir?(abspath)
+ # In a Rails application, `app/models/concerns` is a subdirectory
of
+ # `app/models`, but both of them are root directories.
+ #
+ # To resolve the ambiguity file name -> constant path this
introduces,
+ # the `app/models/concerns` directory is totally ignored as a
namespace,
+ # it counts only as root. The guard checks that.
+ unless root_dirs.key?(abspath)
+ cname = inflector.camelize(basename, abspath).to_sym
+ autoload_subdir(parent, cname, abspath)
+ end
end
+ rescue ::NameError => error
+ path_type = ruby?(abspath) ? "file" : "directory"
+
+ raise NameError, <<~MESSAGE
+ #{error.message} inferred by #{inflector.class} from #{path_type}
+
+ #{abspath}
+
+ Possible ways to address this:
+
+ * Tell Zeitwerk to ignore this particular #{path_type}.
+ * Tell Zeitwerk to ignore one of its parent directories.
+ * Rename the #{path_type} to comply with the naming conventions.
+ * Modify the inflector to handle this case.
+ MESSAGE
end
end
end
@@ -606,8 +623,14 @@
# @param parent [Module]
# @param cname [Symbol]
# @return [String, nil]
- def strict_autoload_path(parent, cname)
- parent.autoload?(cname) if cdef?(parent, cname)
+ if method(:autoload?).arity == 1
+ def strict_autoload_path(parent, cname)
+ parent.autoload?(cname) if cdef?(parent, cname)
+ end
+ else
+ def strict_autoload_path(parent, cname)
+ parent.autoload?(cname, false)
+ end
end
# This method is called this way because I prefer `preload` to be the
method
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/lib/zeitwerk/real_mod_name.rb
new/lib/zeitwerk/real_mod_name.rb
--- old/lib/zeitwerk/real_mod_name.rb 2019-07-16 23:21:48.000000000 +0200
+++ new/lib/zeitwerk/real_mod_name.rb 2019-11-01 12:53:07.000000000 +0100
@@ -8,8 +8,14 @@
# The name method can be overridden, hence the indirection in this method.
#
# @param mod [Class, Module]
- # @return [String]
- def real_mod_name(mod)
- UNBOUND_METHOD_MODULE_NAME.bind(mod).call
+ # @return [String, nil]
+ if UnboundMethod.method_defined?(:bind_call)
+ def real_mod_name(mod)
+ UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
+ end
+ else
+ def real_mod_name(mod)
+ UNBOUND_METHOD_MODULE_NAME.bind(mod).call
+ end
end
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/lib/zeitwerk/version.rb new/lib/zeitwerk/version.rb
--- old/lib/zeitwerk/version.rb 2019-07-16 23:21:48.000000000 +0200
+++ new/lib/zeitwerk/version.rb 2019-11-01 12:53:07.000000000 +0100
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Zeitwerk
- VERSION = "2.1.9"
+ VERSION = "2.2.1"
end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/metadata new/metadata
--- old/metadata 2019-07-16 23:21:48.000000000 +0200
+++ new/metadata 2019-11-01 12:53:07.000000000 +0100
@@ -1,14 +1,14 @@
--- !ruby/object:Gem::Specification
name: zeitwerk
version: !ruby/object:Gem::Version
- version: 2.1.9
+ version: 2.2.1
platform: ruby
authors:
- Xavier Noria
autorequire:
bindir: bin
cert_chain: []
-date: 2019-07-16 00:00:00.000000000 Z
+date: 2019-11-01 00:00:00.000000000 Z
dependencies: []
description: |2
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -20,6 +20,7 @@
extensions: []
extra_rdoc_files: []
files:
+- MIT-LICENSE
- README.md
- lib/zeitwerk.rb
- lib/zeitwerk/error.rb