File Coverage

blib/lib/Mojo/Template.pm
Criterion Covered Total %
statement 140 140 100.0
branch 82 82 100.0
condition 67 76 88.1
subroutine 16 16 100.0
pod 4 4 100.0
total 309 318 97.1


line stmt bran cond sub pod time code
1             package Mojo::Template;
2 55     55   1417 use Mojo::Base -base;
  55         125  
  55         469  
3              
4 55     55   480 use Carp qw(croak);
  55         144  
  55         3862  
5 55     55   417 use Mojo::ByteStream;
  55         113  
  55         2686  
6 55     55   1186 use Mojo::Exception;
  55         107  
  55         2936  
7 55     55   381 use Mojo::File qw(path);
  55         135  
  55         3552  
8 55     55   358 use Mojo::Util qw(decode encode monkey_patch);
  55         126  
  55         4751  
9              
10 55   50 55   360 use constant DEBUG => $ENV{MOJO_TEMPLATE_DEBUG} || 0;
  55         190  
  55         239409  
11              
12             has [qw(append code prepend unparsed)] => '';
13             has [qw(auto_escape compiled vars)];
14             has capture_end => 'end';
15             has capture_start => 'begin';
16             has comment_mark => '#';
17             has encoding => 'UTF-8';
18             has escape => sub { \&Mojo::Util::xml_escape };
19             has [qw(escape_mark expression_mark trim_mark)] => '=';
20             has [qw(line_start replace_mark)] => '%';
21             has name => 'template';
22             has namespace => 'Mojo::Template::Sandbox';
23             has tag_start => '<%';
24             has tag_end => '%>';
25             has tree => sub { [] };
26              
27             sub parse {
28 377     377 1 1094 my ($self, $template) = @_;
29              
30             # Clean start
31 377         2140 $self->unparsed($template)->tree(\my @tree)->compiled(undef);
32              
33 377         1297 my $tag = $self->tag_start;
34 377         1227 my $replace = $self->replace_mark;
35 377         1302 my $expr = $self->expression_mark;
36 377         1268 my $escp = $self->escape_mark;
37 377         1226 my $cpen = $self->capture_end;
38 377         1267 my $cmnt = $self->comment_mark;
39 377         1416 my $cpst = $self->capture_start;
40 377         1142 my $trim = $self->trim_mark;
41 377         1273 my $end = $self->tag_end;
42 377         1270 my $start = $self->line_start;
43              
44 377         6526 my $line_re = qr/^(\s*)\Q$start\E(?:(\Q$replace\E)|(\Q$cmnt\E)|(\Q$expr\E))?(.*)$/;
45 377         9238 my $token_re = qr/
46             (
47             \Q$tag\E(?:\Q$replace\E|\Q$cmnt\E) # Replace
48             |
49             \Q$tag$expr\E(?:\Q$escp\E)?(?:\s*\Q$cpen\E(?!\w))? # Expression
50             |
51             \Q$tag\E(?:\s*\Q$cpen\E(?!\w))? # Code
52             |
53             (?:(?
54             )
55             /x;
56 377         3336 my $cpen_re = qr/^\Q$tag\E(?:\Q$expr\E)?(?:\Q$escp\E)?\s*\Q$cpen\E(.*)$/;
57 377         3064 my $end_re = qr/^(?:(\Q$cpst\E)\s*)?(\Q$trim\E)?\Q$end\E$/;
58              
59             # Split lines
60 377         848 my $op = 'text';
61 377         818 my ($trimming, $capture);
62 377         3967 for my $line (split /\n/, $template) {
63              
64             # Turn Perl line into mixed line
65 4779 100 100     36674 if ($op eq 'text' && $line =~ $line_re) {
66              
67             # Escaped start
68 1483 100       5477 if ($2) { $line = "$1$start$5" }
  9 100       43  
69              
70             # Comment
71 7         47 elsif ($3) { $line = "$tag$3 $trim$end" }
72              
73             # Expression or code
74 1467 100       8110 else { $line = $4 ? "$1$tag$4$5 $end" : "$tag$5 $trim$end" }
75             }
76              
77             # Escaped line ending
78 4779 100 100     20451 $line .= "\n" if $line !~ s/\\\\$/\\\n/ && $line !~ s/\\$//;
79              
80             # Mixed line
81 4779         140519 for my $token (split $token_re, $line) {
82              
83             # Capture end
84 13272 100       54994 ($token, $capture) = ("$tag$1", 1) if $token =~ $cpen_re;
85              
86             # End
87 13272 100 100     74174 if ($op ne 'text' && $token =~ $end_re) {
    100          
    100          
    100          
    100          
    100          
88              
89             # Capture start
90 2113 100       6460 splice @tree, -1, 0, ['cpst'] if $1;
91              
92             # Trim left side
93 2113 100 100     8702 _trim(\@tree) if ($trimming = $2) && @tree > 1;
94              
95             # Hint at end
96 2113         6985 push @tree, [$op = 'text', ''];
97             }
98              
99             # Code
100 894         1681 elsif ($token eq $tag) { $op = 'code' }
101              
102             # Expression
103 1150         2347 elsif ($token eq "$tag$expr") { $op = 'expr' }
104              
105             # Expression that needs to be escaped
106 59         116 elsif ($token eq "$tag$expr$escp") { $op = 'escp' }
107              
108             # Comment
109 10         27 elsif ($token eq "$tag$cmnt") { $op = 'cmnt' }
110              
111             # Text (comments are just ignored)
112             elsif ($op ne 'cmnt') {
113              
114             # Replace
115 9034 100       18515 $token = $tag if $token eq "$tag$replace";
116              
117             # Trim right side (convert whitespace to line noise)
118 9034 100 100     22026 if ($trimming && $token =~ s/^(\s+)//) {
119 839         3054 push @tree, ['code', $1];
120 839         1340 $trimming = 0;
121             }
122              
123             # Token (with optional capture end)
124 9034 100       27630 push @tree, $capture ? ['cpen'] : (), [$op, $token];
125 9034         15681 $capture = 0;
126             }
127             }
128              
129             # Optimize successive text lines separated by a newline
130 4779 100 50     41630 push @tree, ['line'] and next
      100        
      100        
      100        
      100        
      33        
      66        
131             if $tree[-4] && $tree[-4][0] ne 'line'
132             || (!$tree[-3] || $tree[-3][0] ne 'text' || $tree[-3][1] !~ /\n$/)
133             || ($tree[-2][0] ne 'line' || $tree[-1][0] ne 'text');
134 2140         9144 $tree[-3][1] .= pop(@tree)->[1];
135             }
136              
137 377         3961 return $self;
138             }
139              
140             sub process {
141 545     545 1 2204 my $self = shift;
142              
143             # Use a local stack trace for compile exceptions
144 545         2052 my $compiled = $self->compiled;
145 545 100       1677 unless ($compiled) {
146 377         1336 my $code = $self->_compile->code;
147 377         1803 monkey_patch $self->namespace, '_escape', $self->escape;
148 377 100       1702 return Mojo::Exception->new($@)->inspect($self->unparsed, $code)->trace->verbose(1)
149             unless $compiled = eval $self->_wrap($code, @_);
150 370         158630 $self->compiled($compiled);
151             }
152              
153             # Use a real stack trace for normal exceptions
154             local $SIG{__DIE__} = sub {
155 49 100   49   834 CORE::die $_[0] if ref $_[0];
156 46         572 CORE::die Mojo::Exception->new(shift)->trace->inspect($self->unparsed, $self->code)->verbose(1);
157 538         5718 };
158              
159 538         1246 my $output;
160 538 100       1122 return eval { $output = $compiled->(@_); 1 } ? $output : $@;
  538         2002  
  516         12773  
161             }
162              
163 376     376 1 3988 sub render { shift->parse(shift)->process(@_) }
164              
165             sub render_file {
166 58     58 1 262 my ($self, $path) = (shift, shift);
167              
168 58 100       305 $self->name($path) unless defined $self->{name};
169 58         255 my $template = path($path)->slurp;
170 58         727 my $encoding = $self->encoding;
171 58 100 66     420 croak qq{Template "$path" has invalid encoding} if $encoding && !defined($template = decode $encoding, $template);
172              
173 57         297 return $self->render($template, @_);
174             }
175              
176             sub _compile {
177 377     377   1179 my $self = shift;
178              
179 377         1193 my $tree = $self->tree;
180 377         1175 my $escape = $self->auto_escape;
181              
182 377         1140 my @blocks = ('');
183 377         885 my ($i, $capture, $multi);
184 377   100     2447 while (++$i <= @$tree && (my $next = $tree->[$i])) {
185 12384         16959 my ($op, $value) = @{$tree->[$i - 1]};
  12384         26027  
186 12384 100 50     32423 push @blocks, '' and next if $op eq 'line';
187 10120   100     21942 my $newline = chomp($value //= '');
188              
189             # Text (quote and fix line ending)
190 10120 100 100     22722 if ($op eq 'text') {
    100          
    100          
191 6880         16947 $value = join "\n", map { quotemeta $_ } split(/\n/, $value, -1);
  3879         12231  
192 6880 100       14675 $value .= '\n' if $newline;
193 6880 100       16591 $blocks[-1] .= "\$_O .= \"" . $value . "\";" if length $value;
194             }
195              
196             # Code or multi-line expression
197 1779         3698 elsif ($op eq 'code' || $multi) { $blocks[-1] .= $value }
198              
199             # Capture end
200             elsif ($op eq 'cpen') {
201 126         379 $blocks[-1] .= 'return Mojo::ByteStream->new($_O) }';
202              
203             # No following code
204 126 100 50     1316 $blocks[-1] .= ';' if $next->[0] ne 'cpst' && ($next->[1] // '') =~ /^\s*$/;
      100        
205             }
206              
207             # Expression
208 10120 100 100     30439 if ($op eq 'expr' || $op eq 'escp') {
209              
210             # Escaped
211 1223 100 100     8190 if (!$multi && ($op eq 'escp' && !$escape || $op eq 'expr' && $escape)) {
    100 100        
212 1017         2674 $blocks[-1] .= "\$_O .= _escape scalar + $value";
213             }
214              
215             # Raw
216 192         620 elsif (!$multi) { $blocks[-1] .= "\$_O .= scalar + $value" }
217              
218             # Multi-line
219 1223   66     4341 $multi = !$next || $next->[0] ne 'text';
220              
221             # Append semicolon
222 1223 100 100     4703 $blocks[-1] .= ';' unless $multi || $capture;
223             }
224              
225             # Capture start
226 10120 100       42682 if ($op eq 'cpst') { $capture = 1 }
  126 100       549  
227             elsif ($capture) {
228 126         326 $blocks[-1] .= "sub { my \$_O = ''; ";
229 126         573 $capture = 0;
230             }
231             }
232              
233 377         3982 return $self->code(join "\n", @blocks)->tree([]);
234             }
235              
236             sub _line {
237 754     754   2004 my $name = shift->name;
238 754         1905 $name =~ y/"//d;
239 754         1357 return qq{#line @{[shift]} "$name"};
  754         3435  
240             }
241              
242             sub _trim {
243 838     838   1346 my $tree = shift;
244              
245             # Skip captures
246 838 100 100     3392 my $i = $tree->[-2][0] eq 'cpst' || $tree->[-2][0] eq 'cpen' ? -3 : -2;
247              
248             # Only trim text
249 838 100       2032 return unless $tree->[$i][0] eq 'text';
250              
251             # Convert whitespace text to line noise
252 827 100       2629 splice @$tree, $i, 0, ['code', $1] if $tree->[$i][1] =~ s/(\s+)$//;
253             }
254              
255             sub _wrap {
256 377     377   1125 my ($self, $body, $vars) = @_;
257              
258             # Variables
259 377         888 my $args = '';
260 377 100 100     1327 if ($self->vars && (my @vars = grep {/^\w+$/} keys %$vars)) {
  972         4145  
261 135         372 $args = 'my (' . join(',', map {"\$$_"} @vars) . ')';
  394         1145  
262 135         527 $args .= '= @{shift()}{qw(' . join(' ', @vars) . ')};';
263             }
264              
265             # Wrap lines
266 377         3224 my $num = () = $body =~ /\n/g;
267 377         1638 my $code = $self->_line(1) . "\npackage @{[$self->namespace]};";
  377         1106  
268 377         1625 $code .= "use Mojo::Base -strict; no warnings 'ambiguous';";
269 377         741 $code .= "sub { my \$_O = ''; @{[$self->prepend]};{ $args { $body\n";
  377         1207  
270 377         1293 $code .= $self->_line($num + 1) . "\n;}@{[$self->append]}; } \$_O };";
  377         1305  
271              
272 377         885 warn "-- Code for @{[$self->name]}\n@{[encode 'UTF-8', $code]}\n\n" if DEBUG;
273 377         171741 return $code;
274             }
275              
276             1;
277              
278             =encoding utf8
279              
280             =head1 NAME
281              
282             Mojo::Template - Perl-ish templates
283              
284             =head1 SYNOPSIS
285              
286             use Mojo::Template;
287              
288             # Use Perl modules
289             my $mt = Mojo::Template->new;
290             say $mt->render(<<'EOF');
291             % use Time::Piece;
292            
293             % my $now = localtime;
294             Time: <%= $now->hms %>
295            
296             EOF
297              
298             # Render with arguments
299             say $mt->render(<<'EOF', [1 .. 13], 'Hello World!');
300             % my ($numbers, $title) = @_;
301            
302            

<%= $title %>

303             % for my $i (@$numbers) {
304             Test <%= $i %>
305             % }
306            
307             EOF
308              
309             # Render with named variables
310             say $mt->vars(1)->render(<<'EOF', {title => 'Hello World!'});
311            
312            

<%= $title %>

313             %= 5 + 5
314            
315             EOF
316              
317             =head1 DESCRIPTION
318              
319             L is a minimalistic, fast, and very Perl-ish template engine, designed specifically for all those small
320             tasks that come up during big projects. Like preprocessing a configuration file, generating text from heredocs and
321             stuff like that.
322              
323             See L for information on how to generate content with the L renderer.
324              
325             =head1 SYNTAX
326              
327             For all templates L, L, L and Perl 5.16 L are automatically enabled.
328              
329             <% Perl code %>
330             <%= Perl expression, replaced with result %>
331             <%== Perl expression, replaced with XML escaped result %>
332             <%# Comment, useful for debugging %>
333             <%% Replaced with "<%", useful for generating templates %>
334             % Perl code line, treated as "<% line =%>" (explained later)
335             %= Perl expression line, treated as "<%= line %>"
336             %== Perl expression line, treated as "<%== line %>"
337             %# Comment line, useful for debugging
338             %% Replaced with "%", useful for generating templates
339              
340             Escaping behavior can be reversed with the L attribute, this is the default in L C<.ep>
341             templates, for example.
342              
343             <%= Perl expression, replaced with XML escaped result %>
344             <%== Perl expression, replaced with result %>
345              
346             L objects are always excluded from automatic escaping.
347              
348             % use Mojo::ByteStream qw(b);
349             <%= b('
excluded!
') %>
350              
351             Whitespace characters around tags can be trimmed by adding an additional equal sign to the end of a tag.
352              
353             <% for (1 .. 3) { %>
354             <%= 'Trim all whitespace characters around this expression' =%>
355             <% } %>
356              
357             Newline characters can be escaped with a backslash.
358              
359             This is <%= 1 + 1 %> a\
360             single line
361              
362             And a backslash in front of a newline character can be escaped with another backslash.
363              
364             This will <%= 1 + 1 %> result\\
365             in multiple\\
366             lines
367              
368             A newline character gets appended automatically to every template, unless the last character is a backslash. And empty
369             lines at the end of a template are ignored.
370              
371             There is <%= 1 + 1 %> no newline at the end here\
372              
373             You can capture whole template blocks for reuse later with the C and C keywords. Just be aware that both
374             keywords are part of the surrounding tag and not actual Perl code, so there can only be whitespace after C and
375             before C.
376              
377             <% my $block = begin %>
378             <% my $name = shift; =%>
379             Hello <%= $name %>.
380             <% end %>
381             <%= $block->('Baerbel') %>
382             <%= $block->('Wolfgang') %>
383              
384             Perl lines can also be indented freely.
385              
386             % my $block = begin
387             % my $name = shift;
388             Hello <%= $name %>.
389             % end
390             %= $block->('Baerbel')
391             %= $block->('Wolfgang')
392              
393             L templates get compiled to a Perl subroutine, that means you can access arguments simply via C<@_>.
394              
395             % my ($foo, $bar) = @_;
396             % my $x = shift;
397             test 123 <%= $foo %>
398              
399             The compilation of templates to Perl code can make debugging a bit tricky, but L will return
400             L objects that stringify to error messages with context.
401              
402             Bareword "xx" not allowed while "strict subs" in use at template line 4.
403             Context:
404             2:
405             3:
406             4: % my $i = 2; xx
407             5: %= $i * 2
408             6:
409             Traceback (most recent call first):
410             File "template", line 4, in "Mojo::Template::Sandbox"
411             File "path/to/Mojo/Template.pm", line 123, in "Mojo::Template"
412             File "path/to/myapp.pl", line 123, in "main"
413              
414             =head1 ATTRIBUTES
415              
416             L implements the following attributes.
417              
418             =head2 auto_escape
419              
420             my $bool = $mt->auto_escape;
421             $mt = $mt->auto_escape($bool);
422              
423             Activate automatic escaping.
424              
425             # "<html>"
426             Mojo::Template->new(auto_escape => 1)->render("<%= '' %>");
427              
428             =head2 append
429              
430             my $code = $mt->append;
431             $mt = $mt->append('warn "Processed template"');
432              
433             Append Perl code to compiled template. Note that this code should not contain newline characters, or line numbers in
434             error messages might end up being wrong.
435              
436             =head2 capture_end
437              
438             my $end = $mt->capture_end;
439             $mt = $mt->capture_end('end');
440              
441             Keyword indicating the end of a capture block, defaults to C.
442              
443             <% my $block = begin %>
444             Some data!
445             <% end %>
446              
447             =head2 capture_start
448              
449             my $start = $mt->capture_start;
450             $mt = $mt->capture_start('begin');
451              
452             Keyword indicating the start of a capture block, defaults to C.
453              
454             <% my $block = begin %>
455             Some data!
456             <% end %>
457              
458             =head2 code
459              
460             my $code = $mt->code;
461             $mt = $mt->code($code);
462              
463             Perl code for template if available.
464              
465             =head2 comment_mark
466              
467             my $mark = $mt->comment_mark;
468             $mt = $mt->comment_mark('#');
469              
470             Character indicating the start of a comment, defaults to C<#>.
471              
472             <%# This is a comment %>
473              
474             =head2 compiled
475              
476             my $compiled = $mt->compiled;
477             $mt = $mt->compiled($compiled);
478              
479             Compiled template code if available.
480              
481             =head2 encoding
482              
483             my $encoding = $mt->encoding;
484             $mt = $mt->encoding('UTF-8');
485              
486             Encoding used for template files, defaults to C.
487              
488             =head2 escape
489              
490             my $cb = $mt->escape;
491             $mt = $mt->escape(sub {...});
492              
493             A callback used to escape the results of escaped expressions, defaults to L.
494              
495             $mt->escape(sub ($str) { return reverse $str });
496              
497             =head2 escape_mark
498              
499             my $mark = $mt->escape_mark;
500             $mt = $mt->escape_mark('=');
501              
502             Character indicating the start of an escaped expression, defaults to C<=>.
503              
504             <%== $foo %>
505              
506             =head2 expression_mark
507              
508             my $mark = $mt->expression_mark;
509             $mt = $mt->expression_mark('=');
510              
511             Character indicating the start of an expression, defaults to C<=>.
512              
513             <%= $foo %>
514              
515             =head2 line_start
516              
517             my $start = $mt->line_start;
518             $mt = $mt->line_start('%');
519              
520             Character indicating the start of a code line, defaults to C<%>.
521              
522             % $foo = 23;
523              
524             =head2 name
525              
526             my $name = $mt->name;
527             $mt = $mt->name('foo.mt');
528              
529             Name of template currently being processed, defaults to C