Attribute-Default
view release on metacpan or search on metacpan
lib/Attribute/Default.pm view on Meta::CPAN
##
## exsub()
##
## One specifies an expanding subroutine for Default by saying 'exsub
## { YOUR CODE HERE }'. It's run and used as a default at runtime.
##
## Exsubs are marked by being blessed into EXSUB_CLASS.
##
sub exsub(&) {
my ($sub) = @_;
ref $sub eq 'CODE' or die "Sub '$sub' can't be blessed: must be CODE ref";
bless $sub, EXSUB_CLASS;
}
##
## _get_args()
##
## Fairly close to no-op code. Discards the needless
## arguments I get from Attribute::Handlers stuff
## and puts single default arguments into array refs.
##
sub _get_args {
my ($glob, $orig, $attr, $defaults) = @_[1 .. 4];
(ref $defaults && ref $defaults ne 'CODE') or $defaults = [$defaults];
return ($glob, $attr, $defaults, $orig);
}
##
## _is_method()
##
## Returns true if the given reference has a ':method' attribute.
##
sub _is_method {
my ($orig) = @_;
foreach ( attributes::get($orig) ) {
($_ eq 'method') and return 1;
}
return;
}
##
## _extract_exsubs_array()
##
## Arguments:
## DEFAULTS -- arrayref : The list of default arguments
##
## Returns:
## hashref: list of exsubs we found and their array indices
## arrayref: list of defaults without exsubs
##
sub _extract_exsubs_array {
my ($defaults) = @_;
my %exsubs = ();
my @noexsubs = ();
for ( $[ .. $#$defaults ) {
if (UNIVERSAL::isa( $defaults->[$_], EXSUB_CLASS )) {
$exsubs{$_} = $defaults->[$_];
}
else {
$noexsubs[$_] = $defaults->[$_];
}
}
return (\%exsubs, \@noexsubs);
}
##
## _get_fill()
##
## Returns an appropriate subroutine to process the given defaults.
##
sub _get_fill {
my ($defaults) = @_;
if (ref $defaults eq 'ARRAY') {
return _fill_array_sub($defaults);
}
elsif(ref $defaults eq 'HASH') {
return _fill_hash_sub($defaults);
}
else {
return _fill_array_sub([$defaults]);
}
}
##
## _fill_array_sub()
##
## Arguments:
## DEFAULTS: arrayref
##
##
## Returns:
## coderef-- closure to fill sub with defaults
## coderef-- closure to fill in exsubs
##
sub _fill_array_sub {
my ($defaults) = @_;
my ($exsubs, $plain) = _extract_exsubs_array($defaults);
my $fill_sub = sub { return _fill_arr($plain, @_) };
if ( %$exsubs ) {
return ( $fill_sub,
sub {
my ($processed, $exsub_args) = @_;
while (my ($idx, $exsub) = each %$exsubs) {
defined( $processed->[$idx] ) and next;
$processed->[$idx] = &$exsub(@$exsub_args);
}
return $processed;
});
}
else {
return ($fill_sub, undef);
lib/Attribute/Default.pm view on Meta::CPAN
}
}
return %args;
}
##
## _fill_arr()
##
## Arguments:
## DEFAULTS: arrayref -- Array of default arguments
## ARGS: list -- The arguments to be filtered
##
## Returns:
## list -- Arguments with defaults filled in
##
sub _fill_arr {
my $defaults = shift;
my @filled = ();
foreach (0 .. $#_) {
push @filled, ( defined( $_[$_] ) ? $_[$_] : $defaults->[$_] );
}
if ($#$defaults > $#_) {
push(@filled, @$defaults[scalar @_ .. $#$defaults]);
}
return @filled;
}
##
## Defaults()
##
## Arguments:
## GLOB: typeglobref -- Typeglob of name of sub to wrap
## ORIG: coderef -- Ref to original sub
## ATTR: string -- name of the attribute (Always 'Defaults' right now)
## DEFAULTS_LIST -- list of default arguments
##
## Defaults() creates a wrapper subroutine that does a two-layer check on
## incoming arguments. It first processes the toplevel arguments as an
## array, then processes any reference defaults.
##
## If the default and the argument are of differing reference types, the
## argument is passed through unscathed.
##
## An undef of a reference type is treated like someone passing an empty
## array or hash.
##
## Implementation note: Using huge numbers of closures like I am may
## waste too much memory. It's a hell of a lot cleaner than what I was doing
## before, though.
##
##
sub Defaults : ATTR(CODE) {
my ($glob, $orig, $attr, $defaults) = @_[1 .. 4];
(ref $defaults) && (ref $defaults eq 'ARRAY') or $defaults = [$defaults];
my @ref_defaults = ();
my @ref_exsubs = ();
my @toplevel_defaults = ();
foreach ($[ .. $#$defaults) {
if ( (my $type = ref $$defaults[$_]) && (! UNIVERSAL::isa($$defaults[$_], EXSUB_CLASS) ) ) {
my ($fill_sub, $fill_exsub) = _get_fill($$defaults[$_]);
push @ref_defaults, [$_, $type, $fill_sub];
defined $fill_exsub and push @ref_exsubs, [$_, $type, $fill_exsub];
}
else {
$toplevel_defaults[$_] = $$defaults[$_];
}
}
my ($toplevel_sub, $toplevel_exsub) = _fill_array_sub(\@toplevel_defaults);
if ( _is_method($orig) ) {
*$glob =
sub {
my @filled = &$toplevel_sub(@_[ ($[ + 1) .. $#_ ]);
_fill_sublevel(\@filled, \@ref_defaults);
defined ($toplevel_exsub) && &$toplevel_exsub(\@filled, [$_[0], @filled]);
_fill_exsubs(\@filled, \@ref_exsubs, [$_[0], @filled]);
@_ = ($_[0], @filled);
goto $orig;
}
}
else {
*$glob =
sub {
# First, fill toplevel arguments
my @filled = &$toplevel_sub(@_);
# Next, fill all sublevel arguments
_fill_sublevel(\@filled, \@ref_defaults);
defined ($toplevel_exsub) && &$toplevel_exsub(\@filled, \@filled);
_fill_exsubs(\@filled, \@ref_exsubs, \@filled);
@_ = @filled;
goto $orig;
}
}
}
sub _fill_exsubs {
my ($args, $ref_exsubs, $exsub_args) = @_;
foreach (@$ref_exsubs) {
my ($idx, $type, $exsub_sub) = @$_;
($type eq ref $$args[$idx]) || (! defined $$args[$idx]) or next;
if ($type eq 'HASH') {
$$args[$idx] = { @{ &$exsub_sub( [%{ $$args[$idx] } ], $exsub_args ) } };
}
elsif ($type eq 'ARRAY') {
$$args[$idx] = &$exsub_sub( $$args[$idx], $exsub_args );
}
else {
die "Exsub expansion cannot handle '$type'";
}
}
}
sub _fill_sublevel {
my ($filled, $ref_defaults) = @_;
foreach (@$ref_defaults) {
my ($idx, $type, $fill_sub) = @$_;
($type eq ref $$filled[$idx]) || (! defined $$filled[$idx]) or next;
if ($type eq 'HASH') {
$$filled[$idx] = { &$fill_sub( defined $$filled[$idx] ? %{ $$filled[$idx] } : () ) };
} elsif ($type eq 'ARRAY') {
$$filled[$idx] = [ &$fill_sub( defined $$filled[$idx] ? @{ $$filled[$idx] } : () ) ];
} else {
die "I don't know what to do with '$type'";
( run in 0.466 second using v1.01-cache-2.11-cpan-2aafcb1aa8b )