token_re, include cleanup, git cleanup
[urisagit/Template-Simple.git] / extras / cookbook.pl
CommitLineData
f5c8badf 1#!/usr/bin/perl
2
3use strict ;
4use warnings ;
5use lib '../lib' ;
6
7use File::Slurp ;
8
9=head1 Template::Simple Cookbook
10
11This cookbook contains examples of idioms and best practices when
12using the Template::Simple module. It will illustrate all the the
13features of this module and show various combinations of code to get
14the job done. Send any requests for more examples to
15E<lt>template@stemsystems.comE<gt>. Read the source code to see the
16working examples or you can run this script to see all the
17output. Each example has its title printed and rendered templates are
18printed with a KEY: prefix and delimited with [].
19
20By combining these techniques you can create and built complex
21template applications that can do almost any task other templaters can
22do. You will be able to share more code logic and templates as they
23are totally isolated and independent from each other.
24
25=head2 Use Scalar References
26
27When passing text either as templates or in data tree elements, it is
28generally faster to use scalar references than plain scalars. T::S can
29accept text either way so you choose the style you like best. Most of
30the examples here will use scalar references. Note that passing a
31scalar reference to the new() constructor as the template will force
32that to be a template and not a template name so no lookup will
33occur. T::S always treats all text values as read only and never
34modifies incoming data.
35
36=cut
37
38use Template::Simple ;
39my $ts = Template::Simple->new() ;
40my $template ;
41
42
43=head1 Token Expansion
44
45The simplest use of templates is replacing single tokens with
46values. This is vry similar to interpolation of scalar variables in a
47double quoted string. The difference is that the template text can
48come from outside the program whereas double quoted strings must be in
49the code (eval STRING doesn't count).
50
51To replace tokens all you need is a template with token markups
52(e.g. C<[% foo %]>) and a hash with those tokens as keys and the
53values with which to replace them. Remember the top level template is
54treated as an unnamed chunk so you can pass a hash reference to
55render.
56
57=cut
58
59print "\n******************\nToken Expansion\n******************\n\n" ;
60
61$template = <<TMPL ;
62This [% token %] will be replaced as will [%foo%] and [% bar %]
63TMPL
64
65my $token_expansion = $ts->render( $template,
66 {
67 token => 'markup',
68 foo => 'this',
69 bar => 'so will this',
70 }
71) ;
72
73print "TOKEN EXPANSION: [${$token_expansion}]\n" ;
74
75=head1 Token Deletion
76
77Sometimes you want to delete a token and not replace it with text. All
78you need to do is use the null string for its data. Altenatively if
79you are rendering a chunk with a hash (see below for more examples)
80you can just not have any data for the token and it will also be
81deleted. Both styles are shown in this example.
82
83=cut
84
85print "\n******************\nToken Deletion\n******************\n\n" ;
86
87$template = <<TMPL ;
88This [% token %]will be deleted as will [%foo%]
89TMPL
90
91my $token_deletion = $ts->render( $template,
92 {
93 token => '',
94 }
95) ;
96
97print "TOKEN DELETION: [${$token_deletion}]\n" ;
98
99
100=head1 Named Templates
101
102You can pass a template directly to the C<render> method or pass in
103its name. A named template will be searched for in the object cache
104and then in the C<template_paths> directories. Templates can be loaded
105into the cache with in the new() call or added later with the
106C<add_templates> method.
107
108=cut
109
110print "\n******************\nNamed Templates\n******************\n\n" ;
111
112$ts = Template::Simple->new(
113 templates => {
114 foo => <<FOO,
115We have some foo text here with [% data %]
116FOO
117 }
118) ;
119
120my $foo_template = $ts->render( 'foo', { data => 'lots of foo' } ) ;
121
122$ts->add_templates( { bar => <<BAR } ) ;
123We have some bar text here with [% data %]
124BAR
125
126my $bar_template = $ts->render( 'bar', { data => 'some bar' } ) ;
127
128print "FOO TEMPLATE: [${$foo_template}]\n" ;
129print "BAR TEMPLATE: [${$bar_template}]\n" ;
130
131=head1 Include Expansion
132
133You can build up templates by including other templates. This allows a
134template to be reused and shared by other templates. What makes this
135even better, is that by passing different data to the included
136templates in different renderings, you can get different results. If
137the logic was embedded in the template you can't change the rendering
138as easily. You include a template by using the C<[%include name%]>
139markup. The name is used to locate a template by name and its text
140replaces the markup. This example shows a single include in the top
141level template.
142
143=cut
144
145print "\n******************\nInclude Expansion\n******************\n\n" ;
146
147$ts = Template::Simple->new(
148 templates => {
149 top => <<TOP,
150This top level template includes this <<[% include other %]>>text
151TOP
152 other => <<OTHER,
153This is the included text
154OTHER
155} ) ;
156
157my $include_template = $ts->render( 'top', {} ) ;
158
159print "INCLUDE TEMPLATE: [${$include_template}]\n" ;
160
161=head1 Template Paths
162
163You can search for templates in files with the C<search_dirs> option
164to the constructor. If a named template is not found in the object
165cache it will be searched for in the directories listed in the
166C<search_dirs> option. If it is found there, it will be loaded into
167the object cache so future uses of it by name will be faster. The
168default value of C<search_dirs> option is C<templates>. Templates must
169have a suffix of C<.tmpl>. This example makes a directory called
170'templates' and a template file named C<foo.tmpl>. The second example
171makes a directory called C<cookbook> and puts a template in there and
172sets. Note that the option value must be an array reference.
173
174=cut
175
176print "\n******************\nSearch Dirs\n******************\n\n" ;
177
178my $tmpl_dir = 'templates' ;
179mkdir $tmpl_dir ;
180write_file( "$tmpl_dir/foo.tmpl", <<FOO ) ;
181This template was loaded from the dir [%dir%]
182FOO
183
184$ts = Template::Simple->new() ;
185my $foo_file_template = $ts->render( 'foo', { dir => 'templates' } ) ;
186
187print "FOO FILE TEMPLATE: [${$foo_file_template}]\n" ;
188
189unlink "$tmpl_dir/foo.tmpl" ;
190rmdir $tmpl_dir ;
191
192######
193
194my $cook_dir = 'cookbook' ;
195mkdir $cook_dir ;
196write_file( "$cook_dir/bar.tmpl", <<BAR ) ;
197This template was loaded from the $cook_dir [%dir%]
198BAR
199
200$ts = Template::Simple->new( search_dirs => [$cook_dir] ) ;
201my $bar_file_template = $ts->render( 'bar', { dir => 'directory' } ) ;
202
203print "BAR FILE TEMPLATE: [${$bar_file_template}]\n" ;
204
205unlink "$cook_dir/bar.tmpl" ;
206rmdir $cook_dir ;
207
208=head1 Named Chunk Expansion
209
210The core markup in T::S is called a chunk. It is delimited by paired
211C<start> and C<end> markups and the text in between them is the
212chunk. Any chunk can have multiple chunks inside it and they are named
213for the name in the C<start> and C<end> markups. That name is used to
214match the chunk with the data passed to render. This example uses the
215top level template (which is always an unnamed chunk) which contains a
216nested chunk which has a name. The data passed in is a hash reference
217which has a key with the chunk name and its value is another hash
218reference. So the nested chunk match up to the nested hashes.
219
220=cut
221
222print "\n******************\nNested Chunk Expansion\n******************\n\n" ;
223
224$ts = Template::Simple->new(
225 templates => {
226 top => <<TOP,
227This top level template includes this <<[% include nested %]>> chunk
228TOP
229 nested => <<NESTED,
230[%START nested %]This included template just has a [% token %] and another [% one %][%END nested %]
231NESTED
232 }
233) ;
234
235my $nested_template = $ts->render( 'top',
236 {
237 nested => {
238 token => 'nested value',
239 one => 'value from the data',
240 }
241} ) ;
242
243print "NESTED TEMPLATE: [${$nested_template}]\n" ;
244
245=head2 Boolean Chunk
246
247The simplest template decision is when you want to show some text or
248nothing. This is done with an empty hash reference or a null string
249value in the data tree. The empty hash reference will cause the text
250to be kept as is with all markups removed (replaced by the null
251string). A null string (or a reference to one) will cause the text
252chunk to be deleted.
253
254=cut
255
256print "\n******************\nBoolean Text\n******************\n\n" ;
257
258$template = \<<TMPL ;
259[% START boolean %]This is text to be left or deleted[% END boolean %]
260TMPL
261
262my $boolean_kept = $ts->render( $template, { boolean => {} } ) ;
263my $deleted = $ts->render( $template, { default => \'' } ) ;
264
265print "KEPT: [${$boolean_kept}]\n" ;
266print "DELETED: [${$deleted}]\n" ;
267
268=head2 Default vs. Overwrite Text
269
270The next step up from boolean text is overwriting a default text with
271another when rendering. This is done with an empty hash reference or a
272scalar value for the chunk in the data tree. The empty hash reference
273will cause the default text to be kept as is with all markups removed
274(replaced by the null string). A scalar value (or a scalar reference)
275will cause the complete text chunk to be replaced by that value.
276
277=cut
278
279print "\n******************\nDefault vs. Overwrite Text\n******************\n\n" ;
280
281$template = \<<TMPL ;
282[% START default %]This is text to be left or replaced[% END default %]
283TMPL
284
285my $default_kept = $ts->render( $template, { default => {} } ) ;
286my $overwrite = $ts->render( $template, { default => \<<OVER } ) ;
287This text will overwrite the default text
288OVER
289
290print "DEFAULT: [${$default_kept}]\n" ;
291print "OVERWRITE: [${$overwrite}]\n" ;
292
293=head2 Conditional Text
294
295Instead of having the overwrite text in the data tree, it is useful to
296have it in the template itself. This is a conditional where one text
297or the other is rendered. This is done by wrapping each text in its
298own chunk with unique names. The data tree can show either one by
299passing an empty hash reference for that data and a null string for
300the other one. Also you can just not have a value for the text not to
301be rendered and that will also delete it. Both styles are shown here.
302
303=cut
304
305print "\n******************\nConditional Text\n******************\n\n" ;
306
307$template = \<<TMPL ;
308[% START yes_text %]This text shown when yes[% END yes_text %]
309[% START no_text %]This text shown when no[% END no_text %]
310TMPL
311
312my $yes_shown = $ts->render( $template, { yes_text => {} } ) ;
313my $no_shown = $ts->render( $template, {
314 yes_text => '',
315 no_text => {}
316} ) ;
317
318print "YES: [${$yes_shown}]\n" ;
319print "NO: [${$no_shown}]\n" ;
320
321=head1 List Chunk Expansion
322
323T::S has no list markup because of the unique way it handles data
324during rendering. When an array reference is matched to a chunk, the
325array is iterated and the chunk is then rendered with each element of
326the array. This list of rendered texts is concatenated and replaces
327the original chunk in the template. The data and the logic that
328creates the data controls when a template chunk is repeated. This
329example shows the top level (unnamed) template being rendered with an
330array of hashes. Each hash renders the chunk one time. Note that the
331different results you get based on the different hashes in the array.
332
333=cut
334
335print "\n******************\nList Chunk Expansion\n******************\n\n" ;
336
337$ts = Template::Simple->new(
338 templates => {
339 top_array => <<TOP_ARRAY,
340
341This is the [%count%] chunk.
342[%start maybe%]This line may be shown[%end maybe%]
343This is the end of the chunk line
344TOP_ARRAY
345} ) ;
346
347my $top_array = $ts->render( 'top_array', [
348 {
349 count => 'first',
350 maybe => {},
351 },
352 {
353 count => 'second',
354 },
355 {
356 count => 'third',
357 maybe => {},
358 },
359] ) ;
360
361print "TOP_ARRAY: [${$top_array}]\n" ;
362
363
364=head1 Separated List Expansion
365
366A majorly used variation of data lists are list with a separator but
367not one after the last element. This can be done easily with T::S and
368here are two techniques. The first one uses a token for the separator
369in the chunk and passes in a hash with the delimiter string set in all
370but the last element. This requires the code logic to know and set the
371delimiter. The other solution lets the template set the delimiter by
372enclosing it in a chunk of its own and passing an empty hash ref for
373the places to keep it and nothing for the last element. Both examples
374use the same sub to do this work for you and all you need to do is
375pass it the token for the main value and the seperator key and
376optionally its value. You can easily make a variation that puts the
377separator before the element and delete it from the first element. If
378your chunk has more tokens or nested chunks, this sub could be
379generalized to modify a list of hashes instead of generating one.
380
381=cut
382
383print "\n******************\nSeparated List Expansion\n******************\n\n" ;
384
385
386sub make_separated_data {
387 my( $token, $data, $delim_key, $delim ) = @_ ;
388
389# make the delim set from the template (in a chunk) if not passed in
390# an empty hash ref keeps the chunk text as is.
391
392 $delim ||= {} ;
393
394 my @list = map +{ $token => $_, $delim_key => $delim, }, @{$data} ;
395
396# remove the separator from the last element
397
398 delete $list[-1]{$delim_key} ;
399
400 return \@list ;
401}
402
403my @data = qw( one two three four ) ;
404
405$ts = Template::Simple->new(
406 templates => {
407 sep_tmpl => <<SEP_TMPL,
408Number [%count%][%sep%]
409SEP_TMPL
410 sep_data => <<SEP_DATA,
411Number [%count%][%start sep%],[%end sep%]
412SEP_DATA
413} ) ;
414
415my $sep_tmpl = $ts->render( 'sep_tmpl',
416 make_separated_data( 'count', \@data, 'sep', '--' ) ) ;
417
418my $sep_data = $ts->render( 'sep_data',
419 make_separated_data( 'count', \@data, 'sep', {} ) ) ;
420
421print "SEP_DATA: [${$sep_data}]\n" ;
422print "SEP_DATA: [${$sep_data}]\n" ;
423
424exit ;