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   74 use strict;
  10         20  
  10         338  
10 10     10   52 use warnings;
  10         19  
  10         306  
11 10     10   55 use Template::Alloy;
  10         19  
  10         238  
12 10     10   50 use base qw(Exporter);
  10         21  
  10         3434  
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   78 abs => sub { no warnings; abs shift },
  10         19  
  10         598  
32 10     10   65 atan2 => sub { no warnings; atan2($_[0], $_[1]) },
  10         20  
  10         1744  
33             chunk => \&vmethod_chunk,
34             collapse => sub { local $_ = $_[0]; s/^\s+//; s/\s+$//; s/\s+/ /g; $_ },
35 10     10   75 cos => sub { no warnings; cos $_[0] },
  10         19  
  10         1630  
36             defined => sub { defined $_[0] ? 1 : '' },
37             dquote => sub { local $_ = $_[0]; return if ! $_; s/([\"\\])/\\$1/g; s/\n/\\n/g; $_ },
38 10     10   82 exp => sub { no warnings; exp $_[0] },
  10         21  
  10         929  
39             fmt => \&vmethod_fmt_scalar,
40             'format' => \&vmethod_format,
41             hash => sub { {value => $_[0]} },
42 10     10   75 hex => sub { no warnings; hex $_[0] },
  10         17  
  10         1786  
43             html => sub { local $_ = $_[0]; return $_ if ! $_; s/&/&/g; s//>/g; s/\"/"/g; $_ },
44             indent => \&vmethod_indent,
45 10     10   73 int => sub { no warnings; int $_[0] },
  10         21  
  10         3107  
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   74 log => sub { no warnings; log $_[0] },
  10         22  
  10         1306  
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   67 oct => sub { no warnings; oct $_[0] },
  10         26  
  10         557  
60 10     10   67 print => sub { no warnings; "@_" },
  10         21  
  10         636  
61 10     10   70 rand => sub { no warnings; rand shift },
  10         30  
  10         1454  
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   96 sin => sub { no warnings; sin $_[0] },
  10         21  
  10         769  
68             size => sub { 1 },
69             split => \&vmethod_split,
70 10     10   64 sprintf => sub { no warnings; my $pat = shift; sprintf($pat, @_) },
  10         19  
  10         640  
71 10     10   72 sqrt => sub { no warnings; sqrt $_[0] },
  10         20  
  10         1490  
72             squote => sub { local $_ = $_[0]; return if ! $_; s/([\'\\])/\\$1/g; $_ },
73 10     10   71 srand => sub { no warnings; srand $_[0]; '' },
  10         18  
  10         5670  
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   77 grep => sub { no warnings; my ($ref, $pat) = @_; UNIVERSAL::isa($pat, 'CODE') ? [grep {$pat->($_)} @$ref] : [grep {/$pat/} @$ref] },
  10         21  
  10         1220  
100 10     10   109 hash => sub { no warnings; my $list = shift; return {@$list} if ! @_; my $i = shift || 0; return {map {$i++ => $_} @$list} },
  10         20  
  10         2017  
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         19  
  10         1993  
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   122 map => sub { no warnings; my ($ref, $code) = @_; UNIVERSAL::isa($code, 'CODE') ? [map {$code->($_)} @$ref] : [map {$code} @$ref] },
  10         24  
  10         1103  
108 10     10   66 max => sub { no warnings; $#{ $_[0] } },
  10         19  
  10         1172  
109             merge => sub { my $ref = shift; return [ @$ref, grep {defined} map {ref eq 'ARRAY' ? @$_ : undef} @_ ] },
110 10     10   68 new => sub { no warnings; return [@_] },
  10         19  
  10         1875  
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   71 size => sub { no warnings; scalar @{ $_[0] } },
  10         27  
  10         6746  
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   76 new => sub { no warnings; return (@_ == 1 && ref $_[-1] eq 'HASH') ? $_[-1] : {@_} },
  10         19  
  10         6753  
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 196 my $str = shift; $str = '' if ! defined $str;
  96         249  
181 96 100       153 my $pat = shift; $pat = '%s' if ! defined $pat;
  96         208  
182 10     10   99 no warnings;
  10         25  
  10         1239  
183 96 100       812 return @_ ? sprintf($pat, $_[0], $str)
184             : sprintf($pat, $str);
185             }
186              
187             sub vmethod_fmt_list {
188 27   50 27 0 82 my $ref = shift || return '';
189 27 100       53 my $pat = shift; $pat = '%s' if ! defined $pat;
  27         84  
190 27 100       48 my $sep = shift; $sep = ' ' if ! defined $sep;
  27         63  
191 10     10   97 no warnings;
  10         21  
  10         1665  
192 12         79 return @_ ? join($sep, map {sprintf $pat, $_[0], $_} @$ref)
193 27 100       83 : join($sep, map {sprintf $pat, $_} @$ref);
  51         258  
194             }
195              
196             sub vmethod_fmt_hash {
197 30   50 30 0 88 my $ref = shift || return '';
198 30 100       58 my $pat = shift; $pat = "%s\t%s" if ! defined $pat;
  30         91  
199 30 100       53 my $sep = shift; $sep = "\n" if ! defined $sep;
  30         75  
200 10     10   68 no warnings;
  10         29  
  10         8787  
201 42         276 return ! @_ ? join($sep, map {sprintf $pat, $_, $ref->{$_}} sort keys %$ref)
202 6         46 : @_ == 1 ? join($sep, map {sprintf $pat, $_[0], $_, $ref->{$_}} sort keys %$ref) # don't get to pick - it applies to the key
203 30 100       235 : join($sep, map {sprintf $pat, $_[0], $_, $_[1], $ref->{$_}} sort keys %$ref);
  12 100       92  
204             }
205              
206             sub vmethod_chunk {
207 6     6 0 15 my $str = shift;
208 6   50     18 my $size = shift || 1;
209 6         12 my @list;
210 6 100       19 if ($size < 0) { # chunk from the opposite end
211 3         11 $str = reverse $str;
212 3         9 $size = -$size;
213 3         75 unshift(@list, scalar reverse $1) while $str =~ /( .{$size} | .+ )/xg;
214             } else {
215 3         98 push(@list, $1) while $str =~ /( .{$size} | .+ )/xg;
216             }
217 6         33 return \@list;
218             }
219              
220             sub vmethod_indent {
221 12 50   12 0 31 my $str = shift; $str = '' if ! defined $str;
  12         38  
222 12 100       23 my $pre = shift; $pre = 4 if ! defined $pre;
  12         33  
223 12 100       78 $pre = ' ' x $pre if $pre =~ /^\d+$/;
224 12         64 $str =~ s/^/$pre/mg;
225 12         54 return $str;
226             }
227              
228             sub vmethod_format {
229 18 50   18 0 38 my $str = shift; $str = '' if ! defined $str;
  18         46  
230 18 50       36 my $pat = shift; $pat = '%s' if ! defined $pat;
  18         42  
231 18 100       45 if (@_) {
232 9         36 return join "\n", map{ sprintf $pat, $_[0], $_ } split(/\n/, $str);
  9         84  
233             } else {
234 9         50 return join "\n", map{ sprintf $pat, $_ } split(/\n/, $str);
  12         130  
235             }
236             }
237              
238             sub vmethod_list_hash {
239 12     12 0 36 my ($hash, $what) = @_;
240 12 50 33     84 $what = 'pairs' if ! $what || $what !~ /^(keys|values|each|pairs)$/;
241 12         44 return $HASH_OPS->{$what}->($hash);
242             }
243              
244              
245             sub vmethod_match {
246 39     39 0 121 my ($str, $pat, $global) = @_;
247 39 50 33     161 return [] if ! defined $str || ! defined $pat;
248 39 100       454 my @res = $global ? ($str =~ /$pat/g) : ($str =~ /$pat/);
249 39 100       225 return @res ? \@res : '';
250             }
251              
252             sub vmethod_nsort {
253 6     6 0 20 my ($list, $field) = @_;
254             return defined($field)
255 6 0       24 ? [map {$_->[0]} sort {$a->[1] <=> $b->[1]} map {[$_, (ref $_ eq 'HASH' ? $_->{$field}
  3 50       13  
  6         33  
256             : UNIVERSAL::can($_, $field) ? $_->$field()
257             : $_)]} @$list ]
258 6 100       37 : [sort {$a <=> $b} @$list];
  6         27  
259             }
260              
261             sub vmethod_pick {
262 9     9 0 22 my $ref = shift;
263 10     10   82 no warnings;
  10         18  
  10         16389  
264 9         29 my $n = int(shift);
265 9 100       28 $n = 1 if $n < 1;
266 9         26 my @ind = map { $ref->[ rand @$ref ] } 1 .. $n;
  21         72  
267 9 100       58 return $n == 1 ? $ind[0] : \@ind;
268             }
269              
270             sub vmethod_repeat {
271 99     99 0 344 my ($str, $n, $join) = @_;
272 99 50 33     472 return '' if ! defined $str || ! length $str;
273 99 100 66     381 $n = 1 if ! defined($n) || ! length $n;
274 99 100       249 $join = '' if ! defined $join;
275 99         547 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 113 my ($text, $pattern, $replace, $global) = @_;
282 37 50       97 $text = '' unless defined $text;
283 37 50       87 $pattern = '' unless defined $pattern;
284 37 50       107 $replace = '' unless defined $replace;
285 37 100       89 $global = 1 unless defined $global;
286             my $expand = sub {
287 48     48   119 my ($chunk, $start, $end) = @_;
288 48         166 $chunk =~ s{ \\(\\|\$) | \$ (\d+) }{
289 6 50 33     63 $1 ? $1
    50          
290             : ($2 > $#$start || $2 == 0) ? ''
291             : substr($text, $start->[$2], $end->[$2] - $start->[$2]);
292             }exg;
293 48         182 $chunk;
294 37         218 };
295 37 100       105 if ($global) {
296 34         523 $text =~ s{$pattern}{ $expand->($replace, [@-], [@+]) }eg;
  45         263  
297             } else {
298 3         33 $text =~ s{$pattern}{ $expand->($replace, [@-], [@+]) }e;
  3         24  
299             }
300 37         310 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 181 my ($list, $field) = @_;
310 60 100       197 if (! defined $field) {
    100          
311 54         138 return [map {$_->[0]} sort {$a->[1] cmp $b->[1]} map {[$_, lc $_]} @$list ]; # case insensitive
  159         587  
  151         368  
  159         698  
312             } elsif (UNIVERSAL::isa($field, 'CODE')) {
313 3         15 return [sort {int($field->($a, $b))} @$list];
  6         23  
314             } else {
315 3 0       23 return [map {$_->[0]} sort {$a->[1] cmp $b->[1]} map {[$_, lc(ref $_ eq 'HASH' ? $_->{$field}
  6 50       25  
  3         15  
  6         40  
316             : UNIVERSAL::can($_, $field) ? $_->$field()
317             : $_)]} @$list ];
318             }
319             }
320              
321             sub vmethod_splice {
322 12     12 0 42 my ($ref, $i, $len, @replace) = @_;
323 12 50 66     54 @replace = @{ $replace[0] } if @replace == 1 && ref $replace[0] eq 'ARRAY';
  0         0  
324 12 100       39 if (defined $len) {
    100          
325 6   50     68 return [splice @$ref, $i || 0, $len, @replace];
326             } elsif (defined $i) {
327 3         17 return [splice @$ref, $i];
328             } else {
329 3         20 return [splice @$ref];
330             }
331             }
332              
333             sub vmethod_split {
334 18     18 0 50 my ($str, $pat, $lim) = @_;
335 18 50       49 $str = '' if ! defined $str;
336 18 100       43 if (defined $lim) { return defined $pat ? [split $pat, $str, $lim] : [split ' ', $str, $lim] }
  6 100       83  
337 12 100       144 else { return defined $pat ? [split $pat, $str ] : [split ' ', $str ] }
338             }
339              
340             sub vmethod_substr {
341 27     27 0 76 my ($str, $i, $len, $replace) = @_;
342 27   50     127 $i ||= 0;
343 27 50       66 return '' if ! defined $str;
344 27 50       65 return substr($str, $i) if ! defined $len;
345 27 50       278 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 21 my $str = shift;
352 6 50       22 return '' if ! defined $str;
353 6 50       39 utf8::upgrade($str) if defined &utf8::upgrade;
354 6         56 $str =~ s/([^A-Za-z0-9\-_.!~*\'()])/sprintf('%%%02X', ord($1))/eg;
  6         61  
355 6         46 return $str;
356             }
357              
358             sub vmethod_url {
359 2     2 0 5 my $str = shift;
360 2 50       7 return '' if ! defined $str;
361 2 50       12 utf8::upgrade($str) if defined &utf8::upgrade;
362 2         17 $str =~ s/([^;\/?:@&=+\$,A-Za-z0-9\-_.!~*\'()])/sprintf('%%%02X', ord($1))/eg;
  4         26  
363 2         10 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__