/usr/local/CPAN/pee/Pee/FileRunner.pm
package Pee::FileRunner;
#use strict;
use Pee::Tokenizer;
use vars qw($VERSION $PEE_SCRATCH);
$VERSION = "1.03";
# delimiters to be used for regexp
my @delimiters = ('<\?', '\?>');
# CONSTRUCTOR
# new Pee::Tokenizer ($filename, \%options)
sub new {
my $self = {};
$self->{FILE} = $_[1];
$self->{OPTIONS} = ($_[2])? $_[2] : {};
bless ($self);
return $self;
}
sub safe_escape {
my $escaped = $_[0];
$escaped =~ s/\\/\\\\/g;
$escaped =~ s/\n/\\n/g;
$escaped =~ s/\t/\\t/g;
$escaped =~ s/'/\\'/g;
$escaped =~ s/"/\\"/g;
$escaped =~ s/\$/\\\$/g;
$escaped =~ s/\%/\\\%/g;
$escaped =~ s/\@/\\\@/g;
$escaped =~ s/&/\\&/g;
$escaped =~ s/`/\\`/g;
$escaped =~ s/\|/\\\|/g;
return $escaped;
}
sub compile {
my $self = $_[0];
my $opt = $self->{OPTIONS};
# read in the whole file
if (!open (F, $self->{FILE})) {
$self->{errmsg} = "Unable to open file: $!";
return 0;
}
my $buffer;
while (<F>) { # Do String Resource Substitution Here
$buffer .= $_;
}
close (F);
my $tokenizer = Pee::Tokenizer->new($buffer);
my $r = 0;
my $token;
my $done_header = 0;
my $extracted;
while (($r = $tokenizer->getNextToken(\$token)) != -1) {
if ($r == 0) { # normal block
# convert into 'print' statement
# if (!$done_header) {
# print the header as well
# $extracted .= 'print "Content-type: text/html\n\n";';
# $done_header = 1;
# }
$token = &safe_escape($token);
# $extracted .= "print qq|\n$token|;\n";
$extracted .= "print \"$token\";\n";
}
else { # code block
my $block = $token;
$block =~ s/$delimiters[0](.*)$delimiters[1]/$1/s;
if ($block =~ /^-.*$/s) {
# comment block
next;
}
elsif ($block =~ /^=(.*)$/s) {
# my $tmp = eval $1;
$extracted .= 'print ('."$1);\n";
# print $tmp if ($tmp);
}
elsif ($block =~ /^!\s*(\S*)\s+(.*)$/sm) {
# special commands
my $command = $1;
my $args = $2;
# trim trailing white space
$args =~ s/\s*$//;
if ($command =~ /include/i) {
if ($args !~ /^\//) {
# relative path specified
if ($self->{FILE} =~ /^(.*)\/[^\/]*$/) {
$args = "$1/$args";
}
}
my $included = $self->PeeInclude($args);
if (defined ($included)) {
$extracted .= $included;
}
else {
return 0;
}
}
}
else {
# eval $block;
$extracted .= $block;
}
}
}
if ($opt->{debug} && $opt->{scratchdir}) {
write_scratch ($opt->{scratchdir}, $self->{FILE}, $extracted);
}
$self->{extracted} = $extracted;
return 1;
}
# FileRunner::run ($namespace)
# $namespace is 'main' if not specified
sub run {
local $SIG{__WARN__} = sub { print STDERR "Pee::FileRunner warning: $_[0]\n" };
local $SIG{__DIE__} = sub { die @_ if $^S; print STDERR "Pee::FileRunner error: $_[0]\n"; };
my $self = $_[0];
my $ns = ($_[1] or 'main');
# Need to return 1 explicitly at the very end to overcome the problem
# that 'print' returns 0 when run under FCGI
return 1 if (eval "package $ns;\n\n".$self->{extracted}."\n1;");
$self->{errmsg} = $@;
return 0;
}
sub PeeInclude {
return undef if (!$_[0] || !$_[1]);
my $self = shift;
my $args = shift;
# my $basedir;
# if ($self->{FILE} =~ /^(.*)\/[^\/]*$/) {
# $basedir = $1;
# }
# else {
# $self->{errmsg} = "Include directive: Unable to get base directory.";
# return undef;
# }
# my $new = Pee::FileRunner->new("$basedir/$args");
my $new = Pee::FileRunner->new($args);
if ($new->compile()) {
my $extracted = $new->{extracted};
return $extracted;
}
else {
$self->{errmsg} = "Include directive: Unable to include \"$args\": $new->{errmsg}\n";
return undef;
}
}
sub write_scratch {
my ($dir, $filename, $buf) = @_;
return if (!-w $dir);
$filename =~ s/\//_/g;
open (SCRATCH, ">$dir/$filename") or warn "writing scratch: $!\n";
print SCRATCH $buf;
close (SCRATCH);
}