Initial Commit
This commit is contained in:
308
database/perl/lib/Sub/Exporter/Cookbook.pod
Normal file
308
database/perl/lib/Sub/Exporter/Cookbook.pod
Normal file
@@ -0,0 +1,308 @@
|
||||
|
||||
# ABSTRACT: useful, demonstrative, or stupid Sub::Exporter tricks
|
||||
# PODNAME: Sub::Exporter::Cookbook
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Sub::Exporter::Cookbook - useful, demonstrative, or stupid Sub::Exporter tricks
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
version 0.987
|
||||
|
||||
=head1 OVERVIEW
|
||||
|
||||
Sub::Exporter is a fairly simple tool, and can be used to achieve some very
|
||||
simple goals. Its basic behaviors and their basic application (that is,
|
||||
"traditional" exporting of routines) are described in
|
||||
L<Sub::Exporter::Tutorial> and L<Sub::Exporter>. This document presents
|
||||
applications that may not be immediately obvious, or that can demonstrate how
|
||||
certain features can be put to use (for good or evil).
|
||||
|
||||
=head1 THE RECIPES
|
||||
|
||||
=head2 Exporting Methods as Routines
|
||||
|
||||
With Exporter.pm, exporting methods is a non-starter. Sub::Exporter makes it
|
||||
simple. By using the C<curry_method> utility provided in
|
||||
L<Sub::Exporter::Util>, a method can be exported with the invocant built in.
|
||||
|
||||
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 C<import> method.
|
||||
That means that if we were to subclass Object::Strenuous as follows:
|
||||
|
||||
package Object::Strenuous::Repeated;
|
||||
@ISA = 'Object::Strenuous';
|
||||
|
||||
...then importing C<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 C<import> will be called on an instantiated Cypher
|
||||
object:
|
||||
|
||||
BEGIN {
|
||||
my $cypher = Cypher->new( ... );
|
||||
$cypher->import('encypher');
|
||||
}
|
||||
|
||||
Now there is a globally-available C<encypher> routine which calls the encypher
|
||||
method on an otherwise unavailable Cypher object.
|
||||
|
||||
=head2 Exporting Methods as Methods
|
||||
|
||||
While exporting modules usually export subroutines to be called as subroutines,
|
||||
it's easy to use Sub::Exporter to export subroutines meant to be called as
|
||||
methods on the importing package or its objects.
|
||||
|
||||
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 C<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 C<into> line in the configuration says that this plugin will export, by
|
||||
default, into the Number package, not into the C<use>-ing package. It can be
|
||||
exported anyway, though, and will work as long as the destination provides an
|
||||
C<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' };
|
||||
|
||||
=head2 Mixing-in Complex External Behavior
|
||||
|
||||
When exporting methods to be used as methods (see above), one very powerful
|
||||
option is to export methods that are generated routines that maintain an
|
||||
enclosed reference to the exporting module. This allows a user to import a
|
||||
single method which is implemented in terms of a complete, well-structured
|
||||
package.
|
||||
|
||||
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 C<values> method, this plugin will
|
||||
provide a single C<analyze> method that acts as a simple interface to a more
|
||||
complex set of behaviors.
|
||||
|
||||
Even more importantly, because the C<$mixin> value will be the invocant on
|
||||
which the C<import> was actually called, one can subclass C<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.
|
||||
|
||||
=head2 Exporting Constants
|
||||
|
||||
While Sub::Exporter isn't in the constant-exporting business, it's easy to
|
||||
export constants by using one of its sister modules, Package::Generator.
|
||||
|
||||
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.)
|
||||
|
||||
=head2 Altering the Importer's @ISA
|
||||
|
||||
It's trivial to make a collector that changes the inheritance of an importing
|
||||
package:
|
||||
|
||||
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;
|
||||
|
||||
=head2 Eating Exporter.pm's Brain
|
||||
|
||||
You probably shouldn't actually do this in production. It's offered more as a
|
||||
demonstration than a suggestion.
|
||||
|
||||
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
|
||||
C<import> routine. This lets you import C<Toolkit::exported_sub> into the
|
||||
current package with the name C<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 C<can> to find the routine being
|
||||
exported, the new package may be subclassed and some of its exports replaced.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Ricardo Signes <rjbs@cpan.org>
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
This software is copyright (c) 2007 by Ricardo Signes.
|
||||
|
||||
This is free software; you can redistribute it and/or modify it under
|
||||
the same terms as the Perl 5 programming language system itself.
|
||||
|
||||
=cut
|
||||
280
database/perl/lib/Sub/Exporter/Tutorial.pod
Normal file
280
database/perl/lib/Sub/Exporter/Tutorial.pod
Normal file
@@ -0,0 +1,280 @@
|
||||
|
||||
# PODNAME: Sub::Exporter::Tutorial
|
||||
# ABSTRACT: a friendly guide to exporting with Sub::Exporter
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Sub::Exporter::Tutorial - a friendly guide to exporting with Sub::Exporter
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
version 0.987
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
=head2 What's an Exporter?
|
||||
|
||||
When you C<use> a module, first it is required, then its C<import> method is
|
||||
called. The Perl documentation tells us that the following two lines are
|
||||
equivalent:
|
||||
|
||||
use Module LIST;
|
||||
|
||||
BEGIN { require Module; Module->import(LIST); }
|
||||
|
||||
The method named C<import> is the module's I<exporter>, it exports
|
||||
functions and variables into its caller's namespace.
|
||||
|
||||
=head2 The Basics of Sub::Exporter
|
||||
|
||||
Sub::Exporter builds a custom exporter which can then be installed into your
|
||||
module. It builds this method based on configuration passed to its
|
||||
C<setup_exporter> method.
|
||||
|
||||
A very basic use case might look like this:
|
||||
|
||||
package Addition;
|
||||
use Sub::Exporter;
|
||||
Sub::Exporter::setup_exporter({ exports => [ qw(plus) ]});
|
||||
|
||||
sub plus { my ($x, $y) = @_; return $x + $y; }
|
||||
|
||||
This would mean that when someone used your Addition module, they could have
|
||||
its C<plus> routine imported into their package:
|
||||
|
||||
use Addition qw(plus);
|
||||
|
||||
my $z = plus(2, 2); # this works, because now plus is in the main package
|
||||
|
||||
That syntax to set up the exporter, above, is a little verbose, so for the
|
||||
simple case of just naming some exports, you can write this:
|
||||
|
||||
use Sub::Exporter -setup => { exports => [ qw(plus) ] };
|
||||
|
||||
...which is the same as the original example -- except that now the exporter is
|
||||
built and installed at compile time. Well, that and you typed less.
|
||||
|
||||
=head2 Using Export Groups
|
||||
|
||||
You can specify whole groups of things that should be exportable together.
|
||||
These are called groups. L<Exporter> calls these tags. To specify groups, you
|
||||
just pass a C<groups> key in your exporter configuration:
|
||||
|
||||
package Food;
|
||||
use Sub::Exporter -setup => {
|
||||
exports => [ qw(apple banana beef fluff lox rabbit) ],
|
||||
groups => {
|
||||
fauna => [ qw(beef lox rabbit) ],
|
||||
flora => [ qw(apple banana) ],
|
||||
}
|
||||
};
|
||||
|
||||
Now, to import all that delicious foreign meat, your consumer needs only to
|
||||
write:
|
||||
|
||||
use Food qw(:fauna);
|
||||
use Food qw(-fauna);
|
||||
|
||||
Either one of the above is acceptable. A colon is more traditional, but
|
||||
barewords with a leading colon can't be enquoted by a fat arrow. We'll see why
|
||||
that matters later on.
|
||||
|
||||
Groups can contain other groups. If you include a group name (with the leading
|
||||
dash or colon) in a group definition, it will be expanded recursively when the
|
||||
exporter is called. The exporter will B<not> recurse into the same group twice
|
||||
while expanding groups.
|
||||
|
||||
There are two special groups: C<all> and C<default>. The C<all> group is
|
||||
defined for you and contains all exportable subs. You can redefine it,
|
||||
if you want to export only a subset when all exports are requested. The
|
||||
C<default> group is the set of routines to export when nothing specific is
|
||||
requested. By default, there is no C<default> group.
|
||||
|
||||
=head2 Renaming Your Imports
|
||||
|
||||
Sometimes you want to import something, but you don't like the name as which
|
||||
it's imported. Sub::Exporter can rename your imports for you. If you wanted
|
||||
to import C<lox> from the Food package, but you don't like the name, you could
|
||||
write this:
|
||||
|
||||
use Food lox => { -as => 'salmon' };
|
||||
|
||||
Now you'd get the C<lox> routine, but it would be called salmon in your
|
||||
package. You can also rename entire groups by using the C<prefix> option:
|
||||
|
||||
use Food -fauna => { -prefix => 'cute_little_' };
|
||||
|
||||
Now you can call your C<cute_little_rabbit> routine. (You can also call
|
||||
C<cute_little_beef>, but that hardly seems as enticing.)
|
||||
|
||||
When you define groups, you can include renaming.
|
||||
|
||||
use Sub::Exporter -setup => {
|
||||
exports => [ qw(apple banana beef fluff lox rabbit) ],
|
||||
groups => {
|
||||
fauna => [ qw(beef lox), rabbit => { -as => 'coney' } ],
|
||||
}
|
||||
};
|
||||
|
||||
A prefix on a group like that does the right thing. This is when it's useful
|
||||
to use a dash instead of a colon to indicate a group: you can put a fat arrow
|
||||
between the group and its arguments, then.
|
||||
|
||||
use Food -fauna => { -prefix => 'lovely_' };
|
||||
|
||||
eat( lovely_coney ); # this works
|
||||
|
||||
Prefixes also apply recursively. That means that this code works:
|
||||
|
||||
use Sub::Exporter -setup => {
|
||||
exports => [ qw(apple banana beef fluff lox rabbit) ],
|
||||
groups => {
|
||||
fauna => [ qw(beef lox), rabbit => { -as => 'coney' } ],
|
||||
allowed => [ -fauna => { -prefix => 'willing_' }, 'banana' ],
|
||||
}
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
use Food -allowed => { -prefix => 'any_' };
|
||||
|
||||
$dinner = any_willing_coney; # yum!
|
||||
|
||||
Groups can also be passed a C<-suffix> argument.
|
||||
|
||||
Finally, if the C<-as> argument to an exported routine is a reference to a
|
||||
scalar, a reference to the routine will be placed in that scalar.
|
||||
|
||||
=head2 Building Subroutines to Order
|
||||
|
||||
Sometimes, you want to export things that you don't have on hand. You might
|
||||
want to offer customized routines built to the specification of your consumer;
|
||||
that's just good business! With Sub::Exporter, this is easy.
|
||||
|
||||
To offer subroutines to order, you need to provide a generator when you set up
|
||||
your exporter. A generator is just a routine that returns a new routine.
|
||||
L<perlref> is talking about these when it discusses closures and function
|
||||
templates. The canonical example of a generator builds a unique incrementor;
|
||||
here's how you'd do that with Sub::Exporter;
|
||||
|
||||
package Package::Counter;
|
||||
use Sub::Exporter -setup => {
|
||||
exports => [ counter => sub { my $i = 0; sub { $i++ } } ],
|
||||
groups => { default => [ qw(counter) ] },
|
||||
};
|
||||
|
||||
Now anyone can use your Package::Counter module and he'll receive a C<counter>
|
||||
in his package. It will count up by one, and will never interfere with anyone
|
||||
else's counter.
|
||||
|
||||
This isn't very useful, though, unless the consumer can explain what he wants.
|
||||
This is done, in part, by supplying arguments when importing. The following
|
||||
example shows how a generator can take and use arguments:
|
||||
|
||||
package Package::Counter;
|
||||
|
||||
sub _build_counter {
|
||||
my ($class, $name, $arg) = @_;
|
||||
$arg ||= {};
|
||||
my $i = $arg->{start} || 0;
|
||||
return sub { $i++ };
|
||||
}
|
||||
|
||||
use Sub::Exporter -setup => {
|
||||
exports => [ counter => \'_build_counter' ],
|
||||
groups => { default => [ qw(counter) ] },
|
||||
};
|
||||
|
||||
Now, the consumer can (if he wants) specify a starting value for his counter:
|
||||
|
||||
use Package::Counter counter => { start => 10 };
|
||||
|
||||
Arguments to a group are passed along to the generators of routines in that
|
||||
group, but Sub::Exporter arguments -- anything beginning with a dash -- are
|
||||
never passed in. When groups are nested, the arguments are merged as the
|
||||
groups are expanded.
|
||||
|
||||
Notice, too, that in the example above, we gave a reference to a method I<name>
|
||||
rather than a method I<implementation>. By giving the name rather than the
|
||||
subroutine, we make it possible for subclasses of our "Package::Counter" module
|
||||
to replace the C<_build_counter> method.
|
||||
|
||||
When a generator is called, it is passed four parameters:
|
||||
|
||||
=over
|
||||
|
||||
=item * the invocant on which the exporter was called
|
||||
|
||||
=item * the name of the export being generated (not the name it's being installed as)
|
||||
|
||||
=item * the arguments supplied for the routine
|
||||
|
||||
=item * the collection of generic arguments
|
||||
|
||||
=back
|
||||
|
||||
The fourth item is the last major feature that hasn't been covered.
|
||||
|
||||
=head2 Argument Collectors
|
||||
|
||||
Sometimes you will want to accept arguments once that can then be available to
|
||||
any subroutine that you're going to export. To do this, you specify
|
||||
collectors, like this:
|
||||
|
||||
package Menu::Airline
|
||||
use Sub::Exporter -setup => {
|
||||
exports => ... ,
|
||||
groups => ... ,
|
||||
collectors => [ qw(allergies ethics) ],
|
||||
};
|
||||
|
||||
Collectors look like normal exports in the import call, but they don't do
|
||||
anything but collect data which can later be passed to generators. If the
|
||||
module was used like this:
|
||||
|
||||
use Menu::Airline allergies => [ qw(peanuts) ], ethics => [ qw(vegan) ];
|
||||
|
||||
...the consumer would get a salad. Also, all the generators would be passed,
|
||||
as their fourth argument, something like this:
|
||||
|
||||
{ allerges => [ qw(peanuts) ], ethics => [ qw(vegan) ] }
|
||||
|
||||
Generators may have arguments in their definition, as well. These must be code
|
||||
refs that perform validation of the collected values. They are passed the
|
||||
collection value and may return true or false. If they return false, the
|
||||
exporter will throw an exception.
|
||||
|
||||
=head2 Generating Many Routines in One Scope
|
||||
|
||||
Sometimes it's useful to have multiple routines generated in one scope. This
|
||||
way they can share lexical data which is otherwise unavailable. To do this,
|
||||
you can supply a generator for a group which returns a hashref of names and
|
||||
code references. This generator is passed all the usual data, and the group
|
||||
may receive the usual C<-prefix> or C<-suffix> arguments.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
L<Sub::Exporter> for complete documentation and references to other exporters
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Ricardo Signes <rjbs@cpan.org>
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
This software is copyright (c) 2007 by Ricardo Signes.
|
||||
|
||||
This is free software; you can redistribute it and/or modify it under
|
||||
the same terms as the Perl 5 programming language system itself.
|
||||
|
||||
=cut
|
||||
354
database/perl/lib/Sub/Exporter/Util.pm
Normal file
354
database/perl/lib/Sub/Exporter/Util.pm
Normal file
@@ -0,0 +1,354 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
package Sub::Exporter::Util;
|
||||
{
|
||||
$Sub::Exporter::Util::VERSION = '0.987';
|
||||
}
|
||||
# ABSTRACT: utilities to make Sub::Exporter easier
|
||||
|
||||
use Data::OptList ();
|
||||
use Params::Util ();
|
||||
|
||||
|
||||
sub curry_method {
|
||||
my $override_name = shift;
|
||||
sub {
|
||||
my ($class, $name) = @_;
|
||||
$name = $override_name if defined $override_name;
|
||||
sub { $class->$name(@_); };
|
||||
}
|
||||
}
|
||||
|
||||
BEGIN { *curry_class = \&curry_method; }
|
||||
|
||||
|
||||
sub curry_chain {
|
||||
# In the future, we can make \%arg an optional prepend, like the "special"
|
||||
# args to the default Sub::Exporter-generated import routine.
|
||||
my (@opt_list) = @_;
|
||||
|
||||
my $pairs = Data::OptList::mkopt(\@opt_list, 'args', 'ARRAY');
|
||||
|
||||
sub {
|
||||
my ($class) = @_;
|
||||
|
||||
sub {
|
||||
my $next = $class;
|
||||
|
||||
for my $i (0 .. $#$pairs) {
|
||||
my $pair = $pairs->[ $i ];
|
||||
|
||||
unless (Params::Util::_INVOCANT($next)) { ## no critic Private
|
||||
my $str = defined $next ? "'$next'" : 'undef';
|
||||
Carp::croak("can't call $pair->[0] on non-invocant $str")
|
||||
}
|
||||
|
||||
my ($method, $args) = @$pair;
|
||||
|
||||
if ($i == $#$pairs) {
|
||||
return $next->$method($args ? @$args : ());
|
||||
} else {
|
||||
$next = $next->$method($args ? @$args : ());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
# =head2 name_map
|
||||
#
|
||||
# This utility returns an list to be used in specify export generators. For
|
||||
# example, the following:
|
||||
#
|
||||
# exports => {
|
||||
# name_map(
|
||||
# '_?_gen' => [ qw(fee fie) ],
|
||||
# '_make_?' => [ qw(foo bar) ],
|
||||
# ),
|
||||
# }
|
||||
#
|
||||
# is equivalent to:
|
||||
#
|
||||
# exports => {
|
||||
# name_map(
|
||||
# fee => \'_fee_gen',
|
||||
# fie => \'_fie_gen',
|
||||
# foo => \'_make_foo',
|
||||
# bar => \'_make_bar',
|
||||
# ),
|
||||
# }
|
||||
#
|
||||
# This can save a lot of typing, when providing many exports with similarly-named
|
||||
# generators.
|
||||
#
|
||||
# =cut
|
||||
#
|
||||
# sub name_map {
|
||||
# my (%groups) = @_;
|
||||
#
|
||||
# my %map;
|
||||
#
|
||||
# while (my ($template, $names) = each %groups) {
|
||||
# for my $name (@$names) {
|
||||
# (my $export = $template) =~ s/\?/$name/
|
||||
# or Carp::croak 'no ? found in name_map template';
|
||||
#
|
||||
# $map{ $name } = \$export;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# return %map;
|
||||
# }
|
||||
|
||||
|
||||
sub merge_col {
|
||||
my (%groups) = @_;
|
||||
|
||||
my %merged;
|
||||
|
||||
while (my ($default_name, $group) = each %groups) {
|
||||
while (my ($export_name, $gen) = each %$group) {
|
||||
$merged{$export_name} = sub {
|
||||
my ($class, $name, $arg, $col) = @_;
|
||||
|
||||
my $merged_arg = exists $col->{$default_name}
|
||||
? { %{ $col->{$default_name} }, %$arg }
|
||||
: $arg;
|
||||
|
||||
if (Params::Util::_CODELIKE($gen)) { ## no critic Private
|
||||
$gen->($class, $name, $merged_arg, $col);
|
||||
} else {
|
||||
$class->$$gen($name, $merged_arg, $col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return %merged;
|
||||
}
|
||||
|
||||
|
||||
sub __mixin_class_for {
|
||||
my ($class, $mix_into) = @_;
|
||||
require Package::Generator;
|
||||
my $mixin_class = Package::Generator->new_package({
|
||||
base => "$class\:\:__mixin__",
|
||||
});
|
||||
|
||||
## no critic (ProhibitNoStrict)
|
||||
no strict 'refs';
|
||||
if (ref $mix_into) {
|
||||
unshift @{"$mixin_class" . "::ISA"}, ref $mix_into;
|
||||
} else {
|
||||
unshift @{"$mix_into" . "::ISA"}, $mixin_class;
|
||||
}
|
||||
return $mixin_class;
|
||||
}
|
||||
|
||||
sub mixin_installer {
|
||||
sub {
|
||||
my ($arg, $to_export) = @_;
|
||||
|
||||
my $mixin_class = __mixin_class_for($arg->{class}, $arg->{into});
|
||||
bless $arg->{into} => $mixin_class if ref $arg->{into};
|
||||
|
||||
Sub::Exporter::default_installer(
|
||||
{ %$arg, into => $mixin_class },
|
||||
$to_export,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
sub mixin_exporter {
|
||||
Carp::cluck "mixin_exporter is deprecated; use mixin_installer instead; it behaves identically";
|
||||
return mixin_installer;
|
||||
}
|
||||
|
||||
|
||||
sub like {
|
||||
sub {
|
||||
my ($value, $arg) = @_;
|
||||
Carp::croak "no regex supplied to regex group generator" unless $value;
|
||||
|
||||
# Oh, qr//, how you bother me! See the p5p thread from around now about
|
||||
# fixing this problem... too bad it won't help me. -- rjbs, 2006-04-25
|
||||
my @values = eval { $value->isa('Regexp') } ? ($value, undef)
|
||||
: @$value;
|
||||
|
||||
while (my ($re, $opt) = splice @values, 0, 2) {
|
||||
Carp::croak "given pattern for regex group generater is not a Regexp"
|
||||
unless eval { $re->isa('Regexp') };
|
||||
my @exports = keys %{ $arg->{config}->{exports} };
|
||||
my @matching = grep { $_ =~ $re } @exports;
|
||||
|
||||
my %merge = $opt ? %$opt : ();
|
||||
my $prefix = (delete $merge{-prefix}) || '';
|
||||
my $suffix = (delete $merge{-suffix}) || '';
|
||||
|
||||
for my $name (@matching) {
|
||||
my $as = $prefix . $name . $suffix;
|
||||
push @{ $arg->{import_args} }, [ $name => { %merge, -as => $as } ];
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
}
|
||||
}
|
||||
|
||||
use Sub::Exporter -setup => {
|
||||
exports => [ qw(
|
||||
like
|
||||
name_map
|
||||
merge_col
|
||||
curry_method curry_class
|
||||
curry_chain
|
||||
mixin_installer mixin_exporter
|
||||
) ]
|
||||
};
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Sub::Exporter::Util - utilities to make Sub::Exporter easier
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
version 0.987
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module provides a number of utility functions for performing common or
|
||||
useful operations when setting up a Sub::Exporter configuration. All of the
|
||||
utilities may be exported, but none are by default.
|
||||
|
||||
=head1 THE UTILITIES
|
||||
|
||||
=head2 curry_method
|
||||
|
||||
exports => {
|
||||
some_method => curry_method,
|
||||
}
|
||||
|
||||
This utility returns a generator which will produce an invocant-curried version
|
||||
of a method. In other words, it will export a method call with the exporting
|
||||
class built in as the invocant.
|
||||
|
||||
A module importing the code some the above example might do this:
|
||||
|
||||
use Some::Module qw(some_method);
|
||||
|
||||
my $x = some_method;
|
||||
|
||||
This would be equivalent to:
|
||||
|
||||
use Some::Module;
|
||||
|
||||
my $x = Some::Module->some_method;
|
||||
|
||||
If Some::Module is subclassed and the subclass's import method is called to
|
||||
import C<some_method>, the subclass will be curried in as the invocant.
|
||||
|
||||
If an argument is provided for C<curry_method> it is used as the name of the
|
||||
curried method to export. This means you could export a Widget constructor
|
||||
like this:
|
||||
|
||||
exports => { widget => curry_method('new') }
|
||||
|
||||
This utility may also be called as C<curry_class>, for backwards compatibility.
|
||||
|
||||
=head2 curry_chain
|
||||
|
||||
C<curry_chain> behaves like C<L</curry_method>>, but is meant for generating
|
||||
exports that will call several methods in succession.
|
||||
|
||||
exports => {
|
||||
reticulate => curry_chain(
|
||||
new => gather_data => analyze => [ detail => 100 ] => 'results'
|
||||
),
|
||||
}
|
||||
|
||||
If imported from Spliner, calling the C<reticulate> routine will be equivalent
|
||||
to:
|
||||
|
||||
Spliner->new->gather_data->analyze(detail => 100)->results;
|
||||
|
||||
If any method returns something on which methods may not be called, the routine
|
||||
croaks.
|
||||
|
||||
The arguments to C<curry_chain> form an optlist. The names are methods to be
|
||||
called and the arguments, if given, are arrayrefs to be dereferenced and passed
|
||||
as arguments to those methods. C<curry_chain> returns a generator like those
|
||||
expected by Sub::Exporter.
|
||||
|
||||
B<Achtung!> at present, there is no way to pass arguments from the generated
|
||||
routine to the method calls. This will probably be solved in future revisions
|
||||
by allowing the opt list's values to be subroutines that will be called with
|
||||
the generated routine's stack.
|
||||
|
||||
=head2 merge_col
|
||||
|
||||
exports => {
|
||||
merge_col(defaults => {
|
||||
twiddle => \'_twiddle_gen',
|
||||
tweak => \&_tweak_gen,
|
||||
}),
|
||||
}
|
||||
|
||||
This utility wraps the given generator in one that will merge the named
|
||||
collection into its args before calling it. This means that you can support a
|
||||
"default" collector in multiple exports without writing the code each time.
|
||||
|
||||
You can specify as many pairs of collection names and generators as you like.
|
||||
|
||||
=head2 mixin_installer
|
||||
|
||||
use Sub::Exporter -setup => {
|
||||
installer => Sub::Exporter::Util::mixin_installer,
|
||||
exports => [ qw(foo bar baz) ],
|
||||
};
|
||||
|
||||
This utility returns an installer that will install into a superclass and
|
||||
adjust the ISA importing class to include the newly generated superclass.
|
||||
|
||||
If the target of importing is an object, the hierarchy is reversed: the new
|
||||
class will be ISA the object's class, and the object will be reblessed.
|
||||
|
||||
B<Prerequisites>: This utility requires that Package::Generator be installed.
|
||||
|
||||
=head2 like
|
||||
|
||||
It's a collector that adds imports for anything like given regex.
|
||||
|
||||
If you provide this configuration:
|
||||
|
||||
exports => [ qw(igrep imap islurp exhausted) ],
|
||||
collectors => { -like => Sub::Exporter::Util::like },
|
||||
|
||||
A user may import from your module like this:
|
||||
|
||||
use Your::Iterator -like => qr/^i/; # imports igre, imap, islurp
|
||||
|
||||
or
|
||||
|
||||
use Your::Iterator -like => [ qr/^i/ => { -prefix => 'your_' } ];
|
||||
|
||||
The group-like prefix and suffix arguments are respected; other arguments are
|
||||
passed on to the generators for matching exports.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Ricardo Signes <rjbs@cpan.org>
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
This software is copyright (c) 2007 by Ricardo Signes.
|
||||
|
||||
This is free software; you can redistribute it and/or modify it under
|
||||
the same terms as the Perl 5 programming language system itself.
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user