=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;