NAME
Package::Butcher - When you absolutely have to load that damned package.
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.
VERSION
Version 0.02
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);
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.
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 Cannot::Load
or NoSuch::List::MoreUtils
available. What's worse, even if you try to stub them out and fake this, the employees
and recipes
methods might be frustrating. We'll use this as an example of how to use Package::Butcher
.
METHODS
new
The constructor for Package::Butcher
takes a hashref with several allowed keys. For example, the following will allow the Dummy
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:
package
The name of the package to be butchered.
package => 'Hard::To::Load::Package'
do_not_load
Packages which must not be loaded. This is useful when there are a bunch of
use
orrequire
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', ]
predeclare
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' ]
subs
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
subs
section will automatically be predeclared.method_chains
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
Package::Butcher::Inflator
code to see how this works.import_on_use
This defaults to false and you should hopefully not need it.
As a general rule, if you call
$butcher->use
, the package'simport
method will be called after 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
import
method be called at the at time the class is "use"d, then you can specify this in the constructor:import_on_use => 1,
use
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 import
, you're on your own.
require
my $butcher = Package::Butcher->new({ package ... });
$butcher->require;
Like use, but does a require
.
AUTHOR
Curtis 'Ovid' Poe, <ovid at cpan.org>
BUGS
Please report any bugs or feature requests to bug-package-butcher at rt.cpan.org
, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Package-Butcher. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Package::Butcher
You can also look for information at:
RT: CPAN's request tracker
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
ACKNOWLEDGEMENTS
Flavio Glock for help with a parsing error.
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.