File Coverage

blib/lib/Template/Alloy/VMethod.pm
Criterion Covered Total %
statement 217 250 86.8
branch 81 132 61.3
condition 13 37 35.1
subroutine 49 55 89.0
pod 2 24 8.3
total 362 498 72.6


line stmt bran cond sub pod time code
1             package Template::Alloy::VMethod;
2              
3             =head1 NAME
4              
5             Template::Alloy::VMethod - VMethod role.
6              
7             =cut
8              
9 10     10   59 use strict;
  10         28  
  10         445  
10 10     10   52 use warnings;
  10         15  
  10         1294  
11 10     10   87 use Template::Alloy;
  10         16  
  10         257  
12 10     10   61 use base qw(Exporter);
  10         15  
  10         3561  
13             our @EXPORT_OK = qw(define_vmethod
14             $ITEM_OPS $ITEM_METHODS
15             $SCALAR_OPS
16             $LIST_OPS $LIST_METHODS
17             $HASH_OPS
18             $FILTER_OPS
19             $VOBJS);
20              
21 0     0 0 0 sub new { die "This class is a role for use by packages such as Template::Alloy" }
22              
23             ###----------------------------------------------------------------###
24              
25             our ($JSON, $JSONP);
26 0   0 0 1 0 sub json { $JSON ||= do { require JSON; JSON->new->utf8->allow_nonref->allow_unknown->allow_blessed->convert_blessed->canonical } }
  0         0  
  0         0  
27 0   0 0 0 0 sub jsonp { $JSONP ||= do { require JSON; JSON->new->utf8->allow_nonref->allow_unknown->allow_blessed->convert_blessed->canonical->pretty } }
  0         0  
  0         0  
28              
29             our $SCALAR_OPS = our $ITEM_OPS = {
30             '0' => sub { $_[0] },
31 10     10   59 abs => sub { no warnings; abs shift },
  10         17  
  10         578  
32 10     10   49 atan2 => sub { no warnings; atan2($_[0], $_[1]) },
  10         25  
  10         1724  
33             chunk => \&vmethod_chunk,
34             collapse => sub { local $_ = $_[0]; s/^\s+//; s/\s+$//; s/\s+/ /g; $_ },
35 10     10   54 cos => sub { no warnings; cos $_[0] },
  10         19  
  10         2015  
36             defined => sub { defined $_[0] ? 1 : '' },
37             dquote => sub { local $_ = $_[0]; return if ! $_; s/([\"\\])/\\$1/g; s/\n/\\n/g; $_ },
38 10     10   51 exp => sub { no warnings; exp $_[0] },
  10         19  
  10         943  
39             fmt => \&vmethod_fmt_scalar,
40             'format' => \&vmethod_format,
41             hash => sub { {value => $_[0]} },
42 10     10   59 hex => sub { no warnings; hex $_[0] },
  10         22  
  10         1726  
43             html => sub { local $_ = $_[0]; return $_ if ! $_; s/&/&/g; s//>/g; s/\"/"/g; $_ },
44             indent => \&vmethod_indent,
45 10     10   60 int => sub { no warnings; int $_[0] },
  10         18  
  10         3174  
46             item => sub { $_[0] },
47             js => sub { local $_ = $_[0]; return if ! $_; s/\n/\\n/g; s/\r/\\r/g; s/(?
48             json => sub { return json()->encode($_[0]) if ! $_[1]; my $j = jsonp()->encode($_[0]); chomp $j; $j },
49             lc => sub { lc $_[0] },
50             lcfirst => sub { lcfirst $_[0] },
51             length => sub { defined($_[0]) ? length($_[0]) : 0 },
52             list => sub { [$_[0]] },
53 10     10   71 log => sub { no warnings; log $_[0] },
  10         19  
  10         1617  
54             lower => sub { lc $_[0] },
55             match => \&vmethod_match,
56             new => sub { defined $_[0] ? $_[0] : '' },
57             none => sub { $_[0] },
58             null => sub { '' },
59 10     10   56 oct => sub { no warnings; oct $_[0] },
  10         18  
  10         528  
60 10     10   68 print => sub { no warnings; "@_" },
  10         55  
  10         615  
61 10     10   54 rand => sub { no warnings; rand shift },
  10         60  
  10         2110  
62             remove => sub { vmethod_replace(shift, shift, '', 1) },
63             repeat => \&vmethod_repeat,
64             replace => \&vmethod_replace,
65             'return' => \&vmethod_return,
66             search => sub { my ($str, $pat) = @_; return $str if ! defined $str || ! defined $pat; return $str =~ /$pat/ },
67 10     10   63 sin => sub { no warnings; sin $_[0] },
  10         18  
  10         734  
68             size => sub { 1 },
69             split => \&vmethod_split,
70 10     10   57 sprintf => sub { no warnings; my $pat = shift; sprintf($pat, @_) },
  10         14  
  10         571  
71 10     10   52 sqrt => sub { no warnings; sqrt $_[0] },
  10         26  
  10         1377  
72             squote => sub { local $_ = $_[0]; return if ! $_; s/([\'\\])/\\$1/g; $_ },
73 10     10   63 srand => sub { no warnings; srand $_[0]; '' },
  10         22  
  10         5937  
74             stderr => sub { print STDERR $_[0]; '' },
75             substr => \&vmethod_substr,
76             trim => sub { local $_ = $_[0]; s/^\s+//; s/\s+$//; $_ },
77             uc => sub { uc $_[0] },
78             ucfirst => sub { ucfirst $_[0] },
79             upper => sub { uc $_[0] },
80             uri => \&vmethod_uri,
81             url => \&vmethod_url,
82             xml => sub { local $_ = $_[0]; s/&/&/g; s//>/g; s/\"/"/g; s/\'/'/g; $_ },
83             };
84              
85             our $ITEM_METHODS = {
86             eval => \&Template::Alloy::item_method_eval,
87             evaltt => \&Template::Alloy::item_method_eval,
88             file => \&item_method_redirect,
89             redirect => \&item_method_redirect,
90             block_exists => sub { defined($_[1]) && UNIVERSAL::isa($_[0], 'HASH') && $_[0]->{'BLOCKS'} && exists($_[0]->{'BLOCKS'}->{$_[1]}) || 0 },
91             };
92              
93             our $FILTER_OPS = {}; # generally - non-dynamic filters belong in scalar ops
94              
95             our $LIST_OPS = {
96             defined => sub { return 1 if @_ == 1; defined $_[0]->[ defined($_[1]) ? $_[1] : 0 ] },
97             first => sub { my ($ref, $i) = @_; return $ref->[0] if ! $i; return [@{$ref}[0 .. $i - 1]]},
98             fmt => \&vmethod_fmt_list,
99 10     10   63 grep => sub { no warnings; my ($ref, $pat) = @_; UNIVERSAL::isa($pat, 'CODE') ? [grep {$pat->($_)} @$ref] : [grep {/$pat/} @$ref] },
  10         19  
  10         1143  
100 10     10   56 hash => sub { no warnings; my $list = shift; return {@$list} if ! @_; my $i = shift || 0; return {map {$i++ => $_} @$list} },
  10         16  
  10         2196  
101             import => sub { my $ref = shift; push @$ref, grep {defined} map {ref eq 'ARRAY' ? @$_ : undef} @_; '' },
102             item => sub { $_[0]->[ $_[1] || 0 ] },
103 10     10   74 join => sub { my ($ref, $join) = @_; $join = ' ' if ! defined $join; no warnings; return join $join, @$ref },
  10         22  
  10         2081  
104             json => sub { return json()->encode($_[0]) if ! $_[1]; my $j = jsonp()->encode($_[0]); chomp $j; $j },
105             last => sub { my ($ref, $i) = @_; return $ref->[-1] if ! $i; return [@{$ref}[-$i .. -1]]},
106             list => sub { $_[0] },
107 10     10   54 map => sub { no warnings; my ($ref, $code) = @_; UNIVERSAL::isa($code, 'CODE') ? [map {$code->($_)} @$ref] : [map {$code} @$ref] },
  10         19  
  10         1425  
108 10     10   63 max => sub { no warnings; $#{ $_[0] } },
  10         24  
  10         1247  
109             merge => sub { my $ref = shift; return [ @$ref, grep {defined} map {ref eq 'ARRAY' ? @$_ : undef} @_ ] },
110 10     10   74 new => sub { no warnings; return [@_] },
  10         30  
  10         2155  
111             null => sub { '' },
112             nsort => \&vmethod_nsort,
113             pick => \&vmethod_pick,
114             pop => sub { pop @{ $_[0] } },
115             push => sub { my $ref = shift; push @$ref, @_; return '' },
116             'return' => \&vmethod_return,
117             reverse => sub { [ reverse @{ $_[0] } ] },
118             shift => sub { shift @{ $_[0] } },
119 10     10   59 size => sub { no warnings; scalar @{ $_[0] } },
  10         17  
  10         7228  
120             slice => sub { my ($ref, $a, $b) = @_; $a ||= 0; $b = $#$ref if ! defined $b; return [@{$ref}[$a .. $b]] },
121             sort => \&vmethod_sort,
122             splice => \&vmethod_splice,
123             unique => sub { my %u; return [ grep { ! $u{$_}++ } @{ $_[0] } ] },
124             unshift => sub { my $ref = shift; unshift @$ref, @_; return '' },
125             };
126              
127             our $LIST_METHODS = {
128             };
129              
130             our $HASH_OPS = {
131             defined => sub { return 1 if @_ == 1; defined $_[0]->{ defined($_[1]) ? $_[1] : '' } },
132             delete => sub { my $h = shift; delete @{ $h }{map {defined($_) ? $_ : ''} @_}; '' },
133             each => sub { [%{ $_[0] }] },
134             exists => sub { exists $_[0]->{ defined($_[1]) ? $_[1] : '' } },
135             fmt => \&vmethod_fmt_hash,
136             hash => sub { $_[0] },
137             import => sub { my ($a, $b) = @_; @{$a}{keys %$b} = values %$b if ref($b) eq 'HASH'; '' },
138             item => sub { my ($h, $k) = @_; $k = '' if ! defined $k; $Template::Alloy::QR_PRIVATE && $k =~ $Template::Alloy::QR_PRIVATE ? undef : $h->{$k} },
139             items => sub { [ %{ $_[0] } ] },
140             json => sub { return json()->encode($_[0]) if ! $_[1]; my $j = jsonp()->encode($_[0]); chomp $j; $j },
141             keys => sub { [keys %{ $_[0] }] },
142             list => \&vmethod_list_hash,
143 10     10   71 new => sub { no warnings; return (@_ == 1 && ref $_[-1] eq 'HASH') ? $_[-1] : {@_} },
  10         19  
  10         7953  
144             null => sub { '' },
145             nsort => sub { my $ref = shift; [sort { $ref->{$a} <=> $ref->{$b}} keys %$ref] },
146             pairs => sub { [map { {key => $_, value => $_[0]->{$_}} } sort keys %{ $_[0] } ] },
147             'return' => \&vmethod_return,
148             size => sub { scalar keys %{ $_[0] } },
149             sort => sub { my $ref = shift; [sort {lc $ref->{$a} cmp lc $ref->{$b}} keys %$ref] },
150             values => sub { [values %{ $_[0] }] },
151             };
152              
153             our $VOBJS = {
154             Text => $SCALAR_OPS,
155             List => $LIST_OPS,
156             Hash => $HASH_OPS,
157             };
158             foreach (values %$VOBJS) {
159             $_->{'Text'} = $_->{'fmt'};
160             $_->{'Hash'} = $_->{'hash'};
161             $_->{'List'} = $_->{'list'};
162             }
163              
164             ###----------------------------------------------------------------###
165             ### long virtual methods or filters
166             ### many of these vmethods have used code from Template/Stash.pm to
167             ### assure conformance with the TT spec.
168              
169             sub define_vmethod {
170 0     0 1 0 my ($self, $type, $name, $sub) = @_;
171 0 0       0 if ( $type =~ /scalar|item|text/i) { $SCALAR_OPS->{$name} = $sub }
  0 0       0  
    0          
    0          
172 0         0 elsif ($type =~ /array|list/i ) { $LIST_OPS->{ $name} = $sub }
173 0         0 elsif ($type =~ /hash/i ) { $HASH_OPS->{ $name} = $sub }
174 0         0 elsif ($type =~ /filter/i ) { $FILTER_OPS->{$name} = $sub }
175 0         0 else { die "Invalid type vmethod type $type" }
176 0         0 return 1;
177             }
178              
179             sub vmethod_fmt_scalar {
180 96 50   96 0 190 my $str = shift; $str = '' if ! defined $str;
  96         243  
181 96 100       139 my $pat = shift; $pat = '%s' if ! defined $pat;
  96         246  
182 10     10   71 no warnings;
  10         22  
  10         1344  
183 96 100       1068 return @_ ? sprintf($pat, $_[0], $str)
184             : sprintf($pat, $str);
185             }
186              
187             sub vmethod_fmt_list {
188 27   50 27 0 98 my $ref = shift || return '';
189 27 100       44 my $pat = shift; $pat = '%s' if ! defined $pat;
  27         71  
190 27 100       47 my $sep = shift; $sep = ' ' if ! defined $sep;
  27         79  
191 10     10   107 no warnings;
  10         32  
  10         2128  
192 12         85 return @_ ? join($sep, map {sprintf $pat, $_[0], $_} @$ref)
  51         256  
193 27 100       81 : join($sep, map {sprintf $pat, $_} @$ref);
194             }
195              
196             sub vmethod_fmt_hash {
197 30   50 30 0 94 my $ref = shift || return '';
198 30 100       56 my $pat = shift; $pat = "%s\t%s" if ! defined $pat;
  30         105  
199 30 100       51 my $sep = shift; $sep = "\n" if ! defined $sep;
  30         85  
200 10     10   57 no warnings;
  10         21  
  10         10037  
201 42         283 return ! @_ ? join($sep, map {sprintf $pat, $_, $ref->{$_}} sort keys %$ref)
  6         43  
202 12         92 : @_ == 1 ? join($sep, map {sprintf $pat, $_[0], $_, $ref->{$_}} sort keys %$ref) # don't get to pick - it applies to the key
203 30 100       224 : join($sep, map {sprintf $pat, $_[0], $_, $_[1], $ref->{$_}} sort keys %$ref);
    100          
204             }
205              
206             sub vmethod_chunk {
207 6     6 0 16 my $str = shift;
208 6   50     18 my $size = shift || 1;
209 6         8 my @list;
210 6 100       18 if ($size < 0) { # chunk from the opposite end
211 3         8 $str = reverse $str;
212 3         6 $size = -$size;
213 3         105 unshift(@list, scalar reverse $1) while $str =~ /( .{$size} | .+ )/xg;
214             } else {
215 3         137 push(@list, $1) while $str =~ /( .{$size} | .+ )/xg;
216             }
217 6         38 return \@list;
218             }
219              
220             sub vmethod_indent {
221 12 50   12 0 35 my $str = shift; $str = '' if ! defined $str;
  12         35  
222 12 100       17 my $pre = shift; $pre = 4 if ! defined $pre;
  12         30  
223 12 100       95 $pre = ' ' x $pre if $pre =~ /^\d+$/;
224 12         64 $str =~ s/^/$pre/mg;
225 12         60 return $str;
226             }
227              
228             sub vmethod_format {
229 18 50   18 0 37 my $str = shift; $str = '' if ! defined $str;
  18         49  
230 18 50       31 my $pat = shift; $pat = '%s' if ! defined $pat;
  18         43  
231 18 100       41 if (@_) {
232 9         31 return join "\n", map{ sprintf $pat, $_[0], $_ } split(/\n/, $str);
  9         95  
233             } else {
234 9         39 return join "\n", map{ sprintf $pat, $_ } split(/\n/, $str);
  12         86  
235             }
236             }
237              
238             sub vmethod_list_hash {
239 12     12 0 27 my ($hash, $what) = @_;
240 12 50 33     105 $what = 'pairs' if ! $what || $what !~ /^(keys|values|each|pairs)$/;
241 12         42 return $HASH_OPS->{$what}->($hash);
242             }
243              
244              
245             sub vmethod_match {
246 39     39 0 92 my ($str, $pat, $global) = @_;
247 39 50 33     176 return [] if ! defined $str || ! defined $pat;
248 39 100       478 my @res = $global ? ($str =~ /$pat/g) : ($str =~ /$pat/);
249 39 100       284 return @res ? \@res : '';
250             }
251              
252             sub vmethod_nsort {
253 6     6 0 14 my ($list, $field) = @_;
254 6         28 return defined($field)
255 3 0       15 ? [map {$_->[0]} sort {$a->[1] <=> $b->[1]} map {[$_, (ref $_ eq 'HASH' ? $_->{$field}
  6 50       57  
  6         27  
256             : UNIVERSAL::can($_, $field) ? $_->$field()
257             : $_)]} @$list ]
258 6 100       67 : [sort {$a <=> $b} @$list];
259             }
260              
261             sub vmethod_pick {
262 9     9 0 22 my $ref = shift;
263 10     10   64 no warnings;
  10         21  
  10         18464  
264 9         19 my $n = int(shift);
265 9 100       36 $n = 1 if $n < 1;
266 9         102 my @ind = map { $ref->[ rand @$ref ] } 1 .. $n;
  21         73  
267 9 100       61 return $n == 1 ? $ind[0] : \@ind;
268             }
269              
270             sub vmethod_repeat {
271 99     99 0 217 my ($str, $n, $join) = @_;
272 99 50 33     464 return '' if ! defined $str || ! length $str;
273 99 100 66     403 $n = 1 if ! defined($n) || ! length $n;
274 99 100       244 $join = '' if ! defined $join;
275 99         852 return join $join, ($str) x $n;
276             }
277              
278             ### This method is a combination of my submissions along
279             ### with work from Andy Wardley, Sergey Martynoff, Nik Clayton, and Josh Rosenbaum
280             sub vmethod_replace {
281 37     37 0 93 my ($text, $pattern, $replace, $global) = @_;
282 37 50       111 $text = '' unless defined $text;
283 37 50       2155 $pattern = '' unless defined $pattern;
284 37 50       85 $replace = '' unless defined $replace;
285 37 100       80 $global = 1 unless defined $global;
286             my $expand = sub {
287 48     48   104 my ($chunk, $start, $end) = @_;
288 48         156 $chunk =~ s{ \\(\\|\$) | \$ (\d+) }{
289 6 50 33     81 $1 ? $1
    50          
290             : ($2 > $#$start || $2 == 0) ? ''
291             : substr($text, $start->[$2], $end->[$2] - $start->[$2]);
292             }exg;
293 48         170 $chunk;
294 37         213 };
295 37 100       95 if ($global) {
296 34         527 $text =~ s{$pattern}{ $expand->($replace, [@-], [@+]) }eg;
  45         279  
297             } else {
298 3         42 $text =~ s{$pattern}{ $expand->($replace, [@-], [@+]) }e;
  3         22  
299             }
300 37         356 return $text;
301             }
302              
303             sub vmethod_return {
304 0     0 0 0 my $obj = shift;
305 0         0 Template::Alloy->throw('return', {return_val => $obj});
306             }
307              
308             sub vmethod_sort {
309 60     60 0 120 my ($list, $field) = @_;
310 60 100       176 if (! defined $field) {
    100          
311 54         124 return [map {$_->[0]} sort {$a->[1] cmp $b->[1]} map {[$_, lc $_]} @$list ]; # case insensitive
  159         614  
  150         323  
  159         671  
312             } elsif (UNIVERSAL::isa($field, 'CODE')) {
313 3         21 return [sort {int($field->($a, $b))} @$list];
  6         22  
314             } else {
315 3 0       7 return [map {$_->[0]} sort {$a->[1] cmp $b->[1]} map {[$_, lc(ref $_ eq 'HASH' ? $_->{$field}
  6 50       26  
  3         14  
  6         37  
316             : UNIVERSAL::can($_, $field) ? $_->$field()
317             : $_)]} @$list ];
318             }
319             }
320              
321             sub vmethod_splice {
322 12     12 0 34 my ($ref, $i, $len, @replace) = @_;
323 12 50 66     57 @replace = @{ $replace[0] } if @replace == 1 && ref $replace[0] eq 'ARRAY';
  0         0  
324 12 100       49 if (defined $len) {
    100          
325 6   50     64 return [splice @$ref, $i || 0, $len, @replace];
326             } elsif (defined $i) {
327 3         21 return [splice @$ref, $i];
328             } else {
329 3         25 return [splice @$ref];
330             }
331             }
332              
333             sub vmethod_split {
334 18     18 0 39 my ($str, $pat, $lim) = @_;
335 18 50       43 $str = '' if ! defined $str;
336 18 100       41 if (defined $lim) { return defined $pat ? [split $pat, $str, $lim] : [split ' ', $str, $lim] }
  6 100       69  
337 12 100       133 else { return defined $pat ? [split $pat, $str ] : [split ' ', $str ] }
338             }
339              
340             sub vmethod_substr {
341 27     27 0 71 my ($str, $i, $len, $replace) = @_;
342 27   50     102 $i ||= 0;
343 27 50       61 return '' if ! defined $str;
344 27 50       64 return substr($str, $i) if ! defined $len;
345 27 50       476 return substr($str, $i, $len) if ! defined $replace;
346 0         0 substr($str, $i, $len, $replace);
347 0         0 return $str;
348             }
349              
350             sub vmethod_uri {
351 6     6 0 19 my $str = shift;
352 6 50       22 return '' if ! defined $str;
353 6 50       30 utf8::upgrade($str) if defined &utf8::upgrade;
354 6         50 $str =~ s/([^A-Za-z0-9\-_.!~*\'()])/sprintf('%%%02X', ord($1))/eg;
  6         90  
355 6         35 return $str;
356             }
357              
358             sub vmethod_url {
359 2     2 0 4 my $str = shift;
360 2 50       7 return '' if ! defined $str;
361 2 50       11 utf8::upgrade($str) if defined &utf8::upgrade;
362 2         20 $str =~ s/([^;\/?:@&=+\$,A-Za-z0-9\-_.!~*\'()])/sprintf('%%%02X', ord($1))/eg;
  4         25  
363 2         11 return $str;
364             }
365              
366             sub item_method_redirect {
367 0     0 0   my ($t, $text, $file, $options) = @_;
368 0   0       my $path = $t->{'OUTPUT_PATH'} || $t->throw('redirect', 'OUTPUT_PATH is not set');
369 0 0         $t->throw('redirect', 'Invalid filename - cannot include "/../"')
370             if $file =~ m{(^|/)\.\./};
371              
372 0 0         if (! -d $path) {
373 0           require File::Path;
374 0 0         File::Path::mkpath($path) || $t->throw('redirect', "Couldn't mkpath \"$path\": $!");
375             }
376 0 0         open (my $fh, '>', "$path/$file") || $t->throw('redirect', "Couldn't open \"$file\": $!");
377 0 0         if (my $bm = (! $options) ? 0 : ref($options) ? $options->{'binmode'} : $options) {
    0          
    0          
378 0 0         if (+$bm == 1) { binmode $fh }
  0            
379 0           else { binmode $fh, $bm}
380             }
381 0           print $fh $text;
382 0           return '';
383             }
384              
385             ###----------------------------------------------------------------###
386              
387             1;
388              
389             __END__