Peretasan diperlukan karena require
(dan karenanya use
) mengkompilasi dan mengeksekusi modul sebelum kembali.
Sama berlaku untuk eval
. eval
tidak dapat digunakan untuk mengkompilasi kode tanpa juga menjalankannya.
Solusi paling tidak mengganggu yang saya temukan adalah menimpanya DB::postponed
. Ini disebut sebelum mengevaluasi file yang diperlukan dikompilasi. Sayangnya, ini hanya dipanggil saat debugging ( perl -d
).
Solusi lain adalah dengan membaca file, memodifikasinya, dan mengevaluasi file yang dimodifikasi, seperti yang berikut ini:
use File::Slurper qw( read_binary );
eval(read_binary("Foo.pm") . <<'__EOS__') or die $@;
package Foo {
no warnings qw( redefine );
sub bar { 7 }
}
__EOS__
Di atas tidak diatur dengan benar %INC
, itu mengacaukan nama file yang digunakan oleh peringatan dan semacamnya, itu tidak memanggil DB::postponed
, dll. Berikut ini adalah solusi yang lebih kuat:
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;
Saya menggunakan UNITCHECK
(yang dipanggil setelah kompilasi tetapi sebelum eksekusi) karena saya mendahulukan override (menggunakan unread
) daripada membaca di seluruh file dan menambahkan definisi baru. Jika Anda ingin menggunakan pendekatan itu, Anda bisa mendapatkan pegangan file untuk kembali menggunakan
open(my $fh_for_perl, '<', \$modified_code);
return $fh_for_perl;
Kudos to @ Grinnz karena menyebutkan @INC
kait.
Foo::bar
, tetapiuse Foo
akan menjalankan fase kompilasi (mendefinisikan ulang bar jika ada yang sebelumnya didefinisikan di sana) dan fase runtime dari Foo. Satu-satunya hal yang dapat saya pikirkan adalah@INC
kait yang sangat dalam untuk memodifikasi bagaimana Foo dimuat.