File Coverage

blib/lib/Text/MicroTemplate.pm
Criterion Covered Total %
statement 193 226 85.4
branch 81 98 82.6
condition 18 28 64.2
subroutine 28 32 87.5
pod 9 14 64.2
total 329 398 82.6


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