File Coverage

blib/lib/Template/Tiny/Strict.pm
Criterion Covered Total %
statement 76 82 92.6
branch 33 38 86.8
condition 10 11 90.9
subroutine 9 9 100.0
pod 3 4 75.0
total 131 144 90.9


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   282664 use strict;
  4         42  
  4         5583  
8              
9             our $VERSION = '1.18';
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 30309 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     194 name => ( $arg_for{name} || 'template' ),
79             _undefined => {},
80             _used => {},
81             } => $class;
82             }
83              
84 5     5 1 10 sub name { $_[0]->{name} }
85              
86             # Copy and modify
87             sub preprocess {
88 3     3 0 7 my $self = shift;
89 3         4 my $text = shift;
90 3         11 $self->_preprocess( \$text );
91 3         12 return $text;
92             }
93              
94             sub process {
95 16     16 1 3210 my $self = shift;
96 16         32 my $copy = ${ shift() };
  16         29  
97 16   50     48 my $stash = shift || {};
98 16         47 $self->{_undefined} = {};
99 16         31 $self->{_used} = {};
100              
101 16         64 local $^W = 0;
102              
103             # Preprocess to establish unique matching tag sets
104 16         50 $self->_preprocess( \$copy );
105              
106             # Process down the nested tree of conditions
107 16         55 my $result = $self->_process( $stash, $copy );
108 16         30 my $errors = '';
109 16 100       38 if ( $self->{forbid_undef} ) {
110 6 100       11 if ( my %errors = %{ $self->{_undefined} } ) {
  6         24  
111 3         12 $errors = join "\n" => sort keys %errors;
112             }
113             }
114 16 100       34 if ( $self->{forbid_unused} ) {
115 6         11 my @unused;
116 6         15 foreach my $var ( keys %$stash ) {
117 10 100       26 unless ( $self->{_used}{$var} ) {
118 4         9 push @unused => $var;
119             }
120             }
121 6 100       27 if ( my $unused = join ', ' => sort @unused ) {
122 3         11 $errors
123             .= "\nThe following variables were passed to the template but unused: '$unused'";
124             }
125             }
126 16 100       32 if ($errors) {
127 5         29 require Carp;
128 5         15 my $name = $self->name;
129 5         11 my $class = ref $self;
130 5         15 $errors = "$class processing for '$name' failed:\n$errors";
131 5         453 Carp::croak($errors);
132             }
133              
134 11 50       30 if (@_) {
    0          
135 11         18 ${ $_[0] } = $result;
  11         45  
136             }
137             elsif ( defined wantarray ) {
138 0         0 require Carp;
139 0         0 Carp::carp(
140             'Returning of template results is deprecated in Template::Tiny::Strict 0.11'
141             );
142 0         0 return $result;
143             }
144             else {
145 0         0 print $result;
146             }
147             }
148              
149             ######################################################################
150             # Support Methods
151              
152             # The only reason this is a standalone is so we can
153             # do more in-depth testing.
154             sub _preprocess {
155 19     19   34 my $self = shift;
156 19         28 my $copy = shift;
157              
158             # Preprocess to establish unique matching tag sets
159 19         61 my $id = 0;
160 19         1103 1 while $$copy =~ s/
161             $PREPARSE
162             /
163 20         77 my $tag = substr($1, 0, 1) . ++$id;
164 20 100       1384 "\[\% $tag $2 \%\]$3\[\% $tag \%\]"
165             . (defined($4) ? "$4\[\% $tag \%\]" : '');
166             /sex;
167             }
168              
169             sub _process {
170 36     36   105 my ( $self, $stash, $text ) = @_;
171              
172 36         548 $text =~ s/
173             $CONDITION
174             /
175             ($2 eq 'F')
176             ? $self->_foreach($stash, $3, $4, $5)
177 18 100       58 : eval {
    100          
178 16   100     52 $2 eq 'U'
179             xor
180             !! # Force boolification
181             $self->_expression($stash, $4)
182             }
183             ? $self->_process($stash, $5)
184             : $self->_process($stash, $6)
185             /gsex;
186              
187             # Resolve expressions
188 36         489 $text =~ s/
189             $LEFT ( $EXPR ) $RIGHT
190             /
191 41         89 eval {
192 41         87 $self->_expression($stash, $1)
193             . '' # Force stringification
194             }
195             /gsex;
196              
197             # Trim the document
198 36 100       107 $text =~ s/^\s*(.+?)\s*\z/$1/s if $self->{TRIM};
199              
200 36         250 return $text;
201             }
202              
203             # Special handling for foreach
204             sub _foreach {
205 2     2   10 my ( $self, $stash, $term, $expr, $text ) = @_;
206              
207             # Resolve the expression
208 2         9 my $list = $self->_expression( $stash, $expr );
209 2 50       8 unless ( ref $list eq 'ARRAY' ) {
210 0         0 return '';
211             }
212              
213             # Iterate
214             return join '',
215 2         6 map { $self->_process( { %$stash, $term => $_ }, $text ) } @$list;
  4         20  
216             }
217              
218             # Evaluates a stash expression
219             sub _expression {
220 59     59   98 my $cursor = $_[1];
221 59         167 my @path = split /\./, $_[2];
222 59         140 $_[0]->{_used}{ $path[0] } = 1;
223 59         104 foreach (@path) {
224              
225             # Support for private keys
226 83 100       208 return undef if substr( $_, 0, 1 ) eq '_';
227              
228             # Split by data type
229 81         140 my $type = ref $cursor;
230 81 100       189 if ( $type eq 'ARRAY' ) {
    100          
    50          
231 7 100       34 return '' unless /^(?:0|[0-9]\d*)\z/;
232 6         16 $cursor = $cursor->[$_];
233             }
234             elsif ( $type eq 'HASH' ) {
235 72         170 $cursor = $cursor->{$_};
236             }
237             elsif ($type) {
238 2         8 $cursor = $cursor->$_();
239             }
240             else {
241 0         0 return '';
242             }
243             }
244 56 100 100     152 if ( $_[0]->{forbid_undef} && !defined $cursor ) {
245 3         8 my $path = join '.' => @path;
246 3         11 $_[0]->{_undefined}{"Undefined value in template path '$path'"} = 1;
247 3         13 return '';
248             }
249              
250 53         351 return $cursor;
251             }
252              
253             1;
254              
255             __END__