=head1 NAME File::INC - find files via @INC =head1 SYNOPSIS use File::INC; =head1 DESCRIPTION Perl has two almost identical facilities to load "library" files: C and C (C is basically a compile-time C). These search C<@INC> for the file and compile it. Unfortunately, sometimes you want to locate data (or even source) files using the same mechanism. The problem is that Perl I evaluates the files as Perl code, which is not something you want for data files. This module implements the same (hopefully :) C<@INC> walk code as perl, and gives you access to the resulting files, without evaluating them. =head2 ALTERNATIVES There are alternatives to this module, both of which I regularly employ, but they come with their own drawbacks: =over 4 =item Walking C<@INC> manually and treating it as an array of directories. Basically, this: sub find_rcfile($) { my $path; for (@RC_PATH, "") { $path = "$RC_BASE/$_/$_[0]"; return $path if -e $path; } die "FATAL: can't find required file \"$_[0]\" in \"$RC_BASE\"\n"; } my $path = find_rcfile "My/Module/datafile.dat"; This is simple, but fails for any entries in C<@INC> that are not directories. Not handling these makes it fail in many environments, such as in L or L. This module basically implements a full-featured version of this that hopefully gets everything right. =item Embed datafiles using C. You can embed datafiles by, say, uuencoding them and putting them into a F<.pm> file (I think L) does this. This is extremely wasteful, but at least works around the C<@INC> problematic. A slightly better way is to use C. Take this file, put as F into the perl library: < call: my $data = do "My/Module/data.pl"; You can handle arbirary binary data either by finding a suitable C marker that isn't part of it, or you could use utf8 encoding to the rescue, by using a >255 character code as text delimiter: use utf8; # use U+2026 as delimiter q…arbitary binary data....… While this takes care of C<@INC>, perl still has to compile and execute it every time (which might not be a significant problem), and it requires the data to be formatted specifically for this occasion, so is hard for accessing the sources of third-party modules. =back =head1 FUNCTIONS This module only provides one function, not exported, that creates C objects. =over 4 =cut package File::INC; use common::sense; use Carp (); our $VERSION = 0.2; =item $inc = File::INC::find $name The name is a file name of the same syntax as expected by C or C, e.g. F or F. Unlike C et al., absolute paths are not supported. If the file is found, it returns a C object. Otherwise, it returns C. To work around potential bugs in C<@INC> hooks, it is recommended to only keep one C around at any single time. =cut sub find($) { my $file = shift; for my $inc (@INC) { if (ref $inc) { my $func; if (CODE:: eq ref $inc) { $func = $inc; } elsif (ARRAY:: eq ref $inc) { $func = $inc->[0]; } else { $func = $inc->can ("INC"); } if ($func) { my @r = $func->($inc, $file);; my ($cache, $fh, $sub, $state); # undocumented and unsupported if (!eval { fileno $r[0] } and CODE:: ne ref $r[0]) { $cache = shift @r; } if (defined eval { fileno $r[0] }) { $fh = shift @r; } if (CODE:: eq ref $r[0]) { $sub = shift @r; } $state = shift @r; if (defined $fh or defined $cache or defined $sub) { defined $cache and Carp::croak "File::INC: undocumented filter caches are not supported\n"; return bless [undef, $fh, $sub, $state, $cache]; } } } elsif (-e "$inc/$file") { # what a relief, the simple case return bless ["$inc/$file"]; } } undef } =back =head1 THE C CLASS Library files are represented by C objects. You can query it to find more information about the file (such as whether it even is a file :). =over 4 =item $path = $inc->path If the file is actually a file on disk (the most common, but not guaranteed, case), then this method returns the path to it. In other cases, it returns C. =cut sub path { $_[0][0] } =item $path = $inc->force_path #todo =cut sub force_path { die; } =item $fh = $inc->fh =cut sub fh { if (defined $_[0][0]) { # path? open my $fh, "<", $_[0][0]; return $fh; } elsif (defined $_[0][1] && !defined $_[0][2]) { # fh. and no filter? return $_[0][1]; } else { return undef; } } =item $fh = $inc->force_fh #todo =cut sub force_fh { die; } =item $data = $inc->data =cut sub data { if ($_[0][0]) { # path open my $fh, "<", $_[0][0] or die "$_[0][0]: $!"; } elsif (defined $_[0][1]) { # fh if (defined $_[0][2]) { # filter sub my @data; my $status; local $_; while () { $_ = <$_[0][1]>; $status = $_[0][2]->($_[0][3]); push @data, $_; last if $status <= 0; } return join "", @data; } # plain file, slurp local $/; return scalar <$_[0][1]>; } elsif (defined $_[0][2]) { # "filter" sub my @data; my $status; local $_; while () { $_ = ""; $status = $_[0][2]->($_[0][3]); push @data, $_; last if $status <= 0; } return join "", @data; } else { die "internal error"; } } =back =head1 LIMITATIONS As with many of the newer additions to perl, C<@INC> hooks have been poorly designed (the interface is pretty chaotic and does not follow Perl language rules, and the subroutine magically has to know whether the line passed to it is the last line of the file) and documented (for example, the filter cache is completely undocumented), and as a result, it's not (to the best of my knowledge) possible implement this module in pure Perl. I hope this implementation is good enough for real world C<@INC> hooks. Known deficiencies are that file handles are detected differently, only a copy of the state argument is passed to the subroutine, and the undocumented filter cache return value is ignored. =head1 AUTHOR AND CONTACT INFORMATION Marc Lehmann http://software.schmorp.de/pkg/File-INC =cut 1