in reply to What are your favorite tools for building a static site?

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 really 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;

Replies are listed 'Best First'.
Re^2: What are your favorite tools for building a static site?
by perrin (Chancellor) on Dec 16, 2004 at 05:20 UTC
    You don't need a custom Template::Provider just to vary the include path. Changing an existing template object's include path is allowed in TT, and you can do it as often as you like, even in the middle of processing a request.