=head1 NAME

Package::Butcher - When you absolutely B<have> 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<Cannot::Load> or
C<NoSuch::List::MoreUtils> available. What's worse, even if you try to stub
them out and fake this, the C<employees> and C<recipes>  methods might be
frustrating.  We'll use this as an example of how to use C<Package::Butcher>.

=head1 METHODS

=head2 C<new>

The constructor for C<Package::Butcher> takes a hashref with several allowed
keys. For example, the following will allow the C<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:

=over 4

=item * C<package>

The name of the package to be butchered.

package => 'Hard::To::Load::Package'

=item * C<do_not_load>

Packages which must not be loaded. This is useful when there are a bunch of
C<use> or C<require> 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<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' ]

=item * C<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 C<subs> section will automatically be
predeclared.

=item * C<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 C<Package::Butcher::Inflator> code to see how this works.

=item * C<import_on_use>

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<import>
method will be called I<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 C<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,

=back

=head2 C<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 C<import>, you're on your own.

=head2 C<require>

my $butcher = Package::Butcher->new({ package ... });
$butcher->require;

Like use, but does a C<require>.

=head1 AUTHOR

Curtis 'Ovid' Poe, C<< <ovid at cpan.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-package-butcher at
rt.cpan.org>, or through the web interface at
L<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.

=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<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Package-Butcher>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Package-Butcher>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Package-Butcher>

=item * Search CPAN

L<http://search.cpan.org/dist/Package-Butcher/>

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