=head1 NAME
Package::Butcher - When you absolutely B to load that damned package.
=head1 ALPHA CODE
You've been warned. It also has an embarrassingly poor test suite. It was
hacked together in an emergency while sitting in a hospital waiting for my
daughter to be born. Sue me.
=head1 VERSION
Version 0.01
=cut
=head1 SYNOPSIS
my $butcher = Package::Butcher->new(
{
package => 'Dummy',
do_not_load => [qw/Cannot::Load Cannot::Load2 NoSuch::List::MoreUtils/],
predeclare => 'uniq',
subs => {
this => sub { 7 },
that => sub { 3 },
existing => sub { 'replaced existing' },
},
method_chains => [
[
'Cannot::Load' => qw/foo bar baz this that/ => sub {
my $args = join ', ' => @_;
return "end chain: $args";
},
],
],
}
);
$butcher->use(@optional_import_list);
=head1 DESCRIPTION
Sometimes you need to load a module which won't otherwise load. Unit testing
is a good reason. Unfortunately, some modules are just very, very difficult to
load. This module is a nasty hack with a name designed to make this clear.
It's here to provide a standard set of tools to let you load these problem
modules.
=head1 USAGE
To use this module, let's consider the following awful module:
package Dummy;
use strict;
use Cannot::Load;
use NoSuch::List::MoreUtils 'uniq';
use DBI;
use base 'Exporter';
our @EXPORT_OK = qw(existing);
sub existing { 'should never see this' }
# this strange construct forces a syntax error
sub filter {
uniq map {lc} split /\W+/, shift;
}
sub employees {
my @connect =
( 'dbi:Pg:dbname=ourdb', '', '', { AutoCommit => 0 } );
return DBI->connect(@connect)
->selectall_arrayref(
'SELECT id, name, position FROM employees ORDER BY id');
}
sub recipes {
my @connect = ( 'dbi:Pg:dbname=ourdb', '', '', { AutoCommit => 0 } );
return DBI->connect(@connect)
->selectall_arrayref('SELECT id, name FROM recipes');
}
1;
You probably cannot load this. You don't have C or
C available. What's worse, even if you try to stub
them out and fake this, the C and C methods might be
frustrating. We'll use this as an example of how to use C.
=head1 METHODS
=head2 C
The constructor for C takes a hashref with several allowed
keys. For example, the following will allow the C package above to
load:
my $dummy = Package::Butcher->new({
package => 'Dummy',
do_not_load =>
[qw/Cannot::Load NoSuch::List::MoreUtils DBI/],
predeclare => 'uniq',
subs => {
existing => sub { 'replaced existing' },
reverse_string => sub {
my $arg = shift;
return scalar reverse $arg;
},
},
method_chains => [
[
'Cannot::Load' => qw/foo bar baz this that/ => sub {
my $args = join ', ' => @_;
return "end chain: $args";
},
],
[
'DBI' => qw/connect selectall_arrayref/ => sub {
my $sql = shift;
return (
$sql =~ /\brecipes\b/
? [
[qw/1 bob secretary/],
[qw/2 alice ceo/],
[qw/3 ovid idiot/],
]
: [ [ 1, 'Tartiflette' ], [ 2, 'Eggs Benedict' ], ];
},
],
],
});
Here are the allowed keys to the constructor:
=over 4
=item * C
The name of the package to be butchered.
package => 'Hard::To::Load::Package'
=item * C
Packages which must not be loaded. This is useful when there are a bunch of
C or C statements in the code which cause the target code to try
and load packages which may not be loadable.
do_not_load => [
'Apache::Never::Loads',
'Module::I::Do::Not::Have::Installed',
'Win32::Anything',
]
=item * C
Sometimes you need to simply predeclare a method or subroutine to ensure it
parses correctly, even if you don't need to execute that function (for
example, if you're replacing a subroutine which contains the offending code).
To do this, you can simply "predeclare a function or arrayref of functions
with optional prototypes.
predeclare => [ 'uniq (@)', 'some_other_function' ]
=item * C
This should point to a hashref of subroutine names and sub bodies. These will
be added to the package, overwriting any subroutines already there:
subs => {
existing => sub { 'replaced existing' },
reverse_string => sub {
my $arg = shift;
return scalar reverse $arg;
},
},
Note that any subroutinine listed in the C section will automatically be
predeclared.
=item * C
Method "chains" are frequent in bad code (and even in some good code). This is
when you see a class with a list of chained methods getting called. For
example:
return DBI->connect(@connect)
->selectall_arrayref(
'SELECT id, name, position FROM employees ORDER BY id');
The butcher allows you to declare a method chain and a subref which will be
executed. The structure is like this:
method_chains => [
[ $class1, @list_of_methods1, sub { @body } ],
[ $class2, @list_of_methods2, sub { @body } ],
[ $class3, @list_of_methods3, sub { @body } ],
],
For the DBI example above, assuming this was the only method chain in the
code, you would have something like:
method_chains => [
[ 'DBI', qw/connect selectall_arrayref/, \&some_sub ],
],
See C code to see how this works.
=item * C
This defaults to false and you should hopefully not need it.
As a general rule, if you call C<< $butcher->use >>, the package's C
method will be called I you use the class to allow us to inject the new
code before importing. This means that if a class exports a 'foo' method and
you've replaced it with your own, you are generally guaranteed to get your
replacement when you call:
$butcher->use('foo');
However, if you class requires that the C method be called at the at
time the class is "use"d, then you can specify this in the constructor:
import_on_use => 1,
=back
=head2 C
my $butcher = Package::Butcher->new({ package ... });
$butcher->use(@import_list);
Once constructed, this method will "use" the package in question. You may pass
it the same import list that the package you're butchering takes. Note that if
you override C, you're on your own.
=head2 C
my $butcher = Package::Butcher->new({ package ... });
$butcher->require;
Like use, but does a C.
=head1 AUTHOR
Curtis 'Ovid' Poe, C<< >>
=head1 BUGS
Please report any bugs or feature requests to C, or through the web interface at
L. I will be
notified, and then you'll automatically be notified of progress on your bug as
I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Package::Butcher
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker
L
=item * AnnoCPAN: Annotated CPAN documentation
L
=item * CPAN Ratings
L
=item * Search CPAN
L
=back
=head1 ACKNOWLEDGEMENTS
Flavio Glock for help with a parsing error.
=head1 LICENSE AND COPYRIGHT
Copyright 2011 Curtis 'Ovid' Poe.
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
=cut
1;