Collisions in Block Names in Jemplate

Two Blocks with the Same Name

In January this year, I got Jemplate in the CPAN Pull Request Challenge. The module implements Template::Toolkit in JavaScript, so you can transfer the burden to process the templates from the server to the client.

One of the open issues in its GitHub repository caught my attention: Jemplate compiled all the templates coming from different files into one large JavaScript code-block. If you declared two blocks with the same name in two different files, they’d end up in the same hash in JavaScript, and only one would survive—but you couldn’t tell which one. Keeping block names unique across files probably isn’t part of common practice, so making the module warn you in such a case sounded like a reasonable requirement.

Storing the Names

It wasn’t that hard to hash the block names in the Jemplate object. I also discovered that templates themselves are named and duplicate template names lead to the same problems as duplicate blocks, so I added warnings for them, too.

    my $used_names = ref $self ? \$self->{USED_NAMES} : \ {};
    warn "Duplicate template or block name $template_name\n"
      if $$used_names->{$template_name}++;

    # ...

        warn "Duplicate template or block name $function_name\n"
          if $$used_names->{$function_name}++;

Note that $self could be a non-reference (you can call Jemplate->compile_template_files without any instance). In such a case, the block and template names are stored in the lexical variable only, otherwise the instance remembers them.

Interplay with Template::Toolkit

When writing the test for the change, I noticed I could only detect duplicate names in different files (i.e. across different calls to $jemplate->compile_template_files). I spent several hours trying to find the place where duplicate names in the same file could be reported—and I discovered that parsing of the files was still done by Template::Toolkit, so if I really wanted to add a warning for duplicate names in the same file, I’d have to patch Template::Toolkit, too.

And indeed, that’s what I did. It was even easier this time, I just inserted one line:

    warn "Block redefined: $name\n" if exists $defblock->{ $name };
    $defblock->{ $name } = $block;

Testing the Warning

To test the warnings, I used the __WARN__ pseudo-signal. On each warning emitted by the testing code, the handler counts the expected warnings (and fails if unexpected one occurs), and then the number is checked against the expected value.

use strict;
use warnings;

use Test::More tests => 1;

use Template;

my $warning_seen;
local $SIG{__WARN__} = sub {
    my @warnings = @_;
    if ($warnings[0] =~ /Block redefined: b1/) {
        ++$warning_seen;
    } else {
        die "Unexpected warning: ", @warnings;
    }
};

my $t = Template->new;
$t->process(\ << '__TEMPLATE__', {}, \ my $ignore_output);
[% BLOCK b1 %]first[% END %]
[% BLOCK b1 %]second[% END %]
__TEMPLATE__

is $warning_seen, 1, 'warning seen';

The corresponding test for Jemplate is similar, it just counts the names to be sure it catches both the duplicates, block and template.

P.S. The changes to Template have been merged. Jemplate changes are still waiting.

Leave a comment

About E. Choroba

user-pic I blog about Perl.