157a2e5169e837b108b137385a991aaf71525942
[bse.git] / site / cgi-bin / modules / Squirrel / Template / Expr / WrapArray.pm
1 package Squirrel::Template::Expr::WrapArray;
2 use strict;
3 use base qw(Squirrel::Template::Expr::WrapBase);
4 use Scalar::Util ();
5 use List::Util ();
6
7 our $VERSION = "1.010";
8
9 my $list_make_key = sub {
10   my ($item, $field) = @_;
11
12   if (Scalar::Util::blessed($item)) {
13     return $item->can($field) ? $item->$field() : "";
14   }
15   else {
16     return exists $item->{$field} ? $item->{$field} : "";
17   }
18 };
19
20 sub _do_size {
21   my ($self, $args) = @_;
22
23   @$args == 0
24     or die [ error => "list.size takes no parameters" ];
25
26   return scalar @{$self->[0]};
27 }
28
29 sub _do_sort {
30   my ($self, $args) = @_;
31
32   @{$self->[0]} <= 1
33     and return [ @{$self->[0]} ]; # nothing to sort
34
35   if (@$args == 0) {
36     return [ sort @{$self->[0]} ];
37   }
38   elsif (@$args == 1) {
39     if (ref $args->[0]) {
40       my $eval = $self->expreval;
41       return
42         [
43          sort {
44            $eval->call_function($args->[0], [ $a, $b ])
45          } @{$self->[0]}
46         ];
47     }
48     else {
49       my $key = $args->[0];
50       return 
51         [
52          sort {
53            $list_make_key->($a, $key) cmp $list_make_key->($b, $key)
54          } @{$self->[0]}
55         ];
56     }
57   }
58   else {
59     die [ error => "list.sort takes 0 or 1 parameters" ];
60   }
61 }
62
63 sub _do_reverse {
64   my ($self, $args) = @_;
65
66   @$args == 0
67     or die [ error => "list.reverse takes no parameters" ];
68
69   return [ reverse @{$self->[0]} ];
70 }
71
72 sub _do_shuffle {
73   my ($self, $args) = @_;
74
75   @$args == 0
76     or die [ error => "list.shuffle takes no parameters" ];
77
78   return [ List::Util::shuffle(@{$self->[0]}) ];
79 }
80
81 sub _do_join {
82   my ($self, $args) = @_;
83
84   my $join = @$args ? $args->[0] : "";
85
86   return join($join, @{$self->[0]});
87 }
88
89 sub _do_last {
90   my ($self, $args) = @_;
91
92   @$args == 0
93     or die [ error => "list.last takes no parameters" ];
94
95   return @{$self->[0]} ? $self->[0][-1] : ();
96 }
97
98 sub _do_first {
99   my ($self, $args) = @_;
100
101   @$args == 0
102     or die [ error => "list.first takes no parameters" ];
103
104   return @{$self->[0]} ? $self->[0][0] : ();
105 }
106
107 sub _do_shift {
108   my ($self, $args) = @_;
109
110   @$args == 0
111     or die [ error => "list.shift takes no parameters" ];
112
113   return shift @{$self->[0]};
114 }
115
116 sub _do_pop {
117   my ($self, $args) = @_;
118
119   @$args == 0
120     or die [ error => "list.pop takes no parameters" ];
121
122   return pop @{$self->[0]};
123 }
124
125 sub _do_push {
126   my ($self, $args) = @_;
127
128   push @{$self->[0]}, @$args;
129
130   return scalar(@{$self->[0]});
131 }
132
133 sub _do_unshift {
134   my ($self, $args) = @_;
135
136   unshift @{$self->[0]}, @$args;
137
138   return scalar(@{$self->[0]});
139 }
140
141 sub _do_expand {
142   my ($self, $args) = @_;
143
144   @$args == 0
145     or die [ error => "list.expand takes no parameters" ];
146
147   return 
148     [ map {
149       defined 
150         && ref
151           && !Scalar::Util::blessed($_)
152             && Scalar::Util::reftype($_) eq 'ARRAY'
153               ? @$_
154                 : $_
155     } @{$self->[0]} ];
156 }
157
158 sub _do_is_list {
159   return 1;
160 }
161
162 sub _do_is_hash {
163   return 0;
164 }
165
166 sub _do_is_code {
167   return 0;
168 }
169
170 sub _do_defined {
171   return 1;
172 }
173
174 sub _do_set {
175   my ($self, $args) = @_;
176
177   @$args == 2
178     or die [ error => "list.set takes two parameters" ];
179
180   $self->[0][$args->[0]] = $args->[1];
181
182   return $args->[1];
183 }
184
185 sub _do_as_hash {
186   my ($self, $args) = @_;
187
188   @$args == 0
189     or die [ error => "list.as_hash takes no parameters" ];
190
191   my @extra = @{$self->[0]} % 2 ? ( undef ) : ();
192
193   return +{ @{$self->[0]}, @extra };
194 }
195
196 sub _do_grep {
197   my ($self, $args) = @_;
198
199   my $eval = $self->expreval;
200   return
201     [
202      grep $eval->call_function($args->[0], [ $_ ]),
203      @{$self->[0]}
204     ];
205 }
206
207 sub _do_map {
208   my ($self, $args) = @_;
209
210   my $eval = $self->expreval;
211   return
212     [
213      map $eval->call_function($args->[0], [ $_ ]),
214      @{$self->[0]}
215     ];
216 }
217
218 sub _do_slice {
219   my ($self, $args) = @_;
220
221   my @result;
222   if (@$args == 1 && Scalar::Util::reftype($args->[0]) eq "ARRAY") {
223     @result = @{$self->[0]}[@{$args->[0]}];
224   }
225   else {
226     @result = @{$self->[0]}[@$args];
227   }
228
229   return \@result;
230 }
231
232 sub _do_splice {
233   my ($self, $args) = @_;
234
235   @$args >= 1 && @$args <= 3
236     or die [ error => "list.splice() requires 1 to 3 parameters" ];
237   my $offset = $args->[0];
238   $offset < 0 and $offset = @{$self->[0]} + $offset;
239   my $len = @$args >= 2 ? $args->[1] : @{$self->[0]} - $offset;
240   my $replace = [];
241   if (@$args >= 3) {
242     Scalar::Util::reftype($args->[2]) eq "ARRAY"
243       or die [ error => "list.splice() third argument must be a list" ];
244     $replace = $args->[2];
245   }
246
247   return [ splice(@{$self->[0]}, $offset, $len, @$replace) ];
248 }
249
250 sub call {
251   my ($self, $method, $args) = @_;
252
253   my $real_method = "_do_$method";
254   if ($self->can($real_method)) {
255     return $self->$real_method($args);
256   }
257   die [ error => "Unknown method $method for lists" ];
258 }
259
260 1;
261
262 __END__
263
264 =head1 NAME
265
266 Squirrel::Template::Expr::WrapArray - provide virtual methods for arrays
267
268 =head1 SYNOPSIS
269
270   somearray.size
271   sorted = somearray.sort()
272   sorted = somearray.sort(key)
273   reversed = somearray.reverse
274   joined = somearray.join()
275   joined = somearray.join(":")
276   last = somearray.last
277   first = somearray.first
278   first = somearray.shift # modifies somearray
279   somearray.push(avalue)
280   last = somearray.pop # modifies somearray
281   somearray.unshift(avalue)
282   somearray.is_list # always true
283   somearray.is_hash # always false
284   odd = somearray.grep(@{a: a mod 2 == 0 })
285   doubled = somearray.map(@{a: a * 2 })
286
287 =head1 DESCRIPTION
288
289 This class provides virtual methods for arrays (well, array
290 references) in L<Squirrel::Template>'s expression language.
291
292 =head1 METHODS
293
294 =over
295
296 =item size
297
298 The number of elements in the list.
299
300 =item sort()
301
302 The elements sorted by name.
303
304 =item sort(fieldname)
305
306 The elements sorted as objects calling C<fieldname>.
307
308 =item sort(block)
309
310 The elem
311
312 =item reversed
313
314 The elements in reverse order.
315
316 =item join()
317
318 A string with the elements concatenated together.
319
320 =item join(sep)
321
322 A string with the elements concatenated together, separated by C<sep>.
323
324 =item last
325
326 The last element in the array, or undef.
327
328 =item first
329
330 The first element in the array, or undef.
331
332 =item shift
333
334 Remove the first element from the list and return that.
335
336 =item push(element,...)
337
338 Add the given elements to the end of the array.  returns the new size
339 of the array.
340
341 =item pop
342
343 Remove the last element from the list and return that.
344
345 =item unshift(element,...)
346
347 Add the given elements to the start of the array.  returns the new
348 size of the array.
349
350 =item expand
351
352 Return a new array with any contained arrays expanded one level.
353
354   [ [ [ 1 ], 2 ], 3 ].expand => [ [ 1 ], 2, 3 ]
355
356 =item grep(block)
357
358 Return a new list containing only those elements that C<block> returns
359 true for.
360
361 =item map(block)
362
363 Return the list of return values from C<block> as applied to each
364 element.
365
366 =item set(index, value)
367
368 Set the specified I<index> in the array to I<value>.  Returns
369 I<value>.
370
371 =item splice(array_of_indexes)
372
373 =item splice(index1, index2, ...)
374
375 Return a selection of elements from the array as a new array specified
376 by index.  The indexes can be supplied either as an array:
377
378   [ "A" .. "Z" ].slice([ 0 .. 5 ]) # first 6 elements
379
380 or as separate arguments:
381
382   [ "A" .. "Z" ].slice(0, 1, -2, -1) # first 2 and last 2 elements
383
384 =item splice(start)
385
386 =item splice(start, count)
387
388 =item splice(start, count, replace)
389
390 Removes elements from an array, optionally inserting the elements in
391 the I<replace> aray in their place.
392
393 If I<count> is ommitted, all elements to the end of the array are
394 removed.
395
396 If I<replace> is omitted, the elements are simply removed.
397
398   <: .set foo = [ "A" .. "J" ] :>
399   <:= foo.splice(5).join("") :> # FGHIJ
400   <:= foo.join("") :>           # ABCDE since splice() modifies it's argument
401   <: .set bar = [ "A" .. "J" ] :>
402   <:= bar.splice(8, 2, [ "Y", "Z" ]).join("") :> # IJ
403   <:= bar.join("") :> # ABCDEFGHYZ
404
405 =item as_hash
406
407 Returns a hash formed as if the array was formed of key and value
408 pairs.  If the number of elements is odd, the value for the odd key is
409 C<undef>.
410
411  [ "a", 1, "b", 2 ].as_hash => { a:1, b:2 }
412
413 =item is_list
414
415 Test if this object is a list.  Always true for a list.
416
417 =item is_hash
418
419 Test if this object is a hash.  Always false for a list.
420
421 =item is_code
422
423 Test if this object is a code object.  Always false for a list.
424
425 =item defined
426
427 Always true for arrays.
428
429 =back
430
431 =head1 SEE ALSO
432
433 L<Squirrel::Template::Expr>, L<Squirrel::Template>
434
435 =head1 AUTHOR
436
437 Tony Cook <tony@develop-help.com>
438
439 =cut