The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Sub::ParamFrame;

use 5.008007;
use strict; no strict 'refs';
use Carp;
use Sub::ParamLoader; # <= uses Tie::Hash::KeysMask

require Exporter;

our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw( pfrule pfload ) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our $VERSION = '0.01';

########## private ###########

my %register = ();

########## public ###########

sub pfrule
{
    my $calltop = [caller(1)];
    croak 'pfrule must be called inside a subroutine'
        unless $calltop;
    my $subname = $calltop->[3] ;
    $register{$subname} = Sub::ParamLoader->new (@_)
        unless exists $register{$subname};
    # about calling syntax  see  package Sub::ParamLoader
}

sub pfload
{
    my $thisSub = [caller(1)]->[3];
    $register{$thisSub} = Sub::ParamLoader->new # use empty frame
        unless exists $register{$thisSub};

    $register{$thisSub}->load(@_);
}

1;

__END__

=head1 NAME

Sub::ParamFrame - Supply key alias and defaults of named arguments.

=head1 SYNOPSIS

    use Sub::ParamFrame ':all';

    sub myFunc
    {
        # Define the rule how named arguments shell be processed
        pfrule
          'D' => [...,Name(i)=>DefVal(i),...], # assign default key-value  association.
          'M' => [ sub {...}, P(1),...P(k) ];  # keys mask function and fixed arguments

        # access a certain count of positional arguments here
        my @PARGS = splice @_,0,N;

        # Load named arguments. That is create a hash according to arguments
        # passed to pfrule. This hash also contains default arguments

        my $arg = pfload @_;

        # access argument or default of name NAME
        do_something_with $arg->{NAME};
        ......
        # or likewise access key NAME, in addition erase it from $arg
        do_something_with delete $arg->{NAME};
        .......
        # could also be used in list context accessing multiple arguments ..
        ($v1,$v2,...) = delete @$arg{NAME1,NAME2,...};
        ....
    }   ##myFunc

=head1 DESCRIPTION

A couple of modules already deal with named parameters and default values,
see L<Sub::Parameters>,L<Sub::NamedParams>,L<Sub::Declaration>,L<Perl6::Parameters>.

This solution pursues another scope of usability, covers distinct features
and uses another syntactic approach.

Named parameters are identified by a hash built from the argument vector C<@_>.
The generation and behavior of this hash will be controlled by a rule.
If one subroutine C<myFunc()> uses such a rule, this rule  appears as a command
within the subroutine's body.
The rule lays down one ore both of two properties:

1. a name-value-association of defaults and 2. an alias mapping for argument names.

Once this rule is passed by call of C<pfrule> from the first invocation of
C<myFunc>, the generation of the hash by the C<pfload> function shell follow
this rule at once and during future calls.

C<pfrule> appears before  C<pfload> and will be executed only once, only
when the calling subroutine runs first time.

Two named optional arguments are defined for C<pfrule>, neither must be present.

=head3 Arguments of C<pfrule>

        'D' => [...,Name(i)=>DefVal(i),...]

Defines a default key-value-association. C<pfload> stores this
association before arguments usually from C<@_> advance and may override some
default values.

        'M' => $mask                      where  $mask = sub {...}
        'M' => [ $mask, P(1),...P(m) ]    or           = \&fmask

Keys mask function and optionally fixed arguments.
If 'M' is omitted C<pfload> shall return just a hash.
If 'M' is present it causes C<pfload> to return a hash tied to class
C<Tie::Hash::KeysMask> such that each access to the hash triggers a
key translation:

         $k   =>   $mask->($k,P(1),...P(m))

Instead of a CODE C<'M'=E<gt>$mask> could take one of the following particular values

        'M' => 'lc'   or  'M' => 'uc'   or  'M' => \%T

        which will be translated into a CODE as follows

        'lc' => sub { lc $_[0] }        # omit case of character distinction
        'uc' => sub { uc $_[0] }        # with 'uc' or 'lc' translations
        \%T  => sub { exists $T{$_[0]} ? $T{$_[0]} : $_[0]}
                                        # hash %T defines aliases

Contrary to other approaches to named arguments,
one may choose freely the position of the first named argument
within C<@_>. Any amount of C<@_> may be shifted onto positional parameters
before the command C<pfload @_> takes the remaining pairs of key=>value.
Of course arguments different from  C<@_> are also allowed behind C<pfload>.

=head1 DEPENDENCIES

C<Sub::ParamFrame> is not a class, however it relies on a class package
C<Sub::ParamLoader> which inherits from C<Tie::Hash::KeysMask>.

=head1 CAVEATS

As described in L<Tie::Hash::KeysMask> one must take care, that the mask function
(specified by 'M'=>) fit to some restriction.

=head1 EXAMPLE

    use Sub::ParamFrame ':all';

    my @seasons = qw(spring summer autumn winter);

    sub actionA             # Parameter names @seasons
    {                       # case-insensitive by option 'M'=>'c'.
        my $person = shift; # first argument $person passed without a parameter name
        pfrule 'M' => 'uc', 'D' => [qw(winter SKI summer SWIM)];
        my $arg = pfload @_;
        my $pname = sprintf '+%12s does ',$person;
        my $actions = join ', ',map sprintf('%s:%s',$_,$arg->{$_}),@seasons;
        $pname.$actions."\n";
    }

    sub actionB      # Parameter names (case-insensitive, shortable up to length 2)
    {                #      sp[ring], su[mmer], au[tumn], wi[nter]
        my $person = shift; # first argument passed without a name
        pfrule
            'M' =>    # match any left substring of a season-name with length
            sub       # at least 2, ignoring case of characters
            {
                my $ikl  = lc(shift);      my $ikl2 = substr $ikl,0,2;
                my $res  = {qw(sp spring su summer au autumn wi winter)}->{$ikl2};
                return undef unless
                ( defined $res)  && ($ikl eq substr($res,0,length $ikl));
                $res;
            },
        'D' => [qw(wi SNOWBOARD spr BIKE su WATERJUMP aut EAT)];

        my $arg = pfload @_;
        my $pname = sprintf '-%12s does ',$person;
        my $actions = join ', ',map sprintf('%s:%s',$_,$arg->{$_}),@seasons;
        $pname.$actions."\n";
    }

    # For ease visual distinction default values were chosen in uppercase
    print "Default values uppercase ('D'=>[.. 'winter'=>'SKI'...]), others lowercase!\n";
    print '-'x74,"\n";
    print actionA qw(Alice Spring run Autumn ride);
    print actionB qw(Alice Spri   run AU     ride);
    print actionA qw(Ann spring sing SUMMER dive AuTumn study);
    print actionB qw(Ann spr    sing SUM    dive Au     study);
    print actionA qw(Iris AUTUMN travel WINTER shop Spring marry summer bike);
    print actionB qw(Iris AUT    travel WIN    shop Spring marry summer bike);

=head1 AUTHOR

Josef SchE<ouml>nbrunner E<lt>j.schoenbrunner@onemail.atE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2005 by Josef SchE<ouml>nbrunner
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.7 or,
at your option, any later version of Perl 5 you may have available.

=cut