Se requiere un hack porque require
(y por lo use
tanto) compila y ejecuta el módulo antes de regresar.
Lo mismo vale para eval
. eval
no se puede usar para compilar código sin ejecutarlo también.
La solución menos intrusiva que he encontrado sería anular DB::postponed
. Esto se llama antes de evaluar un archivo requerido compilado. Desafortunadamente, solo se llama cuando se depura ( perl -d
).
Otra solución sería leer el archivo, modificarlo y evaluar el archivo modificado, algo así como lo siguiente:
use File::Slurper qw( read_binary );
eval(read_binary("Foo.pm") . <<'__EOS__') or die $@;
package Foo {
no warnings qw( redefine );
sub bar { 7 }
}
__EOS__
Lo anterior no se establece correctamente %INC
, desordena el nombre de archivo utilizado por las advertencias y demás, no llama DB::postponed
, etc. La siguiente es una solución más sólida:
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;
Usé UNITCHECK
(que se llama después de la compilación pero antes de la ejecución) porque antepuse la anulación (usandounread
) en lugar de leer todo el archivo y agregar la nueva definición. Si desea utilizar ese enfoque, puede obtener un identificador de archivo para volver utilizando
open(my $fh_for_perl, '<', \$modified_code);
return $fh_for_perl;
Felicitaciones a @Grinnz por mencionar @INC
ganchos.
Foo::bar
, perouse Foo
ejecutará tanto la fase de compilación (redefiniendo la barra si algo se definió previamente allí) como la fase de tiempo de ejecución de Foo. Lo único que se me ocurre sería un@INC
gancho profundamente hacky para modificar cómo se carga Foo.