token_re, include cleanup, git cleanup
[urisagit/Template-Simple.git] / extras / cookbook.pl
diff --git a/extras/cookbook.pl b/extras/cookbook.pl
new file mode 100644 (file)
index 0000000..f48b7b5
--- /dev/null
@@ -0,0 +1,424 @@
+#!/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 ;