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