Exporter::Extensible - Create easy-to-extend modules which export symbols
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();
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:
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.
import
This exporter supports lots of ways to add custom processing during 'import' without needing to dig into the implementation.
This module supports exporting foo, $foo, @foo, %foo, or even *foo. It also supports tags (:foo) and options (-foo).
foo
$foo
@foo
%foo
*foo
:foo
-foo
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.
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 than caller, etc.
-prefix
-suffix
-as
-not
caller
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.
__PACKAGE__->exporter_ ...
Because nobody likes big dependency trees.
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.
If the first argument to import is a hashref, it is used as configuration of import. 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).
-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.
-
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 like My::Class::Exports instead of My::Class and then delegate to its "import_into" method.
exporter_
__PACKAGE__->new
My::Class::Exports
My::Class
package My::Class; package My::Class::Exports { use Exporter::Extensible -exporter_setup => 1; ... } sub import { My::Class::Exports->import_into(scalar caller, @_) }
This is the method that gets called when you use DerivedModule @list.
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.
-tag
:tag
The elements of @list are handled according to the following patterns:
@list
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")
use DerivedModule -name, ...;
Run custom processing defined by module author, possibly consuming arguments that follow it.
use DerivedModule { ... }, ...;
If the first argument to import is a hashref, these fields are recognized:
Package name or hashref to which all symbols will be exported. Defaults to caller.
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 wrap clean in a BEGIN block.
no MyModule @args
$scope->clean
clean
BEGIN
{ 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)
use B::Hooks::EndOfScope 'on_scope_end'
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 { ... } ] };
If true, then the list of symbols will be uninstalled from the into package. For example, no MyModule @args is the same as MyModule->import({ no => 1 }, @args)
into
MyModule->import({ no => 1 }, @args)
Determines what to do if the symbol already exists in the target package:
1
Replace the symbol with no warning.
'warn'
'carp'
Replace the symbol but warn about it using carp.
carp
'die'
'fatal'
'croak'
Don't import the symbol, and die by calling croak.
croak
'skip'
Don't import the symbol and don't warn about it.
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 all imported names with this string.
Append this string to all imported names.
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:
Install the thing as this exact name. (no sigil, but relative to into)
Same as global option prefix, limited to this one symbol/tag.
prefix
Same as global option suffix, limited to this one symbol/tag.
suffix
Same as global option not, limited to this one tag.
not
Same as global option replace, limited to this one tag.
replace
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
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:
%EXPORT
%EXPORT_TAGS
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.
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".
__PACKAGE__
-export_setup => 1
export(@list) is a convenient alias for __PACKAGE__->exporter_export(@list); which you receive from -exporter_setup.
export(@list)
__PACKAGE__->exporter_export(@list);
-exporter_setup
__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.
export
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 override foo 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
-foo => $CODEREF
-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
'=$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.
_generate_foo
_generateSCALAR_foo
_generateARRAY_foo
_generateHASH_foo
__PACKAGE__->exporter_register_symbol($name_with_sigil, $ref);
__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.
$method
$arg_count
import(...)
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.
*
__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.
$name_with_sigil
':foo'
__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.
@members
$tag_name
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:
Export
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.
This requests that the export-name get added to the named tag. You may specify any number of tags.
-(N)
-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
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".
_generate_
_generate$REFTYPE_
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:
'-foo'
'$foo'
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)
[ $method_name, $arg_count_or_star ]
{ '-exporter_setup' => [ "exporter_setup", 1 ] }
This means "call $self->exporter_setup($arg1) when you see import('-exporter_setup', $arg1, ... ). Because it is a method call, subclasses of your module can override it.
$self->exporter_setup($arg1)
import('-exporter_setup', $arg1, ... )
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.
all
: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.
@EXPORT
'default'
@{$EXPORT_TAGS{default}}
If the first element of the arrayref is undef, it means "don't inherit the tag members from the parent class".
undef
# Don't want to inherit members of ':foo' from parent: foo => [ undef, 'x', 'y', 'z' ]
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] ],
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",
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
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';
':all'
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.
$symbol
$args
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.
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:
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.
next::method
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.
Michael Conrad <mike@nrdvana.net>
Tabulo <dev-git.perl@tabulo.net>
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.
To install Exporter::Extensible, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Exporter::Extensible
CPAN shell
perl -MCPAN -e shell install Exporter::Extensible
For more information on module installation, please visit the detailed CPAN module installation guide.