아주 간단한 것을 봅시다.
# Foo.pm
package Foo {
my $baz = bar();
sub bar { 42 }; ## Overwrite this
print $baz; ## Before this is executed
}
어쨌든 설정된 test.pl
것을 변경 하고 화면에 다른 것을 인쇄 하는 실행 코드를 사용할 수 있습니까?$baz
Foo.pm
# maybe something here.
use Foo;
# maybe something here
컴파일러 단계에서 위의 내용을 강제로 인쇄 할 수 7
있습니까?
답변
해킹 때문에 필요하다 require
(및 use
) 모두 컴파일 실행 모듈을 반환하기 전에.
동일합니다 eval
. eval
코드를 실행하지 않고 컴파일하는 데 사용할 수 없습니다.
내가 찾은 가장 방해가 적은 솔루션은 재정의하는 것 DB::postponed
입니다. 컴파일 된 필수 파일을 평가하기 전에 호출됩니다. 불행히도 디버깅 할 때만 호출됩니다 ( perl -d
).
다른 해결책은 다음과 같이 파일을 읽고 수정하고 수정 된 파일을 평가하는 것입니다.
use File::Slurper qw( read_binary );
eval(read_binary("Foo.pm") . <<'__EOS__') or die $@;
package Foo {
no warnings qw( redefine );
sub bar { 7 }
}
__EOS__
위의 내용은 올바르게 설정되지 않았 %INC
으며 경고에 사용되는 파일 이름을 엉망으로 만들고 호출하지 않습니다 DB::postponed
. 다음은 더 강력한 솔루션입니다.
use IO::Unread qw( unread );
use Path::Class qw( dir );
BEGIN {
my $preamble = '
UNITCHECK {
no warnings qw( redefine );
*Foo::bar = sub { 7 };
}
';
my @libs = @INC;
unshift @INC, sub {
my (undef, $fn) = @_;
return undef if $_[1] ne 'Foo.pm';
for my $qfn (map dir($_)->file($fn), @libs) {
open(my $fh, '<', $qfn)
or do {
next if $!{ENOENT};
die $!;
};
unread $fh, "$preamble\n#line 1 $qfn\n";
return $fh;
}
return undef;
};
}
use Foo;
내가 사용하는 UNITCHECK
내가 (사용하여 재정의를 앞에 추가하기 때문에 (실행 컴파일 후하지만 전에 호출되는) unread
대신에 전체 파일을 읽고 새로운 정의를 추가하는 것보다). 해당 접근 방식을 사용하려면 파일 핸들을 사용하여 반환 할 수 있습니다
open(my $fh_for_perl, '<', \$modified_code);
return $fh_for_perl;
@INC
후크 를 언급하기 위해 @Grinnz에게 찬사를 보냅니다 .
답변
여기서 유일한 옵션은 매우 해킹이 될 것이기 때문에, 우리가 정말로 원하는 것은 서브 루틴이 %Foo::
숨김 에 추가 된 후 코드를 실행하는 것입니다 .
use strict;
use warnings;
# bless a coderef and run it on destruction
package RunOnDestruct {
sub new { my $class = shift; bless shift, $class }
sub DESTROY { my $self = shift; $self->() }
}
use Variable::Magic 0.58 qw(wizard cast dispell);
use Scalar::Util 'weaken';
BEGIN {
my $wiz;
$wiz = wizard(store => sub {
return undef unless $_[2] eq 'bar';
dispell %Foo::, $wiz; # avoid infinite recursion
# Variable::Magic will destroy returned object *after* the store
return RunOnDestruct->new(sub { no warnings 'redefine'; *Foo::bar = sub { 7 } });
});
cast %Foo::, $wiz;
weaken $wiz; # avoid memory leak from self-reference
}
use lib::relative '.';
use Foo;
답변
경고가 표시되지만 7을 인쇄합니다.
sub Foo::bar {}
BEGIN {
$SIG{__WARN__} = sub {
*Foo::bar = sub { 7 };
};
}
먼저을 정의 Foo::bar
합니다. 이 값은 Foo.pm의 선언에 의해 재정의되지만 “서브 루틴 Foo :: bar 재정의 됨”경고가 트리거되며, 서브 루틴을 다시 재정의하는 신호 처리기를 호출하여 7을 반환합니다.
답변
다음은 모듈 로딩 프로세스 후킹을 Readonly 모듈의 읽기 전용 기능과 결합한 솔루션입니다.
$ cat Foo.pm
package Foo {
my $baz = bar();
sub bar { 42 }; ## Overwrite this
print $baz; ## Before this is executed
}
$ cat test.pl
#!/usr/bin/perl
use strict;
use warnings;
use lib qw(.);
use Path::Tiny;
use Readonly;
BEGIN {
my @remap = (
'$Foo::{bar} => \&mybar'
);
my $pre = join ' ', map "Readonly::Scalar $_;", @remap;
my @inc = @INC;
unshift @INC, sub {
return undef if $_[1] ne 'Foo.pm';
my ($pm) = grep { $_->is_file && -r } map { path $_, $_[1] } @inc
or return undef;
open my $fh, '<', \($pre. "#line 1 $pm\n". $pm->slurp_raw);
return $fh;
};
}
sub mybar { 5 }
use Foo;
$ ./test.pl
5
답변
m-conrad의 답변Readonly.pm
에 따라 매우 간단한 대안을 놓쳤다는 것을 알게 된 후에 더 이상 의지하지 않기 위해 여기에서 솔루션 을 수정했습니다.
Foo.pm ( 오픈 포스트와 동일 )
package Foo {
my $baz = bar();
sub bar { 42 }; ## Overwrite this
print $baz; ## Before this is executed
}
# Note, even though print normally returns true, a final line of 1; is recommended.
OverrideSubs.pm 업데이트
package OverrideSubs;
use strict;
use warnings;
use Path::Tiny;
use List::Util qw(first);
sub import {
my (undef, %overrides) = @_;
my $default_pkg = caller; # Default namespace when unspecified.
my %remap;
for my $what (keys %overrides) {
( my $with = $overrides{$what} ) =~ s/^([^:]+)$/${default_pkg}::$1/;
my $what_pkg = $what =~ /^(.*)\:\:/ ? $1 : $default_pkg;
my $what_file = ( join '/', split /\:\:/, $what_pkg ). '.pm';
push @{ $remap{$what_file} }, "*$what = *$with";
}
my @inc = grep !ref, @INC; # Filter out any existing hooks; strings only.
unshift @INC, sub {
my $remap = $remap{ $_[1] } or return undef;
my $pre = join ';', @$remap;
my $pm = first { $_->is_file && -r } map { path $_, $_[1] } @inc
or return undef;
# Prepend code to override subroutine(s) and reset line numbering.
open my $fh, '<', \( $pre. ";\n#line 1 $pm\n". $pm->slurp_raw );
return $fh;
};
}
1;
test-run.pl
#!/usr/bin/env perl
use strict;
use warnings;
use lib qw(.); # Needed for newer Perls that typically exclude . from @INC by default.
use OverrideSubs
'Foo::bar' => 'mybar';
sub mybar { 5 } # This can appear before or after 'use OverrideSubs',
# but must appear before 'use Foo'.
use Foo;
실행 및 출력 :
$ ./test-run.pl
5
답변
경우 sub bar
내부가 Foo.pm
기존과는 다른 프로토 타입이 Foo::bar
기능을 펄을 덮어 쓰지 않습니다? 그것은 사실 인 것처럼 보이고 솔루션을 매우 간단하게 만듭니다.
# test.pl
BEGIN { *Foo::bar = sub () { 7 } }
use Foo;
또는 같은 것
# test.pl
package Foo { use constant bar => 7 };
use Foo;
업데이트 : 아니요, 이것이 작동하는 이유는 Perl이 “일정한”서브 루틴을 정의하지 않기 ()
때문에 (프로토 타입 ), 모의 함수가 일정하면 실행 가능한 솔루션 일뿐입니다.
답변
골프 콘테스트를하자!
sub _override { 7 }
BEGIN {
my ($pm)= grep -f, map "$_/Foo.pm", @INC or die "Foo.pm not found";
open my $fh, "<", $pm or die;
local $/= undef;
eval "*Foo::bar= *main::_override;\n#line 1 $pm\n".<$fh> or die $@;
$INC{'Foo.pm'}= $pm;
}
use Foo;
이것은 단지 모듈의 코드 앞에 메소드를 대체하는 것으로, 컴파일 단계 이후와 실행 단계 전에 실행되는 첫 번째 코드 행이됩니다.
그런 다음, %INC
장래의로드가 use Foo
원본을 끌어 오지 않도록 항목을 채우십시오 .