The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

use strict ;
use warnings ;
use lib '../lib' ;

use File::Slurp ;

=head1 Template::Simple Cookbook

This cookbook contains examples of idioms and best practices when
using the Template::Simple module. It will illustrate all the the
features of this module and show various combinations of code to get
the job done. Send any requests for more examples to
E<lt>template@stemsystems.comE<gt>. Read the source code to see the
working examples or you can run this script to see all the
output. Each example has its title printed and rendered templates are
printed with a KEY: prefix and delimited with [].

By combining these techniques you can create and built complex
template applications that can do almost any task other templaters can
do. You will be able to share more code logic and templates as they
are totally isolated and independent from each other.

=head2 Use Scalar References

When passing text either as templates or in data tree elements, it is
generally faster to use scalar references than plain scalars. T::S can
accept text either way so you choose the style you like best. Most of
the examples here will use scalar references. Note that passing a
scalar reference to the new() constructor as the template will force
that to be a template and not a template name so no lookup will
occur. T::S always treats all text values as read only and never
modifies incoming data.

=cut

use Template::Simple ;
my $ts = Template::Simple->new() ;
my $template ;


=head1 Token Expansion

The simplest use of templates is replacing single tokens with
values. This is vry similar to interpolation of scalar variables in a
double quoted string. The difference is that the template text can
come from outside the program whereas double quoted strings must be in
the code (eval STRING doesn't count).

To replace tokens all you need is a template with token markups
(e.g. C<[% foo %]>) and a hash with those tokens as keys and the
values with which to replace them. Remember the top level template is
treated as an unnamed chunk so you can pass a hash reference to
render.

=cut

print "\n******************\nToken Expansion\n******************\n\n" ;

$template = <<TMPL ;
This [% token %] will be replaced as will [%foo%] and [% bar %]
TMPL

my $token_expansion = $ts->render( $template,
	{
	token	=> 'markup',
	foo	=> 'this',
	bar	=> 'so will this',
	}
) ;

print "TOKEN EXPANSION: [${$token_expansion}]\n" ;

=head1 Token Deletion

Sometimes you want to delete a token and not replace it with text. All
you need to do is use the null string for its data. Altenatively if
you are rendering a chunk with a hash (see below for more examples)
you can just not have any data for the token and it will also be
deleted. Both styles are shown in this example.

=cut

print "\n******************\nToken Deletion\n******************\n\n" ;

$template = <<TMPL ;
This [% token %]will be deleted as will [%foo%]
TMPL

my $token_deletion = $ts->render( $template,
	{
	token	=> '',
	}
) ;

print "TOKEN DELETION: [${$token_deletion}]\n" ;


=head1 Named Templates

You can pass a template directly to the C<render> method or pass in
its name. A named template will be searched for in the object cache
and then in the C<template_paths> directories. Templates can be loaded
into the cache with in the new() call or added later with the
C<add_templates> method.

=cut

print "\n******************\nNamed Templates\n******************\n\n" ;

$ts = Template::Simple->new(
	templates	=> {
		foo	=> <<FOO,
We have some foo text here with [% data %]
FOO
	}
) ;

my $foo_template = $ts->render( 'foo', { data => 'lots of foo' } ) ;

$ts->add_templates( { bar => <<BAR } ) ;
We have some bar text here with [% data %]
BAR

my $bar_template = $ts->render( 'bar', { data => 'some bar' } ) ;

print "FOO TEMPLATE: [${$foo_template}]\n" ;
print "BAR TEMPLATE: [${$bar_template}]\n" ;

=head1 Include Expansion

You can build up templates by including other templates. This allows a
template to be reused and shared by other templates. What makes this
even better, is that by passing different data to the included
templates in different renderings, you can get different results. If
the logic was embedded in the template you can't change the rendering
as easily. You include a template by using the C<[%include name%]>
markup. The name is used to locate a template by name and its text
replaces the markup. This example shows a single include in the top
level template.

=cut

print "\n******************\nInclude Expansion\n******************\n\n" ;

$ts = Template::Simple->new(
	templates	=> {
		top	=> <<TOP,
This top level template includes this <<[% include other %]>>text
TOP
		other	=> <<OTHER,
This is the included text
OTHER
} ) ;

my $include_template = $ts->render( 'top', {} ) ;

print "INCLUDE TEMPLATE: [${$include_template}]\n" ;

=head1 Template Paths

You can search for templates in files with the C<search_dirs> option
to the constructor. If a named template is not found in the object
cache it will be searched for in the directories listed in the
C<search_dirs> option. If it is found there, it will be loaded into
the object cache so future uses of it by name will be faster. The
default value of C<search_dirs> option is C<templates>. Templates must
have a suffix of C<.tmpl>. This example makes a directory called
'templates' and a template file named C<foo.tmpl>. The second example
makes a directory called C<cookbook> and puts a template in there and
sets. Note that the option value must be an array reference.

=cut

print "\n******************\nSearch Dirs\n******************\n\n" ;

my $tmpl_dir = 'templates' ;
mkdir $tmpl_dir ;
write_file( "$tmpl_dir/foo.tmpl", <<FOO ) ;
This template was loaded from the dir [%dir%]
FOO

$ts = Template::Simple->new() ;
my $foo_file_template = $ts->render( 'foo', { dir => 'templates' } ) ;

print "FOO FILE TEMPLATE: [${$foo_file_template}]\n" ;

unlink "$tmpl_dir/foo.tmpl" ;
rmdir $tmpl_dir ;

######

my $cook_dir = 'cookbook' ;
mkdir $cook_dir ;
write_file( "$cook_dir/bar.tmpl", <<BAR ) ;
This template was loaded from the $cook_dir [%dir%]
BAR

$ts = Template::Simple->new( search_dirs => [$cook_dir] ) ;
my $bar_file_template = $ts->render( 'bar', { dir => 'directory' } ) ;

print "BAR FILE TEMPLATE: [${$bar_file_template}]\n" ;

unlink "$cook_dir/bar.tmpl" ;
rmdir $cook_dir ;

=head1 Named Chunk Expansion

The core markup in T::S is called a chunk. It is delimited by paired
C<start> and C<end> markups and the text in between them is the
chunk. Any chunk can have multiple chunks inside it and they are named
for the name in the C<start> and C<end> markups. That name is used to
match the chunk with the data passed to render. This example uses the
top level template (which is always an unnamed chunk) which contains a
nested chunk which has a name. The data passed in is a hash reference
which has a key with the chunk name and its value is another hash
reference. So the nested chunk match up to the nested hashes.

=cut

print "\n******************\nNested Chunk Expansion\n******************\n\n" ;

$ts = Template::Simple->new(
	templates	=> {
		top	=> <<TOP,
This top level template includes this <<[% include nested %]>> chunk
TOP
		nested	=> <<NESTED,
[%START nested %]This included template just has a [% token %] and another [% one %][%END nested %]
NESTED
	}
) ;

my $nested_template = $ts->render( 'top',
	{
	nested => {
		token	=> 'nested value',
		one	=> 'value from the data',
	}
} ) ;

print "NESTED TEMPLATE: [${$nested_template}]\n" ;

=head2 Boolean Chunk

The simplest template decision is when you want to show some text or
nothing.  This is done with an empty hash reference or a null string
value in the data tree. The empty hash reference will cause the text
to be kept as is with all markups removed (replaced by the null
string). A null string (or a reference to one) will cause the text
chunk to be deleted.

=cut

print "\n******************\nBoolean Text\n******************\n\n" ;

$template = \<<TMPL ;
[% START boolean %]This is text to be left or deleted[% END boolean %]
TMPL

my $boolean_kept = $ts->render( $template, { boolean => {} } ) ;
my $deleted = $ts->render( $template, { default => \'' } ) ;

print "KEPT: [${$boolean_kept}]\n" ;
print "DELETED: [${$deleted}]\n" ;

=head2 Default vs. Overwrite Text

The next step up from boolean text is overwriting a default text with
another when rendering. This is done with an empty hash reference or a
scalar value for the chunk in the data tree. The empty hash reference
will cause the default text to be kept as is with all markups removed
(replaced by the null string). A scalar value (or a scalar reference)
will cause the complete text chunk to be replaced by that value.

=cut

print "\n******************\nDefault vs. Overwrite Text\n******************\n\n" ;

$template = \<<TMPL ;
[% START default %]This is text to be left or replaced[% END default %]
TMPL

my $default_kept = $ts->render( $template, { default => {} } ) ;
my $overwrite = $ts->render( $template, { default => \<<OVER } ) ;
This text will overwrite the default text
OVER

print "DEFAULT: [${$default_kept}]\n" ;
print "OVERWRITE: [${$overwrite}]\n" ;

=head2 Conditional Text

Instead of having the overwrite text in the data tree, it is useful to
have it in the template itself. This is a conditional where one text
or the other is rendered. This is done by wrapping each text in its
own chunk with unique names. The data tree can show either one by
passing an empty hash reference for that data and a null string for
the other one. Also you can just not have a value for the text not to
be rendered and that will also delete it. Both styles are shown here.

=cut

print "\n******************\nConditional Text\n******************\n\n" ;

$template = \<<TMPL ;
[% START yes_text %]This text shown when yes[% END yes_text %]
[% START no_text %]This text shown when no[% END no_text %]
TMPL

my $yes_shown = $ts->render( $template, { yes_text => {} } ) ;
my $no_shown = $ts->render( $template, {
	yes_text => '',
	no_text => {}
} ) ;

print "YES: [${$yes_shown}]\n" ;
print "NO: [${$no_shown}]\n" ;

=head1 List Chunk Expansion

T::S has no list markup because of the unique way it handles data
during rendering. When an array reference is matched to a chunk, the
array is iterated and the chunk is then rendered with each element of
the array. This list of rendered texts is concatenated and replaces
the original chunk in the template. The data and the logic that
creates the data controls when a template chunk is repeated. This
example shows the top level (unnamed) template being rendered with an
array of hashes. Each hash renders the chunk one time. Note that the
different results you get based on the different hashes in the array.

=cut

print "\n******************\nList Chunk Expansion\n******************\n\n" ;

$ts = Template::Simple->new(
	templates => {
		top_array => <<TOP_ARRAY,

This is the [%count%] chunk.
[%start maybe%]This line may be shown[%end maybe%]
This is the end of the chunk line
TOP_ARRAY
} ) ;

my $top_array = $ts->render( 'top_array', [
	{
		count	=> 'first',
		maybe	=> {},
	},
	{
		count	=> 'second',
	},
	{
		count	=> 'third',
		maybe	=> {},
	},
] ) ;

print "TOP_ARRAY: [${$top_array}]\n" ;


=head1 Separated List Expansion

A majorly used variation of data lists are list with a separator but
not one after the last element. This can be done easily with T::S and
here are two techniques. The first one uses a token for the separator
in the chunk and passes in a hash with the delimiter string set in all
but the last element. This requires the code logic to know and set the
delimiter. The other solution lets the template set the delimiter by
enclosing it in a chunk of its own and passing an empty hash ref for
the places to keep it and nothing for the last element. Both examples
use the same sub to do this work for you and all you need to do is
pass it the token for the main value and the seperator key and
optionally its value. You can easily make a variation that puts the
separator before the element and delete it from the first element.  If
your chunk has more tokens or nested chunks, this sub could be
generalized to modify a list of hashes instead of generating one.

=cut

print "\n******************\nSeparated List Expansion\n******************\n\n" ;


sub make_separated_data {
 	my( $token, $data, $delim_key, $delim ) = @_ ;

# make the delim set from the template (in a chunk) if not passed in
# an empty hash ref keeps the chunk text as is.

	$delim ||= {} ;

	my @list = map +{ $token => $_, $delim_key => $delim, }, @{$data} ;

# remove the separator from the last element

	delete $list[-1]{$delim_key} ;

	return \@list ;
}

my @data = qw( one two three four ) ;

$ts = Template::Simple->new(
	templates	=> {
		sep_tmpl	=> <<SEP_TMPL,
Number [%count%][%sep%]
SEP_TMPL
		sep_data	=> <<SEP_DATA,
Number [%count%][%start sep%],[%end sep%]
SEP_DATA
} ) ;

my $sep_tmpl = $ts->render( 'sep_tmpl',
	make_separated_data( 'count', \@data, 'sep', '--' ) ) ;

my $sep_data = $ts->render( 'sep_data',
	make_separated_data( 'count', \@data, 'sep', {} ) ) ;

print "SEP_DATA: [${$sep_data}]\n" ;
print "SEP_DATA: [${$sep_data}]\n" ;

exit ;