Functional-Utility
view release on metacpan or search on metacpan
t/lib/Test/Resub.pm view on Meta::CPAN
package Test::Resub;
use strict;
use warnings;
use base qw(Exporter);
our @EXPORT = qw(resub bulk_resub);
our $VERSION = 2.02;
use Carp qw(croak);
use Storable qw(dclone);
use Scalar::Util qw(weaken);
sub default_replacement_sub { sub {} }
sub set_prototype(&$) {
if (_implements('Scalar::Util','set_prototype')) {
goto \&Scalar::Util::set_prototype;
} else {
my $code = shift;
my $proto = shift;
$proto = defined $proto ? "($proto)" : '';
local $@;
return eval "sub $proto { goto \$code }";
}
}
sub resub {
my ($name, $code, %args) = @_;
die "give me a fully qualified function name: $name ain't good enough\n"
unless $name =~ /::/;
return __PACKAGE__->new(
%args,
name => $name,
code => $code,
);
}
sub bulk_resub {
my ($target, $data, %args) = @_;
my %rs;
foreach (keys %$data) {
$rs{$_} = resub "$target\::$_", $data->{$_}, %args;
}
return %rs;
}
sub _validate_params_lameley {
my ($class, %args) = @_;
my %known =
map { $_ => 1 }
qw(name code create call capture deep_copy);
my %bad =
map { $_ => $args{$_} }
grep { ! $known{$_} }
keys %args;
if (scalar keys %bad) {
my $bad = join ', ', map { "$_ => $bad{$_}" } keys %bad;
croak "$class->new - not sure how to handle unknown arg '$bad'\n";
}
croak "don't know how to handle 'call => $args{call}'"
if exists $args{call} && ! in($args{call}, qw(optional required forbidden));
return (
deep_copy => 1,
call => 'required',
%args,
);
}
sub new {
my $class = shift;
# lame adaptor for old-style users of Test::Resub (are there any?)
my %args = ref($_[0]) eq 'HASH' ? %{$_[0]} : @_;
# I'm not gonna lie, this really is stupidly ugly
%args = $class->_validate_params_lameley(%args);
croak "I return a highly useful object, gotta call me in non-void context!\n"
unless defined wantarray;
my $name = $args{name};
(my $sane = $name) =~ s{->}{::}g;
$sane =~ s{[^\w:]}{}g;
croak "bad method name: $args{name} (expected: $sane)" if $args{name} ne $sane;
my $code = $args{code} || $class->default_replacement_sub;
my ($orig_code, $autovivified) = $class->_get_orig_code(%args);
my ($package, $sub) = $args{name} =~ m{^(.*)::(.*?)$};
my $self = bless {
%args,
target_package => $package,
target_sub => $sub,
orig_code => $orig_code,
called => 0,
args => [],
autovivified => $autovivified,
stashed_variables => _save_variables($args{name}),
deep_copy => $args{deep_copy},
}, $class;
weaken(my $weak_self = $self);
my $wrapper_for_code = set_prototype(sub {
$weak_self->{called}++;
$weak_self->{was_called} = 1;
push @{$weak_self->{args}}, ($weak_self->{deep_copy}
? do {
local $Storable::Deparse = 1;
local $Storable::Eval = 1;
dclone(\@_);
}
: [@_]);
# Are you debugging? Here's where we call the original code in its original context.
return $code->(@_);
}, prototype(\&{$self->{name}}));
$self->swap_out($wrapper_for_code);
_restore_variables($self->{name}, $self->{stashed_variables});
return $self;
}
sub _context {
my ($class) = @_;
my $wantarray = (caller(1))[5];
my $context = $wantarray
? 'list'
: defined $wantarray
? 'scalar'
: 'void';
return $context;
}
sub _save_variables {
my ($varname) = @_;
no strict 'refs';
return {
scalar => $$varname,
array => \@$varname,
hash => \%$varname,
};
}
sub _restore_variables {
my ($varname, $data) = @_;
no strict 'refs';
no warnings 'uninitialized';
$$varname = $data->{scalar};
@$varname = @{$data->{array}};
%$varname = %{$data->{hash}};
}
sub _implements {
my ($package, $sub) = @_;
local $@;
my %stash = eval "\%$package\::";
croak "finding $package\'s stash: $@\n" if $@;
return exists $stash{$sub} && *{$stash{$sub}}{CODE} && *{$stash{$sub}}{NAME} eq $sub;
}
( run in 2.138 seconds using v1.01-cache-2.11-cpan-98e64b0badf )