File Coverage

blib/lib/Attribute/Default.pm
Criterion Covered Total %
statement 193 197 97.9
branch 56 68 82.3
condition 13 24 54.1
subroutine 40 40 100.0
pod 0 3 0.0
total 302 332 90.9


line stmt bran cond sub pod time code
1             package Attribute::Default;
2             {
3             $Attribute::Default::VERSION = '1.35';
4             }
5              
6             ####
7             #### Attribute::Default
8             ####
9             #### $Id$
10             ####
11             #### See perldoc for details.
12             ####
13              
14 3     3   775849 use 5.0010;
  3         11  
  3         126  
15 3     3   18 use strict;
  3         5  
  3         103  
16 3     3   15 use warnings;
  3         12  
  3         99  
17 3     3   16 no warnings 'redefine';
  3         6  
  3         98  
18 3     3   3125 use attributes;
  3         4500  
  3         17  
19 3     3   3123 use Attribute::Handlers 0.79;
  3         10298  
  3         43  
20              
21 3     3   111 use base qw(Attribute::Handlers Exporter);
  3         5  
  3         410  
22              
23 3     3   17 use Carp;
  3         5  
  3         168  
24 3     3   2799 use Symbol;
  3         2726  
  3         326  
25              
26             our @EXPORT_OK = qw(exsub);
27              
28 3     3   42 use constant EXSUB_CLASS => ( __PACKAGE__ . '::ExSub' );
  3         7  
  3         344  
29              
30             ##
31             ## import()
32             ##
33             ## Apparently I found it necessary to export 'exsub'
34             ## by hand. I don't know why. Eventually, it may
35             ## be necessary to turn on some specific functionality
36             ## once 'exsub' is exported for compile-time speed.
37             ##
38             sub import {
39 5     5   43 my $class = shift;
40 5         10 my ($subname) = @_;
41 5         15 my $callpkg = (caller())[0];
42              
43 5 100 66     41 if (defined($subname) && $subname eq 'exsub') {
44 3     3   14 no strict 'refs';
  3         8  
  3         858  
45 2         4 *{ "${callpkg}::exsub" } = \&exsub;
  2         53  
46             }
47             else {
48 3         146 SUPER->import(@_);
49             }
50            
51             }
52              
53             ##
54             ## exsub()
55             ##
56             ## One specifies an expanding subroutine for Default by saying 'exsub
57             ## { YOUR CODE HERE }'. It's run and used as a default at runtime.
58             ##
59             ## Exsubs are marked by being blessed into EXSUB_CLASS.
60             ##
61             sub exsub(&) {
62 13     13 0 46167 my ($sub) = @_;
63 13 50       45 ref $sub eq 'CODE' or die "Sub '$sub' can't be blessed: must be CODE ref";
64 13         183 bless $sub, EXSUB_CLASS;
65             }
66              
67             ##
68             ## _get_args()
69             ##
70             ## Fairly close to no-op code. Discards the needless
71             ## arguments I get from Attribute::Handlers stuff
72             ## and puts single default arguments into array refs.
73             ##
74             sub _get_args {
75 10     10   23 my ($glob, $orig, $attr, $defaults) = @_[1 .. 4];
76 10 50 33     59 (ref $defaults && ref $defaults ne 'CODE') or $defaults = [$defaults];
77              
78 10         26 return ($glob, $attr, $defaults, $orig);
79             }
80              
81             ##
82             ## _is_method()
83             ##
84             ## Returns true if the given reference has a ':method' attribute.
85             ##
86             sub _is_method {
87 19     19   26 my ($orig) = @_;
88              
89 19         60 foreach ( attributes::get($orig) ) {
90 8 50       162 ($_ eq 'method') and return 1;
91             }
92              
93 11         250 return;
94             }
95              
96             ##
97             ## _extract_exsubs_array()
98             ##
99             ## Arguments:
100             ## DEFAULTS -- arrayref : The list of default arguments
101             ##
102             ## Returns:
103             ## hashref: list of exsubs we found and their array indices
104             ## arrayref: list of defaults without exsubs
105             ##
106             sub _extract_exsubs_array {
107 18     18   23 my ($defaults) = @_;
108              
109 18         30 my %exsubs = ();
110 18         21 my @noexsubs = ();
111              
112 3     3   2698 for ( $[ .. $#$defaults ) {
  3         1393  
  3         3539  
  18         75  
113 26 100       108 if (UNIVERSAL::isa( $defaults->[$_], EXSUB_CLASS )) {
114 7         31 $exsubs{$_} = $defaults->[$_];
115             }
116             else {
117 19         48 $noexsubs[$_] = $defaults->[$_];
118             }
119             }
120              
121 18         63 return (\%exsubs, \@noexsubs);
122             }
123              
124              
125             ##
126             ## _get_fill()
127             ##
128             ## Returns an appropriate subroutine to process the given defaults.
129             ##
130             sub _get_fill {
131 22     22   30 my ($defaults) = @_;
132              
133 22 100       122 if (ref $defaults eq 'ARRAY') {
    100          
134 6         19 return _fill_array_sub($defaults);
135             }
136             elsif(ref $defaults eq 'HASH') {
137 13         36 return _fill_hash_sub($defaults);
138             }
139             else {
140 3         13 return _fill_array_sub([$defaults]);
141             }
142             }
143              
144             ##
145             ## _fill_array_sub()
146             ##
147             ## Arguments:
148             ## DEFAULTS: arrayref
149             ##
150             ##
151             ## Returns:
152             ## coderef-- closure to fill sub with defaults
153             ## coderef-- closure to fill in exsubs
154             ##
155             sub _fill_array_sub {
156 18     18   25 my ($defaults) = @_;
157              
158 18         35 my ($exsubs, $plain) = _extract_exsubs_array($defaults);
159 18     23   63 my $fill_sub = sub { return _fill_arr($plain, @_) };
  23         70  
160 18 100       40 if ( %$exsubs ) {
161             return ( $fill_sub,
162             sub {
163 8     8   15 my ($processed, $exsub_args) = @_;
164 8         50 while (my ($idx, $exsub) = each %$exsubs) {
165 8 100       36 defined( $processed->[$idx] ) and next;
166 7         227 $processed->[$idx] = &$exsub(@$exsub_args);
167             }
168 8         87 return $processed;
169 7         40 });
170             }
171             else {
172 11         33 return ($fill_sub, undef);
173             }
174             }
175              
176             ##
177             ## _extract_exsubs_hash()
178             ##
179             ## Arguments:
180             ##
181             ## DEFAULTS: hashref -- Name-value pairs of defaults
182             ##
183             ## Returns: (array)
184             ##
185             ## hashref -- name-value pairs of all exsubs
186             ## hashref -- name-value pairs of all non-exsub defaults
187             ##
188             ## Returns the exsubs in a hash of defaults.
189             ##
190             sub _extract_exsubs_hash {
191 13     13   35 my ($defaults) = @_;
192              
193 13         23 my %exsubs = ();
194 13         17 my %noexsubs = ();
195 13         55 while ( my ($key, $value) = each %$defaults ) {
196 22 100       81 if (UNIVERSAL::isa( $value, EXSUB_CLASS ) ) {
197 6         29 $exsubs{$key} = $value;
198             }
199             else {
200 16         59 $noexsubs{$key} = $value;
201             }
202             }
203 13         33 return (\%exsubs, \%noexsubs);
204             }
205              
206             ##
207             ## _fill_hash_sub()
208             ##
209             ## Arguments
210             ## DEFAULTS: hashref -- name-value pairs of defaults
211             ##
212             ## Returns: list
213             ## coderef -- closure to fill default values
214             ## coderef -- closure to fill exsubs
215             ##
216             ## Returns the appropriate preprocessor to fill a hash
217             ## with defaults.
218             ##
219             sub _fill_hash_sub {
220 13     13   47 my ($defaults) = @_;
221              
222 13         28 my ($exsubs, $plain) = _extract_exsubs_hash($defaults);
223 13     16   51 my $fill_sub = sub { return _fill_hash($plain, @_); };
  16         49  
224 13 100       32 if ( %$exsubs ) {
225             return ($fill_sub,
226             sub {
227 8     8   14 my ($filled, $exsub_args) = @_;
228 8         17 my %processed = @$filled;
229 8         39 while (my ($key, $exsub) = each %$exsubs) {
230 8 100       25 (! defined $processed{$key}) or next;
231 7         189 $processed{$key} = &$exsub(@$exsub_args);
232             }
233 8         116 @$filled = %processed;
234 8         44 return $filled;
235 6         46 });
236             }
237             else {
238 7         23 return ( $fill_sub, undef );
239             }
240             }
241              
242             ##
243             ## _get_sub()
244             ##
245             ## Arguments:
246             ## DEFAULTS: arrayref -- Array of defaults to a subroutine
247             ## ORIG: code ref -- The subroutine we're applying defaults to
248             ##
249             ## Returns the appropriate subroutine wrapper that
250             ## will call ORIG with the given default values.
251             ##
252             sub _get_sub {
253 10     10   14 my ($defaults, $orig) = @_;
254              
255 10         22 my ($fill_sub, $exsub_sub) = _get_fill($defaults);
256              
257 10 100       26 if ( _is_method($orig) ) {
258 5 100       13 if (defined $exsub_sub) {
259             return sub {
260 4     4   2011 my ($self, @args) = @_;
261 4         15 my @filled = &$fill_sub(@args);
262 4         7 @_ = ($self, @{ &$exsub_sub( \@filled, [$self, @filled] ) } );
  4         15  
263 4         20 goto $orig;
264 4         26 };
265             }
266             else {
267             return sub {
268 1     1   533 my ($self, @args) = @_;
269 1         5 @_ = ($self, &$fill_sub(@args));
270 1         6 goto $orig;
271 1         9 };
272             }
273             }
274             else {
275 5 100       13 if (defined $exsub_sub) {
276             return sub {
277 5     5   2584 my @filled = &$fill_sub(@_);
278 5         9 @_ = @{ &$exsub_sub( \@filled, \@filled ) };
  5         15  
279 5         49 goto $orig;
280 2         16 };
281             }
282             else {
283             return sub {
284 7     7   4438 @_ = &$fill_sub(@_);
285 7         27 goto $orig;
286 3         21 };
287             }
288             }
289             }
290              
291              
292             sub Default : ATTR(CODE) {
293 10     10 0 4031 my ($glob, $attr, $defaults_arg, $orig) = _get_args(@_);
294            
295 10         15 my $defaults = $defaults_arg;
296            
297 10 100 33     52 if ( defined $defaults && (ref $defaults eq 'ARRAY') && ( scalar @{ $defaults } == 1 ) ) {
  10   66     33  
298 7         12 $defaults = $defaults_arg->[0];
299             }
300              
301 10         20 *$glob = _get_sub($defaults, $orig);
302              
303 3     3   24 }
  3         7  
  3         25  
304              
305              
306             ##
307             ## _fill_hash()
308             ##
309             ## Arguments:
310             ## DEFAULTS: hashref -- Hash table of default arguments
311             ## ARGS: list -- The arguments to be filtered
312             ##
313             ## Returns:
314             ## list -- Arguments with defaults filled in
315             ##
316             sub _fill_hash {
317 16     16   31 my $defaults = shift;
318 16         33 my %args = @_;
319 16         74 while (my ($key, $value) = each %$defaults) {
320 19 100       64 unless ( defined($args{$key}) ) {
321 14 50       115 if ( UNIVERSAL::isa( $value, EXSUB_CLASS ) ) {
322 0         0 $args{$key} = undef;
323             }
324             else {
325 14         66 $args{$key} = $value;
326             }
327             }
328             }
329 16         91 return %args;
330             }
331              
332             ##
333             ## _fill_arr()
334             ##
335             ## Arguments:
336             ## DEFAULTS: arrayref -- Array of default arguments
337             ## ARGS: list -- The arguments to be filtered
338             ##
339             ## Returns:
340             ## list -- Arguments with defaults filled in
341             ##
342             sub _fill_arr {
343 23     23   36 my $defaults = shift;
344 23         46 my @filled = ();
345 23         70 foreach (0 .. $#_) {
346 12 100       54 push @filled, ( defined( $_[$_] ) ? $_[$_] : $defaults->[$_] );
347             }
348 23 100       79 if ($#$defaults > $#_) {
349 10         40 push(@filled, @$defaults[scalar @_ .. $#$defaults]);
350             }
351              
352 23         86 return @filled;
353             }
354              
355             ##
356             ## Defaults()
357             ##
358             ## Arguments:
359             ## GLOB: typeglobref -- Typeglob of name of sub to wrap
360             ## ORIG: coderef -- Ref to original sub
361             ## ATTR: string -- name of the attribute (Always 'Defaults' right now)
362             ## DEFAULTS_LIST -- list of default arguments
363             ##
364             ## Defaults() creates a wrapper subroutine that does a two-layer check on
365             ## incoming arguments. It first processes the toplevel arguments as an
366             ## array, then processes any reference defaults.
367             ##
368             ## If the default and the argument are of differing reference types, the
369             ## argument is passed through unscathed.
370             ##
371             ## An undef of a reference type is treated like someone passing an empty
372             ## array or hash.
373             ##
374             ## Implementation note: Using huge numbers of closures like I am may
375             ## waste too much memory. It's a hell of a lot cleaner than what I was doing
376             ## before, though.
377             ##
378             ##
379             sub Defaults : ATTR(CODE) {
380 9     9 0 784 my ($glob, $orig, $attr, $defaults) = @_[1 .. 4];
381 9 50 33     64 (ref $defaults) && (ref $defaults eq 'ARRAY') or $defaults = [$defaults];
382              
383 9         13 my @ref_defaults = ();
384 9         29 my @ref_exsubs = ();
385 9         13 my @toplevel_defaults = ();
386              
387 9         48 foreach ($[ .. $#$defaults) {
388 18 100 100     124 if ( (my $type = ref $$defaults[$_]) && (! UNIVERSAL::isa($$defaults[$_], EXSUB_CLASS) ) ) {
389 12         30 my ($fill_sub, $fill_exsub) = _get_fill($$defaults[$_]);
390 12         40 push @ref_defaults, [$_, $type, $fill_sub];
391 12 100       52 defined $fill_exsub and push @ref_exsubs, [$_, $type, $fill_exsub];
392             }
393             else {
394 6         18 $toplevel_defaults[$_] = $$defaults[$_];
395             }
396             }
397              
398 9         26 my ($toplevel_sub, $toplevel_exsub) = _fill_array_sub(\@toplevel_defaults);
399              
400 9 100       22 if ( _is_method($orig) ) {
401             *$glob =
402             sub {
403 2     2   1157 my @filled = &$toplevel_sub(@_[ ($[ + 1) .. $#_ ]);
404 2         13 _fill_sublevel(\@filled, \@ref_defaults);
405 2 50       9 defined ($toplevel_exsub) && &$toplevel_exsub(\@filled, [$_[0], @filled]);
406 2         13 _fill_exsubs(\@filled, \@ref_exsubs, [$_[0], @filled]);
407 2         8 @_ = ($_[0], @filled);
408 2         10 goto $orig;
409             }
410 3         26 }
411             else {
412             *$glob =
413             sub {
414            
415             # First, fill toplevel arguments
416 8     8   10101 my @filled = &$toplevel_sub(@_);
417            
418             # Next, fill all sublevel arguments
419 8         28 _fill_sublevel(\@filled, \@ref_defaults);
420              
421 8 100       30 defined ($toplevel_exsub) && &$toplevel_exsub(\@filled, \@filled);
422 8         35 _fill_exsubs(\@filled, \@ref_exsubs, \@filled);
423 8         22 @_ = @filled;
424 8         34 goto $orig;
425             }
426            
427 6         77 }
428            
429              
430 3     3   3576 }
  3         9  
  3         16  
431              
432             sub _fill_exsubs {
433 10     10   19 my ($args, $ref_exsubs, $exsub_args) = @_;
434              
435 10         22 foreach (@$ref_exsubs) {
436 4         11 my ($idx, $type, $exsub_sub) = @$_;
437 4 50 33     15 ($type eq ref $$args[$idx]) || (! defined $$args[$idx]) or next;
438 4 50       10 if ($type eq 'HASH') {
    0          
439 4         5 $$args[$idx] = { @{ &$exsub_sub( [%{ $$args[$idx] } ], $exsub_args ) } };
  4         5  
  4         16  
440             }
441             elsif ($type eq 'ARRAY') {
442 0         0 $$args[$idx] = &$exsub_sub( $$args[$idx], $exsub_args );
443             }
444             else {
445 0         0 die "Exsub expansion cannot handle '$type'";
446             }
447             }
448             }
449              
450              
451              
452             sub _fill_sublevel {
453 10     10   14 my ($filled, $ref_defaults) = @_;
454              
455 10         24 foreach (@$ref_defaults) {
456 12         30 my ($idx, $type, $fill_sub) = @$_;
457 12 50 66     68 ($type eq ref $$filled[$idx]) || (! defined $$filled[$idx]) or next;
458 12 100       39 if ($type eq 'HASH') {
    50          
459 9 100       35 $$filled[$idx] = { &$fill_sub( defined $$filled[$idx] ? %{ $$filled[$idx] } : () ) };
  2         8  
460             } elsif ($type eq 'ARRAY') {
461 3 100       15 $$filled[$idx] = [ &$fill_sub( defined $$filled[$idx] ? @{ $$filled[$idx] } : () ) ];
  1         5  
462             } else {
463 0           die "I don't know what to do with '$type'";
464             }
465              
466             }
467              
468             }
469              
470             1;
471             __END__