I've used a couple of different template systems but right now my favorite is a simple system based on Template::Toolkit. I used a custom template provider to do the kind of includes I want to do. It looks for includes in the current directory, then the parent dir etc.. Which makes it
easy (for instance) to use a standard index.html file which can include a different header.html file in some subdirectories.
Read along for code. warning: the following code is probably full of bugs, and could be written better, but it's what I use for my (small) site without any problems. Do with it what you want.
Oh yes: it needs a file called "process.files" in the current dir which is just a text file containing (one per line) the path of the files to be generated and an optional title of that file (seperated from the path by a space).
Source templates go in src dir, destination goes to build directory.
#!/usr/bin/perl -w
use strict;
use lib './lib';
use Template;
use Template::Context;
use Template::Exception;
use FindProvider;
use Cwd;
use File::Basename;
use File::Find;
use File::Copy;
my $cwd = getcwd;
my $incpath = "$cwd/src";
my $outpath = "$cwd/build";
my %names;
my %shortnames;
my (@paths);
open(CONFIG,"< process.files") or die "Cannot open process.files: $!";
while (<CONFIG>) {
next if /^\s*#/ || /^$/;
chomp;
my ($path,$name) = split / /,$_,2;
push @paths,$path;
if ($name =~ /(.+)\|(.+)/) {
$names{$path} = $1;
$shortnames{$path} = $2;
}
else {
$names{$path} = $name;
$shortnames{$path} = $name;
}
warn "$path => $name\n";
}
close CONFIG;
my $provider = FindProvider->new({
INCLUDE_PATH => $incpath,
ABSOLUTE => 1,
});
my $context = Template::Context->new({
EVAL_PERL => 1,
INCLUDE_PATH => $incpath,
LOAD_TEMPLATES => [$provider],
});
my $tt = Template->new({
CONTEXT => $context,
OUTPUT_PATH => $outpath,
});
if (-d $outpath) {
warn "Removing build dir\n";
system ("rm -rf $outpath") == 0 or die "Cannot remove $outpath: $!
+";
}
warn "Creating build dir";
mkdir $outpath,0755 or die "Cannot create $outpath: $!";
for my $file (@paths) {
my ($base,$dir,$sfx) = fileparse($file,".html");
chdir "$incpath/$dir" or die "Cannot chdir to $dir: $!";
unless (-d "$outpath/$dir") {
mkdir "$outpath/$dir",0755 or die "Cannot make $outpath/$dir:
+$!";
}
warn "Processing $dir/$base$sfx\n";
$tt->process("$base$sfx",{
path => $file,
title => $names{$file},
short => $shortnames{$file},
paths => \@paths,
find => \&find_template,
parent => \&parent,
children => \&children,
titles => \%names,
short => \%shortnames,
root => root($file),
subnav => [subnav($file)],
dir => \&dir,
top => \&top,
},"$dir/$base$sfx") || die $tt->error;
}
chdir $incpath;
find({
wanted => sub {
return unless /(?:\.css|\.png|\.jpg|\.pdf|\.txt|\.ico|\.cgi|\.
+asc)$/;
return unless -f $_;
warn "Copying $_\n";
copy($_,"$outpath/$_") or die "Cannot copy $_: $!";
},
no_chdir => 1,
},'.');
sub find_template {
my ($file) = @_;
my $cwd = getcwd;
my $rel = $file;
warn "cwd = $cwd, rel = $file";
while (index($cwd,$incpath) == 0) {
if (-f "$cwd/$file") {
return $rel;
}
$cwd =~ s#/[^/]+/?$## or last;
$rel = "../$rel";
}
die Template::Exception->new('find error',"Cannot find $file anywh
+ere in path\n");
}
sub parent {
my ($path) = @_;
return unless $path =~ m#/#;
$path =~ s#/[^/]+/?$##;
$path .= "/index.html";
return $path if $names{$path};
return;
}
sub children {
my ($path) = @_;
$path =~ s#[^/]+$##;
my @children;
for (@paths) {
if (m#^\Q$path\E[^/]+/[^/]+$#) {
push @children,$_;
warn "child: $_\n";
}
}
return @children;
}
sub root {
my ($path) = @_;
warn "path =$path";
$path =~ s#^\./##;
$path =~ s#[^/]+$##;
$path =~ s#[^/]+#..#g;
return $path;
}
sub subnav {
my $file = shift;
if ($file =~ m#/#) {
return children(top($file));
}
}
sub top {
my $file = shift;
if ($file !~ m#/#) {
return $file;
}
if ($file =~ m#^([^/]+)/index\.html#) {
return $file;
}
if ($file =~ m#^([^/]+)/#) {
return "$1/index.html";
}
die "No top";
}
sub dir {
my $file = shift;
$file =~ s/index\.html$//;
return $file;
}
Code for FindProvider.pm:
package FindProvider;
use strict;
use base qw(Template::Provider);
use Template::Constants qw(STATUS_DECLINED);
use Cwd qw();
use File::Basename;
sub fetch {
my ($self,$filename) = @_;
if (ref $filename or index($filename,'..') >= 0 or index($filename
+,'/') >= 0) {
return $self->SUPER::fetch($filename);
}
my $path = ${$self->paths()}[0];
my $cwd = Cwd::getcwd();
while (index($cwd,$path) ==0) { # only recurse if current director
+y is in the first path
if (-f "$cwd/$filename") {
return $self->SUPER::fetch("$cwd/$filename");
}
$cwd =~ s#/[^/]+/?$##;
}
return undef,STATUS_DECLINED;
}
1;