You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

164 lines
4.4 KiB

package ksb::RecursiveFH;
use strict;
use warnings;
use 5.014;
our $VERSION = '0.10';
use ksb::Util;
use File::Basename; # dirname
# TODO: Replace make_exception with appropriate croak_* function.
sub new
{
my ($class, $rcfile) = @_;
my $data = {
'filehandles' => [], # Stack of filehandles to read
'filenames' => [], # Corresponding tack of filenames (full paths)
'base_path' => [], # Base directory path for relative includes
'current' => undef, # Current filehandle to read
'current_fn' => undef, # Current filename
};
my $self = bless($data, $class);
$self->pushBasePath(dirname($rcfile)); # rcfile should already be absolute
return $self;
}
# Adds a new filehandle to read config data from.
#
# This should be called in conjunction with pushBasePath to allow for recursive
# includes from different folders to maintain the correct notion of the current
# cwd at each recursion level.
sub addFile
{
my ($self, $fh, $fn) = @_;
push @{$self->{filehandles}}, $fh;
push @{$self->{filenames}}, $fn;
$self->setCurrentFile($fh, $fn);
}
sub popFilehandle
{
my $self = shift;
pop @{$self->{filehandles}};
pop @{$self->{filenames}};
my $newFh = scalar @{$self->{filehandles}} ? ${$self->{filehandles}}[-1]
: undef;
my $newFilename = scalar @{$self->{filenames}} ? ${$self->{filenames}}[-1]
: undef;
$self->setCurrentFile($newFh, $newFilename);
}
sub currentFilehandle
{
my $self = shift;
return $self->{current};
}
sub currentFilename
{
my $self = shift;
return $self->{current_fn};
}
sub setCurrentFile
{
my ($self, $fh, $fn) = @_;
$self->{current} = $fh;
$self->{current_fn} = $fn;
}
# Sets the base directory to use for any future encountered include entries
# that use relative notation, and saves the existing base path (as on a stack).
# Use in conjunction with addFile, and use popFilehandle and popBasePath
# when done with the filehandle.
sub pushBasePath
{
my $self = shift;
push @{$self->{base_path}}, shift;
}
# See above
sub popBasePath
{
my $self = shift;
return pop @{$self->{base_path}};
}
# Returns the current base path to use for relative include declarations.
sub currentBasePath
{
my $self = shift;
my $curBase = $self->popBasePath();
$self->pushBasePath($curBase);
return $curBase;
}
# Reads the next line of input and returns it.
# If a line of the form "include foo" is read, this function automatically
# opens the given file and starts reading from it instead. The original
# file is not read again until the entire included file has been read. This
# works recursively as necessary.
#
# No further modification is performed to returned lines.
#
# undef is returned on end-of-file (but only of the initial filehandle, not
# included files from there)
sub readLine
{
my $self = shift;
# Starts a loop so we can use evil things like "redo"
READLINE: {
my $line;
my $fh = $self->currentFilehandle();
# Sanity check since different methods might try to read same file reader
return undef unless defined $fh;
if (eof($fh) || !defined($line = <$fh>)) {
$self->popFilehandle();
$self->popBasePath();
my $fh = $self->currentFilehandle();
return undef if !defined($fh);
redo READLINE;
}
elsif ($line =~ /^\s*include\s+\S/) {
# Include found, extract file name and open file.
chomp $line;
my ($filename) = ($line =~ /^\s*include\s+(.+?)\s*$/);
if (!$filename) {
die make_exception('Config',
"Unable to handle file include on line $., '$line'");
}
my $newFh;
my $prefix = $self->currentBasePath();
$filename =~ s/^~\//$ENV{HOME}\//; # Tilde-expand
$filename = "$prefix/$filename" unless $filename =~ m(^/);
open ($newFh, '<', $filename) or
die make_exception('Config',
"Unable to open file $filename which was included from line $.");
$prefix = dirname($filename); # Recalculate base path
$self->addFile($newFh, $filename);
$self->pushBasePath($prefix);
redo READLINE;
}
else {
return $line;
}
}
}
1;