File Coverage

blib/lib/Tenjin/Template.pm
Criterion Covered Total %
statement 116 151 76.8
branch 50 80 62.5
condition 4 14 28.5
subroutine 24 27 88.8
pod 20 20 100.0
total 214 292 73.2


line stmt bran cond sub pod time code
1             package Tenjin::Template;
2              
3 8     8   29 use strict;
  8         11  
  8         193  
4 8     8   28 use warnings;
  8         9  
  8         239  
5 8     8   37 use Fcntl qw/:flock/;
  8         6  
  8         1037  
6 8     8   35 use Carp;
  8         15  
  8         15039  
7              
8             our $VERSION = "1.000000";
9             $VERSION = eval $VERSION;
10              
11             =head1 NAME
12              
13             Tenjin::Template - A Tenjin template object, either built from a file or from memory.
14              
15             =head1 SYNOPSIS
16              
17             # mostly used internally, but you can manipulate
18             # templates like so
19              
20             my $template = Tenjin::Template->new('/path/to/templates/template.html');
21             my $context = { scalar => 'scalar', arrayref => ['one', 2, "3"] };
22             $template->render($context);
23              
24             =head1 DESCRIPTION
25              
26             This module is in charge of the task of compiling Tenjin templates.
27             Templates in Tenjin are compiled into standard Perl code (combined with
28             any Perl code used inside the templates themselves). Rendering a template
29             means Cuating that Perl code and returning its output.
30              
31             The Tenjin engine reads a template file or a template string, and creates
32             a Template object from it. Then the object compiles itself by traversing
33             the template, parsing Tenjin macros like 'include' and 'start_capture',
34             replaces Tenjin expressions (i.e. C<[== $expr =]> or C<[= $expr =]>) with the
35             appropriate Perl code, etc. This module ties a template object with
36             a context object, but all context manipulation (and the actual Cuation
37             of the Perl code) is done by L.
38              
39             If you're planning on using this module by itself (i.e. without the L
40             engine), keep in mind that template caching and layout templates are not
41             handled by this module.
42              
43             =cut
44              
45             our $MACRO_HANDLER_TABLE = {
46             'include' => sub { my $arg = shift;
47             " \$_buf .= \$_context->{'_engine'}->render($arg, \$_context, 0);";
48             },
49             'start_capture' => sub { my $arg = shift;
50             " my \$_buf_bkup=\$_buf; \$_buf=''; my \$_capture_varname=$arg;";
51             },
52             'stop_capture' => sub { my $arg = shift;
53             " \$_context->{\$_capture_varname}=\$_buf; \$_buf=\$_buf_bkup;";
54             },
55             'start_placeholder' => sub { my $arg = shift;
56             " if (\$_context->{$arg}) { \$_buf .= \$_context->{$arg}; } else {";
57             },
58             'stop_placeholder' => sub { my $arg = shift;
59             " }";
60             },
61             'echo' => sub { my $arg = shift;
62             " \$_buf .= $arg;";
63             },
64             };
65              
66             =head1 METHODS
67              
68             =head2 new( [$filename, \%opts] )
69              
70             Creates a new Tenjin::Template object, possibly from a file on the file
71             system (in which case C<$filename> must be provided and be an absolute
72             path to a template file). Optionally, a hash-ref of options can be
73             passed to set some customizations. Available options are 'escapefunc',
74             which will be in charge of escaping expressions (from C<[= $expr =]>) instead
75             of the internal method (which uses L); and 'rawclass',
76             which can be used to prevent variables and objects of a certain class
77             from being escaped, in which case the variable must be a hash-ref
78             that has a key named 'str', which will be used instead. So, for example,
79             if you have a variable named C<$var> which is a hash-ref, and 'rawclass'
80             is set as 'HASH', then writing C<[= $var =]> on your templates will replace
81             C<$var> with C<< $var->{str} >>.
82              
83             =cut
84              
85             sub new {
86 18     18 1 32 my ($class, $filename, $template_name, $opts) = @_;
87              
88 18 50 33     86 my $escapefunc = defined($opts) && exists($opts->{escapefunc}) ? $opts->{escapefunc} : undef;
89 18 50 33     69 my $rawclass = defined($opts) && exists($opts->{rawclass}) ? $opts->{rawclass} : undef;
90              
91 18         91 my $self = bless {
92             'filename' => $filename,
93             'name' => $template_name,
94             'script' => undef,
95             'escapefunc' => $escapefunc,
96             'rawclass' => $rawclass,
97             'timestamp' => undef,
98             'args' => undef,
99             }, $class;
100            
101 18 50       44 $self->convert_file($filename) if $filename;
102              
103 18         43 return $self;
104             }
105              
106             =head2 render( [$_context] )
107              
108             Renders the template, possibly with a context hash-ref, and returns the
109             rendered output. If errors have occured when rendering the template (which
110             might happen since templates have and are Perl code), then this method
111             will croak.
112              
113             =cut
114              
115             sub render {
116 23     23 1 27 my ($self, $_context) = @_;
117              
118 23   50     46 $_context ||= {};
119              
120 23 50       55 if ($self->{func}) {
121 0         0 return $self->{func}->($_context);
122             } else {
123 23 100       141 $_context = $Tenjin::CONTEXT_CLASS->new($_context) if ref $_context eq 'HASH';
124              
125 23         40 my $script = $self->{script};
126 23 50       93 $script = $_context->_build_decl() . $script unless $self->{args};
127            
128             # rendering is actually done inside the context object
129             # with the evaluate method. We pass either the name of
130             # the template or the filename of the template for debug
131             # purposes
132            
133 23   33     105 return $_context->evaluate($script, $self->{filename} || $self->{name});
134             }
135             }
136              
137             =head1 INTERNAL METHODS
138              
139             =head2 convert_file( $filename )
140              
141             Receives an absolute path to a template file, converts that file
142             to Perl code by calling L and
143             returns that code.
144              
145             =cut
146              
147             sub convert_file {
148 0     0 1 0 my ($self, $filename) = @_;
149              
150 0         0 return $self->convert($self->_read_file($filename, 1), $filename);
151             }
152              
153             =head2 convert( $input, [$filename] )
154              
155             Receives a text of a template (i.e. the template itself) and possibly
156             an absolute path to the template file (if the template comes from a file),
157             and converts the template into Perl code, which is later Cuated
158             for rendering. Conversion is done by parsing the statements in the
159             template (see L).
160              
161             =cut
162              
163             sub convert {
164 18     18 1 54 my ($self, $input, $filename) = @_;
165              
166 18         55 $self->{filename} = $filename;
167 18         36 my @buf = ('my $_buf = ""; my $_V; ', );
168 18         48 $self->parse_stmt(\@buf, $input);
169              
170 18         209 return $self->{script} = $buf[0] . " \$_buf;\n";
171             }
172              
173             =head2 compile_stmt_pattern( $pl )
174              
175             Receives a string which denotes the Perl code delimiter which is used
176             inside templates. Tenjin uses 'C<< >>' and 'C<< >>'
177             (the latter for preprocessing), so C<$pl> will be 'pl'. This method
178             returns a tranlsation regular expression which will be used for reading
179             embedded Perl code.
180              
181             =cut
182              
183             sub compile_stmt_pattern {
184 18     18 1 23 my $pl = shift;
185              
186 18         36 my $pat = '((^[ \t]*)?<\?'.$pl.'( |\t|\r?\n)(.*?) ?\?>([ \t]*\r?\n)?)';
187 18         321 return qr/$pat/sm;
188             }
189              
190             =head2 stmt_pattern
191              
192             Returns the default pattern (which uses 'pl') with the
193             L.
194              
195             =cut
196              
197             sub stmt_pattern {
198 18     18 1 39 return compile_stmt_pattern('pl');
199             }
200              
201             =head2 expr_pattern()
202              
203             Defines how expressions are written in Tenjin templates (C<[== $expr =]>
204             and C<[= $expr =]>).
205              
206             =cut
207              
208             sub expr_pattern {
209 23     23 1 52 return qr/\[=(=?)(.*?)(=?)=\]/s;
210             }
211              
212             =head2 parse_stmt( $bufref, $input )
213              
214             Receives a buffer which is used for saving a template's expressions
215             and the template's text, parses all expressions in the templates and
216             pushes them to the buffer.
217              
218             =cut
219              
220             sub parse_stmt {
221 18     18 1 35 my ($self, $bufref, $input) = @_;
222              
223 18         32 my $pos = 0;
224 18         40 my $pat = $self->stmt_pattern();
225 18         159 while ($input =~ /$pat/g) {
226 14         42 my ($pi, $lspace, $mspace, $stmt, $rspace) = ($1, $2, $3, $4, $5);
227 14         37 my $start = $-[0];
228 14         50 my $text = substr($input, $pos, $start - $pos);
229 14         21 $pos = $start + length($pi);
230 14 100       34 $self->parse_expr($bufref, $text) if $text;
231 14 100       35 $mspace = '' if $mspace eq ' ';
232 14         25 $stmt = $self->hook_stmt($stmt);
233 14 100       31 $stmt .= $rspace if $rspace;
234 14 100       25 $stmt = $mspace . $stmt if $mspace;
235 14 50       20 $stmt = $lspace . $stmt if $lspace;
236 14         23 $self->add_stmt($bufref, $stmt);
237             }
238 18 100       63 my $rest = $pos == 0 ? $input : substr($input, $pos);
239 18 100       85 $self->parse_expr($bufref, $rest) if $rest;
240             }
241              
242             =head2 hook_stmt( $stmt )
243              
244             =cut
245              
246             sub hook_stmt {
247 14     14 1 17 my ($self, $stmt) = @_;
248              
249             ## macro expantion
250 14 100       45 if ($stmt =~ /\A(\s*)(\w+)\((.*?)\);?(\s*)\Z/) {
251 7         16 my ($lspace, $funcname, $arg, $rspace) = ($1, $2, $3, $4);
252 7         14 my $s = $self->expand_macro($funcname, $arg);
253 7 100       24 return $lspace . $s . $rspace if defined($s);
254             }
255              
256             ## template arguments
257 8 50       23 unless ($self->{args}) {
258 8 50       24 if ($stmt =~ m/\A(\s*)\#\@ARGS\s+(.*)(\s*)\Z/) {
259 0         0 my ($lspace, $argstr, $rspace) = ($1, $2, $3);
260 0         0 my @args = ();
261 0         0 my @declares = ();
262 0         0 foreach my $arg (split(/,/, $argstr)) {
263 0         0 $arg =~ s/(^\s+|\s+$)//g;
264 0 0       0 next unless $arg;
265 0 0       0 $arg =~ m/\A([\$\@\%])?([a-zA-Z_]\w*)\Z/ or croak "[Tenjin] $arg: invalid template argument.";
266 0 0 0     0 croak "[Tenjin] $arg: only '\$var' is available for template argument." unless (!$1 || $1 eq '$');
267 0         0 my $name = $2;
268 0         0 push(@args, $name);
269 0         0 push(@declares, "my \$$name = \$_context->{$name}; ");
270             }
271 0         0 $self->{args} = \@args;
272 0         0 return $lspace . join('', @declares) . $rspace;
273             }
274             }
275              
276 8         13 return $stmt;
277             }
278              
279             =head2 expand_macro( $funcname, $arg )
280              
281             This method is in charge of invoking macro functions which might be used
282             inside templates. The following macros are available:
283              
284             =over
285              
286             =item * C
287              
288             Includes another template, whose name is C<$filename>, inside the current
289             template. The included template will be placed inside the template as if
290             they were one unit, so the context variable applies to both.
291              
292             =item * C and C
293              
294             Tells Tenjin to capture the output of the rendered template from the point
295             where C was called to the point where C
296             was called. You must provide a name for the captured portion, which will be
297             made available in the context as C<< $_context->{$name} >> for immediate
298             usage. Note that the captured portion will not be printed unless you do
299             so explicilty with C<< $_context->{$name} >>.
300              
301             =item * C and C
302              
303             This is a special method which can be used for making your templates a bit
304             cleaner. Suppose your context might have a variable whose name is defined
305             in C<$var>. If that variable exists in the context, you simply want to print
306             it, but if it's not, you want to print and/or perform other things. In that
307             case you can call C with the name of the context
308             variable you want printed, and if it's not, anything you do between
309             C and C will be printed instead.
310              
311             =item * echo( $exr )
312              
313             Just prints the provided expression. You might want to use it if you're
314             a little too comfortable with PHP.
315              
316             =back
317              
318             =cut
319              
320             sub expand_macro {
321 7     7 1 9 my ($self, $funcname, $arg) = @_;
322              
323 7         13 my $handler = $MACRO_HANDLER_TABLE->{$funcname};
324 7 100       16 return $handler ? $handler->($arg) : undef;
325             }
326              
327             =head2 get_expr_and_escapeflag( $not_escape, $expr, $delete_newline )
328              
329             =cut
330              
331             ## ex. get_expr_and_escapeflag('=', '$item->{name}', '') => 1, '$item->{name}', 0
332             sub get_expr_and_escapeflag {
333 17     17 1 60 my ($self, $not_escape, $expr, $delete_newline) = @_;
334              
335 17         48 return $expr, $not_escape eq '', $delete_newline eq '=';
336             }
337              
338             =head2 parse_expr( $bufref, $input )
339              
340             =cut
341              
342             sub parse_expr {
343 23     23 1 42 my ($self, $bufref, $input) = @_;
344              
345 23         28 my $pos = 0;
346 23         47 $self->start_text_part($bufref);
347 23         59 my $pat = $self->expr_pattern();
348 23         158 while ($input =~ /$pat/g) {
349 17         32 my $start = $-[0];
350 17         51 my $text = substr($input, $pos, $start - $pos);
351 17         35 my ($expr, $flag_escape, $delete_newline) = $self->get_expr_and_escapeflag($1, $2, $3);
352 17         35 $pos = $+[0];
353 17 100       52 $self->add_text($bufref, $text) if $text;
354 17 50       51 $self->add_expr($bufref, $expr, $flag_escape) if $expr;
355 17 100       95 if ($delete_newline) {
356 4         7 my $end = $+[0];
357 4 100       28 if (substr($input, $end, 1) eq "\n") {
358 1         2 $bufref->[0] .= "\n";
359 1         6 $pos++;
360             }
361             }
362             }
363 23 100       93 my $rest = $pos == 0 ? $input : substr($input, $pos);
364 23         45 $self->add_text($bufref, $rest);
365 23         40 $self->stop_text_part($bufref);
366             }
367              
368             =head2 start_text_part( $bufref )
369              
370             =cut
371              
372             sub start_text_part {
373 23     23 1 25 my ($self, $bufref) = @_;
374              
375 23         50 $bufref->[0] .= ' $_buf .= ';
376             }
377              
378             =head2 stop_text_part( $bufref )
379              
380             =cut
381              
382             sub stop_text_part {
383 23     23 1 28 my ($self, $bufref) = @_;
384              
385 23         100 $bufref->[0] .= '; ';
386             }
387              
388             =head2 add_text( $bufref, $text )
389              
390             =cut
391              
392             sub add_text {
393 34     34 1 53 my ($self, $bufref, $text) = @_;
394              
395 34 100       78 return unless $text;
396 30         54 $text =~ s/([`\\])/\\$1/g;
397 30         77 my $is_start = $bufref->[0] =~ / \$_buf \.= \Z/;
398 30 100       153 $bufref->[0] .= $is_start ? "q`$text`" : " . q`$text`";
399             }
400              
401             =head2 add_stmt( $bufref, $stmt )
402              
403             =cut
404              
405             sub add_stmt {
406 14     14 1 17 my ($self, $bufref, $stmt) = @_;
407              
408 14         120 $bufref->[0] .= $stmt;
409             }
410              
411             =head2 add_expr( $bufref, $expr, $flag_escape )
412              
413             =cut
414              
415             sub add_expr {
416 17     17 1 21 my ($self, $bufref, $expr, $flag_escape) = @_;
417              
418 17 100       55 my $dot = $bufref->[0] =~ / \$_buf \.= \Z/ ? '' : ' . ';
419 17 50       73 $bufref->[0] .= $dot . ($flag_escape ? $self->escaped_expr($expr) : "($expr)");
420             }
421              
422             =head2 defun( $funcname, @args )
423              
424             =cut
425              
426             sub defun { ## (experimental)
427 0     0 1 0 my ($self, $funcname, @args) = @_;
428              
429 0 0       0 unless ($funcname) {
430 0         0 my $funcname = $self->{filename};
431 0 0       0 if ($funcname) {
432 0         0 $funcname =~ s/\.\w+$//;
433 0         0 $funcname =~ s/[^\w]/_/g;
434             }
435 0         0 $funcname = 'render_' . $funcname;
436             }
437              
438 0         0 my $str = "sub $funcname { my (\$_context) = \@_; ";
439 0         0 foreach (@args) {
440 0         0 $str .= "my \$$_ = \$_context->{'$_'}; ";
441             }
442 0         0 $str .= $self->{script};
443 0         0 $str .= "}\n";
444              
445 0         0 return $str;
446             }
447              
448             =head2 compile()
449              
450             =cut
451              
452             ## compile $self->{script} into closure.
453             sub compile {
454 18     18 1 28 my $self = shift;
455              
456 18 50       50 if ($self->{args}) {
457 0         0 $self->{func} = $Tenjin::CONTEXT_CLASS->to_func($self->{script}, $self->{name});
458 0         0 return $self->{func};
459             }
460 18         31 return;
461             }
462              
463             =head2 escaped_expr( $expr )
464              
465             Receives a Perl expression (from C<[= $expr =]>) and escapes it. This will
466             happen in one of three ways: with the escape function defined in
467             C<< $opts->{escapefunc} >> (if defined), with a scalar string (if
468             C<< $opts->{rawclass} >> is defined), or with C from
469             L, which uses L.
470              
471             =cut
472              
473             sub escaped_expr {
474 0     0 1 0 my ($self, $expr) = @_;
475              
476 0 0       0 return "$self->{escapefunc}($expr)" if $self->{escapefunc};
477              
478 0 0       0 return "(ref(\$_V = ($expr)) eq '$self->{rawclass}' ? \$_V->{str} : escape_xml($expr)" if $self->{rawclass};
479              
480 0         0 return "escape_xml($expr)";
481             }
482              
483             =head2 _read_file( $filename, [$lock_required] )
484              
485             Receives an absolute path to a template file, reads its content and
486             returns it. If C<$lock_required> is passed (and has a true value), the
487             file will be locked for reading.
488              
489             =cut
490              
491             sub _read_file {
492 18     18   27 my ($self, $filename, $lock_required) = @_;
493              
494 18 50   7   513 open(IN, "<:encoding($Tenjin::ENCODING)", $filename)
  7         52  
  7         11  
  7         42  
495             or croak "[Tenjin] Can't open $filename for reading: $!";
496 18 50       62267 flock(IN, LOCK_SH) if $lock_required;
497              
498 18         442 read(IN, my $content, -s $filename);
499              
500 18         327 close(IN);
501              
502 18         115 return $content;
503             }
504              
505             =head2 _write_file( $filename, $content, [$lock_required] )
506              
507             Receives an absolute path to a template file and the templates contents,
508             and creates the file (or truncates it, if existing) with that contents.
509             If C<$lock_required> is passed (and has a true value), the file will be
510             locked exclusively when writing.
511              
512             =cut
513              
514             sub _write_file {
515 16     16   27 my ($self, $filename, $content, $lock_required) = @_;
516              
517 16 50       43 my $enc = $Tenjin::ENCODING eq 'UTF-8' ? '>:utf8' : ">:encoding($Tenjin::ENCODING)";
518              
519 16 50       1070 open(OUT, $enc, $filename)
520             or croak "[Tenjin] Can't open $filename for writing: $!";
521 16 50       81 flock(OUT, LOCK_EX) if $lock_required;
522 16         130 print OUT $content;
523 16         548 close(OUT);
524             }
525              
526             1;
527              
528             =head1 SEE ALSO
529              
530             L.
531              
532             =head1 AUTHOR, LICENSE AND COPYRIGHT
533              
534             See L.
535              
536             =cut