package Object::Strenuous;
use Sub::Exporter::Util 'curry_method';
use Sub::Exporter -setup => {
exports => [ objection => curry_method('new') ],
};
With this configuration, the importing code may contain:
my $obj = objection("irrelevant");
...and this will be equivalent to:
my $obj = Object::Strenuous->new("irrelevant");
The built-in invocant is determined by the invocant for the "import" method. That means that if we were to subclass Object::Strenuous as follows:
package Object::Strenuous::Repeated; @ISA = 'Object::Strenuous';
...then importing "objection" from the subclass would build-in that subclass.
Finally, since the invocant can be an object, you can write something like this:
package Cypher;
use Sub::Exporter::Util 'curry_method';
use Sub::Exporter -setup => {
exports => [ encypher => curry_method ],
};
with the expectation that "import" will be called on an instantiated Cypher object:
BEGIN {
my $cypher = Cypher->new( ... );
$cypher->import('encypher');
}
Now there is a globally-available "encypher" routine which calls the encypher method on an otherwise unavailable Cypher object.
Here's a trivial (and naive) example:
package Mixin::DumpObj;
use Data::Dumper;
use Sub::Exporter -setup => {
exports => [ qw(dump) ]
};
sub dump {
my ($self) = @_;
return Dumper($self);
}
When writing your own object class, you can then import "dump" to be used as a method, called like so:
$object->dump;
By assuming that the importing class will provide a certain interface, a method-exporting module can be used as a simple plugin:
package Number::Plugin::Upto;
use Sub::Exporter -setup => {
into => 'Number',
exports => [ qw(upto) ],
groups => [ default => [ qw(upto) ] ],
};
sub upto {
my ($self) = @_;
return 1 .. abs($self->as_integer);
}
The "into" line in the configuration says that this plugin will export, by default, into the Number package, not into the "use"-ing package. It can be exported anyway, though, and will work as long as the destination provides an "as_integer" method like the one it expects. To import it to a different destination, one can just write:
use Number::Plugin::Upto { into => 'Quantity' };
Here is a very small example:
package Data::Analyzer;
use Sub::Exporter -setup => {
exports => [ analyze => \'_generate_analyzer' ],
};
sub _generate_analyzer {
my ($mixin, $name, $arg, $col) = @_;
return sub {
my ($self) = @_;
my $values = [ $self->values ];
my $analyzer = $mixin->new($values);
$analyzer->perform_analysis;
$analyzer->aggregate_results;
return $analyzer->summary;
};
}
If imported by any package providing a "values" method, this plugin will provide a single "analyze" method that acts as a simple interface to a more complex set of behaviors.
Even more importantly, because the $mixin value will be the invocant on which the "import" was actually called, one can subclass "Data::Analyzer" and replace only individual pieces of the complex behavior, making it easy to write complex, subclassable toolkits with simple single points of entry for external interfaces.
package Important::Constants;
use Sub::Exporter -setup => {
collectors => [ constants => \'_set_constants' ],
};
sub _set_constants {
my ($class, $value, $data) = @_;
Package::Generator->assign_symbols(
$data->{into},
[
MEANING_OF_LIFE => \42,
ONE_TRUE_BASE => \13,
FACTORS => [ 6, 9 ],
],
);
return 1;
}
Then, someone can write:
use Important::Constants 'constants'; print "The factors @FACTORS produce $MEANING_OF_LIFE in $ONE_TRUE_BASE.";
(The constants must be exported via a collector, because they are effectively altering the importing class in a way other than installing subroutines.)
use Sub::Exporter -setup => {
collectors => { -base => \'_make_base' },
};
sub _make_base {
my ($class, $value, $data) = @_;
my $target = $data->{into};
push @{"$target\::ISA"}, $class;
}
Then, the user of your class can write:
use Some::Class -base;
and become a subclass. This can be quite useful in building, for example, a module that helps build plugins. We may want a few utilities imported, but we also want to inherit behavior from some base plugin class;
package Framework::Util;
use Sub::Exporter -setup => {
exports => [ qw(log global_config) ],
groups => [ _plugin => [ qw(log global_config) ]
collectors => { '-plugin' => \'_become_plugin' },
};
sub _become_plugin {
my ($class, $value, $data) = @_;
my $target = $data->{into};
push @{"$target\::ISA"}, $class->plugin_base_class;
push @{ $data->{import_args} }, '-_plugin';
}
Now, you can write a plugin like this:
package Framework::Plugin::AirFreshener; use Framework::Util -plugin;
sub exporter_upgrade {
my ($pkg) = @_;
my $new_pkg = "$pkg\::UsingSubExporter";
return $new_pkg if $new_pkg->isa($pkg);
Sub::Exporter::setup_exporter({
as => 'import',
into => $new_pkg,
exports => [ @{"$pkg\::EXPORT_OK"} ],
groups => {
%{"$pkg\::EXPORT_TAG"},
default => [ @{"$pkg\::EXPORTS"} ],
},
});
@{"$new_pkg\::ISA"} = $pkg;
return $new_pkg;
}
This routine, given the name of an existing package configured to use Exporter.pm, returns the name of a new package with a Sub::Exporter-powered "import" routine. This lets you import "Toolkit::exported_sub" into the current package with the name "foo" by writing:
BEGIN {
require Toolkit;
exporter_upgrade('Toolkit')->import(exported_sub => { -as => 'foo' })
}
If you're feeling particularly naughty, this routine could have been declared in the UNIVERSAL package, meaning you could write:
BEGIN {
require Toolkit;
Toolkit->exporter_upgrade->import(exported_sub => { -as => 'foo' })
}
The new package will have all the same exporter configuration as the original, but will support export and group renaming, including exporting into scalar references. Further, since Sub::Exporter uses "can" to find the routine being exported, the new package may be subclassed and some of its exports replaced.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.