NAME
Exporter::Extensible - Create easy-to-extend modules which export symbols
SYNOPSIS
Define a module with exports
package My::Utils;
use Exporter::Extensible -exporter_setup => 1;
export(qw( foo $x @STUFF -strict_and_warnings ), ':baz' => ['foo'] );
sub foo { ... }
sub strict_and_warnings {
strict->import;
warnings->import;
}
Create a new module which exports all that, and more
package My::MoreUtils;
use My::Utils -exporter_setup => 1;
sub util_fn3 : Export(:baz) { ... }
Use the module
use My::MoreUtils qw( -strict_and_warnings :baz @STUFF );
# Use the exported things
push @STUFF, foo(), util_fn3();
DESCRIPTION
As a module author, you have dozens of exporters to choose from, so I'll try to get straight to the pros/cons of this module:
Pros
- Extend Your Module
-
This exporter focuses on the ability and ease of letting you "subclass" a module-with-exports to create a derived module-with-exports. It supports multiple inheritance, for things like tying together all your utility modules into a mega-utility module.
- Extend Behavior of
import
-
This exporter supports lots of ways to add custom processing during 'import' without needing to dig into the implementation.
- More than just subs
-
This module supports exporting
foo
,$foo
,@foo
,%foo
, or even*foo
. It also supports tags (:foo
) and options (-foo
). - Be Lazy
-
This exporter supports on-demand generators for symbols, as well as tags! So if you have a complicated or expensive list of exports you can wait until the first time each is requested before finding out whether it is available or loading the dependent module.
- Full-featured
-
This exporter attempts to copy useful features from other popular exporters, like renaming imports with
-prefix
/-suffix
/-as
, excluding symbols with-not
, scoped-unimport, passing options to generators, importing to things other thancaller
, etc. - More-Than-One-Way-To-Declare-Exports
-
Pick your favorite. You can use the export do-what-I-mean function, method attributes, the
__PACKAGE__->exporter_ ...
API, or declare package variables similar to Exporter. - No Non-core Dependencies (for Perl ≥ 5.12)
-
Because nobody likes big dependency trees.
- Speed
-
The features are written so you only pay for what you use, with emphasis on "make the common case fast". Benchmarking is difficult since there are so many patterns to compare, but in general, this module is significantly faster than Sub::Exporter, faster than Exporter::Tiny for medium or large export workload, and even beats Exporter itself for large sets of exported subs.
Cons
- Imposes meaning on hashrefs in import list
-
If the first argument to
import
is a hashref, it is used as configuration ofimport
. Hashref arguments following a symbol name are treated as arguments to the generator (if any) or config overides for a tag. (but you can control hashref processing on your own for-NAME
in the API you are authoring). - Imposes meaning for notation
-NAME
-
This module follows the Exporter convention for symbol names but with the additional convention that names beginning with dash
-
are treated as requests for runtime behavior. Additionally, it may consume the arguments that follow it, at the discresion of the module author. This feature is designed to feel like command-line option processing. - Inheritance and Namespace pollution
-
This module uses the package hierarchy for combining exportable sets of things. It also defines lots of methods starting with the prefix
exporter_
. It also calls__PACKAGE__->new
every time a user imports something. If you want to make an object-oriented class but also export a few symbols, you need to use a second namespace likeMy::Class::Exports
instead ofMy::Class
and then delegate to its "import_into" method.package My::Class; package My::Class::Exports { use Exporter::Extensible -exporter_setup => 1; ... } sub import { My::Class::Exports->import_into(scalar caller, @_) }
IMPORT API (for consumer)
import
This is the method that gets called when you use DerivedModule @list
.
The user-facing API is mostly the same as Sub::Exporter or Exporter::Tiny, except that -tag
is not an alias for :tag
.
The elements of @list
are handled according to the following patterns:
Plain Name With Sigil
use DerivedModule 'name', '$name', '@name', '%name', '*name', ':name';
Same as Exporter, except it might be generated on the fly, and can be followed by an options hashref for further control over how it gets imported. (see "In-line Options")
Option
use DerivedModule -name, ...;
Run custom processing defined by module author, possibly consuming arguments that follow it.
Global Options
use DerivedModule { ... }, ...;
If the first argument to import
is a hashref, these fields are recognized:
- into
-
Package name or hashref to which all symbols will be exported. Defaults to
caller
. - scope
-
Empty scalar-ref whose scope determines when to unimport the things just imported. After a successful import, this will be assigned a scope-guard object whose destructor un-imports those same symbols. This saves you the hassle of calling
no MyModule @args
. You can also call$scope->clean
to trigger the unimport in a more direct manner. If you need the methods cleaned out at the end of compilation (i.e. before execution) you can wrapclean
in aBEGIN
block.{ use MyModule { scope => \my $scope }, ':sugar_methods'; # use sugar methods ... # you could "BEGIN { $scope->clean }" if you want them removed sooner } # All those symbols are now neatly removed from your package
This also works well when combined with
use B::Hooks::EndOfScope 'on_scope_end'
. (I would have added a stronger integration with B::Hooks::EndOfScope but I didn't want to depend on an XS module) - not
-
Only applies to tags. Can be a scalar, regex, coderef, or list of any of those that filters out un-wanted imports.
use MyModule ':foo' => { -not => 'log' }; use MyModule ':foo' => { -not => qr/^log/ }; use MyModule ':foo' => { -not => sub { $forbidden{$_} } }; use MyModule ':foo' => { -not => [ 'log', qr/^log/, sub { ... } ] };
- no
-
If true, then the list of symbols will be uninstalled from the
into
package. For example,no MyModule @args
is the same asMyModule->import({ no => 1 }, @args)
- replace
-
Determines what to do if the symbol already exists in the target package:
- installer
-
A coderef which will be called instead of "exporter_install" or "export_uninstall". Uses the same arguments:
installer => sub { my ($exporter, $list)= @_; for (my $i= 0; $i < @$list; $i+= 2) { my ($name, $ref)= @{$list}[$i..1+$i]; ... }
- prefix (or -prefix)
-
Prefix all imported names with this string.
- suffix (or -suffix)
-
Append this string to all imported names.
In-line Options
use DerivedModule ':name' => { ... };
The arguments to import
are generally plain strings. If one is followed by a hashref, the hashref becomes the argument to the generator (if any), but may also contain:
- -as => $name
-
Install the thing as this exact name. (no sigil, but relative to
into
) - -prefix
-
Same as global option
prefix
, limited to this one symbol/tag. - -suffix
-
Same as global option
suffix
, limited to this one symbol/tag. - -not
-
Same as global option
not
, limited to this one tag. - -replace
-
Same as global option
replace
, limited to this one tag.
import_into
DerivedModule->import_into('DestinationPackage', @list);
This is a shorthand for
DerivedModule->import({ into => 'DestinationPackage }, @list);
There is also a more generic way to handle this underlying need though - see Import::Into
EXPORT API (for author)
The underlying requirements for using this exporter are to inherit from it, and declare your exports in the variables %EXPORT
and %EXPORT_TAGS
. The quickest way to do that is:
package My::Module;
use Exporter::Extensible -exporter_setup => 1;
export(...);
Those lines are shorthand for:
package My::Module;
use strict;
use warnings;
use parent 'Exporter::Extensible';
our (@EXPORT, %EXPORT, %EXPORT_TAGS);
$EXPORT_TAGS{default} ||= \@EXPORT;
$EXPORT{...}= ...; # for each argument to export()
Everything else below is just convenience and shorthand to make this easier.
Export by API
This module provides an api for specifying the exports. You can call these methods on __PACKAGE__
, or if you ask for version-1 as -export_setup => 1
you can use the convenience function "export".
export
export(@list)
is a convenient alias for __PACKAGE__->exporter_export(@list);
which you receive from -exporter_setup
.
exporter_export
__PACKAGE__->exporter_export(@things);
__PACKAGE__->exporter_export(qw( $foo bar ), ':baz' => \@tag_members, ... );
This class method takes a list of keys (which must be scalars), with optional values which must be refs. If the value is omitted, export
attempts to do-what-you-mean to find it.
foo => \&CODE
-
This declares a normal exported function. If the ref is omitted,
export
looks for it in the the current package. Note that this lookup happens immediately, so derived packages that want to overridefoo
must re-declare it. '$foo' => \$SCALAR
,'@foo' => \@ARRAY
,'%foo' => \%HASH
,'*foo' => \*GLOB
-
This exports a normal variable or typeglob. If the ref is omitted,
export
looks for it in the current package. Note: this exports a global variable which can be modified. In general, that's bad practice, but might be desired for efficiency. If your goal is efficient access to a singleton object, consider a generator instead like=$foo
. -foo => $CODEREF
or-foo => \"methodname"
-
This differs from a normal exported function in that it will execute the coderef at import time, and sub-packages can override it, since it gets called as a method. The default is to derive the method name by removing the
-
. ':foo' => \@LIST
-
Declaring a tag is nothing special; just give it an arrayref of what should be imported when the tag is encountered.
'=$foo' => $CODEREF
or'=$foo' => \"methodname"
-
Prefixing an export name with an equal sign means you want to generate the export on the fly. The ref is understood to be the coderef or method name to call (as a method) which will return the ref of the correct type to be exported. The default is to look for
_generate_foo
,_generateSCALAR_foo
,_generateARRAY_foo
,_generateHASH_foo
, etc.
exporter_register_symbol
__PACKAGE__->exporter_register_symbol($name_with_sigil, $ref);
exporter_register_option
__PACKAGE__->exporter_register_option($name, $method, $arg_count);
This declares an "option" like -foo
. The name should not include the leading -
. The $method
argument can either be a package method name, or a coderef. The $arg_count
is the number of options to consume from the import(...)
list following the option.
To declare an option that consumes a variable number of arguments, specify *
for the count and then write your method so that it returns the number of arguments it consumed.
exporter_register_generator
__PACKAGE__->exporter_register_generator($name_with_sigil, $method);
This declares that you want to generate $name_with_sigil
on demand, using $method
. $name_with_sigil
may be a tag like ':foo'
. $method
can be either a coderef or method name. The function will be called as a method on an instance of your package. The instance is the blessed hash of options passed by the current consumer of your module.
exporter_register_tag_members
__PACKAGE__->exporter_register_tag_members($tag_name, @members);
This pushes a list of @members
onto the end of the named tag. $tag_name
should not include the leading ':'. These @members
are cumulative with tags inherited from parent packages. To avoid inheriting tag members, register a generator for the tag, instead.
Export by Attribute
Attributes are fun. If you enjoy artistic code, you might like to declare your exports like so:
sub foo : Export( :foo ) {}
sub bar : Export(-) {}
sub _generate_baz : Export(= :foo) {}
instead of
export( 'foo', '-bar', '=baz', ':foo' => [ 'foo','baz' ] );
The notations supported in the Export
attribute are different but similar to those in the "export" function. You may include one or more of the following in the parenthesees:
foo
-
This indicates the export-name of a sub. A sub may be exported as more than one name. Note that the first name in the list becomes the official name (ignoring the actual name of the sub) which will be added to any tags you listed.
:foo
-
This requests that the export-name get added to the named tag. You may specify any number of tags.
-
,-(N)
,-foo
,-foo(N)
-
This sets up the sub as an option, capturing N arguments. In the cases without a name, the name of the sub is used. N may be
'*'
or'?'
; see "IMPLEMENTING OPTIONS". =
,=$
,=@
,=%
,=*
,=foo
,=$foo
, ...-
This sets up the sub as a generator for the export-name. If the word portion of the name is omitted, it is taken to be the sub name minus the prefix
_generate_
or_generate$REFTYPE_
. See "IMPLEMENTING GENERATORS".
Export by Variables
As shown above, the configuration for your exports is the variable %EXPORT
. If you want the fastest possible module load time, you might decide to populate %EXPORT
manually.
The keys of this hash are the strings that the user would specify as the import
arguments, like '-foo'
, '$foo'
, etc. The value should be some kind of reference matching the sigil. Functions should be a coderef, scalars should be a scalarref, etc. But, there are two special cases:
- Options
-
An option is any key starting with
-
, like this module's own-exporter_setup
. The values for these must be a pair of[ $method_name, $arg_count_or_star ]
. (the default structure is subject to change, but this notation will always be supported){ '-exporter_setup' => [ "exporter_setup", 1 ] }
This means "call
$self->exporter_setup($arg1)
when you seeimport('-exporter_setup', $arg1, ... )
. Because it is a method call, subclasses of your module can override it. - Generators
-
Sometimes you want to generate the thing to be exported. To indicate this, use a ref-ref of the method name, or a ref of the coderef to execute. For example:
{ foo => "_generate_foo", bar => \\&generate_bar, baz => \sub { ... }, }
Again, this is subject to change, but these notations will always be supported for backward-compatibility.
Meanwhile the %EXPORT_TAGS
variable is almost identical to the one used by Exporter, but with a few enhancements:
:all
-
You don't need to declare the tag
all
, because this module calculates it for you, from the list of all keys of%EXPORT
excluding tags or options. You can override this default though. :default
-
@EXPORT
is added to%EXPORT_TAGS
as'default'
. So, you can push items into@EXPORT
or into@{$EXPORT_TAGS{default}}
and it is the same arrayref. - Resetting the Tag Members
-
If the first element of the arrayref is
undef
, it means "don't inherit the tag members from the parent class".# Don't want to inherit members of ':foo' from parent: foo => [ undef, 'x', 'y', 'z' ]
- Data in a Tag
-
The elements of a tag can include parameters to generators, or arguments to an option, etc; anything that could be passed to
import
will work as expected.foo => [ 'x', 'y', -init => [1,2,3] ],
- Generators
-
If the value in
%EXPORT_TAGS
is not an arrayref, then it should be a REF-ref of either the scalar name of a generator, or a coderef of the generator.foo => \\"_generate_foo",
IMPLEMENTING OPTIONS
Exporter::Extensible lets you run whatever code you like when it encounters "-name" in the import list. To accommodate all the different ways I wanted to use this, I decided to let the option decide how many arguments to consume. So, the API is as follows:
# By default, no arguments are captured. A ref may not follow this option.
sub name : Export( -name ) {
my $exporter= shift;
...
}
# Ask for three arguments (regardless of whether they are refs)
sub name : Export( -name(3) ) {
my ($exporter, $arg1, $arg2, $arg3)= @_;
...
}
# Ask for one argument but only if it is a ref of some kind.
# If it is a hashref, this also processes import options like -prefix, -replace, etc.
sub name : Export( -name(?) ) {
my ($exporter, $maybe_arg)= @_;
...
}
# Might need any number of arguments. Return the number we consumed.
sub name : Export( -name(*) ) {
my $exporter = shift;
while (@_) {
last if ...;
...
++$consumed;
}
return $consumed;
}
The first argument $exporter
is a instance of the exporting package, and you can inspect it or even reconfigure it. For instance, if you want your option to automatically select some symbols as if they had been passed to "import", you could call "exporter_also_import".
exporter_also_import
This method can be used *during* a call to import
for an option or generator to request that aditional things be imported into the caller as if the caller had requested them on the import line. For example:
sub foo : Export(-) {
shift->exporter_also_import(':all');
}
This causes the option -foo
to be equivalent to the tag ':all'
;
IMPLEMENTING GENERATORS
A generator is just a function that returns the thing to be imported. A generator is called as:
$exporter->$generator($symbol, $args);
where $exporter
is an instance of your package, $symbol
is the name of the thing as specified to import
(with sigil) and $args
is the optional hashref the user might have given following $symbol
.
If you wanted to implement something like Sub::Exporter's "Collectors", you can just write some options that take an argument and store it in the $exporter
instance. Then, your generator can retrieve the values from there.
package MyExports;
use Exporter::Extensible -exporter_setup => 1;
export(
# be sure to use hash keys that won't conflict with Exporter::Extensible's internals
'-foo(1)' => sub { $_[0]{foo}= $_[1] },
'-bar(1)' => sub { $_[0]{bar}= $_[1] },
'=foobar' => sub { my $foobar= $_[0]{foo} . $_[0]{bar}; sub { $foobar } },
);
package User;
use MyModule qw/ -foo abc -bar def foobar -foo xyz foobar /, { -as => "x" };
# This exports a sub as "foobar" which returns "abcdef", and a sub as "x" which
# returns "xyzdef". Note that if the second one didn't specify {-as => "x"},
# it would get ignored because 'foobar' was already queued to be installed.
AUTOLOADING SYMBOLS AND TAGS
In the same spirit that Perl lets you AUTOLOAD methods on demand, this exporter lets you define symbols and tags on demand. Simply override one of these methods:
exporter_autoload_symbol
my $ref= $self->exporter_autoload_symbol($sym);
This takes a symbol (including sigil), and returns a ref which should be installed. The ref is cached, but not added to the package %EXPORT
to be inherited by subclasses. If you want that to happen, you need to do it yourself. This method is called once at the end of checking the package hierarchy, rather than per-class of the hierarchy, so you should call next::method
if you don't recognize the symbol.
exporter_autoload_tag
my $arrayref= $self->exporter_autoload_tag($name);
This takes a tag name (no sigil) and returns an arrayref of items which should be added to the tag. The combined tag members are cached, but not added to the package %EXPORT_TAGS
to be inherited by subclasses. This method is called only if no package in the hierarchy defined the tag, which could cause confusion if a derived class wants to add a few symbols to a tag which is otherwise autoloaded by a parent. This method is called once at the end of iterating the package hierarchy, so you should call next::method
to collect any inherited autoloaded members of this tag.
SEE ALSO
AUTHOR
Michael Conrad <mike@nrdvana.net>
CONTRIBUTOR
Tabulo <dev-git.perl@tabulo.net>
COPYRIGHT AND LICENSE
This software is copyright (c) 2023 by Michael Conrad.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.