From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

use strict;
use FileHandle ();
use Carp 'croak';
use vars qw($VERSION);
$VERSION = (qw$Revision: 1.10 $)[1];
sub valid_package_name {
my($self, $string) = @_;
# Escape everything into valid perl identifiers
$string =~ s/([^A-Za-z0-9\/])/sprintf("_%2x",unpack("C",$1))/eg;
# second pass cares for slashes and words starting with a digit
$string =~ s{
(/) # directory
(\d?) # package's first character
}[
"::" . ($2 ? sprintf("_%2x",unpack("C",$2)) : "")
]egx;
return "Embed" . $string;
}
sub cached {
my($self, $filename, $package, $mtime) = @_;
$$mtime = -M $filename;
if(defined $self->{FileCache}{$package}{mtime}
&&
$self->{FileCache}{$package}{mtime} <= $$mtime) {
return 1;
}
return 0;
}
sub cache {
my($self, $package, $mtime) = @_;
$self->{FileCache}{$package}{mtime} = $mtime;
}
sub uncache {
my($self, $package) = @_;
delete $self->{FileCache}{$package};
}
sub new {
my $class = shift;
return bless {
FileCache => {
},
@_,
} => $class;
}
sub prepare {
my($self, $filename, $package) = @_;
my $fh = FileHandle->new($filename) or die "open '$filename' $!";
local($/) = undef;
my $sub = <$fh>;
$fh->close;
#new object, same class
return bless {
CODE => $sub,
FILENAME => $filename,
PACKAGE => $package,
}, ref($self) || $self;
}
sub compile {
my($self) = @_;
my $code = $self->{CODE};
my $package = $self->{PACKAGE};
my $eval = qq{package $package; sub handler { $code; }};
{
# hide our variables within this block
my($package,$code);
eval $eval;
}
croak $@ if $@;
}
sub run {
my($self) = @_;
eval {$self->{PACKAGE}->handler;};
croak $@ if $@;
}
#borrowed from Safe.pm
sub delete {
my($self) = @_;
my $pkg = $self->{PACKAGE};
$self->uncache($pkg);
my ($stem, $leaf);
no strict 'refs';
$pkg = "main::$pkg\::"; # expand to full symbol table name
($stem, $leaf) = $pkg =~ m/(.*::)(\w+::)$/;
my $stem_symtab = *{$stem}{HASH};
delete $stem_symtab->{$leaf};
}
sub eval_file {
my($self, $filename, $delete) = @_;
my $package = $self->valid_package_name($filename);
my $mtime;
if($self->cached($filename, $package, \$mtime))
{
# we have compiled this subroutine already,
# it has not been updated on disk, nothing left to do
print STDERR "already compiled $package->handler\n" if $self->{DEBUG};
}
else {
my $code = $self->prepare($filename, $package);
#wrap the code into a subroutine inside our unique package
$code->compile;
#cache it unless we're cleaning out each time
$self->cache($package, $mtime) unless $delete;
$code->run;
$code->delete if $delete;
}
#take a look if you want
#print Devel::Symdump->rnew($package)->as_string, $/;
}
1;
__END__