Import Upstream version 0.928

This commit is contained in:
denghao 2022-09-08 09:25:56 +03:00
commit 57ba26f170
12 changed files with 756 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.build
Sub-Install-*

56
Changes Normal file
View File

@ -0,0 +1,56 @@
Revision history for Sub-Install
{{$NEXT}}
0.928 2014-06-26 18:57:27-04:00 America/New_York
- cope with subroutines with spaces in their names when catching
warnings
- don't assume that the source sub isn't blessed in tests (!)
0.927 2013-10-15 22:46:38 America/New_York
rebuild using Dist::Zilla, update links to repo, metadata, etc.
typo fixes, thanks David Steinbrunner
0.926 2012-02-26
cope with terminal dots from new Carp
0.925 2009-01-16
add repo loc to metadata
0.924 2006-11-13
packaging improvements
0.922 2006-07-21
fix filename-matching in output tests for win32 (20617, thanks Jerry
Gay!) (Couldn't reproduce this one. Argh!)
0.921 2006-07-16
fix filename-matching in output tests for win32 (20484, thanks Alex
Chorny!)
0.92 2006-05-11
allow other users to build exporters like our tiny one (&exporter)
rename _CALLABLE to _CODELIKE to keep up with Params::Util
0.91 2006-04-30
use _CALLABLE to determine callability of code, not ref
0.90 2006-04-15
rewrite warning handling:
reinstall suppresses fewer warnings: only sub redefinition
relevant warnings warn from caller (like Carp)
YOUR CODE MAY BREAK:
the test/code and documentation differed on install_installers
the code has been changed to match the documentation
0.03 2005-11-22 19:00
install_installers to make Sub::Installer history
0.02 2005-11-22 11:26
improved detection of sub names via B
100% coverage
0.01 2005-11-21 22:28
the first version

7
dist.ini Normal file
View File

@ -0,0 +1,7 @@
name = Sub-Install
author = Ricardo SIGNES <rjbs@cpan.org>
license = Perl_5
copyright_holder = Ricardo SIGNES
copyright_year = 2005
[@RJBS]

300
lib/Sub/Install.pm Normal file
View File

@ -0,0 +1,300 @@
use strict;
use warnings;
package Sub::Install;
# ABSTRACT: install subroutines into packages easily
use Carp;
use Scalar::Util ();
=head1 SYNOPSIS
use Sub::Install;
Sub::Install::install_sub({
code => sub { ... },
into => $package,
as => $subname
});
=head1 DESCRIPTION
This module makes it easy to install subroutines into packages without the
unsightly mess of C<no strict> or typeglobs lying about where just anyone can
see them.
=func install_sub
Sub::Install::install_sub({
code => \&subroutine,
into => "Finance::Shady",
as => 'launder',
});
This routine installs a given code reference into a package as a normal
subroutine. The above is equivalent to:
no strict 'refs';
*{"Finance::Shady" . '::' . "launder"} = \&subroutine;
If C<into> is not given, the sub is installed into the calling package.
If C<code> is not a code reference, it is looked for as an existing sub in the
package named in the C<from> parameter. If C<from> is not given, it will look
in the calling package.
If C<as> is not given, and if C<code> is a name, C<as> will default to C<code>.
If C<as> is not given, but if C<code> is a code ref, Sub::Install will try to
find the name of the given code ref and use that as C<as>.
That means that this code:
Sub::Install::install_sub({
code => 'twitch',
from => 'Person::InPain',
into => 'Person::Teenager',
as => 'dance',
});
is the same as:
package Person::Teenager;
Sub::Install::install_sub({
code => Person::InPain->can('twitch'),
as => 'dance',
});
=func reinstall_sub
This routine behaves exactly like C<L</install_sub>>, but does not emit a
warning if warnings are on and the destination is already defined.
=cut
sub _name_of_code {
my ($code) = @_;
require B;
my $name = B::svref_2object($code)->GV->NAME;
return $name unless $name =~ /\A__ANON__/;
return;
}
# See also Params::Util, to which this code was donated.
sub _CODELIKE {
(Scalar::Util::reftype($_[0])||'') eq 'CODE'
|| Scalar::Util::blessed($_[0])
&& (overload::Method($_[0],'&{}') ? $_[0] : undef);
}
# do the heavy lifting
sub _build_public_installer {
my ($installer) = @_;
sub {
my ($arg) = @_;
my ($calling_pkg) = caller(0);
# I'd rather use ||= but I'm whoring for Devel::Cover.
for (qw(into from)) { $arg->{$_} = $calling_pkg unless $arg->{$_} }
# This is the only absolutely required argument, in many cases.
Carp::croak "named argument 'code' is not optional" unless $arg->{code};
if (_CODELIKE($arg->{code})) {
$arg->{as} ||= _name_of_code($arg->{code});
} else {
Carp::croak
"couldn't find subroutine named $arg->{code} in package $arg->{from}"
unless my $code = $arg->{from}->can($arg->{code});
$arg->{as} = $arg->{code} unless $arg->{as};
$arg->{code} = $code;
}
Carp::croak "couldn't determine name under which to install subroutine"
unless $arg->{as};
$installer->(@$arg{qw(into as code) });
}
}
# do the ugly work
my $_misc_warn_re;
my $_redef_warn_re;
BEGIN {
$_misc_warn_re = qr/
Prototype\ mismatch:\ sub\ .+? |
Constant subroutine .+? redefined
/x;
$_redef_warn_re = qr/Subroutine\ .+?\ redefined/x;
}
my $eow_re;
BEGIN { $eow_re = qr/ at .+? line \d+\.\Z/ };
sub _do_with_warn {
my ($arg) = @_;
my $code = delete $arg->{code};
my $wants_code = sub {
my $code = shift;
sub {
my $warn = $SIG{__WARN__} ? $SIG{__WARN__} : sub { warn @_ }; ## no critic
local $SIG{__WARN__} = sub {
my ($error) = @_;
for (@{ $arg->{suppress} }) {
return if $error =~ $_;
}
for (@{ $arg->{croak} }) {
if (my ($base_error) = $error =~ /\A($_) $eow_re/x) {
Carp::croak $base_error;
}
}
for (@{ $arg->{carp} }) {
if (my ($base_error) = $error =~ /\A($_) $eow_re/x) {
return $warn->(Carp::shortmess $base_error);
}
}
($arg->{default} || $warn)->($error);
};
$code->(@_);
};
};
return $wants_code->($code) if $code;
return $wants_code;
}
sub _installer {
sub {
my ($pkg, $name, $code) = @_;
no strict 'refs'; ## no critic ProhibitNoStrict
*{"$pkg\::$name"} = $code;
return $code;
}
}
BEGIN {
*_ignore_warnings = _do_with_warn({
carp => [ $_misc_warn_re, $_redef_warn_re ]
});
*install_sub = _build_public_installer(_ignore_warnings(_installer));
*_carp_warnings = _do_with_warn({
carp => [ $_misc_warn_re ],
suppress => [ $_redef_warn_re ],
});
*reinstall_sub = _build_public_installer(_carp_warnings(_installer));
*_install_fatal = _do_with_warn({
code => _installer,
croak => [ $_redef_warn_re ],
});
}
=func install_installers
This routine is provided to allow Sub::Install compatibility with
Sub::Installer. It installs C<install_sub> and C<reinstall_sub> methods into
the package named by its argument.
Sub::Install::install_installers('Code::Builder'); # just for us, please
Code::Builder->install_sub({ name => $code_ref });
Sub::Install::install_installers('UNIVERSAL'); # feeling lucky, punk?
Anything::At::All->install_sub({ name => $code_ref });
The installed installers are similar, but not identical, to those provided by
Sub::Installer. They accept a single hash as an argument. The key/value pairs
are used as the C<as> and C<code> parameters to the C<install_sub> routine
detailed above. The package name on which the method is called is used as the
C<into> parameter.
Unlike Sub::Installer's C<install_sub> will not eval strings into code, but
will look for named code in the calling package.
=cut
sub install_installers {
my ($into) = @_;
for my $method (qw(install_sub reinstall_sub)) {
my $code = sub {
my ($package, $subs) = @_;
my ($caller) = caller(0);
my $return;
for (my ($name, $sub) = %$subs) {
$return = Sub::Install->can($method)->({
code => $sub,
from => $caller,
into => $package,
as => $name
});
}
return $return;
};
install_sub({ code => $code, into => $into, as => $method });
}
}
=head1 EXPORTS
Sub::Install exports C<install_sub> and C<reinstall_sub> only if they are
requested.
=head2 exporter
Sub::Install has a never-exported subroutine called C<exporter>, which is used
to implement its C<import> routine. It takes a hashref of named arguments,
only one of which is currently recognize: C<exports>. This must be an arrayref
of subroutines to offer for export.
This routine is mainly for Sub::Install's own consumption. Instead, consider
L<Sub::Exporter>.
=cut
sub exporter {
my ($arg) = @_;
my %is_exported = map { $_ => undef } @{ $arg->{exports} };
sub {
my $class = shift;
my $target = caller;
for (@_) {
Carp::croak "'$_' is not exported by $class" if !exists $is_exported{$_};
install_sub({ code => $_, from => $class, into => $target });
}
}
}
BEGIN { *import = exporter({ exports => [ qw(install_sub reinstall_sub) ] }); }
=head1 SEE ALSO
=over
=item L<Sub::Installer>
This module is (obviously) a reaction to Damian Conway's Sub::Installer, which
does the same thing, but does it by getting its greasy fingers all over
UNIVERSAL. I was really happy about the idea of making the installation of
coderefs less ugly, but I couldn't bring myself to replace the ugliness of
typeglobs and loosened strictures with the ugliness of UNIVERSAL methods.
=item L<Sub::Exporter>
This is a complete Exporter.pm replacement, built atop Sub::Install.
=back
=head1 EXTRA CREDITS
Several of the tests are adapted from tests that shipped with Damian Conway's
Sub-Installer distribution.
=cut
1;

47
t/SI_install.t Normal file
View File

@ -0,0 +1,47 @@
use Sub::Install;
Sub::Install::install_installers('UNIVERSAL');
# This test, from here on out, is the verbatim "install.t" test from
# Sub::Installer 0.0.2
use Test::More 'no_plan';
use Scalar::Util qw/reftype/;
use warnings;
# Install a sub in a package...
my $sub_ref = main->install_sub({ ok1 => \&ok });
is reftype $sub_ref, 'CODE' => 'install returns code ref';
is_deeply \&ok, $sub_ref => 'install returns correct code ref';
ok1(1 => 'installed sub runs');
# Install the same sub in the same package...
$SIG{__WARN__} = sub { ok 1 => 'warned as expected' if $_[0] =~ /redefined/ };
$sub_ref = main->install_sub({ ok1 => \&is });
is reftype $sub_ref, 'CODE' => 'install2 returns code ref';
is_deeply \&is, $sub_ref => 'install2 returns correct code ref';
ok1(1,1 => 'installed sub reruns');
# Install in another package...
$sub_ref = Other->install_sub({ ok2 => \&ok });
is reftype $sub_ref, 'CODE' => 'install2 returns code ref';
is_deeply \&ok, $sub_ref => 'install2 returns correct code ref';
ok1(1,1 => 'installed sub reruns');
package Other;
ok2(1 => 'remotely installed sub runs');

46
t/SI_reinstall.t Normal file
View File

@ -0,0 +1,46 @@
use Sub::Install;
Sub::Install::install_installers('UNIVERSAL');
# This test, from here on out, is the verbatim "reinstall.t" test from
# Sub::Installer 0.0.2
use Test::More 'no_plan';
use Scalar::Util qw/reftype/;
use warnings;
# Install a sub in a package...
my $sub_ref = main->reinstall_sub({ ok1 => \&ok });
is reftype $sub_ref, 'CODE' => 'reinstall returns code ref';
is_deeply \&ok, $sub_ref => 'reinstall returns correct code ref';
ok1(1 => 'reinstalled sub runs');
# Install the same sub in the same package...
$SIG{__WARN__} = sub { ok 0 => "warned unexpected: @_" if $_[0] =~ /redefined/ };
$sub_ref = main->reinstall_sub({ ok1 => \&is });
is reftype $sub_ref, 'CODE' => 'reinstall2 returns code ref';
is_deeply \&is, $sub_ref => 'reinstall2 returns correct code ref';
ok1(1,1 => 'reinstalled sub reruns');
# Install in another package...
$sub_ref = Other->reinstall_sub({ ok2 => \&ok });
is reftype $sub_ref, 'CODE' => 'reinstall2 returns code ref';
is_deeply \&ok, $sub_ref => 'reinstall2 returns correct code ref';
ok1(1,1 => 'reinstalled sub reruns');
package Other;
ok2(1 => 'remotely reinstalled sub runs');

30
t/auto_as.t Normal file
View File

@ -0,0 +1,30 @@
use Sub::Install qw(install_sub);
use Test::More 'no_plan';
use strict;
use warnings;
sub source_method {
my ($package) = @_;
return $package;
}
{ # install named method and let the name be the same
install_sub({ code => "source_method", into => "By::Name" });
is(
By::Name->source_method,
'By::Name',
"method installed by name"
);
}
{ # install via a coderef and let name be looked up
install_sub({ code => \&source_method, into => "By::Ref" });
is(
By::Ref->source_method,
'By::Ref',
"method installed by ref, without name"
);
}

24
t/export.t Normal file
View File

@ -0,0 +1,24 @@
use Sub::Install;
use Test::More tests => 4;
use strict;
use warnings;
BEGIN { use_ok('Sub::Install'); }
package Bar;
{ no warnings 'once';
*import = Sub::Install::exporter { exports => [ qw(foo) ] };
}
sub foo { return 10; }
package main;
eval { Bar->import('bar'); };
like($@, qr/'bar' is not exported/, "exception on bad import");
eval { foo(); };
like($@, qr/Undefined subroutine/, "foo isn't imported yet");
Bar->import(qw(foo));
is(foo(), 10, "foo imported from Bar OK");

19
t/inst-blessed.t Normal file
View File

@ -0,0 +1,19 @@
use Sub::Install;
use Test::More 'no_plan';
use strict;
use warnings;
BEGIN { use_ok("Sub::Install"); }
my $code = sub { return 'FOO' };
bless $code, "Sub::Install::Bogus";
Sub::Install::install_sub({
code => $code,
as => 'code',
});
is(code(), "FOO", "installed sub is OK");

113
t/install.t Normal file
View File

@ -0,0 +1,113 @@
use Sub::Install;
use Test::More tests => 17;
use strict;
use warnings;
# These tests largely copied from Damian Conway's Sub::Installer tests.
{ # Install a sub in a package...
my $sub_ref = Sub::Install::install_sub({ code => \&ok, as => 'ok1' });
isa_ok($sub_ref, 'CODE', 'return value of first install_sub');
is_deeply($sub_ref, \&ok, 'it returns the correct code ref');
ok1(1, 'installed sub runs');
}
{
my $to_avail = eval "use Test::Output; 1";
SKIP: {
skip "can't run this test without Test::Output", 1 unless $to_avail;
Sub::Install::install_sub({ code => \&ok, as => 'tmp_ok' });
my $expected_warning = <<'END_WARNING';
Subroutine main::tmp_ok redefined at t/install.t line 31
Prototype mismatch: sub main::tmp_ok ($;$) vs ($$;$) at t/install.t line 31
END_WARNING
my $stderr = Test::Output::stderr_from(
sub { Sub::Install::install_sub({ code => \&is, as => 'tmp_ok' }) }
);
$stderr =~ s!\.$!!gm;
$stderr =~ s!\\!/!g;
is(
$stderr,
$expected_warning,
"got expected warning",
);
}
}
{ # Install the same sub in the same package...
my $redef = 0;
my $proto = 0;
local $SIG{__WARN__} = sub {
return ($redef = 1) if $_[0] =~ m{Subroutine \S+ redef.+t.install\.t};
return ($proto = 1) if $_[0] =~ m{Prototype mismatch.+t.install\.t};
# pass("warned as expected: $_[0]") if $_[0] =~ /redefined/;
die "unexpected warning: @_";
};
my $sub_ref = Sub::Install::install_sub({ code => \&is, as => 'ok1' });
ok($redef, 'correct redefinition warning went to $SIG{__WARN__}');
ok($proto, 'correct prototype warning went to $SIG{__WARN__}');
isa_ok($sub_ref, 'CODE', 'return value of second install_sub');
is_deeply($sub_ref, \&is, 'install2 returns correct code ref');
ok1(1,1, 'installed sub runs (with new arguments)');
}
{ # Install in another package...
my $sub_ref = Sub::Install::install_sub({
code => \&ok,
into => 'Other',
as => 'ok1'
});
isa_ok($sub_ref, 'CODE', 'return value of third install_sub');
is_deeply($sub_ref, \&ok, 'it returns the correct code ref');
ok1(1,1, 'sub previously installed into main still runs properly');
package Other;
ok1(1, 'remotely installed sub runs properly');
}
{ # cross-package installation
sub Other::Another::foo { return $_[0] }
my $sub_ref = Sub::Install::install_sub({
code => 'foo',
from => 'Other::Another',
into => 'Other::YetAnother',
as => 'return_lhs'
});
isa_ok($sub_ref, 'CODE', 'return value of fourth install_sub');
is_deeply(
$sub_ref,
\&Other::Another::foo,
'it returns the correct code ref'
);
is(
Other::Another->foo,
'Other::Another',
'the original code does what we want',
);
is(
Other::YetAnother->return_lhs,
'Other::YetAnother',
'and the installed code works, too',
);
}

23
t/misc_errors.t Normal file
View File

@ -0,0 +1,23 @@
use Sub::Install qw(install_sub);
use Test::More 'no_plan';
use strict;
use warnings;
{ # you have to install /something/!
eval { install_sub({ into => "Doesn't::Matter" }); };
like($@, qr/code.+not optional/, "you must supply something to install");
}
{ # you can't just make names up and expect Sub::Install to know what you mean
eval { install_sub({ code => 'none_such', into => 'Whatever' }); };
like($@, qr/couldn't find subroutine/, "error on unfound sub name");
}
{ # can't install anonymous subs without a name
eval { install_sub({ code => sub { return 1; } }); };
like($@, qr/couldn't determine name/, "anon subs need names to install");
}

89
t/reinstall.t Normal file
View File

@ -0,0 +1,89 @@
use Sub::Install qw(reinstall_sub);
use Test::More tests => 15;
use strict;
use warnings;
# These tests largely copied from Damian Conway's Sub::Installer tests.
{ # Install a sub in a package...
my $sub_ref = reinstall_sub({ code => \&ok, as => 'ok1' });
isa_ok($sub_ref, 'CODE', 'return value of first install_sub');
is_deeply($sub_ref, \&Test::More::ok, 'it returned the right coderef');
$sub_ref->(1, 'returned code ref runs');
ok1(1, "reinstalled sub runs");
}
{
my $to_avail = eval "use Test::Output; 1";
SKIP: {
skip "can't run this test without Test::Output", 1 unless $to_avail;
Sub::Install::reinstall_sub({ code => \&ok, as => 'tmp_ok' });
my $expected_warning = <<'END_WARNING';
Prototype mismatch: sub main::tmp_ok ($;$) vs ($$;$) at t/reinstall.t line 32
END_WARNING
my $stderr = Test::Output::stderr_from(
sub { Sub::Install::reinstall_sub({ code => \&is, as => 'tmp_ok' }) }
);
$stderr =~ s!\\!/!g;
$stderr =~ s!\.$!!g;
is(
$stderr,
$expected_warning,
"got expected warning",
);
}
}
{ # Install the same sub in the same package...
my $proto = 0;
local $SIG{__WARN__} = sub {
return ($proto = 1) if $_[0] =~ m{Prototype mismatch.+t.reinstall\.t};
die "unexpected warning: @_";
};
my $sub_ref = reinstall_sub({ code => \&is, as => 'ok1' });
ok($proto, 'correct warning went to $SIG{__WARN__}');
isa_ok($sub_ref, 'CODE', 'return value of second install_sub');
is_deeply($sub_ref, \&Test::More::is, 'it returned the right coderef');
$sub_ref->(1, 1, 'returned code ref runs');
ok1(1,1, 'reinstalled sub reruns');
}
{ # Install in another package...
my $new_code = sub { ok(1, "remotely installed sub runs") };
my $sub_ref = reinstall_sub({
code => $new_code,
into => 'Other',
as => 'ok1',
});
isa_ok($sub_ref, 'CODE', 'return value of third install_sub');
is_deeply($sub_ref, $new_code, 'it returned the right coderef');
ok1(1,1, 'reinstalled sub reruns');
package Other;
ok1();
}
eval {
my $arg = { code => sub {}, into => 'Other', as => 'ok1' };
Sub::Install::_build_public_installer(\&Sub::Install::_install_fatal)->($arg);
};
like($@, qr/redefine/, "(experimental fatal installer should croak)");