Reputation: 2582
I am refactoring some old, badly made Perl code. In many places, data that was stored in external .pl
files like this:
sub data{{ huge => 'datastructure', etc => '...' }};
1;
is imported like that:
require 'file_with_data.pl';
my $data = data();
I cannot change the way this data is stored, but I am moving all this require
logic to a single package which will make the data accessible to anyone that wants it.
My problem is, I have to do this progressively, i.e. for some time, some modules will use the old, ugly way, whereas some others will use the new, slightly less ugly way.
Here is one of the methods I have in my new package:
sub load_from_subroutine {
no strict 'refs';
my ($self, $file, $subroutine) = @_;
my $data;
try {
if (exists $INC{$file}) {
die "What should I do now?"; # <-- this is what I want to change
}
else {
untaint $file;
require $file;
if (defined &$subroutine) {
$data = &$subroutine;
}
else {
die "Subroutine $subroutine is not defined";
}
}
}
catch {
croak "Unable to load resource from file $file: $_";
};
return $data;
}
In case exists $INC{$file}
tests to true, is there any trick I can use to be able to get at &$subroutine
? It is implied that, in this namespace, defined &$subroutine
is false.
Temporary code
In the meanwhile, this is what I'm using:
warn "Doing dirty trick in order to be able to load $file!\n";
my $tmp = '/tmp/data' . time . rand(100) . '.pl';
copy $file, $tmp;
require $tmp;
# do the loading stuff as shown...
unlink $tmp;
This is really awful and I'm longing for a better solution.
Upvotes: 0
Views: 109
Reputation: 35198
It sounds like ikegami helped with your initial problem.
Please note that because you're importing all of these files into a single package there is likely to be some name overlap, especially if the previous coder reused the name sub data
. I would therefore suggest that you give each of these files their own package and to cache the result.
The follow demonstrates now to do that:
package MyNamespace::DataLoader;
use strict;
use warnings;
use autodie;
use Carp;
# Both of the below subs output a string, like: sub data { "foo" }
print load_from_subroutine(qw(data2.pl data)), "\n";
print load_from_subroutine(qw(data3.pl data)), "\n";
our %files;
our $inc = 0;
sub load_from_subroutine {
my ($file, $subroutine) = @_;
# Load the file
if (!$files{$file}) {
my $data = do {
local $/;
open my $fh, '<', $file;
<$fh>;
};
carp "File already has a package declaration: $file" if $data =~ /^package/;
my $pkg = __PACKAGE__ . '::Package' . ++$inc;
$files{$file}{pkg} = $pkg;
eval "package $pkg;\n$data\n1;";
croak "Unable to load '$file': $@" if $@;
}
# Reference the subroutine
if (!$files{$file}{sub}{$subroutine}) {
my $subname = $files{$file}{pkg} . '::' . $subroutine;
no strict 'refs';
if (! defined &{"$subname"}) {
croak "Subroutine doesn't exist: $file->$subroutine";
}
$files{$file}{sub}{$subroutine} = \&{"$subname"};
}
return $files{$file}{sub}{$subroutine}->();
}
Upvotes: 1
Reputation: 385496
It wasn't loaded in a different namespace (note the lack of package
). The lack of package
also means you should be using do
instead of require
. do
ignores %INC
.
Upvotes: 1