I updated my perl cut-n-paste templating code.
- No modules if you cutnpaste
- All in one method
- Draws from a Hash-of-things
- Interpolate
- Iterate
- No "if", no "include
- No hash-of-hashes
Fixes: some bugs, sigils can be escaped, array element access.
And revised it a bit. Now 33 lines of perl (not counting comments/blank-lines).
Get it from github (you must right-click/download or you'll just get it displayed): cutnpaste_template.pm
You'll see that the file has some extra stuff in it. You can run it to process input as a template, run it to process the example template, or "use" it and call render(...).
The __DATA__ template in the github file documents the full usage/behavior.
Example
Assuming data:
%data = (
simple => "some text",
iterate => [ { name => "Amy", pet => "Dog" }, { name => "Bob", pet => "Cat" } ],
);
Here's a simple example template:
Interpolate $simple @iterate[I'm $name, I like $pet.]
Is there a way to use Text::Balanced to parse out the @word[...] chunks? That would make the code more compact.
Here it is, but I think I'll stop posting the source and let you get it from github from now on:
# version 2: supports escaping the sigils
# version 3: supports catenation, $_ in iteration, and $0..$9 for arrays
use strict; use warnings; no warnings 'uninitialized';
use Text::Balanced qw(extract_bracketed);
sub render {
my ($template, $data) = @_;
my @rez;
# 1st iteration: head@name[block]rest...
# $template = "rest" for next split
# Split gives just "head" if no "@", so processes each piece
# avoid $' with a split
# We actually split on \@ or @, so we can remove the \
while (my ($head, $escape, $field_name, $block) = split(/(\\?)@([a-zA-Z]\w*|_)(?=\[)/, $template,2)) {
# this was a \@word[..., so remove the \
if ($escape) {
$head = $head."@".$field_name;
}
# fix bracket escapes
$head =~ s/\\\[/[/g;
$head =~ s/\\\]/]/g;
# interpolate scalars in the "head"
# Have to capture the \ so we can do the right thing
my $interp = sub {
# escape, fieldname
if ($1) { '$'.$2 } # escaped, so remove /
elsif (index('0123456789',$2) >= 0) {
$data->{'_'}->[$2];
}
else { $data->{$2}; } # interp
};
$head =~ s/(\\?)\$([a-zA-Z]\w*|_|[0-9])/&$interp/eg;
push @rez, $head;
last if ! $field_name;
# this was a \@word[..., so next on ...
if ($field_name =~ /^\\(.+)/) {
$template = $block;
next;
}
# Get the "block" and "rest"
my $bracketed;
($bracketed, $template) = extract_bracketed( $block, '[');
$bracketed =~ s/^\[//;
$bracketed =~ s/\]$//;
# Repeat the block
my $list = (ref($data->{$field_name}) eq 'ARRAY') ? $data->{$field_name} : [$data->{$field_name}];
foreach my $sub_data ( @$list ) {
# recurse on this block with our block's data
push @rez, render( $bracketed, {%$data, (ref($sub_data) eq 'HASH' ? %$sub_data : ()), '_' => $sub_data});
}
}
join("",@rez);
}