File Coverage

blib/lib/Text/MicroTemplate.pm
Criterion Covered Total %
statement 196 229 85.5
branch 81 98 82.6
condition 18 28 64.2
subroutine 29 33 87.8
pod 9 14 64.2
total 333 402 82.8


line stmt bran cond sub pod time code
1             # modified for NanoA by kazuho, some modified by tokuhirom
2             # based on Mojo::Template. Copyright (C) 2008, Sebastian Riedel.
3              
4             package Text::MicroTemplate;
5              
6             require Exporter;
7              
8 15     15   182779 use strict;
  15         24  
  15         426  
9 15     15   49 use warnings;
  15         15  
  15         457  
10 15   50 15   136 use constant DEBUG => $ENV{MICRO_TEMPLATE_DEBUG} || 0;
  15         18  
  15         1310  
11 15     15   272 use 5.00800;
  15         41  
  15         427  
12              
13 15     15   49 use Carp 'croak';
  15         18  
  15         760  
14 15     15   54 use Scalar::Util;
  15         13  
  15         23478  
15              
16             our $VERSION = '0.22';
17             our @ISA = qw(Exporter);
18             our @EXPORT_OK = qw(encoded_string build_mt render_mt);
19             our %EXPORT_TAGS = (
20             all => [ @EXPORT_OK ],
21             );
22             our $_mt_setter = '';
23              
24             sub new {
25 38     38 1 340 my $class = shift;
26 2         18 my $self = bless {
27             code => undef,
28             comment_mark => '#',
29             expression_mark => '=',
30             line_start => '?',
31             template => undef,
32             tree => [],
33             tag_start => '
34             tag_end => '?>',
35             escape_func => \&_inline_escape_html,
36             prepend => '',
37             package_name => undef, # defaults to caller
38 38 100       445 @_ == 1 ? ref($_[0]) ? %{$_[0]} : (template => $_[0]) : @_,
    100          
39             }, $class;
40 38 100       171 if (defined $self->{template}) {
41 30         75 $self->parse($self->{template});
42             }
43 38 100       79 unless (defined $self->{package_name}) {
44 36         43 $self->{package_name} = 'main';
45 36         29 my $i = 0;
46 36         96 while (my $c = caller(++$i)) {
47 58 100       147 if ($c !~ /^Text::MicroTemplate\b/) {
48 32         37 $self->{package_name} = $c;
49 32         37 last;
50             }
51             }
52             }
53 38         50 $self;
54             }
55              
56             sub escape_func {
57 0     0 1 0 my $self = shift;
58 0 0       0 if (@_) {
59 0         0 $self->{escape_func} = shift;
60             }
61 0         0 $self->{escape_func};
62             }
63              
64             sub package_name {
65 1     1 1 3 my $self = shift;
66 1 50       4 if (@_) {
67 0         0 $self->{package_name} = shift;
68             }
69 1         3 $self->{package_name};
70             }
71              
72 0     0 1 0 sub template { shift->{template} }
73              
74             sub code {
75 57     57 1 94 my $self = shift;
76 57 100       126 unless (defined $self->{code}) {
77 54         82 $self->_build();
78             }
79 57         460 $self->{code};
80             }
81              
82             sub _build {
83 54     54   39 my $self = shift;
84            
85 54   100     103 my $escape_func = $self->{escape_func} || '';
86              
87             my $embed_escape_func = ref($escape_func) eq 'CODE'
88             ? $escape_func
89 54 100   1   166 : sub{ $escape_func . "(@_)" };
  1         3  
90              
91             # Compile
92 54         41 my @lines;
93             my $last_was_code;
94 0         0 my $last_text;
95 54         38 for my $line (@{$self->{tree}}) {
  54         86  
96              
97             # New line
98 130         108 push @lines, '';
99 130         106 for (my $j = 0; $j < @{$line}; $j += 2) {
  302         513  
100 172         157 my $type = $line->[$j];
101 172         150 my $value = $line->[$j + 1];
102              
103 172 100 100     379 if ($type ne 'text' && defined $last_text) {
104             # do not mess the start of current line, since it might be
105             # the start of "=pod", etc.
106 46 100 66     153 $lines[
107             $j == 0 && @lines >= 2 ? -2 : -1
108             ] .= "\$_MT .=\"$last_text\";";
109 46         47 undef $last_text;
110             }
111            
112             # Need to fix line ending?
113 172         202 my $newline = chomp $value;
114              
115             # add semicolon to last line of code
116 172 100 100     285 if ($last_was_code && $type ne 'code') {
117 22         27 $lines[-1] .= ';';
118 22         18 undef $last_was_code;
119             }
120              
121             # Text
122 172 100       224 if ($type eq 'text') {
123              
124             # Quote and fix line ending
125 99         99 $value = quotemeta($value);
126 99 100       128 $value .= '\n' if $newline;
127              
128 99 100       132 $last_text = defined $last_text ? "$last_text$value" : $value;
129             }
130              
131             # Code
132 172 100       208 if ($type eq 'code') {
133 30         32 $lines[-1] .= $value;
134 30         24 $last_was_code = 1;
135             }
136              
137             # Expression
138 172 100       242 if ($type eq 'expr') {
139 43         63 my $escaped = $embed_escape_func->('$_MT_T');
140 43 100 66     89 if ($newline && $value =~ /\n/) {
141 1         2 $value .= "\n"; # temporary workaround for t/13-heredoc.t
142             }
143 43         127 $lines[-1] .= "\$_MT_T = $value;\$_MT .= ref \$_MT_T eq 'Text::MicroTemplate::EncodedString' ? \$\$_MT_T : $escaped; \$_MT_T = '';";
144             }
145             }
146             }
147              
148             # add semicolon to last line of code
149 54 100       84 if ($last_was_code) {
150 3         2 $lines[-1] .= "\n;";
151             }
152             # add last text line(s)
153 54 100       82 if (defined $last_text) {
154 35         59 $lines[-1] .= "\$_MT .=\"$last_text\";";
155             }
156            
157             # Wrap
158 54 100       153 $lines[0] = q/sub { my $_MT = ''; local $/ . $self->{package_name} . q/::_MTREF = \$_MT; my $_MT_T = '';/ . (@lines ? $lines[0] : '');
159 54         68 $lines[-1] .= q/return $_MT; }/;
160              
161 54         121 $self->{code} = join "\n", @lines;
162 54         79 return $self;
163             }
164              
165             # I am so smart! I am so smart! S-M-R-T! I mean S-M-A-R-T...
166             sub parse {
167 54     54 0 70 my ($self, $tmpl) = @_;
168 54         70 $self->{template} = $tmpl;
169              
170             # Clean start
171 54         109 delete $self->{tree};
172 54         69 delete $self->{code};
173              
174             # Tags
175 54         80 my $line_start = quotemeta $self->{line_start};
176 54         62 my $tag_start = quotemeta $self->{tag_start};
177 54         58 my $tag_end = quotemeta $self->{tag_end};
178 54         61 my $cmnt_mark = quotemeta $self->{comment_mark};
179 54         52 my $expr_mark = quotemeta $self->{expression_mark};
180              
181             # Tokenize
182 54         44 my $state = 'text';
183 54         312 my @lines = split /(\n)/, $tmpl;
184 54         219 my $tokens = [];
185 54         118 while (@lines) {
186 142         169 my $line = shift @lines;
187 142         99 my $newline = undef;
188 142 100       182 if (@lines) {
189 121         73 shift @lines;
190 121         97 $newline = 1;
191             }
192            
193 142 100       202 if ($state eq 'text') {
194             # Perl line without return value
195 130 100       622 if ($line =~ /^$line_start\s+(.*)$/) {
196 24         19 push @{$self->{tree}}, ['code', $1];
  24         57  
197 24         44 next;
198             }
199             # Perl line with return value
200 106 100       618 if ($line =~ /^$line_start$expr_mark\s+(.+)$/) {
201 7 50       6 push @{$self->{tree}}, [
  7         28  
202             'expr', $1,
203             $newline ? ('text', "\n") : (),
204             ];
205 7         14 next;
206             }
207             # Comment line, dummy token needed for line count
208 99 100       654 if ($line =~ /^$line_start$cmnt_mark/) {
209 10         5 push @{$self->{tree}}, [];
  10         11  
210 10         17 next;
211             }
212             }
213              
214             # Escaped line ending?
215 101 50       530 if ($line =~ /(\\+)$/) {
216 0         0 my $length = length $1;
217             # Newline escaped
218 0 0       0 if ($length == 1) {
219 0         0 $line =~ s/\\$//;
220             }
221             # Backslash escaped
222 0 0       0 if ($length >= 2) {
223 0         0 $line =~ s/\\\\$/\\/;
224 0         0 $line .= "\n";
225             }
226             } else {
227 101 100       197 $line .= "\n" if $newline;
228             }
229              
230             # Mixed line
231 101         1277 for my $token (split /
232             (
233             $tag_start$expr_mark # Expression
234             |
235             $tag_start$cmnt_mark # Comment
236             |
237             $tag_start # Code
238             |
239             $tag_end # End
240             )
241             /x, $line) {
242              
243             # handle tags and bail out
244 258 100       1313 if ($token eq '') {
    100          
    100          
    100          
    100          
245 26         30 next;
246             } elsif ($token =~ /^$tag_end$/) {
247 42         33 $state = 'text';
248 42         49 next;
249             } elsif ($token =~ /^$tag_start$/) {
250 6         5 $state = 'code';
251 6         7 next;
252             } elsif ($token =~ /^$tag_start$cmnt_mark$/) {
253 2         2 $state = 'cmnt';
254 2         7 next;
255             } elsif ($token =~ /^$tag_start$expr_mark$/) {
256 36         32 $state = 'expr';
257 36         53 next;
258             }
259              
260             # value
261 146 100       658 if ($state eq 'text') {
    100          
    100          
262 92         200 push @$tokens, $state, $token;
263             } elsif ($state eq 'cmnt') {
264 2         4 next; # ignore comments
265             } elsif ($state eq 'cont') {
266 10         20 $tokens->[-1] .= $token;
267             } else {
268             # state is code or expr
269 42         49 push @$tokens, $state, $token;
270 42         100 $state = 'cont';
271             }
272             }
273 101 100       189 if ($state eq 'text') {
274 89         63 push @{$self->{tree}}, $tokens;
  89         134  
275 89         190 $tokens = [];
276             }
277             }
278 54 50       98 push @{$self->{tree}}, $tokens
  0         0  
279             if @$tokens;
280            
281 54         87 return $self;
282             }
283              
284             sub _context {
285 0     0   0 my ($self, $text, $line) = @_;
286 0         0 my @lines = split /\n/, $text;
287            
288 0 0 0     0 join '', map {
289 0         0 0 < $_ && $_ <= @lines ? sprintf("%4d: %s\n", $_, $lines[$_ - 1]) : ''
290             } ($line - 2) .. ($line + 2);
291             }
292              
293             # Debug goodness
294             sub _error {
295 1     1   2 my ($self, $error, $line_offset, $from) = @_;
296            
297             # Line
298 1 50       13 if ($error =~ /^(.*)\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)/) {
299 0         0 my $reason = $1;
300 0         0 my $line = $2 - $line_offset;
301 0         0 my $delim = '-' x 76;
302            
303 0         0 my $report = "$reason at line $line in template passed from $from.\n";
304 0         0 my $template = $self->_context($self->{template}, $line);
305 0         0 $report .= "$delim\n$template$delim\n";
306              
307             # Advanced debugging
308 0         0 if (DEBUG) {
309             my $code = $self->_context($self->code, $line);
310             $report .= "$code$delim\n";
311             $report .= $error;
312             }
313              
314 0         0 return $report;
315             }
316              
317             # No line found
318 1         4 return "Template error: $error";
319             }
320              
321             # create raw string (that does not need to be escaped)
322             sub encoded_string {
323 59     59 1 199 Text::MicroTemplate::EncodedString->new($_[0]);
324             }
325              
326              
327             sub _inline_escape_html{
328 42     42   41 my($variable) = @_;
329              
330 42         80 my $source = qq{
331             do{
332             $variable =~ s/([&><"'])/\$Text::MicroTemplate::_escape_table{\$1}/ge;
333             $variable;
334             }
335             }; #" for poor editors
336 42         221 $source =~ s/\n//g; # to keep line numbers
337 42         59 return $source;
338             }
339              
340             our %_escape_table = ( '&' => '&', '>' => '>', '<' => '<', q{"} => '"', q{'} => ''' );
341             sub escape_html {
342 0     0 0 0 my $str = shift;
343 0 0       0 return ''
344             unless defined $str;
345 0 0       0 return $str->as_string
346             if ref $str eq 'Text::MicroTemplate::EncodedString';
347 0         0 $str =~ s/([&><"'])/$_escape_table{$1}/ge; #' for poor editors
  0         0  
348 0         0 return $str;
349             }
350              
351             sub build_mt {
352 27     27 1 101 my $mt = Text::MicroTemplate->new(@_);
353 27         61 $mt->build();
354             }
355              
356             sub build {
357 50     50 0 42 my $_mt = shift;
358 50 100       101 Scalar::Util::weaken($_mt) if $_mt_setter;
359 50         79 my $_code = $_mt->code;
360             my $_from = sub {
361 50     50   43 my $i = 0;
362 50         341 while (my @c = caller(++$i)) {
363 104 100       477 return "$c[1] at line $c[2]"
364             if $c[0] ne __PACKAGE__;
365             }
366 0         0 '';
367 50         227 }->();
368 50         205 my $line_offset = (() = ($_mt->{prepend} =~ /\n/sg)) + 5;
369 50         151 my $expr = << "...";
370             package $_mt->{package_name};
371             sub {
372             ${_mt_setter}local \$SIG{__WARN__} = sub { print STDERR \$_mt->_error(shift, $line_offset, \$_from) };
373             $_mt->{prepend}
374             Text::MicroTemplate::encoded_string((
375             $_code
376             )->(\@_));
377             }
378             ...
379              
380 50         38 if(DEBUG >= 2){
381             DEBUG >= 3 ? die $expr : warn $expr;
382             }
383              
384 50         37 my $die_msg;
385             {
386 50         40 local $@;
  50         43  
387 50 100       8537 if (my $_builder = eval($expr)) {
388 49         121 return $_builder;
389             }
390 1         9 $die_msg = $_mt->_error($@, $line_offset, $_from);
391             }
392 1         6 die $die_msg;
393             }
394              
395             sub render_mt {
396 27     27 1 953 my $builder = build_mt(shift);
397 26         446 $builder->(@_);
398             }
399              
400             # ? $_mt->filter(sub { s/\s+//smg; s/[\r\n]//g; })->(sub { ... ? });
401             sub filter {
402 2     2 1 2 my ($self, $callback) = @_;
403 2         2 my $mtref = do {
404 15     15   80 no strict 'refs';
  15         18  
  15         1052  
405 2         2 ${"$self->{package_name}::_MTREF"};
  2         6  
406             };
407 2         2 my $before = $$mtref;
408 2         3 $$mtref = '';
409             return sub {
410 2     2   3 my $inner_func = shift;
411 2         31 $inner_func->(@_);
412              
413             ## sub { s/foo/bar/g } is a valid filter
414             ## sub { DateTime::Format::Foo->parse_string(shift) } is valid too
415 2         3 local $_ = $$mtref;
416 2         29 my $retval = $callback->($$mtref);
417 15     15   53 no warnings 'uninitialized';
  15         18  
  15         1619  
418 2 100 66     20 if (($retval =~ /^\d+$/ and $_ ne $$mtref) or (defined $retval and !$retval)) {
      33        
      66        
419 1         15 $$mtref = $before . $_;
420             } else {
421 1         15 $$mtref = $before . $retval;
422             }
423             }
424 2         7 }
425              
426             package Text::MicroTemplate::EncodedString;
427              
428 15     15   56 use strict;
  15         15  
  15         351  
429 15     15   153 use warnings;
  15         16  
  15         619  
430              
431 15     15   12686 use overload q{""} => sub { shift->as_string }, fallback => 1;
  15     2   9641  
  15         108  
  2         123  
432              
433             sub new {
434 59     59 0 59 my ($klass, $str) = @_;
435 59         439 bless \$str, $klass;
436             }
437              
438             sub as_string {
439 51     51 0 53 my $self = shift;
440 51         495 $$self;
441             }
442              
443             1;
444             __END__