Sunday, October 10, 2010

Updated Cut-n-paste Templating Code

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); }

No comments:

Post a Comment

Be relevant. Be constructive.