File Coverage

blib/lib/Template/Tiny.pm
Criterion Covered Total %
statement 56 62 90.3
branch 21 26 80.7
condition 5 6 83.3
subroutine 8 8 100.0
pod 2 3 66.6
total 92 105 87.6


line stmt bran cond sub pod time code
1             package Template::Tiny; # git description: v1.15-3-g3c92468
2             # ABSTRACT: Template Toolkit reimplemented in as little code as possible
3              
4              
5             # Load overhead: 40k
6              
7 5     5   584214 use strict;
  5         11  
  5         7428  
8              
9             our $VERSION = '1.16';
10              
11             # Evaluatable expression
12             my $EXPR = qr/ [a-z_][\w.]* /xs;
13              
14             sub new {
15 15     15 1 2886839 my $self = bless {
16             start_tag => '[%',
17             end_tag => '%]',
18             @_[ 1 .. $#_ ]
19             },
20             $_[0];
21              
22             # Opening tag including whitespace chomping rules
23 15         528 my $LEFT = $self->{LEFT} = qr/
24             (?:
25             (?: (?:^|\n) [ \t]* )? \Q$self->{start_tag}\E\-
26             |
27             \Q$self->{start_tag}\E \+?
28             ) \s*
29             /xs;
30              
31             # Closing %] tag including whitespace chomping rules
32 15         319 my $RIGHT = $self->{RIGHT} = qr/
33             \s* (?:
34             \+? \Q$self->{end_tag}\E
35             |
36             \-\Q$self->{end_tag}\E (?: [ \t]* \n )?
37             )
38             /xs;
39              
40             # Preparsing run for nesting tags
41 15         1926 $self->{PREPARSE} = qr/
42             $LEFT ( IF | UNLESS | FOREACH ) \s+
43             (
44             (?: \S+ \s+ IN \s+ )?
45             \S+ )
46             $RIGHT
47             (?!
48             .*?
49             $LEFT (?: IF | UNLESS | FOREACH ) \b
50             )
51             ( .*? )
52             (?:
53             $LEFT ELSE $RIGHT
54             (?!
55             .*?
56             $LEFT (?: IF | UNLESS | FOREACH ) \b
57             )
58             ( .+? )
59             )?
60             $LEFT END $RIGHT
61             /xs;
62              
63 15         2043 $self->{CONDITION} = qr/
64             \Q$self->{start_tag}\E\s
65             ( ([IUF])\d+ ) \s+
66             (?:
67             ([a-z]\w*) \s+ IN \s+
68             )?
69             ( $EXPR )
70             \s\Q$self->{end_tag}\E
71             ( .*? )
72             (?:
73             \Q$self->{start_tag}\E\s \1 \s\Q$self->{end_tag}\E
74             ( .+? )
75             )?
76             \Q$self->{start_tag}\E\s \1 \s\Q$self->{end_tag}\E
77             /xs;
78              
79 15         83 $self;
80             }
81              
82             # Copy and modify
83             sub preprocess {
84 3     3 0 7 my $self = shift;
85 3         6 my $text = shift;
86 3         12 $self->_preprocess(\$text);
87 3         33 return $text;
88             }
89              
90             sub process {
91 12     12 1 2996 my $self = shift;
92 12         48 my $copy = ${shift()};
  12         26  
93 12   50     60 my $stash = shift || {};
94              
95 12         25 local $@ = '';
96 12         47 local $^W = 0;
97              
98             # Preprocess to establish unique matching tag sets
99 12         44 $self->_preprocess( \$copy );
100              
101             # Process down the nested tree of conditions
102 12         60 my $result = $self->_process( $stash, $copy );
103 12 50       27 if ( @_ ) {
    0          
104 12         19 ${$_[0]} = $result;
  12         62  
105             } elsif ( defined wantarray ) {
106 0         0 require Carp;
107 0         0 Carp::carp('Returning of template results is deprecated in Template::Tiny 0.11');
108 0         0 return $result;
109             } else {
110 0         0 print $result;
111             }
112             }
113              
114              
115              
116              
117              
118             ######################################################################
119             # Support Methods
120              
121             # The only reason this is a standalone is so we can
122             # do more in-depth testing.
123             sub _preprocess {
124 15     15   26 my $self = shift;
125 15         27 my $copy = shift;
126              
127             # Preprocess to establish unique matching tag sets
128 15         25 my $id = 0;
129 15         2553 1 while $$copy =~ s/
130             $self->{ PREPARSE }
131             /
132 20         69 my $tag = substr($1, 0, 1) . ++$id;
133 20 100       1391 "\[\% $tag $2 \%\]$3\[\% $tag \%\]"
134             . (defined($4) ? "$4\[\% $tag \%\]" : '');
135             /sex;
136             }
137              
138             sub _process {
139 32     32   72 my ($self, $stash, $text) = @_;
140              
141 32         1384 $text =~ s/
142             $self->{ CONDITION }
143             /
144             ($2 eq 'F')
145             ? $self->_foreach($stash, $3, $4, $5)
146 18 100       40 : eval {
    100          
147 16   100     24 $2 eq 'U'
148             xor
149             !! # Force boolification
150             $self->_expression($stash, $4)
151             }
152             ? $self->_process($stash, $5)
153             : $self->_process($stash, $6)
154             /gsex;
155              
156             # Resolve expressions
157 32         1087 $text =~ s/
158             $self->{ LEFT } ( $EXPR ) $self->{ RIGHT}
159             /
160 36         84 eval {
161 36         72 $self->_expression($stash, $1)
162             . '' # Force stringification
163             }
164             /gsex;
165              
166             # Trim the document
167 32 100       74 $text =~ s/^\s*(.+?)\s*\z/$1/s if $self->{TRIM};
168              
169 32         158 return $text;
170             }
171              
172             # Special handling for foreach
173             sub _foreach {
174 2     2   10 my ($self, $stash, $term, $expr, $text) = @_;
175              
176             # Resolve the expression
177 2         5 my $list = $self->_expression($stash, $expr);
178 2 50       7 unless ( ref $list eq 'ARRAY' ) {
179 0         0 return '';
180             }
181              
182             # Iterate
183             return join '', map {
184 2         4 $self->_process( { %$stash, $term => $_ }, $text )
  4         12  
185             } @$list;
186             }
187              
188             # Evaluates a stash expression
189             sub _expression {
190 54     54   62 my $cursor = $_[1];
191 54         139 my @path = split /\./, $_[2];
192 54         79 foreach ( @path ) {
193             # Support for private keys
194 76 100       131 return undef if substr($_, 0, 1) eq '_';
195              
196             # Split by data type
197 74         92 my $type = ref $cursor;
198 74 100       119 if ( $type eq 'ARRAY' ) {
    100          
    50          
199 5 100       15 return '' unless /^(?:0|[0-9]\d*)\z/;
200 4         6 $cursor = $cursor->[$_];
201             } elsif ( $type eq 'HASH' ) {
202 67         105 $cursor = $cursor->{$_};
203             } elsif ( $type ) {
204 2         7 $cursor = $cursor->$_();
205             } else {
206 0         0 return '';
207             }
208             }
209              
210             # If the last expression is a coderef, execute it.
211 51 100       92 ref $cursor eq 'CODE' and $cursor = $cursor->();
212 51         254 return $cursor;
213             }
214              
215             1;
216              
217             __END__