Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

But... SimpleTemplate really is.

by sauoq (Abbot)
on Dec 15, 2004 at 06:23 UTC ( [id://414947]=sourcecode: print w/replies, xml ) Need Help??
Category: Text Processing
Author/Contact Info sauoq
PM me.
Description:

Yeah... sigh... it's another templating system. I know CPAN is a bit overrun with them. (Just try to eke out a reasonable namespace for another templating module.) But, I've found it to be so useful over the years I can't not share it, so I'm sharing it here. I've cleaned it up a bit and fleshed out the documentation for public consumption, but it started as a quickie hack, so forgive me if it still seems like one.

I've always called it Local::SimpleTemplate. I've removed the "Local" for posting here. If you don't like the name for any reason feel free to change it; it's just a matter of changing the package line.

Originally, I just needed a replacement for a brittle little shell script that generated a bunch of config files from here documents and shell variables. I broke it up into a bunch of separate but similar perl scripts, one for each config file with the config's template in the __DATA__ section of the script. Each script read key/value pairs from stdin and substituted corresponding tags in the template with the input values. That little bit of history will explain this module's quirky (but surprisingly useful) default behavior.

The included POD documentation should be sufficient to learn how it works, but if you have questions please let me know. If you find any bugs, please let me know about those too. It has worked for me in a variety of uses but I did make a few tweaks before posting and may not have tested completely.

package SimpleTemplate;

use strict;
use warnings;
use vars qw( $VERSION );

$VERSION = '1.00';

my $default; # Holds the default template object when class methods ar
+e used.

sub new {
  my $class = shift;
  my $content = shift || _ST_default_content();
  my $self = {
    '_delims'  => [qw/ <% %> /],
    '_content' => $content,
    '_listsep' => "\n",
  };
  bless $self, $class;
}

sub list_separator {
  my $self = shift;
  if (! ref $self) { $self = $default = $default || __PACKAGE__->new()
+ }
  return @_ ? $self->{_listsep} = shift : $self->{_listsep};
}

sub delimiters {
  my $self = shift;
  if (! ref $self) { $self = $default = $default || __PACKAGE__->new()
+ }
  $self->{_delims} = shift if @_ == 1;
  $self->{_delims} = [shift, shift] if @_ == 2;
  return $self->{_delims};
}

sub tags {
  my $self    = shift;
  if (! ref $self) { $self = $default = $default || __PACKAGE__->new()
+ }
  my ($L, $R) = @{$self->{_delims}};
  my @list    = $self->{_content} =~ m/\Q$L\E\s*(.*?)\s*\Q$R\E/gm;
  return @list;
}

sub fill {
  my $self    = shift;
  if (! ref $self) { $self = $default = $default || __PACKAGE__->new()
+ }
  my $args    = shift;
  my $replace = shift;
  my ($L, $R) = @{$self->{_delims}};
  my $text    = $self->{_content};
  $text =~ s/\Q$L\E\s*(.*?)\s*\Q$R\E/$self->_expand($1,$args)/egm;
  $self->{_content} = $text if $replace;
  return \$text;
}


# Private Methods

sub _expand {
  my $self = shift;
  my ($key, $hashr) = @_;
  my $value = $hashr->{$key};

  return '' unless defined $value;

  for (ref $value) {
    /CODE/   and do { return $value->() };
    /ARRAY/  and do { return join $self->{_listsep}, @$value };
    /SCALAR/ and do { return $$value };
    /^$/     and do { return $value };
  }
}



# Private class methods

sub _ST_default_content {

  my $FH;
  my $pkg = caller;

  no strict 'refs';

  # if DATA exists in the calling package use that.
  if ( exists ${ $pkg . '::' }{DATA} and defined *{${$pkg . '::'}{DATA
+}}{IO}) {
    $FH = *{${$pkg . '::'}{DATA}}{IO};

  # else if DATA exists in main, use that.
  } elsif ( exists $main::{DATA} and defined *main::DATA{IO} ){
    $FH = *main::DATA{IO};

  # else use <>
  } else {
    $FH = \*ARGV;
  }

  do { undef $/; <$FH> };

}

q( My two cents aren't worth a dime. );

__END__

=head1 NAME

SimpleTemplate - Perl extension for very simple templates.

=head1 SYNOPSIS

  use SimpleTemplate;

  my $template = SimpleTemplate->new("Hello, <%World%>!\n");
  my $ref = $template->fill({ World => 'PerlMonks' });
  print $$ref
  exit(0);

  # Also try the bells, whistles, and variations! Like...

  # Filling placeholders with other thingies!
  my $textref = $template->fill( { Foo => \$scalar_ref,
                                   Bar => \@or_a_list,
                                   Baz => \&a_sub_ref, } );

  # Default Content!
  my $template = SimpleTemplate->new();

  # Default Content via a class method!
  SimpleTemplate->fill({ PlaceHolder => 'Your Favorite Value' });

  # Change your delimiters!
  $template->delimiters('[:', ':]');

  # Change your list separator!
  $template->list_separator(':');

  # Find the tag names in your Content!
  my @tags = $template->tags();


=head1 DESCRIPTION

This module allows for filling in placeholders in templates. It is mea
+nt to
exceedingly simple. It does not make it possible for you to actually p
+ut code
in your templates. If that is what you want, there are many other (les
+s simple)
modules that would be a better choice.

SimpleTemplate works in two (intermixable) modes. When the provided me
+thods are
called as class methods, they operate on a default template object sto
+red in a
private class variable. This default template is created the first tim
+e any of
the methods are called as class methods. It is created by calling the
constructor without any arguments.

I've found this module to be extreeeeeeeeeeeeeeeeeeeeeeeemely useful. 
+I use it
for all manner of things. Here's a hint... just because SimpleTemplate
+ is
simple that doesn't mean your use of it has to be. SimpleTemplate can 
+do a lot,
especially if you make heavy use of the delimiters() method and the op
+tional
argument to fill().


=head1 METHODS

=over 8

=item new()

The constructor can be called with either a single scalar argument or 
+none at
all. When it is called with an argument, the argument is taken to be t
+he
template content. When it is called without an argument, the template 
+content
is read from a filehandle. If the DATA filehandle is found in the call
+ing
package, the template content is read from it. Else if the DATA fileha
+ndle is
found in main::, the template content is read from there. Otherwise, t
+he
template content is read from the ARGV filehandle.

=item fill()

This method expects a single hashref as an argument. The keys of the h
+ash
referred to by the hashref should coincide with the placeholder names 
+and the
values should be the data to be substituted (or references to the data
+.) If a
value is found to be a reference, it will be called if it is a code re
+ference,
dereferenced and joined with the defined list separator if it is a ref
+erence to
an array, or dereferenced if it is a reference to a scalar. Note that 
+a
reference to a hash isn't handled specially at all. An optional argume
+nt can
be supplied. If it is a true value, the template object's content will
+ be
replaced with the result of filling in its placeholders. This can be u
+seful for
recursively filling templates.

=item delimiters()

This method takes 0, 1, or 2 arguments. When called with at least
one argument, it sets the delimiters used to define placeholders in th
+e
template. If there is exactly one argument, it is assumed to be a refe
+rence to
an array containing the left and right delimiters in that order. When 
+called
with two arguments, they are assumed to be the left and right delimite
+rs in
that order.  When called with no arguments no attempt is made to set t
+he
delimiters. This method returns a reference to an array containing the
+ left and
right delimiters in that order. The default delimiters are '<%' and '%
+>' but
feel free to change them; that's what this method is here for.


=item list_separator()

This method takes either 0 arguments or exactly one argument.
If it is called with an argument, the list separator value is set to t
+hat
value. It returns the list separator value. By default, the list separ
+ator is a
newline ("\n"). The list separator is a string which is used to separa
+te the
values when a placeholder is filled with an array.

=item tags()

This method takes no arguments. It returns a list of the placeholder n
+ames
used in the template.

=back

=head1 IMPORTANT NOTE

Placeholder names must not have leading/trailing whitespace. Whitespac
+e should 
be avoided in delimiters too.

=head1 LIMITATIONS

SimpleTemplate reads the whole template into memory. If your templates
+ are
huge, it will be a memory hog. This helps to keep SimpleTemplate SIMPL
+E!

=head1 BUGS

Yeah, right! As if... Look - this module is just too simple to have bu
+gs.
But, if you find any anyway, please let me know at perlmonks.org.

=head1 AUTHOR

sauoq

=head1 SEE ALSO

L<perl>.

=cut

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://414947]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (8)
As of 2024-03-28 12:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found