Требуется взлом, потому что 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;
Kudos @Grinnz за упоминание @INC
перехватчиков.