File Coverage

blib/lib/Template/Tiny/Strict.pm
Criterion Covered Total %
statement 73 79 92.4
branch 33 38 86.8
condition 8 9 88.8
subroutine 8 8 100.0
pod 2 3 66.6
total 124 137 90.5


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