File Coverage

blib/lib/Template/Simple.pm
Criterion Covered Total %
statement 192 196 97.9
branch 43 54 79.6
condition 6 7 85.7
subroutine 33 34 97.0
pod 6 6 100.0
total 280 297 94.2


line stmt bran cond sub pod time code
1             package Template::Simple ;
2              
3 8     8   201281 use warnings ;
  8         19  
  8         299  
4 8     8   48 use strict ;
  8         15  
  8         570  
5              
6 8     8   48 use Carp ;
  8         19  
  8         720  
7 8     8   7744 use Data::Dumper ;
  8         83332  
  8         818  
8 8     8   79 use Scalar::Util qw( reftype blessed ) ;
  8         15  
  8         847  
9 8     8   7493 use File::Slurp ;
  8         163576  
  8         20593  
10              
11             our $VERSION = '0.06';
12              
13             my %opt_defaults = (
14              
15             pre_delim => qr/\[%/,
16             post_delim => qr/%\]/,
17             token_re => qr/\w+?/,
18             greedy_chunk => 0,
19             # upper_case => 0,
20             # lower_case => 0,
21             search_dirs => [ qw( templates ) ],
22             ) ;
23              
24             sub new {
25              
26 57     57 1 30487 my( $class, %opts ) = @_ ;
27              
28 57         160 my $self = bless {}, $class ;
29              
30             # get all the options or defaults into the object
31              
32             # support the old name 'include_paths' ;
33              
34 57   100     290 $opts{search_dirs} ||= delete $opts{include_paths} ;
35              
36 57         545 while( my( $name, $default ) = each %opt_defaults ) {
37              
38 285 100       1444 $self->{$name} = defined( $opts{$name} ) ?
39             $opts{$name} : $default ;
40             }
41              
42 57 50       186 croak "search_dirs is not an ARRAY reference" unless
43             ref $self->{search_dirs} eq 'ARRAY' ;
44              
45             # make up the regexes to parse the markup from templates
46              
47             # this matches scalar markups and grabs the name
48              
49 57         1209 $self->{scalar_re} = qr{
50             $self->{pre_delim}
51             \s* # optional leading whitespace
52             ($self->{token_re}) # grab scalar name
53             \s* # optional trailing whitespace
54             $self->{post_delim}
55             }xi ; # case insensitive
56              
57             #print "RE <$self->{scalar_re}>\n" ;
58              
59             # this grabs the body of a chunk in either greedy or non-greedy modes
60              
61 57 100       424 my $chunk_body = $self->{greedy_chunk} ? qr/.+/s : qr/.+?/s ;
62              
63             # this matches a marked chunk and grabs its name and text body
64              
65 57         1908 $self->{chunk_re} = qr{
66             $self->{pre_delim}
67             \s* # optional leading whitespace
68             START # required START token
69             \s+ # required whitespace
70             ($self->{token_re}) # grab the chunk name
71             \s* # optional trailing whitespace
72             $self->{post_delim}
73             ($chunk_body) # grab the chunk body
74             $self->{pre_delim}
75             \s* # optional leading whitespace
76             END # required END token
77             \s+ # required whitespace
78             \1 # match the grabbed chunk name
79             \s* # optional trailing whitespace
80             $self->{post_delim}
81             }xi ; # case insensitive
82              
83             #print "RE <$self->{chunk_re}>\n" ;
84              
85             # this matches a include markup and grabs its template name
86              
87 57         1017 $self->{include_re} = qr{
88             $self->{pre_delim}
89             \s* # optional leading whitespace
90             INCLUDE # required INCLUDE token
91             \s+ # required whitespace
92             ($self->{token_re}) # grab the included template name
93             \s* # optional trailing whitespace
94             $self->{post_delim}
95             }xi ; # case insensitive
96              
97             # load in any templates
98              
99 57         221 $self->add_templates( $opts{templates} ) ;
100              
101 57         276 return $self ;
102             }
103              
104             sub compile {
105              
106 28     28 1 217 my( $self, $template_name ) = @_ ;
107              
108 28         37 my $tmpl_ref = eval {
109 28         55 $self->_get_template( $template_name ) ;
110             } ;
111              
112             #print Dumper $self ;
113              
114 28 50       76 croak "Template::Simple $@" if $@ ;
115              
116 28         56 my $included = $self->_render_includes( $tmpl_ref ) ;
117              
118             # compile a copy of the template as it will be destroyed
119              
120 26         38 my $code_body = $self->_compile_chunk( '', "${$included}", "\t" ) ;
  26         120  
121              
122 26         95 my $source = <
123             no warnings ;
124              
125             sub {
126             my( \$data ) = \@_ ;
127              
128             my \$out ;
129              
130             use Scalar::Util qw( reftype ) ;
131              
132             $code_body
133             return \\\$out ;
134             }
135             CODE
136              
137             #print $source ;
138              
139 26     5   2092 my $code_ref = eval $source ;
  5     5   41  
  5     5   9  
  5     5   446  
  5     5   25  
  5     5   8  
  5     4   1027  
  5     4   31  
  5     2   7  
  5     2   306  
  5     2   24  
  5     2   8  
  5         1102  
  5         24  
  5         8  
  5         287  
  5         22  
  5         9  
  5         918  
  4         25  
  4         8  
  4         264  
  4         22  
  4         8  
  4         847  
  2         11  
  2         3  
  2         131  
  2         10  
  2         10  
  2         499  
  2         11  
  2         4  
  2         117  
  2         11  
  2         4  
  2         502  
140              
141             #print $@ if $@ ;
142              
143 26         183 $self->{compiled_cache}{$template_name} = $code_ref ;
144 26         133 $self->{source_cache}{$template_name} = $source ;
145             }
146              
147             sub _compile_chunk {
148              
149 43     43   85 my( $self, $chunk_name, $template, $indent ) = @_ ;
150              
151 43 50       95 return '' unless length $template ;
152              
153             # generate a lookup in data for this chunk name (unless it is the top
154             # level). this descends down the data tree during rendering
155              
156 43 100       94 my $data_init = $chunk_name ? "\$data->{$chunk_name}" : '$data' ;
157              
158 43         201 my $code = <
159             ${indent}my \@data = $data_init ;
160             ${indent}while( \@data ) {
161              
162             ${indent} my \$data = shift \@data ;
163             ${indent} if ( reftype \$data eq 'ARRAY' ) {
164             ${indent} push \@data, \@{\$data} ;
165             ${indent} next ;
166             ${indent} }
167              
168             CODE
169              
170 43         59 $indent .= "\t" ;
171              
172             # loop all nested chunks and the text separating them
173              
174 43         303 while( my( $parsed_name, $parsed_body ) =
175             $template =~ m{$self->{chunk_re}} ) {
176              
177 17         42 my $chunk_left_index = $-[0] ;
178 17         40 my $chunk_right_index = $+[0] ;
179              
180             # get the pre-match text and compile its scalars and text. append to the code
181              
182 17         50 $code .= $self->_compile_scalars(
183             substr( $template, 0, $chunk_left_index ), $indent ) ;
184              
185             # print "CHUNK: [$1] BODY [$2]\n\n" ;
186             # print "TRUNC: [", substr( $template, 0, $chunk_right_index ), "]\n\n" ;
187             # print "PRE: [", substr( $template, 0, $chunk_left_index ), "]\n\n" ;
188              
189             # chop off the pre-match and the chunk
190              
191 17         43 substr( $template, 0, $chunk_right_index, '' ) ;
192              
193             # print "REMAIN: [$template]\n\n" ;
194              
195             # compile the nested chunk and append to the code
196              
197 17         52 $code .= $self->_compile_chunk(
198             $parsed_name, $parsed_body, $indent
199             ) ;
200             }
201              
202             # compile trailing text for scalars and append to the code
203              
204 43         95 $code .= $self->_compile_scalars( $template, $indent ) ;
205              
206 43         74 chop $indent ;
207              
208             # now we end the loop for this chunk
209 43         59 $code .= <
210             $indent}
211             CODE
212              
213 43         207 return $code ;
214             }
215              
216             sub _compile_scalars {
217              
218 60     60   108 my( $self, $template, $indent ) = @_ ;
219              
220             # if the template is empty return no parts
221              
222 60 100       116 return '' unless length $template ;
223              
224 53         65 my @parts ;
225              
226 53         281 while( $template =~ m{$self->{scalar_re}}g ) {
227              
228             # get the pre-match text before the scalar markup and generate code to
229             # access the scalar
230              
231 34         113 push( @parts,
232             _dump_text( substr( $template, 0, $-[0] ) ),
233             "\$data->{$1}"
234             ) ;
235              
236             # truncate the matched text so the next match starts at begining of string
237              
238 34         284 substr( $template, 0, $+[0], '' ) ;
239             }
240              
241             # keep any trailing text part
242              
243 53         93 push @parts, _dump_text( $template ) ;
244              
245 53         139 my $parts_code = join( "\n$indent.\n$indent", @parts ) ;
246              
247 53         197 return <
248              
249             ${indent}\$out .= reftype \$data ne 'HASH' ? \$data :
250             ${indent}$parts_code ;
251              
252             CODE
253             }
254              
255              
256             # internal sub to dump text for the template compiler. the output is
257             # a legal perl double quoted string without any leading text before
258             # the opening " and no trailing newline or ;
259              
260             sub _dump_text {
261              
262 87     87   143 my( $text ) = @_ ;
263              
264 87 100       190 return unless length $text ;
265              
266 71         116 local( $Data::Dumper::Useqq ) = 1 ;
267              
268 71         192 my $dumped = Dumper $text ;
269              
270 71         3775 $dumped =~ s/^[^"]+// ;
271 71         225 $dumped =~ s/;\n$// ;
272              
273 71         201 return $dumped ;
274             }
275              
276             sub get_source {
277              
278 0     0 1 0 my( $self, $template_name ) = @_ ;
279              
280 0         0 return $self->{source_cache}{$template_name} ;
281             }
282              
283             sub render {
284              
285 57     57 1 739 my( $self, $template_name, $data ) = @_ ;
286              
287 57 50       147 my $tmpl_ref = ref $template_name eq 'SCALAR' ? $template_name : '' ;
288              
289 57 50       140 unless( $tmpl_ref ) {
290              
291             # render with cached code and return if we precompiled this template
292              
293 57 50       169 if ( my $compiled = $self->{compiled_cache}{$template_name} ) {
294              
295 0         0 return $compiled->($data) ;
296             }
297              
298             # not compiled so try to get this template by name or
299             # assume the template name are is the actual template
300              
301             $tmpl_ref =
302 57   50     78 eval{ $self->_get_template( $template_name ) } ||
303             \$template_name ;
304             }
305              
306 57         4377 my $rendered = $self->_render_includes( $tmpl_ref ) ;
307              
308             #print "INC EXP <$rendered>\n" ;
309              
310 55         76 $rendered = eval {
311 55         133 $self->_render_chunk( $rendered, $data ) ;
312             } ;
313              
314 55 100       402 croak "Template::Simple $@" if $@ ;
315              
316 53         141 return $rendered ;
317             }
318              
319             sub _render_includes {
320              
321 85     85   120 my( $self, $tmpl_ref ) = @_ ;
322              
323             # make a copy of the initial template so we can render it.
324              
325 85         94 my $rendered = ${$tmpl_ref} ;
  85         148  
326              
327             # loop until we can render no more include markups
328              
329 85         758 1 while $rendered =~
330 40         40 s{$self->{include_re}}{ ${ $self->_get_template($1) }}e ;
  40         70  
331              
332 81         159 return \$rendered ;
333             }
334              
335             my %renderers = (
336              
337             SCALAR => sub { return $_[2] },
338             '' => sub { return \$_[2] },
339             HASH => \&_render_hash,
340             ARRAY => \&_render_array,
341             CODE => \&_render_code,
342             # if no ref then data is a scalar so replace the template with just the data
343             ) ;
344              
345              
346             sub _render_chunk {
347              
348 127     127   189 my( $self, $tmpl_ref, $data ) = @_ ;
349              
350             #print "T ref [$tmpl_ref] [$$tmpl_ref]\n" ;
351             #print "CHUNK ref [$tmpl_ref] TMPL\n<$$tmpl_ref>\n" ;
352              
353             #print Dumper $data ;
354              
355 127 50       279 return \'' unless defined $data ;
356              
357             # get the type of this data. handle blessed types
358              
359 127         251 my $reftype = blessed( $data ) ;
360              
361             #print "REF $reftype\n" ;
362              
363             # handle the case of a qr// which blessed returns as Regexp
364              
365 127 100       208 if ( $reftype ) {
366              
367 3 100       15 $reftype = reftype $data unless $reftype eq 'Regexp' ;
368             }
369             else {
370 124         195 $reftype = ref $data ;
371             }
372              
373             #print "REF2 $reftype\n" ;
374              
375             # now render this chunk based on the type of data
376              
377 127   100     316 my $renderer = $renderers{ $reftype || ''} ;
378              
379             #print "EXP $renderer\nREF $reftype\n" ;
380              
381 127 100       340 croak "unknown template data type '$data'\n" unless defined $renderer ;
382              
383 126         245 return $self->$renderer( $tmpl_ref, $data ) ;
384             }
385              
386             sub _render_hash {
387              
388 92     92   122 my( $self, $tmpl_ref, $href ) = @_ ;
389              
390 92 100       91 return $tmpl_ref unless keys %{$href} ;
  92         248  
391              
392             # we need a local copy of the template to render
393              
394 80         93 my $rendered = ${$tmpl_ref} ;
  80         130  
395              
396             # recursively render all top level chunks in this chunk
397              
398 80         457 $rendered =~ s{$self->{chunk_re}}
399             {
400             # print "CHUNK $1\nBODY\n----\n<$2>\n\n------\n" ;
401             # print "CHUNK $1\nBODY\n----\n<$2>\n\n------\n" ;
402             # print "pre CHUNK [$`]\n" ;
403 40         47 ${ $self->_render_chunk( \"$2", $href->{$1} ) }
  40         170  
404             }gex ;
405              
406             # now render scalars
407              
408             #print "HREF: ", Dumper $href ;
409              
410 80         457 $rendered =~ s{$self->{scalar_re}}
411             {
412             # print "SCALAR $1 VAL $href->{$1}\n" ;
413 102 100       555 defined $href->{$1} ? $href->{$1} : ''
414             }ge ;
415              
416             #print "HASH REND3\n<$rendered>\n" ;
417              
418 80         336 return \$rendered ;
419             }
420              
421             sub _render_array {
422              
423 16     16   26 my( $self, $tmpl_ref, $aref ) = @_ ;
424              
425             # render this $tmpl_ref for each element of the aref and join them
426              
427 16         19 my $rendered ;
428              
429             #print "AREF: ", Dumper $aref ;
430              
431 16         28 $rendered .= ${$self->_render_chunk( $tmpl_ref, $_ )} for @{$aref} ;
  16         43  
  32         65  
432              
433 16         70 return \$rendered ;
434             }
435              
436             sub _render_code {
437              
438 2     2   4 my( $self, $tmpl_ref, $cref ) = @_ ;
439              
440 2         9 my $rendered = $cref->( $tmpl_ref ) ;
441              
442 2 100       121 croak <
443             data callback to code didn't return a scalar or scalar reference
444             DIE
445              
446 1         3 return $rendered ;
447             }
448              
449             sub add_templates {
450              
451 85     85 1 724 my( $self, $tmpls ) = @_ ;
452              
453             #print Dumper $tmpls ;
454 85 100       250 return unless defined $tmpls ;
455              
456 34 50       92 ref $tmpls eq 'HASH' or croak "templates argument is not a hash ref" ;
457              
458             # copy all the templates from the arg hash and force the values to be
459             # scalar refs
460              
461 34         45 while( my( $name, $tmpl ) = each %{$tmpls} ) {
  74         237  
462              
463 40 50       82 defined $tmpl or croak "undefined template value for '$name'" ;
464              
465             # cache the a scalar ref of the template
466              
467 40 50       209 $self->{tmpl_cache}{$name} = ref $tmpl eq 'SCALAR' ?
468 0         0 \"${$tmpl}" : \"$tmpl"
469             }
470              
471             #print Dumper $self->{tmpl_cache} ;
472              
473 34         65 return ;
474             }
475              
476             sub delete_templates {
477              
478 2     2 1 605 my( $self, @names ) = @_ ;
479              
480             # delete all the cached stuff or just the names passed in
481              
482 2 50       7 @names = keys %{$self->{tmpl_cache}} unless @names ;
  2         9  
483              
484             #print "NAMES @names\n" ;
485             # clear out all the caches
486             # TODO: reorg these into a hash per name
487              
488 2         3 delete @{$self->{tmpl_cache}}{ @names } ;
  2         6  
489 2         3 delete @{$self->{compiled_cache}}{ @names } ;
  2         8  
490 2         4 delete @{$self->{source_cache}}{ @names } ;
  2         4  
491              
492             # also remove where we found it to force a fresh search
493              
494 2         2 delete @{$self->{template_paths}}{ @names } ;
  2         5  
495              
496 2         7 return ;
497             }
498              
499             sub _get_template {
500              
501 125     125   193 my( $self, $tmpl_name ) = @_ ;
502              
503             #print "INC $tmpl_name\n" ;
504              
505 125         199 my $tmpls = $self->{tmpl_cache} ;
506              
507             # get the template from the cache and send it back if it was found there
508              
509 125         180 my $template = $tmpls->{ $tmpl_name } ;
510 125 100       441 return $template if $template ;
511              
512             # not found, so find, slurp in and cache the template
513              
514 73         150 $template = $self->_find_template( $tmpl_name ) ;
515 12         1399 $tmpls->{ $tmpl_name } = $template ;
516              
517 12         131 return $template ;
518             }
519              
520             sub _find_template {
521              
522 73     73   106 my( $self, $tmpl_name ) = @_ ;
523              
524             #print "FIND $tmpl_name\n" ;
525 73         75 foreach my $dir ( @{$self->{search_dirs}} ) {
  73         182  
526              
527 91         205 my $tmpl_path = "$dir/$tmpl_name.tmpl" ;
528              
529             #print "PATH: $tmpl_path\n" ;
530              
531 91 100       326 next if $tmpl_path =~ /\n/ ;
532 49 100       680 next unless -r $tmpl_path ;
533              
534             # cache the path to this template
535              
536 12         36 $self->{template_paths}{$tmpl_name} = $tmpl_path ;
537              
538             # slurp in the template file and return it as a scalar ref
539              
540             #print "FOUND $tmpl_name\n" ;
541              
542 12         43 return read_file( $tmpl_path, scalar_ref => 1 ) ;
543             }
544              
545             #print "CAN'T FIND $tmpl_name\n" ;
546              
547 61         143 croak <
548 61         8262 can't find template '$tmpl_name' in '@{$self->{search_dirs}}'
549             DIE
550              
551             }
552              
553             1; # End of Template::Simple
554              
555             __END__