File Coverage

blib/lib/Template/Alloy/Velocity.pm
Criterion Covered Total %
statement 174 205 84.8
branch 112 160 70.0
condition 40 61 65.5
subroutine 5 6 83.3
pod 2 3 66.6
total 333 435 76.5


line stmt bran cond sub pod time code
1             package Template::Alloy::Velocity;
2              
3             =head1 NAME
4              
5             Template::Alloy::Velocity - Velocity (VTL) role
6              
7             =cut
8              
9 3     3   23 use strict;
  3         12  
  3         155  
10 3     3   19 use warnings;
  3         6  
  3         215  
11 3     3   18 use Template::Alloy;
  3         7  
  3         32  
12              
13             our $VERSION = $Template::Alloy::VERSION;
14              
15 0     0 0 0 sub new { die "This class is a role for use by packages such as Template::Alloy" }
16              
17             ###----------------------------------------------------------------###
18              
19             sub parse_tree_velocity {
20 266     266 1 449 my $self = shift;
21 266         349 my $str_ref = shift;
22 266 50 33     1058 if (! $str_ref || ! defined $$str_ref) {
23 0         0 $self->throw('parse.no_string', "No string or undefined during parse", undef, 1);
24             }
25              
26 266   50     1568 local $self->{'V2EQUALS'} = $self->{'V2EQUALS'} || 0;
27 266 50       987 local $self->{'INTERPOLATE'} = defined($self->{'INTERPOLATE'}) ? $self->{'INTERPOLATE'} : 1;
28 266 50       852 local $self->{'V1DOLLAR'} = defined($self->{'V1DOLLAR'}) ? $self->{'V1DOLLAR'} : 1;
29 266 50       777 local $self->{'ANYCASE'} = defined($self->{'ANYCASE'}) ? $self->{'ANYCASE'} : 1;
30 266 100       784 local $self->{'AUTO_EVAL'} = defined($self->{'AUTO_EVAL'}) ? $self->{'AUTO_EVAL'} : 1;
31 266 50       833 local $self->{'SHOW_UNDEFINED_INTERP'} = defined($self->{'SHOW_UNDEFINED_INTERP'}) ? $self->{'SHOW_UNDEFINED_INTERP'} : 1;
32              
33 266         1190 local $self->{'START_TAG'} = qr{\#};
34 266 50       2142 local $self->{'_start_tag'} = (! $self->{'INTERPOLATE'}) ? $self->{'START_TAG'} : qr{(?: $self->{'START_TAG'} | (\$))}sx;
35 266         678 local $self->{'_end_tag'}; # changes over time
36              
37 266         402 local @{ $Template::Alloy::Parse::ALIASES }{qw(PARSE INCLUDE _INCLUDE ELSEIF)}
  266         1315  
38             = qw(PROCESS INSERT INCLUDE ELSIF);
39 266         602 my $dirs = $Template::Alloy::Parse::DIRECTIVES;
40 266         341 my $aliases = $Template::Alloy::Parse::ALIASES;
41 266         884 local @{ $dirs }{ keys %$aliases } = values %$aliases; # temporarily add to the table
  266         1384  
42 266         681 local @{ $self }{@Template::Alloy::CONFIG_COMPILETIME} = @{ $self }{@Template::Alloy::CONFIG_COMPILETIME};
  266         3397  
  266         1555  
43 266 50       1018 delete $dirs->{'JS'} if ! $self->{'COMPILE_JS'};
44              
45 266         361 my @tree; # the parsed tree
46 266         389 my $pointer = \@tree; # pointer to current tree to handle nested blocks
47 266         325 my @state; # maintain block levels
48 266         3151 local $self->{'_state'} = \@state; # allow for items to introspect (usually BLOCKS)
49 266         562 local $self->{'_no_interp'} = 0; # no interpolation in perl
50 266         279 my @in_view; # let us know if we are in a view
51             my @blocks; # storage for defined blocks
52 0         0 my @meta; # place to store any found meta information (to go into META)
53 266         515 my $post_chomp = 0; # previous post_chomp setting
54 266         313 my $continue = 0; # flag for multiple directives in the same tag
55 266         352 my $post_op = 0; # found a post-operative DIRECTIVE
56 266         323 my $capture; # flag to start capture
57             my $func;
58 0         0 my $pre_chomp;
59 0         0 my $node;
60 0         0 my $macro_block;
61 266         878 pos($$str_ref) = 0;
62              
63 266         456 while (1) {
64             ### allow for #set(foo = PROCESS foo)
65 784 100       1539 if ($capture) {
66 28 100       111 if ($macro_block) {
    50          
67 16         31 $macro_block = 0;
68 16         28 push @state, $capture;
69 16   50     90 $pointer = $capture->[4] ||= [];
70 16         31 undef $capture;
71 16         30 next;
72             } elsif ($$str_ref =~ m{ \G \s* (\w+)\b }gcx) {
73 12 50       59 $func = $self->{'ANYCASE'} ? uc($1) : $1;
74 12 50       35 $func = $aliases->{$func} if $aliases->{$func};
75 12 50       39 $self->throw('parse', "Found unknown DIRECTIVE ($func)", undef, pos($$str_ref) - length($func))
76             if ! $dirs->{$func};
77 12         56 $node = [$func, pos($$str_ref) - length($func), undef];
78             } else {
79 0         0 $self->throw('parse', "Error looking for block in capture DIRECTIVE", undef, pos($$str_ref));
80             }
81              
82 12         14 push @{ $capture->[4] }, $node;
  12         33  
83 12         21 undef $capture;
84              
85             ### handle all other
86             } else {
87             ### find the next opening tag
88 756 100       5108 $$str_ref =~ m{ \G (.*?) $self->{'_start_tag'} }gcxs
89             || last;
90 496         1371 my ($text, $dollar) = ($1, $2);
91              
92             ### found a text portion - chomp it and store it
93 496 100       1163 if (length $text) {
94 160 100       424 if (! $post_chomp) { }
    50          
    50          
    0          
95 0         0 elsif ($post_chomp == 1) { $text =~ s{ ^ [^\S\n]* \n }{}x }
96 16         64 elsif ($post_chomp == 2) { $text =~ s{ ^ \s+ }{ }x }
97 0         0 elsif ($post_chomp == 3) { $text =~ s{ ^ \s+ }{}x }
98 160 50       587 push @$pointer, $text if length $text;
99             }
100              
101             ### handle variable interpolation ($2 eq $)
102 496 100       1168 if ($dollar) {
103             ### inspect previous text chunk for escape slashes
104 168 100       492 my $n = ($text =~ m{ (\\+) $ }x) ? length($1) : 0;
105 168 100 66     525 if ($n && ! $self->{'_no_interp'}) {
106 12         32 my $chop = int(($n + 1) / 2); # were there odd escapes
107 12 50 33     79 substr($pointer->[-1], -$chop, $chop, '') if defined($pointer->[-1]) && ! ref($pointer->[-1]);
108             }
109 168 100 66     924 if ($self->{'_no_interp'} || $n % 2) {
110 8         17 push @$pointer, $dollar;
111 8         16 next;
112             }
113              
114 160         358 my $not = $$str_ref =~ m{ \G ! }gcx;
115 160         232 my $mark = pos($$str_ref);
116 160         174 my $ref;
117 160 100       418 if ($$str_ref =~ m{ \G \{ }gcx) {
118 8         33 local $self->{'_operator_precedence'} = 0; # allow operators
119 8         34 local $self->{'_end_tag'} = qr{\}};
120 8         31 $ref = $self->parse_expr($str_ref);
121 8 50       81 $$str_ref =~ m{ \G \s* $Template::Alloy::Parse::QR_COMMENTS \} }gcxo
122             || $self->throw('parse', 'Missing close }', undef, pos($$str_ref));
123             } else {
124 152         402 local $self->{'_operator_precedence'} = 1; # no operators
125 152         579 local $Template::Alloy::Parse::QR_COMMENTS = qr{};
126 152         548 $ref = $self->parse_expr($str_ref);
127             }
128 160 50       452 $self->throw('parse', "Error while parsing for interpolated string", undef, pos($$str_ref))
129             if ! defined $ref;
130 160 100 66     929 if (! $not && $self->{'SHOW_UNDEFINED_INTERP'}) {
131 148         816 $ref = [[undef, '//', $ref, '$'.substr($$str_ref, $mark, pos($$str_ref)-$mark)], 0];
132             }
133 160         497 push @$pointer, ['GET', $mark, pos($$str_ref), $ref];
134 160         233 $post_chomp = 0; # no chomping after dollar vars
135 160         314 next;
136             }
137              
138             ### allow for escaped #
139 328 50       902 my $n = ($text =~ m{ (\\+) $ }x) ? length($1) : 0;
140 328 50       827 if ($n) {
141 0         0 my $chop = int(($n + 1) / 2); # were there odd escapes
142 0 0 0     0 substr($pointer->[-1], -$chop, $chop, '') if defined($pointer->[-1]) && ! ref($pointer->[-1]);
143 0 0       0 if ($n % 2) {
144 0         0 push @$pointer, '#';
145 0         0 next;
146             }
147             }
148 328 100 100     1998 if ($$str_ref =~ m{ \G \# .*\n? }gcx # single line comment
149             || $$str_ref =~ m{ \G \* .*? \*\# }gcxs) { # multi-line comment
150 10         21 next;
151             }
152              
153 318 100 100     2279 $$str_ref =~ m{ \G (\w+) }gcx
154             || $$str_ref =~ m{ \G \{ (\w+) (\}) }gcx
155             || $self->throw('parse', 'Missing directive name', undef, pos($$str_ref));
156 316 50       1413 $func = $self->{'ANYCASE'} ? uc($1) : $1;
157              
158             ### make sure we know this directive - if we don't then allow fallback to macros (velocity allows them as directives)
159 316 100       946 $func = $aliases->{$func} if $aliases->{$func};
160 316 100       768 if (! $dirs->{$func}) {
161 12         29 my $name = $1;
162 12 50       51 my $mark = pos($$str_ref) - length($func) - ($2 ? 2 : 0);
163 12         19 my $args = 0;
164 12 50       49 if ($$str_ref =~ m{ \G \( }gcx) {
165 12         32 local $self->{'_operator_precedence'} = 0; # reset precedence
166 12         80 $args = $self->parse_args($str_ref, {is_parened => 1});
167 12 50       105 $$str_ref =~ m{ \G \s* $Template::Alloy::Parse::QR_COMMENTS \) }gcxo
168             || $self->throw('parse.missing.paren', "Missing close \) in directive args", undef, pos($$str_ref));
169             }
170 12         59 $node = ['GET', $mark, pos($$str_ref), [$name, $args]];
171 12         59 push @$pointer, $node;
172 12         28 next;
173             #$self->throw('parse', "Found unknow DIRECTIVE ($func)", undef, pos($$str_ref) - length($func));
174             }
175 304         1011 $node = [$func, pos($$str_ref), undef];
176              
177 304 100       984 if ($$str_ref =~ m{ \G \( ([+=~-]?) }gcx) {
178 198         781 $self->{'_end_tag'} = qr{\s*([+=~-]?)\)};
179 198         434 $pre_chomp = $1;
180             } else {
181 106         375 $self->{'_end_tag'} = '';
182 106         223 $pre_chomp = '';
183             }
184              
185             ### take care of chomping (this is an extention to velocity
186 304   100     1328 $pre_chomp ||= $self->{'PRE_CHOMP'};
187 304 100       905 $pre_chomp =~ y/-=~+/1230/ if $pre_chomp;
188 304 100 100     1026 if ($pre_chomp && $pointer->[-1] && ! ref $pointer->[-1]) {
      100        
189 14 100       73 if ($pre_chomp == 1) { $pointer->[-1] =~ s{ (?:\n|^) [^\S\n]* \z }{}x }
  2 50       15  
    50          
190 0         0 elsif ($pre_chomp == 2) { $pointer->[-1] =~ s{ (\s+) \z }{ }x }
191 12         58 elsif ($pre_chomp == 3) { $pointer->[-1] =~ s{ (\s+) \z }{}x }
192 14 50       58 splice(@$pointer, -1, 1, ()) if ! length $pointer->[-1]; # remove the node if it is zero length
193             }
194              
195 304         942 push @$pointer, $node;
196             }
197              
198 316         1097 $$str_ref =~ m{ \G \s+ }gcx;
199              
200             ### parse remaining tag details
201 316 100       705 if ($func ne 'END') {
202 236         353 $node->[3] = eval { $dirs->{$func}->[0]->($self, $str_ref, $node) };
  236         991  
203 236 100       691 if (my $err = $@) {
204 2 50 33     20 $err->node($node) if UNIVERSAL::can($err, 'node') && ! $err->node;
205 2         42 die $err;
206             }
207 234         527 $node->[2] = pos $$str_ref;
208             }
209              
210             ### handle ending tags - or continuation blocks
211 314 100 100     2044 if ($func eq 'END' || $dirs->{$func}->[4]) {
    100          
    50          
212 96 50       234 if (! @state) {
213 0         0 print Data::Dumper::Dumper(\@tree);
214 0         0 $self->throw('parse', "Found an $func tag while not in a block", $node, pos($$str_ref));
215             }
216 96         164 my $parent_node = pop @state;
217              
218             ### handle continuation blocks such as elsif, else, catch etc
219 96 100       285 if ($dirs->{$func}->[4]) {
220 16         26 pop @$pointer; # we will store the node in the parent instead
221 16         34 $parent_node->[5] = $node;
222 16         25 my $parent_type = $parent_node->[0];
223 16 50       72 if (! $dirs->{$func}->[4]->{$parent_type}) {
224 0         0 $self->throw('parse', "Found unmatched nested block", $node, pos($$str_ref));
225             }
226             }
227              
228             ### restore the pointer up one level (because we hit the end of a block)
229 96 100       255 $pointer = (! @state) ? \@tree : $state[-1]->[4];
230              
231             ### normal end block
232 96 100       296 if (! $dirs->{$func}->[4]) {
233 80 100       415 if ($parent_node->[0] eq 'BLOCK') { # move BLOCKS to front
    50          
    50          
234 14 50 33     83 if (defined($parent_node->[3]) && @in_view) {
235 0         0 push @{ $in_view[-1] }, $parent_node;
  0         0  
236             } else {
237 14         31 push @blocks, $parent_node;
238             }
239 14 100 66     90 if ($pointer->[-1] && ! $pointer->[-1]->[6]) { # capturing doesn't remove the var
240 4         14 splice(@$pointer, -1, 1, ());
241             }
242             } elsif ($parent_node->[0] eq 'VIEW') {
243 0         0 my $ref = { map {($_->[3] => $_->[4])} @{ pop @in_view }};
  0         0  
  0         0  
244 0         0 unshift @{ $parent_node->[3] }, $ref;
  0         0  
245             } elsif ($dirs->{$parent_node->[0]}->[5]) { # allow no_interp to turn on and off
246 0         0 $self->{'_no_interp'}--;
247             }
248              
249              
250             ### continuation block - such as an elsif
251             } else {
252 16         27 push @state, $node;
253 16   50     78 $pointer = $node->[4] ||= [];
254             }
255 96         207 $node->[2] = pos $$str_ref;
256              
257             ### handle block directives
258             } elsif ($dirs->{$func}->[2]) {
259 64         124 push @state, $node;
260 64   50     347 $pointer = $node->[4] ||= []; # allow future parsed nodes before END tag to end up in current node
261 64 50       159 push @in_view, [] if $func eq 'VIEW';
262 64 50       215 $self->{'_no_interp'}++ if $dirs->{$node->[0]}->[5] # allow no_interp to turn on and off
263              
264             } elsif ($func eq 'META') {
265 0         0 unshift @meta, @{ $node->[3] }; # first defined win
  0         0  
266 0         0 $node->[3] = undef; # only let these be defined once - at the front of the tree
267             }
268              
269              
270             ### look for the closing tag
271 314 100       4981 if ($$str_ref =~ m{ \G $self->{'_end_tag'} }gcxs) {
    100          
272 300   100     1466 $post_chomp = $1 || $self->{'POST_CHOMP'};
273 300 100       672 $post_chomp =~ y/-=~+/1230/ if $post_chomp;
274 300         363 $continue = 0;
275 300         331 $post_op = 0;
276              
277 300 100 66     824 if ($node->[6] && $node->[0] eq 'MACRO') { # allow for MACRO's without a BLOCK
278 16         24 $capture = $node;
279 16         29 $macro_block = 1;
280             }
281 300         650 next;
282              
283             ### setup capturing
284             } elsif ($node->[6]) {
285 12         22 $capture = $node;
286 12         38 next;
287              
288             ### no closing tag
289             } else {
290 2         14 $self->throw('parse', "Not sure how to handle tag", $node, pos($$str_ref));
291             }
292             }
293              
294             ### cleanup the tree
295 260 100       864 unshift(@tree, @blocks) if @blocks;
296 260 50       581 unshift(@tree, ['META', 1, 1, \@meta]) if @meta;
297 260 50       870 $self->throw('parse', "Missing end tag", $state[-1], pos($$str_ref)) if @state > 0;
298              
299             ### pull off the last text portion - if any
300 260 100       730 if (pos($$str_ref) != length($$str_ref)) {
301 80         228 my $text = substr $$str_ref, pos($$str_ref);
302 80 100       212 if (! $post_chomp) { }
    50          
    0          
    0          
303 2         11 elsif ($post_chomp == 1) { $text =~ s{ ^ [^\S\n]* \n }{}x }
304 0         0 elsif ($post_chomp == 2) { $text =~ s{ ^ \s+ }{ }x }
305 0         0 elsif ($post_chomp == 3) { $text =~ s{ ^ \s+ }{}x }
306 80 100       296 push @$pointer, $text if length $text;
307             }
308              
309 260         5231 return \@tree;
310             }
311              
312             sub merge {
313 200     200 1 2008 my ($self, $in, $swap, $out) = @_;
314 200   50     1183 local $self->{'SYNTAX'} = $self->{'SYNTAX'} || 'velocity';
315 200         710 return $self->process_simple($in, $swap, $out);
316             }
317              
318             ###----------------------------------------------------------------###
319              
320             1;
321              
322             __END__