File Coverage

blib/lib/Template/Tiny/Strict.pm
Criterion Covered Total %
statement 77 83 92.7
branch 33 38 86.8
condition 10 11 90.9
subroutine 9 9 100.0
pod 3 4 75.0
total 132 145 91.0


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