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.

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.

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 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).

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 like My::Class::Exports instead of My::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 wrap clean in a BEGIN 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 as MyModule->import({ no => 1 }, @args)

replace

Determines what to do if the symbol already exists in the target package:

1

Replace the symbol with no warning.

'warn' (or 'carp')

Replace the symbol but warn about it using carp.

'die' (or 'fatal' or 'croak')

Don't import the symbol, and die by calling croak.

'skip'

Don't import the symbol and don't warn about it.

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 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 => $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 see import('-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

Exporter::Almighty
Exporter::Tiny
Sub::Exporter
Export::Declare
Badger::Exporter

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.