views:

91

answers:

3

My static web pages are built from a huge bunch of templates which are inter-included using Template Toolkit's "import" and "include", so page.html looks like this:

[% INCLUDE top %]
[% IMPORT middle %]

Then top might have even more files included.

I have very many of these files, and they have to be run through to create the web pages in various languages (English, French, etc., not computer languages). This is a very complicated process and when one file is updated I would like to be able to automatically remake only the necessary files, using a makefile or something similar.

Are there any tools like makedepend for C files which can parse template toolkit templates and create a dependency list for use in a makefile?

Or are there better ways to automate this process?

+4  A: 

Template Toolkit does come with its own command line script called ttree for building TT websites ala make.

Here is an ttree.cfg file I use often use on TT website projects here on my Mac:

# directories
src = ./src
lib = ./lib
lib = ./content
dest = ./html

# pre process these site file
pre_process = site.tt

# copy these files
copy = \.(png|gif|jpg)$

# ignore following
ignore = \b(CVS|RCS)\b
ignore = ^#
ignore = ^\.DS_Store$
ignore = ^._

# other options
verbose
recurse

Just running ttree -f ttree.cfg will rebuild the site in dest only updating whats been changed at source (in src) or in my libraries (in lib).

For more fine grained dependencies have a look a Template Dependencies.

Update - And here is my stab at getting dependency list by subclassing Template::Provider:

{
    package MyProvider;
    use base 'Template::Provider';

    # see _dump_cache in Template::Provider
    sub _dump_deps {
        my $self = shift;

        if (my $node = $self->{ HEAD }) {
            while ($node) {
                my ($prev, $name, $data, $load, $next) = @$node;

                say {*STDERR} "$name called from " . $data->{caller}
                    if exists $data->{caller};

                $node = $node->[ 4 ];
            }
        }
    }
}


use Template;

my $provider = MyProvider->new;

my $tt = Template->new({
    LOAD_TEMPLATES => $provider,
});

$tt->process( 'root.tt', {} ) or die $tt->error;

$provider->_dump_deps;

The code above displays all dependencies called (via INCLUDE, INSERT, PROCESS and WRAPPER) and where called from within the whole root.tt tree. So from this you could build a ttree dependency file.

/I3az/

draegtun
I find `depend` in `.ttreerc` a little flaky and inconvenient with long lists of dependencies. I should look in `ttree` to see how they are dealing with it.
Sinan Ünür
@Aaahh, it uses `Text::ParseWords` to parse the dependencies. Now, that explains a lot ;-) In any case, I think the OP wants to know if he can generate that dependency information automatically based on the `[%- INCLUDE -%]` etc directives in the template files. However, +1 for pointing out `.ttreerc`.
Sinan Ünür
@Sinan: I had a 2-3 ideas on the dependency bit but felt best leave it out till I had something concrete. See my update.
draegtun
Ahh! `Template::Provider`! Thank you for the code. I had looked at `Template::Parser` trying to figure out how to do this but apparently I was looking in the wrong places.
Sinan Ünür
@Sinan: This is one area where the excellent TT docs are a little weaker on. I also looked at `Template::Parser` and `Template::Directive` before plumping on `Template::Provider` (initially looking at _fetch method before noticing that _dump_cache method was pretty much what was needed).
draegtun
+1  A: 

In case all you care about are finding file names mentioned in directives such as INCLUDE, PROCESS, WRAPPER etc, one imagine even using sed or perl from the command line to generate the dependencies.

However, if there are subtler dependencies (e.g., you reference an image using <img> in your HTML document whose size is calculated using the Image plugin, the problem can become much less tractable.

I haven't really tested it but something like the following might work:

#!/usr/bin/perl

use strict; use warnings;

use File::Find;
use File::Slurp;
use Regex::PreSuf;

my ($top) = @ARGV;

my $directive_re = presuf qw( INCLUDE IMPORT PROCESS );

my $re = qr{
    \[%-? \s+ $directive_re \s+ (\S.+) \s+ -?%\]
}x;

find(\&wanted => $top);

sub wanted {
    return unless /\.html\z/i;

    my $doc = read_file $File::Find::name;
    printf "%s : %s\n", $_, join(" \\\n", $doc =~ /$re/g );
}
Sinan Ünür
There are lots and lots of dependencies like the `<img>` one you mentioned, but I would like to at least make a start on pulling them out of the file. I keep missing dependencies from my makefile, and then builds don't reflect changes when the templates are edited.
Kinopiko
A: 

After reading the ttree documentation, I decided to create something myself. I'm posting it here in case it's useful to the next person who comes along. However, this is not a general solution, but one which works only for a few limited cases. It worked for this project since all the files are in the same directory and there are no duplicate includes. I've documented the deficiencies as comments before each of the routines.

If there is a simple way to do what this does with ttree which I missed, please let me know.

my @dependencies = make_depend ("first_file.html.tmpl");

# Bugs:
# Insists files end with .tmpl (mine all do)
# Does not check the final list for duplicates.

sub make_depend
{
    my ($start_file) = @_;
    die unless $start_file && $start_file =~ /\.tmpl/ && -f $start_file;
    my $dir = $start_file;
    $dir =~ s:/[^/]*$::;
    $start_file =~ s:\Q$dir/::;
    my @found_files;
    find_files ([$start_file], \@found_files, $dir);
    return @found_files;
}

# Bugs:
# Doesn't check for including the same file twice.
# Doesn't allow for a list of directories or subdirectories to find the files.
# Warning about files which aren't found switched off, due to
# [% INCLUDE $file %]

sub find_files
{
    my ($files_ref, $foundfiles_ref, $dir) = @_;
    for my $file (@$files_ref) {
        my $full_name = "$dir/$file";
        if (-f $full_name) {
            push @$foundfiles_ref, $full_name;
            my @includes = get_includes ($full_name);
            if (@includes) {
                find_files (\@includes, $foundfiles_ref, $dir);
            }
        } else {
#            warn "$full_name not found";
        }
    }
}

# Only recognizes two includes, [% INCLUDE abc.tmpl %] and [% INCLUDE "abc.tmpl" %]

sub get_includes
{
    my ($start_file) = @_;
    my @includes;
    open my $input, "<", $start_file or die "Can't open $start_file: $!";
    while (<$input>) {
        while (/\[\%-?\s+INCLUDE\s+(?:"([^"]+)"|(.*))\s+-?\%\]/g) {
            my $filename = $1 ? $1 : $2;
            push @includes, $filename;
        }
    }
    close $input or die $!;
    return @includes;
}
Kinopiko