File Coverage

blib/lib/Text/Haml.pm
Criterion Covered Total %
statement 799 822 97.2
branch 329 384 85.6
condition 119 152 78.2
subroutine 102 102 100.0
pod 27 30 90.0
total 1376 1490 92.3


line stmt bran cond sub pod time code
1             package Text::Haml;
2              
3 31     31   171096 use strict;
  31         41  
  31         773  
4 31     31   100 use warnings;
  31         37  
  31         1498  
5              
6 31     31   9516 use IO::File;
  31         146994  
  31         2261  
7 31     31   147 use Scalar::Util qw/weaken/;
  31         34  
  31         1985  
8 31     31   11842 use Encode qw/decode/;
  31         167417  
  31         1453  
9 31     31   137 use Carp ();
  31         30  
  31         1036  
10 31     31   95 use File::Spec;
  31         34  
  31         494  
11 31     31   93 use File::Basename ();
  31         31  
  31         1018  
12 31     31   8532 use URI::Escape ();
  31         26551  
  31         691  
13 31     31   352 use Digest::MD5;
  31         35  
  31         1990  
14              
15             our $VERSION = '0.990117';
16              
17 31     31   110 use constant CHUNK_SIZE => 4096;
  31         32  
  31         2489  
18              
19 9         774 BEGIN {
20 31     31   56 my $cache_dir = '.text_haml_cache';
21 31         1845 for my $dir ($ENV{HOME}, File::Spec->tmpdir) {
22 31 50 33     579 if (defined($dir) && -d $dir && -w _) {
      33        
23 31         188 $cache_dir = File::Spec->catdir($dir, '.text_haml_cache');
24 31         129319 last;
25             }
26             }
27 31     31   116 use constant _DEFAULT_CACHE_DIR => $cache_dir;
  31         34  
  31         1325  
28             }
29              
30             my $ESCAPE = {
31             '\"' => "\x22",
32             "\'" => "\x27",
33             '\\' => "\x5c",
34             '\/' => "\x2f",
35             '\b' => "\x8",
36             '\f' => "\xC",
37             '\n' => "\xA",
38             '\r' => "\xD",
39             '\t' => "\x9",
40             '\\\\' => "\x5c\x5c"
41             };
42              
43             my $UNESCAPE_RE = qr/
44             \\[\"\'\/\\bfnrt]
45             /x;
46              
47             my $STRING_DOUBLE_QUOTES_RE = qr/
48             \"
49             (?:
50             $UNESCAPE_RE
51             |
52             [\x20-\x21\x23-\x5b\x5b-\x{10ffff}]
53             )*
54             \"
55             /x;
56              
57             my $STRING_SINGLE_QUOTES_RE = qr/
58             \'
59             (?:
60             $UNESCAPE_RE
61             |
62             [\x20-\x26\x28-\x5b\x5b-\x{10ffff}]
63             )*
64             \'
65             /x;
66              
67             my $STRING_RE = qr/
68             $STRING_SINGLE_QUOTES_RE
69             |
70             $STRING_DOUBLE_QUOTES_RE
71             /x;
72              
73             sub new {
74 29     29 1 5502 my $class = shift;
75              
76             # Default attributes
77 29         51 my $attrs = {};
78 29         105 $attrs->{vars_as_subs} = 0;
79 29         64 $attrs->{tape} = [];
80 29         137 $attrs->{encoding} = 'utf-8';
81 29         231 $attrs->{escape_html} = 1;
82 28         50 $attrs->{helpers} = {};
83 28         47 $attrs->{helpers_options} = {};
84 28         90 $attrs->{format} = 'xhtml';
85 28         372 $attrs->{prepend} = '';
86 28         47 $attrs->{append} = '';
87 28         258 $attrs->{namespace} = '';
88 28         54 $attrs->{path} = ['.'];
89 28         42 $attrs->{cache} = 1; # 0: not cached, 1: checks mtime, 2: always cached
90 28         66 $attrs->{cache_dir} = _DEFAULT_CACHE_DIR;
91              
92 28         43 $attrs->{escape} = <<'EOF';
93             my $s = shift;
94             return unless defined $s;
95             $s =~ s/&/&/g;
96             $s =~ s/
97             $s =~ s/>/>/g;
98             $s =~ s/"/"/g;
99             $s =~ s/'/'/g;
100             return $s;
101             EOF
102              
103             $attrs->{filters} = {
104 4     5   88 plain => sub { $_[0] =~ s/\n*$//; $_[0] },
  4         9  
105 2     3   2 escaped => sub { $_[0] },
106 3     3   43 preserve => sub { $_[0] =~ s/\n/ /g; $_[0] },
  3         5  
107             javascript => sub {
108 6     6   21 "";
113             },
114             css => sub {
115 3     3   165 "";
120             },
121 28         302 };
122              
123 28         254 my $self = {%$attrs, @_};
124 28         54 bless $self, $class;
125              
126             # Convert to template fullpath
127             $self->path([
128 28 100       876 map { ref($_) ? $_ : File::Spec->rel2abs($_) }
129 28 100       126 ref($self->path) eq 'ARRAY' ? @{$self->path} : $self->path
  26         62  
130             ]);
131              
132 28   33     299 $self->{helpers_arg} ||= $self;
133 28         97 weaken $self->{helpers_arg};
134              
135 28         104 return $self;
136             }
137              
138             # Yes, i know!
139 21 50   21 1 107 sub vars_as_subs { @_ > 1 ? $_[0]->{vars_as_subs} = $_[1] : $_[0]->{vars_as_subs}; }
140 47 100   47 1 2376 sub format { @_ > 1 ? $_[0]->{format} = $_[1] : $_[0]->{format} }
141 16 50   16 1 76 sub encoding { @_ > 1 ? $_[0]->{encoding} = $_[1] : $_[0]->{encoding} }
142 555 100   555 1 4229 sub escape_html { @_ > 1 ? $_[0]->{escape_html} = $_[1] : $_[0]->{escape_html}; }
143 256 100   256 1 661 sub code { @_ > 1 ? $_[0]->{code} = $_[1] : $_[0]->{code} }
144 252 100   252 1 542 sub compiled { @_ > 1 ? $_[0]->{compiled} = $_[1] : $_[0]->{compiled} }
145 140 50   140 1 503 sub helpers { @_ > 1 ? $_[0]->{helpers} = $_[1] : $_[0]->{helpers} }
146 7 50   7 0 18 sub helpers_options { @_ > 1 ? $_[0]->{helpers_options} = $_[1] : $_[0]->{helpers_options} }
147 13 50   13 1 31 sub filters { @_ > 1 ? $_[0]->{filters} = $_[1] : $_[0]->{filters} }
148 127 50   127 1 301 sub prepend { @_ > 1 ? $_[0]->{prepend} = $_[1] : $_[0]->{prepend} }
149 127 50   127 1 255 sub append { @_ > 1 ? $_[0]->{append} = $_[1] : $_[0]->{append} }
150 127 50   127 1 308 sub escape { @_ > 1 ? $_[0]->{escape} = $_[1] : $_[0]->{escape} }
151 1820 100   1820 1 3466 sub tape { @_ > 1 ? $_[0]->{tape} = $_[1] : $_[0]->{tape} }
152 87 100   87 1 375 sub path { @_ > 1 ? $_[0]->{path} = $_[1] : $_[0]->{path} }
153 20 50   20 1 71 sub cache { @_ > 1 ? $_[0]->{cache} = $_[1] : $_[0]->{cache} }
154 45 100   45 0 525 sub fullpath { @_ > 1 ? $_[0]->{fullpath} = $_[1] : $_[0]->{fullpath}; }
155 8 50   8 1 46 sub cache_dir { @_ > 1 ? $_[0]->{cache_dir} = $_[1] : $_[0]->{cache_dir}; }
156 21 100   21 0 569 sub cache_path { @_ > 1 ? $_[0]->{cache_path} = $_[1] : $_[0]->{cache_path}; }
157 284 100   284 1 748 sub namespace { @_ > 1 ? $_[0]->{namespace} = $_[1] : $_[0]->{namespace}; }
158 9 100   9 1 1044 sub error { @_ > 1 ? $_[0]->{error} = $_[1] : $_[0]->{error} }
159              
160             sub helpers_arg {
161 6 50   6 1 8 if (@_ > 1) {
162 2         285 $_[0]->{helpers_arg} = $_[1];
163 2         9 weaken $_[0]->{helpers_arg};
164             }
165             else {
166 6         12 return $_[0]->{helpers_arg};
167             }
168             }
169              
170              
171             our @AUTOCLOSE = (qw/meta img link br hr input area param col base/);
172              
173             sub add_helper {
174 7     5 1 1058 my $self = shift;
175 7         19 my ($name, $code, %options) = @_;
176              
177 7         9 $self->helpers->{$name} = $code;
178 7         218 $self->helpers_options->{$name} = \%options;
179             }
180              
181             sub add_filter {
182 3     1 1 247 my $self = shift;
183 3         4 my ($name, $code) = @_;
184              
185 3         32 $self->filters->{$name} = $code;
186             }
187              
188             sub parse {
189 146     144 1 141 my $self = shift;
190 146         128 my $tmpl = shift;
191              
192 146 100       348 $tmpl = '' unless defined $tmpl;
193              
194 146         273 $self->tape([]);
195              
196 146         506 my $level_token = quotemeta ' ';
197 146         156 my $escape_token = quotemeta '&';
198 146         114 my $unescape_token = quotemeta '!';
199 146         110 my $expr_token = quotemeta '=';
200 146         246 my $tag_start = quotemeta '%';
201 146         124 my $class_start = quotemeta '.';
202 146         112 my $id_start = quotemeta '#';
203              
204 146         134 my $attributes_start = quotemeta '{';
205 146         114 my $attributes_end = quotemeta '}';
206 146         108 my $attribute_arrow = quotemeta '=>';
207 146         310 my $attributes_sep = quotemeta ',';
208 144         107 my $attribute_prefix = quotemeta ':';
209 144         1957 my $attribute_name = qr/(?:$STRING_RE|.*?(?= |$attribute_arrow))/;
210 144         1308 my $attribute_value =
211             qr/(?:$STRING_RE|[^ $attributes_sep$attributes_end]+)/x;
212              
213 144         164 my $attributes_start2 = quotemeta '(';
214 144         122 my $attributes_end2 = quotemeta ')';
215 144         115 my $attribute_arrow2 = quotemeta '=';
216 144         121 my $attributes_sep2 = ' ';
217 144         1155 my $attribute_name2 = qr/(?:$STRING_RE|.*?(?= |$attribute_arrow2))/;
218 144         1125 my $attribute_value2 =
219             qr/(?:$STRING_RE|[^ $attributes_sep2$attributes_end2]+)/;
220              
221 144         142 my $filter_token = quotemeta ':';
222 144         114 my $quote = "'";
223 144         105 my $comment_token = quotemeta '-#';
224 144         107 my $trim_in = quotemeta '<';
225 144         108 my $trim_out = quotemeta '>';
226 144         133 my $autoclose_token = quotemeta '/';
227 144         106 my $multiline_token = quotemeta '|';
228              
229 144         1492 my $tag_name = qr/([^
230             $level_token
231             $attributes_start
232             $attributes_start2
233             $class_start
234             $id_start
235             $trim_in
236             $trim_out
237             $unescape_token
238             $escape_token
239             $expr_token
240             $autoclose_token]+)/;
241              
242 144         251 my $tape = $self->tape;
243              
244 144         129 my $level;
245             my @multiline_el_queue;
246 144         143 my $multiline_code_el = undef;
247 144         441 my @lines = split /\n/, $tmpl;
248 144 100       602 push @lines, '' if $tmpl =~ m/\n$/;
249 144 100       244 @lines = ('') if $tmpl eq "\n";
250 144         305 for (my $i = 0; $i < @lines; $i++) {
251 675         613 my $line = $lines[$i];
252              
253 675 100       1806 if ($line =~ s/^($level_token+)//) {
254 298         342 $level = length $1;
255             }
256             else {
257 377         313 $level = 0;
258             }
259              
260 675         1512 my $el = {level => $level, type => 'text', line => $line, lineno => $i+1};
261              
262 675 100 100     1181 if (defined $multiline_code_el && $line =~ /^[-!=%#.:]/) {
263 6         7 push @$tape, $multiline_code_el;
264 6         5 undef $multiline_code_el;
265             }
266              
267             # Haml comment
268 675 100       1580 if ($line =~ m/^$comment_token(?: (.*))?/) {
269 10         12 $el->{type} = 'comment';
270 10 100       24 $el->{text} = $1 if $1;
271 10         11 push @$tape, $el;
272 10         21 next;
273             }
274              
275             # Inside a filter
276 665         562 my $prev = $tape->[-1];
277 665 100 100     1746 if ($prev && $prev->{type} eq 'filter') {
278 34 100 100     109 if ($prev->{level} < $el->{level}
      66        
279             || ($i + 1 < @lines && $line eq ''))
280             {
281 22 100       35 $prev->{text} .= "\n" if $prev->{text};
282 22         29 $prev->{text} .= $line;
283 22         50 $prev->{line} .= "\n" . (' ' x $el->{level}) . $el->{line};
284 22         33 _update_lineno($prev, $i);
285 22         58 next;
286             }
287             }
288              
289             # Filter
290 643 100       810 if ($line =~ m/^:(\w+)/) {
291 12         17 $el->{type} = 'filter';
292 12         25 $el->{name} = $1;
293 12         14 $el->{text} = '';
294 12         14 push @$tape, $el;
295 12         29 next;
296             }
297              
298             # Doctype
299 631 100       769 if ($line =~ m/^!!!(?: ([^ ]+)(?: (.*))?)?$/) {
300 16         20 $el->{type} = 'text';
301 16         18 $el->{escape} = 0;
302 16         28 $el->{text} = $self->_doctype($1, $2);
303 16         49 push @$tape, $el;
304 16         69 next;
305             }
306              
307             # HTML comment
308 615 100       763 if ($line =~ m/^\/(?:\[if (.*)?\])?(?: *(.*))?/) {
309 8         10 $el->{type} = 'html_comment';
310 8 100       20 $el->{if} = $1 if $1;
311 8 100       21 $el->{text} = $2 if $2;
312 8         9 push @$tape, $el;
313 8         17 next;
314             }
315              
316             # Escaping, everything after is a text
317 607 100       760 if ($line =~ s/^\\//) {
318 2         5 $el->{type} = 'text', $el->{text} = $line;
319 2         3 push @$tape, $el;
320 2         5 next;
321             }
322              
323             # Block (note even the final multiline block must end in |)
324 605 100 100     2501 if ($line =~ s/^- \s*(.*)(\s\|\s*)$// ||
      66        
      66        
325             $line =~ s/^- \s*(.*)// ||
326             (defined $multiline_code_el && $line =~ s/^(.*)(\s\|\s*)$//)) {
327              
328 54         107 $el->{type} = 'block';
329            
330 54 100       108 if ($2) {
331 18   66     36 $multiline_code_el ||= $el;
332 18   100     33 $multiline_code_el->{text} ||= '';
333 18         22 $multiline_code_el->{text} .= $1;
334              
335 18         44 next;
336             }
337            
338 36         69 $el->{text} = $1;
339 36         39 push @$tape, $el;
340 36         93 next;
341            
342             }
343              
344             # Preserve whitespace
345 551 100       704 if ($line =~ s/^~ \s*(.*)//) {
346 1         2 $el->{type} = 'text';
347 1         2 $el->{text} = $1;
348 1         2 $el->{expr} = 1;
349 1         1 $el->{preserve_whitespace} = 1;
350 1         1 push @$tape, $el;
351 1         3 next;
352             }
353              
354             # Tag
355 550 100       1966 if ($line =~ m/^(?:$tag_start
356             |$class_start
357             |$id_start
358             )/x
359             )
360             {
361 243         249 $el->{type} = 'tag';
362 243         225 $el->{name} = '';
363              
364 243 100       1799 if ($line =~ s/^$tag_start$tag_name//) {
365 221         379 $el->{name} = $1;
366             }
367              
368 243         216 while (1) {
369 287 100       2476 if ($line =~ s/^$class_start$tag_name//) {
    100          
370 31         74 my $class = join(' ', split(/\./, $1));
371              
372 31   100     87 $el->{name} ||= 'div';
373 31   100     95 $el->{class} ||= [];
374 31         27 push @{$el->{class}}, $class;
  31         70  
375             }
376             elsif ($line =~ s/^$id_start$tag_name//) {
377 13         23 my $id = $1;
378              
379 13   100     34 $el->{name} ||= 'div';
380 13         26 $el->{id} = $id;
381             }
382             else {
383 243         310 last;
384             }
385             }
386              
387 243 100       3574 if ($line =~ m/^
388             (?:
389             $attributes_start\s*
390             $attribute_prefix?
391             $attribute_name\s*
392             $attribute_arrow\s*
393             $attribute_value
394             |
395             $attributes_start2\s*
396             $attribute_name2\s*
397             $attribute_arrow2\s*
398             $attribute_value2
399             )
400             /x
401             )
402             {
403 40         51 my $attrs = [];
404              
405 40         38 my $type = 'html';
406 40 100       183 if ($line =~ s/^$attributes_start//) {
407 30         35 $type = 'perl';
408             }
409             else {
410 10         55 $line =~ s/^$attributes_start2//;
411             }
412              
413 40         38 while (1) {
414 104 100 100     624 if (!$line) {
    100 100        
    100          
415 3   50     8 $line = $lines[++$i] || last;
416 3         7 $el->{line} .= "\n$line";
417 3         8 _update_lineno($el, $i);
418             }
419             elsif ($type eq 'perl' && $line =~ s/^$attributes_end//) {
420 30         38 last;
421             }
422             elsif ($type eq 'html' && $line =~ s/^$attributes_end2//)
423             {
424 10         13 last;
425             }
426             else {
427 61         55 my ($name, $value);
428              
429 61 100       1698 if ($line =~ s/^\s*$attribute_prefix?
    50          
430             ($attribute_name)\s*
431             $attribute_arrow\s*
432             ($attribute_value)\s*
433             (?:$attributes_sep\s*)?//x
434             )
435             {
436 44         68 $name = $1;
437 44         61 $value = $2;
438             }
439             elsif (
440             $line =~ s/^\s*
441             ($attribute_name2)\s*
442             $attribute_arrow2\s*
443             ($attribute_value2)\s*
444             (?:$attributes_sep2\s*)?//x
445             )
446             {
447 17         80 $name = $1;
448 17         21 $value = $2;
449             }
450             else {
451 0         0 $self->error('Tag attributes parsing error');
452 0         0 return;
453             }
454              
455 61 100       171 if ($name =~ s/^(?:'|")//) {
456 3         8 $name =~ s/(?:'|")$//;
457 3         24 $name =~ s/($UNESCAPE_RE)/$ESCAPE->{$1}/g;
458             }
459              
460 61 100 100     171 if ($value =~ s/^(?:'|")//) {
    100          
461 47         110 $value =~ s/(?:'|")$//;
462 47         194 $value =~ s/($UNESCAPE_RE)/$ESCAPE->{$1}/g;
463 47         177 push @$attrs,
464             $name => {type => 'text', text => $value};
465             }
466             elsif ($value eq 'true' || $value eq 'false') {
467 7 100       26 push @$attrs, $name => {
468             type => 'boolean',
469             text => $value eq 'true' ? 1 : 0
470             };
471             }
472             else {
473 7         24 push @$attrs,
474             $name => {type => 'expr', text => $value};
475             }
476             }
477             }
478              
479 40         54 $el->{type} = 'tag';
480 40 50       103 $el->{attrs} = $attrs if @$attrs;
481             }
482              
483 243 100       598 if ($line =~ s/^$trim_out ?//) {
484 1         2 $el->{trim_out} = 1;
485             }
486              
487 243 100       615 if ($line =~ s/^$trim_in ?//) {
488 1         3 $el->{trim_in} = 1;
489             }
490             }
491              
492 550 100       1530 if ($line =~ s/^($escape_token|$unescape_token)?$expr_token //) {
493 55         76 $el->{expr} = 1;
494 55 100       114 if ($1) {
495 8 100       26 $el->{escape} = quotemeta($1) eq $escape_token ? 1 : 0;
496             }
497             }
498              
499 550 100 100     1442 if ($el->{type} eq 'tag'
      66        
500             && ($line =~ s/$autoclose_token$//
501             || grep { $el->{name} eq $_ } @AUTOCLOSE)
502             )
503             {
504 15         114 $el->{autoclose} = 1;
505             }
506              
507 550 100       787 $line =~ s/^ // if $line;
508              
509             # Multiline
510 550 100 100     1621 if ($line && $line =~ s/(\s*)$multiline_token$//) {
511              
512             # For the first time
513 20 100 100     69 if (!$tape->[-1] || ref $tape->[-1]->{text} ne 'ARRAY') {
514 6         14 $el->{text} = [$line];
515 6   33     10 $el->{line} ||= $line . "$1|"; # XXX: is this really necessary?
516              
517 6         7 push @$tape, $el;
518 6         20 push @multiline_el_queue, $el;
519             }
520              
521             # Continue concatenation
522             else {
523 14         13 my $prev_stack_el = $tape->[-1];
524 14         9 push @{$prev_stack_el->{text}}, $line;
  14         24  
525 14         31 $prev_stack_el->{line} .= "\n" . $line . "$1|";
526 14         17 _update_lineno($prev_stack_el, $i);
527             }
528             }
529              
530             # Normal text
531             else {
532 530 100       745 $el->{text} = $line if $line;
533              
534 530         1361 push @$tape, $el;
535             }
536             }
537              
538             # Finalize multilines
539 144         689 for my $el (@multiline_el_queue) {
540 6         7 $el->{text} = join(" ", @{$el->{text}});
  6         42  
541             }
542             }
543              
544             # Updates lineno entry on the tape element
545             # for itens spanning more than one line
546             sub _update_lineno {
547 39     39   41 my ($el, $lineno) = @_;
548 39         30 $lineno++; # report line numbers starting at 1 instead of 0
549 39         193 $el->{lineno} =~ s/^(\d+)(?:-\d+)?/$1-$lineno/;
550 39         111 return;
551             }
552              
553             sub _open_implicit_brace {
554 775     775   581 my ($lines) = @_;
555 775 100 100     2075 if (scalar(@$lines) && $lines->[-1] eq '}') {
556 411         409 pop @$lines;
557             } else {
558 364         427 push @$lines, '{';
559             }
560             }
561              
562             sub _close_implicit_brace {
563 775     775   547 my ($lines) = @_;
564 775 100 66     2011 if (scalar(@$lines) && $lines->[-1] eq '{') {
565 95         90 pop @$lines;
566             } else {
567 680         736 push @$lines, '}';
568             }
569             }
570              
571             sub build {
572 125     125 1 126 my $self = shift;
573 125         186 my %vars = @_;
574              
575 125         108 my $code;
576              
577 125         193 my $ESCAPE = $self->escape;
578 125         249 $ESCAPE = <<"EOF";
579             no strict 'refs'; no warnings 'redefine';
580             sub escape;
581             *escape = sub {
582             $ESCAPE
583             };
584             use strict; use warnings;
585             EOF
586              
587 125         725 $ESCAPE =~ s/\n//g;
588              
589             # ensure namespace is set so that (for now) helpers
590             # can access outs & outs_raw (until we correctly allow
591             # helpers in `=` lines to capture their blocks eg. for `surrounds`
592            
593 125 100       227 if (! $self->namespace) {
594 24         72 $self->namespace(ref($self) . '::template');
595             }
596              
597 125         171 my $namespace = $self->namespace;
598 125         187 $code .= qq/package $namespace;/;
599              
600 125         187 $code .= qq/sub { my \$_H = ''; $ESCAPE; /;
601              
602 125         114 $code .= qq/my \$self = shift;/;
603 125         177 $code .= qq/\$${namespace}::__self = \$self;/;
604              
605 125         138 $code .= qq/my \%____vars = \@_;/;
606              
607 125         102 $code .= qq/no strict 'refs'; no warnings 'redefine';/;
608              
609             # using [1] since when called with arrow from namespace, [0] will be the namespace
610 125         191 $code .= qq/*${namespace}::outs = sub { \$_H .= escape(\$_[1]) };/;
611 125         149 $code .= qq/*${namespace}::outs_raw = sub { \$_H .= \$_[1] };/;
612 125         165 $code .= qq/*${namespace}::out_chomp = sub { chomp \$_H };/;
613              
614             # Install helpers
615 125         98 for my $name (sort keys %{$self->helpers}) {
  125         239  
616 11 50       23 next unless $name =~ m/^\w+$/;
617              
618 11   100     24 my $options = $self->{helpers_options}{$name} || {};
619              
620             # allow bareword helpers and block capturing with optional helper prototypes
621 11         8 my $prototype = $options->{prototype};
622 11 100       16 $prototype = defined $prototype ? "($prototype)" : '';
623              
624             # this option allows per-helper overriding of the helper_arg, important for builtin
625             # helpers to be safe in assuming the arg is self
626 11 100       17 my $helper_arg_code = $options->{arg_force_self} ? "\$${namespace}::__self" : "\$${namespace}::__self->helpers_arg";
627              
628             # sub must be defined inside BEGIN {} for the prototype to be ready before main helper code is
629             # compiled
630 11         17 $code .= "BEGIN { \*${namespace}::${name} = sub $prototype { ";
631 11         19 $code .= "\$${namespace}::__self->helpers->{'$name'}->($helper_arg_code, \@_) }; } ";
632             }
633              
634             # Install variables
635 125         211 foreach my $var (sort keys %vars) {
636 20 100       74 next unless $var =~ m/^\w+$/;
637 19 100       36 if ($self->vars_as_subs) {
638 2 50       3 next if $self->helpers->{$var};
639 2         5 $code
640             .= qq/sub $var() : lvalue; *$var = sub () : lvalue {\$____vars{'$var'}};/;
641             }
642             else {
643 17         44 $code .= qq/my \$$var = \$____vars{'$var'};/;
644             }
645             }
646              
647 125         126 $code .= qq/use strict; use warnings;/;
648              
649 125         209 $code .= $self->prepend;
650              
651 125         141 my $stack = [];
652              
653 125         148 my $output = '';
654 125         111 my @lines;
655 125         137 my $count = 0;
656 125         145 my $in_block = 0;
657             ELEM:
658 125         88 for my $el (@{$self->tape}) {
  125         212  
659 568         538 my $level = $el->{level};
660 568 100       718 $level -= 2 * $in_block if $in_block;
661              
662 568         436 my $offset = '';
663 568 100       816 $offset .= ' ' x $level if $level > 0;
664              
665 568         394 my $escape = '';
666 568 100 100     1281 if ( (!exists $el->{escape} && $self->escape_html)
      100        
      66        
667             || (exists $el->{escape} && $el->{escape} == 1))
668             {
669 522         425 $escape = 'escape';
670             }
671              
672 568         670 my $prev_el = $self->tape->[$count - 1];
673 568         608 my $next_el = $self->tape->[$count + 1];
674              
675 568         462 my $prev_stack_el = $stack->[-1];
676              
677 568 100 100     1359 if ($prev_stack_el && $prev_stack_el->{type} eq 'comment') {
678 25 100 100     63 if ( $el->{line}
679             && $prev_stack_el->{level} >= $el->{level})
680             {
681 8         3 pop @$stack;
682 8         8 undef $prev_stack_el;
683 8         12 _close_implicit_brace(\@lines);
684             }
685             else {
686 17         16 next ELEM;
687             }
688             }
689              
690 551 100 100     1846 if ( $el->{line}
      100        
691             && $prev_stack_el
692             && $prev_stack_el->{level} >= $el->{level})
693             {
694             STACKEDBLK:
695 67         127 while (my $poped = pop @$stack) {
696 85         76 my $level = $poped->{level};
697 85 100       113 $level -= 2 * $in_block if $in_block;
698 85 100       139 my $poped_offset = $level > 0 ? ' ' x $level : '';
699              
700 85         71 my $ending = '';
701 85 100       135 if ($poped->{type} eq 'tag') {
    50          
702 49         70 $ending .= "{name}>";
703             }
704             elsif ($poped->{type} eq 'html_comment') {
705 0 0       0 $ending .= "{if};
706 0         0 $ending .= "-->";
707             }
708              
709 85 100       129 if ($poped->{type} ne 'block') {
710 49         88 push @lines, qq|\$_H .= "$poped_offset$ending\n";|;
711             }
712              
713 85         101 _close_implicit_brace(\@lines);
714            
715 85 100       146 if ($poped->{type} eq 'block') {
716 36         47 _close_implicit_brace(\@lines);
717             }
718              
719 85 100       178 last STACKEDBLK if $poped->{level} == $el->{level};
720             }
721             }
722              
723              
724             SWITCH: {
725              
726 551 100       383 if ($el->{type} eq 'tag') {
  551         775  
727             my $ending =
728 219 100 100     472 $el->{autoclose} && $self->format eq 'xhtml' ? ' /' : '';
729              
730 219         161 my $attrs = '';
731 219 100       301 if ($el->{attrs}) {
732             ATTR:
733 27         34 for (my $i = 0; $i < @{$el->{attrs}}; $i += 2) {
  65         140  
734 38         44 my $name = $el->{attrs}->[$i];
735 38         48 my $value = $el->{attrs}->[$i + 1];
736 38         42 my $text = $value->{text};
737              
738 38 100       99 if ($name eq 'class') {
    100          
739 6   100     21 $el->{class} ||= [];
740 6 100       12 if ($value->{type} eq 'text') {
741 4         6 push @{$el->{class}}, $self->_parse_text($text);
  4         9  
742             }
743             else {
744 2         2 push @{$el->{class}}, qq/" . $text . "/;
  2         6  
745             }
746 6         12 next ATTR;
747             }
748             elsif ($name eq 'id') {
749 3   100     30 $el->{id} ||= '';
750 3 100       8 $el->{id} = $el->{id} . '_' if $el->{id};
751 3         9 $el->{id} .= $self->_parse_text($value->{text});
752 3         9 next ATTR;
753             }
754              
755 29 100 100     87 if ( $value->{type} eq 'text'
    100 33        
756             || $value->{type} eq 'expr')
757             {
758 26         30 $attrs .= ' ';
759 26         27 $attrs .= $name;
760 26         25 $attrs .= '=';
761              
762 26 100       41 if ($value->{type} eq 'text') {
763 25         51 $attrs
764             .= "'" . $self->_parse_text($text) . "'";
765             }
766             else {
767 1         2 $attrs .= qq/'" . $text . "'/;
768             }
769             }
770             elsif ($value->{type} eq 'boolean' && $value->{text})
771             {
772 2         2 $attrs .= ' ';
773 2         3 $attrs .= $name;
774 2 100       3 if ($self->format eq 'xhtml') {
775 1         1 $attrs .= '=';
776 1         3 $attrs .= qq/'$name'/;
777             }
778             }
779             } #end:for ATTR
780             }
781              
782 219         178 my $tail = '';
783 219 100       295 if ($el->{class}) {
784 28         34 $tail .= qq/ class='"./;
785 28         17 $tail .= qq/join(' ', sort(/;
786 28         27 $tail .= join(',', map {"\"$_\""} @{$el->{class}});
  35         73  
  28         42  
787 28         27 $tail .= qq/))/;
788 28         26 $tail .= qq/."'/;
789             }
790              
791 219 100       299 if ($el->{id}) {
792 14         29 $tail .= qq/ id='$el->{id}'/;
793             }
794              
795 219         373 $output .= qq|"$offset<$el->{name}$tail$attrs$ending>"|;
796              
797 219 100 66     1161 if ($el->{text} && $el->{expr}) {
    100 66        
    100 33        
    50          
798 17 100       28 if ($escape eq 'escape') {
799 13         38 $output .= '. ( do { my $ret = ' . qq/ $escape( do { $el->{text} } )/ . '; defined($ret) ? $ret : "" } )';
800 13         19 $output .= qq| . "{name}>"|;
801             } else {
802 4         7 $output .= '. ( do {' . $el->{text} . '} || "")';
803 4         6 $output .= qq| . "{name}>"|;
804             }
805             }
806             elsif ($el->{text}) {
807             $output .= qq/. $escape(/ . '"'
808 60         168 . $self->_parse_text($el->{text}) . '");';
809             $output .= qq|\$_H .= "{name}>"|
810 60 50       199 unless $el->{autoclose};
811             }
812             elsif (
813             !$next_el
814             || ( $next_el
815             && $next_el->{level} <= $el->{level})
816             )
817             {
818 28 100       60 $output .= qq|. "{name}>"| unless $el->{autoclose};
819             }
820             elsif (!$el->{autoclose}) {
821 114         110 push @$stack, $el;
822 114         152 _open_implicit_brace(\@lines);
823             }
824              
825 219         232 $output .= qq|. "\n"|;
826 219         164 $output .= qq|;|;
827 219         339 last SWITCH;
828             }
829              
830 332 100 100     829 if ($el->{line} && $el->{type} eq 'text') {
831 145         169 $output = qq/"$offset"/;
832              
833 145 50       255 $el->{text} = '' unless defined $el->{text};
834              
835 145 100       262 if ($el->{expr}) {
    50          
836 31         72 $output .= '. ( do { my $ret = ' . qq/ $escape( do { $el->{text} } )/ . '; defined($ret) ? $ret : "" } )';
837 31         31 $output .= qq/;\$_H .= "\n"/;
838             }
839             elsif ($el->{text}) {
840             $output
841             .= '.'
842             . qq/$escape / . '"'
843 114         238 . $self->_parse_text($el->{text}) . '"';
844 114         114 $output .= qq/. "\n"/;
845             }
846              
847 145         116 $output .= qq/;/;
848 145         183 last SWITCH;
849             }
850              
851 187 100       289 if ($el->{type} eq 'block') {
852 41         60 _open_implicit_brace(\@lines);
853 41         67 push @lines, ';' . $el->{text};
854 41         43 push @$stack, $el;
855 41         59 _open_implicit_brace(\@lines);
856              
857 41 100 66     141 if ($prev_el && $prev_el->{level} > $el->{level}) {
858 9         8 $in_block--;
859             }
860              
861 41 100 66     136 if ($next_el && $next_el->{level} > $el->{level}) {
862 14         13 $in_block++;
863             }
864 41         54 last SWITCH;
865             }
866              
867 146 100       224 if ($el->{type} eq 'html_comment') {
868 6         7 $output = qq/"$offset"/;
869              
870 6         6 $output .= qq/ . "\n"/;
876             }
877             else {
878 2         2 $output .= qq/. "\n"/;
879 2         3 push @$stack, $el;
880 2         4 _open_implicit_brace(\@lines);
881             }
882              
883 6         6 $output .= qq/;/;
884 6         8 last SWITCH;
885             }
886              
887 140 100       235 if ($el->{type} eq 'comment') {
888 9         10 push @$stack, $el;
889 9         10 _open_implicit_brace(\@lines);
890 9         10 last SWITCH;
891             }
892              
893 131 100       215 if ($el->{type} eq 'filter') {
894 10         14 my $filter = $self->filters->{$el->{name}};
895 10 50       19 die "unknown filter: $el->{name}" unless $filter;
896              
897 10 100       15 if ($el->{name} eq 'escaped') {
898             $output =
899             qq/escape "/
900             . $self->_parse_text($el->{text})
901 1         3 . qq/\n";/;
902             }
903             else {
904 9         15 $el->{text} = $filter->($el->{text});
905              
906 9         21 my $text = $self->_parse_text($el->{text});
907 9         26 $text =~ s/\\\n/\\n/g;
908 9         26 $output = qq/"/ . $text . qq/\n";/;
909             }
910 10         25 last SWITCH;
911             }
912              
913 121 50       224 unless ($el->{text}) {
914 121         143 last SWITCH;
915             }
916              
917 0         0 die "unknown type=" . $el->{type};
918              
919             } #end:SWITCH
920             } #end:ELEM
921             continue {
922              
923             # by bracing the content blocks, we will continue any existing block at the same level.
924             # this is important eg. if previously at this level the template has declared a `my`
925             # variable.
926            
927 568         690 _open_implicit_brace(\@lines);
928 568 100       1082 push @lines, '$_H .= ' . $output if $output;
929 568         649 _close_implicit_brace(\@lines);
930 568         431 $output = '';
931 568         577 $count++;
932             } #ELEM
933              
934 125         119 my $last_empty_line = 0;
935             $last_empty_line = 1
936 125 100 66     164 if $self->tape->[-1] && $self->tape->[-1]->{line} eq '';
937              
938             # Close remaining conten tblocks, last-seen first
939 125         209 foreach my $el (reverse @$stack) {
940 73         101 my $offset = ' ' x $el->{level};
941 73         64 my $ending = '';
942 73 100       110 if ($el->{type} eq 'tag') {
    100          
943 65         95 $ending = "{name}>";
944             }
945             elsif ($el->{type} eq 'html_comment') {
946 2 100       5 $ending .= '{if};
947 2         1 $ending .= "-->";
948             }
949              
950 73 100       174 push @lines, qq|\$_H .= "$offset$ending\n";| if $ending;
951              
952 73         103 _close_implicit_brace(\@lines);
953 73 100       155 if ($el->{type} eq 'block') {
954 5         8 _close_implicit_brace(\@lines);
955             }
956              
957             }
958              
959 125 100 66     424 if ($lines[-1] && !$last_empty_line) {
960             # usually (always?) there will be a closing '}' after the actual last .=
961 12 50 33     65 if ($lines[-2] && $lines[-1] eq '}') {
962 12         46 $lines[-2] =~ s/\n";$/";/;
963             } else {
964 0         0 $lines[-1] =~ s/\n";$/";/;
965             }
966             }
967              
968 125         350 $code .= join("\n", @lines);
969              
970 125         198 $code .= $self->append;
971              
972 125         148 $code .= q/return $_H; };/;
973              
974 125         192 $self->code($code);
975              
976 125         440 return $self;
977             }
978              
979             sub _parse_text {
980 216     216   172 my $self = shift;
981 216         183 my $text = shift;
982              
983 216         169 my $expr = 0;
984 216 50 33     481 if ($text =~ m/^\"/ && $text =~ m/\"$/) {
985 0         0 $text =~ s/^"//;
986 0         0 $text =~ s/"$//;
987 0         0 $expr = 1;
988             }
989              
990 216         787 $text =~ s/($UNESCAPE_RE)/$ESCAPE->{$1}/g;
991              
992 216         179 my $output = '';
993 216         157 while (1) {
994 243         170 my $t;
995 243         181 my $escape = 0;
996 243         181 my $found = 0;
997 243         169 my $variable;
998              
999 243         144 our $curly_brace_n;
1000 243         863 $curly_brace_n = qr/ (?> [^{}]+ | \{ (??{ $curly_brace_n }) \} )* /x;
1001              
1002 243 100       9417 if ($text =~ s/^(.*?)?(?
    100          
1003 25         29 $found = 1;
1004 25         37 $t = $1;
1005 25         31 $variable = $2;
1006             }
1007             elsif ($text =~ s/^(.*?)?\\\\(\#\{$curly_brace_n\})//xms) {
1008 2         5 $found = 1;
1009 2         3 $t = $1;
1010 2         3 $variable = $2;
1011 2         2 $escape = 1;
1012             }
1013              
1014 243 100       466 if ($t) {
1015 20         19 $t =~ s/\\\#/\#/g;
1016 20 50       63 $output .= $expr ? $t : quotemeta($t);
1017             }
1018              
1019 243 100       299 if ($found) {
1020 27         88 $variable =~ s/\#\{(.*)\}/$1/;
1021              
1022 27 100       62 my $prefix = $escape ? quotemeta("\\") : '';
1023 27         58 $output .= qq/$prefix".do { $variable }."/;
1024             }
1025             else {
1026 216         363 $text = $self->_parse_interpolation($text);
1027 216         358 $output .= $text;
1028 216         270 last;
1029             }
1030             }
1031              
1032 216 50       479 return $expr ? qq/$output/ : $output;
1033             }
1034              
1035             sub _parse_interpolation {
1036 216     216   187 my $self = shift;
1037 216         229 my ($text) = @_;
1038              
1039 216         169 my @parts;
1040              
1041 216         333 my $start_tag = qr{(?
1042 216         284 my $end_tag = qr{\}};
1043              
1044 216         395 pos $text = 0;
1045 216         465 while (pos $text < length $text) {
1046 212 50       1951 if ($text =~ m/\G $start_tag (.*?) $end_tag/xgcms) {
    50          
1047 0         0 push @parts, 'do {' . $1 . '}';
1048             }
1049             elsif ($text =~ m/\G (.*?) (?=$start_tag)/xgcms) {
1050 0         0 push @parts, 'qq{' . quotemeta($1) . '}';
1051             }
1052             else {
1053 212         328 my $leftover = substr($text, pos($text));
1054 212         397 push @parts, 'qq{' . quotemeta($leftover) . '}';
1055 212         294 last;
1056             }
1057             }
1058              
1059 216 100       396 return '' unless @parts;
1060              
1061 212         244 return '" . ' . join('.', map {s/\\\\#\\\{/#\\\{/; $_} @parts) . '."';
  212         249  
  212         818  
1062             }
1063              
1064             sub compile {
1065 125     125 1 119 my $self = shift;
1066              
1067 125         148 my $code = $self->code;
1068 125 50       209 return unless $code;
1069              
1070 30 50   30   180 my $compiled = eval $code;
  30     30   34  
  30     30   921  
  30     28   98  
  30     28   23  
  30     28   3377  
  28     28   114  
  28     28   34  
  28     26   617  
  28     26   92  
  28     26   31  
  28     25   1763  
  28     25   91  
  28     25   33  
  28     25   688  
  28     25   85  
  28     22   38  
  28     22   2210  
  28     22   126  
  28     21   29  
  27     21   477  
  28     21   92  
  28     21   30  
  27     21   3014  
  26     17   106  
  26     17   30  
  26     17   634  
  26     16   73  
  26     16   25  
  26     16   2476  
  25     16   92  
  25     16   24  
  25     14   488  
  25     14   71  
  25     14   23  
  25     12   1428  
  25     12   82  
  25     12   30  
  25     12   485  
  25     12   67  
  25     6   26  
  25         2083  
  25         87  
  25         21  
  25         462  
  25         67  
  25         27  
  25         2293  
  22         174  
  21         22  
  21         508  
  22         114  
  22         28  
  21         2150  
  21         89  
  20         22  
  20         436  
  21         64  
  21         35  
  21         1170  
  21         65  
  21         24  
  21         503  
  21         65  
  21         25  
  21         1480  
  21         118  
  21         24  
  21         484  
  21         148  
  21         19  
  21         1386  
  17         109  
  17         20  
  17         420  
  17         53  
  17         16  
  17         1597  
  16         64  
  16         17  
  16         382  
  16         180  
  15         30  
  15         985  
  16         93  
  15         25  
  15         304  
  16         90  
  15         24  
  15         1157  
  16         84  
  16         28  
  15         255  
  16         74  
  16         20  
  15         998  
  14         70  
  14         23  
  13         325  
  14         41  
  14         18  
  14         1355  
  13         59  
  13         14  
  13         349  
  12         40  
  12         11  
  12         658  
  12         42  
  12         16  
  12         249  
  12         33  
  12         16  
  12         895  
  12         38  
  12         18  
  12         203  
  12         33  
  12         10  
  12         746  
  125         9200  
  15         16  
  21         33  
  40         40  
  48         57  
  48         51  
  48         35  
  48         48  
  48         60  
  35         36  
  27         39  
  14         23  
1071              
1072 125 100       313 if ($@) {
1073 2         5 $self->error($@);
1074 2         8 return undef;
1075             }
1076              
1077 123         273 $self->compiled($compiled);
1078              
1079 123         275 return $self;
1080             }
1081              
1082             sub interpret {
1083 126     125 1 119 my $self = shift;
1084              
1085 126         171 my $compiled = $self->compiled;
1086              
1087 125         127 my $output = eval { $compiled->($self, @_) };
  125         2782  
1088              
1089 125 100       248 if ($@) {
1090 1         8 $self->error($@);
1091 1         3 return undef;
1092             }
1093              
1094 124         300 return $output;
1095             }
1096              
1097             sub render {
1098 125     125 1 30272 my $self = shift;
1099 125         149 my $tmpl = shift;
1100              
1101             # Parse
1102 125         237 $self->parse($tmpl);
1103              
1104             # Build
1105 125 50       289 return unless defined $self->build(@_);
1106              
1107             # Compile
1108 125 100       196 $self->compile || return undef;
1109              
1110             # Interpret
1111 123         247 return $self->interpret(@_);
1112             }
1113              
1114             # For templates in __DATA__ section
1115             sub _eq_checksum {
1116 1     1   2 my $self = shift;
1117              
1118             # Exit if not virtual path
1119 1 50       3 return 0 unless ref $self->fullpath eq 'SCALAR';
1120              
1121 0 0       0 return 1 if $self->cache == 2;
1122 0 0       0 return 0 if $self->cache == 0;
1123              
1124 0         0 my $fullpath = $self->fullpath;
1125 0         0 $fullpath = $$fullpath;
1126              
1127 0         0 my $file = IO::File->new;
1128 0 0       0 $file->open($self->cache_path, 'r') or return;
1129 0         0 $file->sysread(my $cache_md5_checksum, 33); # 33 = # + hashsum
1130 0         0 $file->close;
1131              
1132 0         0 my $orig_md5_checksum = '#'.$self->_digest($fullpath);
1133              
1134 0         0 return $cache_md5_checksum eq $orig_md5_checksum;
1135             }
1136              
1137             sub _digest {
1138 1     1   2 my ($self, $content) = @_;
1139              
1140 1         8 my $md5 = Digest::MD5->new();
1141 1 50       3 $content = decode($self->encoding, $content) if $self->encoding;
1142 1         37 $md5->add($content);
1143 1         11 return $md5->hexdigest();
1144             }
1145              
1146             sub render_file {
1147 8     8 1 1952 my $self = shift;
1148 8         7 my $path = shift;
1149              
1150             # Set file fullpath
1151 8         17 $self->_fullpath($path);
1152              
1153 8 100       14 if ($self->cache >= 1) {
1154             # Make cache directory
1155 6         11 my $cache_dir = $self->_cache_dir;
1156             # Set cache path
1157 6         19 $self->_cache_path($path, $cache_dir);
1158              
1159             # Exists same cache file?
1160 6 100 66     11 if (-e $self->cache_path && ($self->_eq_mtime || $self->_eq_checksum)) {
      66        
1161 2         6 return $self->_interpret_cached(@_);
1162             }
1163             }
1164              
1165 6         9 my $content = '';
1166 6         31 my $file = IO::File->new;
1167 6 100       141 if (ref $self->fullpath eq 'SCALAR') { # virtual path
1168 1         3 $content = $self->fullpath;
1169 1         2 $content = $$content;
1170             } else {
1171             # Open file
1172 5 50       10 $file->open($self->fullpath, 'r') or die "Can't open template '$path': $!";
1173              
1174             # Slurp file
1175 5         250 while ($file->sysread(my $buffer, CHUNK_SIZE, 0)) {
1176 5         77 $content .= $buffer;
1177             }
1178 5         35 $file->close;
1179             }
1180              
1181 6         46 $content =~ s/\r//g;
1182              
1183             # Encoding
1184 6 50       14 $content = decode($self->encoding, $content) if $self->encoding;
1185              
1186             # Render
1187 6         508 my $output;
1188 6 50       18 if ($output = $self->render($content, @_)) {
1189 6 100       19 if ($self->cache >= 1) {
1190             # Create cache
1191 4 50       8 if ($file->open($self->cache_path, 'w')) {
1192 4         465 binmode $file, ':utf8';
1193              
1194 4 100       10 if (ref $self->fullpath eq 'SCALAR') {
1195 1         4 my $md5_checksum = $self->_digest($content);
1196 1         4 print $file '#'.$md5_checksum."\n".$self->code; # Write with file checksum (virtual path)
1197             } else {
1198 3         5 my $mtime = (stat($self->fullpath))[9];
1199 3         11 print $file '#'.$mtime."\n".$self->code; # Write with file mtime
1200             }
1201              
1202 4         20 $file->close;
1203             }
1204             }
1205             }
1206              
1207 6         241 return $output;
1208             }
1209              
1210             sub _fullpath {
1211 8     8   8 my $self = shift;
1212 8         6 my $path = shift;
1213              
1214 8 100 66     75 if (File::Spec->file_name_is_absolute($path) and -r $path) {
1215 1         4 $self->fullpath($path);
1216 1         1 return;
1217             }
1218              
1219 7         9 for my $p (@{$self->path}) {
  7         12  
1220 7 100       13 if (ref $p eq 'HASH') { # virtual path
1221 1 50       5 if (defined(my $content = $p->{$path})) {
1222 1         3 $self->fullpath(\$content);
1223 1         2 return;
1224             }
1225             } else {
1226 6         49 my $fullpath = File::Spec->catfile($p, $path);
1227 6 50       106 if (-r $fullpath) { # is readable ?
1228 6         13 $self->fullpath($fullpath);
1229 6         8 return;
1230             }
1231             }
1232             }
1233              
1234 0         0 Carp::croak("Can't find template '$path'");
1235             }
1236              
1237             sub _cache_dir {
1238 6     6   6 my $self = shift;
1239              
1240 6 100       11 my $cache_prefix = (ref $self->fullpath eq 'SCALAR')
1241             ? 'HASH'
1242             : URI::Escape::uri_escape(
1243             File::Basename::dirname($self->fullpath)
1244             );
1245              
1246 6         116 my $cache_dir = File::Spec->catdir(
1247             $self->cache_dir,
1248             $cache_prefix,
1249             );
1250              
1251 6 100       101 if (not -e $cache_dir) {
1252 3         19 require File::Path;
1253 3         8 eval { File::Path::mkpath($cache_dir) };
  3         343  
1254 3 50       10 Carp::carp("Can't mkpath '$cache_dir': $@") if $@;
1255             }
1256              
1257 6         8 return $cache_dir;
1258             }
1259              
1260             sub _cache_path {
1261 6     6   7 my $self = shift;
1262 6         6 my $path = shift;
1263 6         6 my $cache_dir = shift;
1264              
1265 6         158 $self->cache_path(File::Spec->catfile(
1266             $cache_dir,
1267             File::Basename::basename($path).'.pl',
1268             ));
1269             }
1270              
1271             sub _eq_mtime {
1272 3     3   4 my $self = shift;
1273              
1274             # Exit if virtual path
1275 3 50       4 return 0 if ref $self->fullpath eq 'SCALAR';
1276              
1277 3 100       6 return 1 if $self->cache == 2;
1278 1 50       2 return 0 if $self->cache == 0;
1279              
1280 1         8 my $file = IO::File->new;
1281 1 50       31 $file->open($self->cache_path, 'r') or return;
1282 1         47 $file->sysread(my $cache_mtime, length('#xxxxxxxxxx'));
1283 1         10 $file->close;
1284 1         9 my $orig_mtime = '#'.(stat($self->fullpath))[9];
1285              
1286 1         7 return $cache_mtime eq $orig_mtime;
1287             }
1288              
1289             sub _interpret_cached {
1290 2     2   3 my $self = shift;
1291              
1292 2         4 my $compiled = do $self->cache_path;
1293 2         6 $self->compiled($compiled);
1294 2         4 return $self->interpret(@_);
1295             }
1296              
1297             sub _doctype {
1298 16     16   17 my $self = shift;
1299 16         30 my ($type, $encoding) = @_;
1300              
1301 16   100     37 $type ||= '';
1302 16   100     69 $encoding ||= 'utf-8';
1303              
1304 16         20 $type = lc $type;
1305              
1306 16 100       25 if ($type eq 'xml') {
1307 2 50       4 return '' if $self->format eq 'html5';
1308 2 50       3 return '' if $self->format eq 'html4';
1309              
1310 2         7 return qq||;
1311             }
1312              
1313 14 100       22 if ($self->format eq 'xhtml') {
    100          
    50          
1314 10 100       47 if ($type eq 'strict') {
    100          
    100          
    100          
    100          
    100          
1315             return
1316 1         2 q||;
1317             }
1318             elsif ($type eq 'frameset') {
1319             return
1320 1         3 q||;
1321             }
1322             elsif ($type eq '5') {
1323 1         2 return '';
1324             }
1325             elsif ($type eq '1.1') {
1326             return
1327 1         2 q||;
1328             }
1329             elsif ($type eq 'basic') {
1330             return
1331 1         2 q||;
1332             }
1333             elsif ($type eq 'mobile') {
1334             return
1335 1         2 q||;
1336             }
1337             else {
1338             return
1339 4         8 q||;
1340             }
1341             }
1342             elsif ($self->format eq 'html4') {
1343 3 100       6 if ($type eq 'strict') {
    100          
1344             return
1345 1         2 q||;
1346             }
1347             elsif ($type eq 'frameset') {
1348             return
1349 1         2 q||;
1350             }
1351             else {
1352             return
1353 1         2 q||;
1354             }
1355             }
1356             elsif ($self->format eq 'html5') {
1357 1         2 return '';
1358             }
1359              
1360 0           return '';
1361             }
1362              
1363             1;
1364             __END__