commit 57ba26f170a58eb570c00f46af2dd4ac9d393f3e Author: denghao Date: Thu Sep 8 09:25:56 2022 +0300 Import Upstream version 0.928 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4279ce1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.build +Sub-Install-* diff --git a/Changes b/Changes new file mode 100644 index 0000000..f99219f --- /dev/null +++ b/Changes @@ -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 + diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..d28de63 --- /dev/null +++ b/dist.ini @@ -0,0 +1,7 @@ +name = Sub-Install +author = Ricardo SIGNES +license = Perl_5 +copyright_holder = Ricardo SIGNES +copyright_year = 2005 + +[@RJBS] diff --git a/lib/Sub/Install.pm b/lib/Sub/Install.pm new file mode 100644 index 0000000..635c0ab --- /dev/null +++ b/lib/Sub/Install.pm @@ -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 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 is not given, the sub is installed into the calling package. + +If C is not a code reference, it is looked for as an existing sub in the +package named in the C parameter. If C is not given, it will look +in the calling package. + +If C is not given, and if C is a name, C will default to C. +If C is not given, but if C is a code ref, Sub::Install will try to +find the name of the given code ref and use that as C. + +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>, 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 and C 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 and C parameters to the C routine +detailed above. The package name on which the method is called is used as the +C parameter. + +Unlike Sub::Installer's C 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 and C only if they are +requested. + +=head2 exporter + +Sub::Install has a never-exported subroutine called C, which is used +to implement its C routine. It takes a hashref of named arguments, +only one of which is currently recognize: C. This must be an arrayref +of subroutines to offer for export. + +This routine is mainly for Sub::Install's own consumption. Instead, consider +L. + +=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 + +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 + +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; diff --git a/t/SI_install.t b/t/SI_install.t new file mode 100644 index 0000000..6d52d92 --- /dev/null +++ b/t/SI_install.t @@ -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'); diff --git a/t/SI_reinstall.t b/t/SI_reinstall.t new file mode 100644 index 0000000..887e077 --- /dev/null +++ b/t/SI_reinstall.t @@ -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'); diff --git a/t/auto_as.t b/t/auto_as.t new file mode 100644 index 0000000..fea40b4 --- /dev/null +++ b/t/auto_as.t @@ -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" + ); +} diff --git a/t/export.t b/t/export.t new file mode 100644 index 0000000..05b911a --- /dev/null +++ b/t/export.t @@ -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"); diff --git a/t/inst-blessed.t b/t/inst-blessed.t new file mode 100644 index 0000000..952c705 --- /dev/null +++ b/t/inst-blessed.t @@ -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"); + diff --git a/t/install.t b/t/install.t new file mode 100644 index 0000000..d8557a2 --- /dev/null +++ b/t/install.t @@ -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', + ); +} diff --git a/t/misc_errors.t b/t/misc_errors.t new file mode 100644 index 0000000..09892a5 --- /dev/null +++ b/t/misc_errors.t @@ -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"); +} diff --git a/t/reinstall.t b/t/reinstall.t new file mode 100644 index 0000000..9e99400 --- /dev/null +++ b/t/reinstall.t @@ -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)");