The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  Copyright (C) 2011 - Anthony J. Lucas - kaoyoriketsu@ansoni.com



package Config::YAARG;
use base qw( Exporter );



our $VERSION = '0.023';



use strict;
use warnings;
use feature 'switch';



use Class::ISA ( );
use Getopt::Long ( );



#EXPORT CONFIGURATION


our @PUBLIC_CONSTANTS = qw/
    ARG_PASSTHROUGH
    ARG_IGNORE
/;

our @SCRIPT_ROUTINES = qw/ARGS/;
our @CLASS_METHODS = qw/process_args/;
our @STANDARD_ROUTINES = qw/ProcessArgs/;

our @EXPORT_OK = (
    @PUBLIC_CONSTANTS,
    @STANDARD_ROUTINES,
    @SCRIPT_ROUTINES,
    @CLASS_METHODS
);

our %EXPORT_TAGS = (
    'class' => [ @PUBLIC_CONSTANTS, @CLASS_METHODS ],
    'script' => [ @PUBLIC_CONSTANTS, @SCRIPT_ROUTINES ]
);



#CONSTANTS


use constant ARG_PASSTHROUGH => 'pass';
use constant ARG_IGNORE => 'ignore';


sub ARG_NAME_LIST {};
sub ARG_NAME_MAP {};
sub ARG_VALUE_TRANS {};



#SCRIPT HELPER ROUTINE


sub ARGS {

    my $class = $_[0] || caller();
    my $config = _yaarg_fetch_config(__PACKAGE__, $class);
    my $names = $config->{names};
    return unless ($names);

    my @names = @$names;
    s/=.*?$// foreach(@$names);

    my %args = ();
    Getopt::Long::GetOptions(\%args,
        map { (!s/=b$// and !/=/) ? "$_=s" : $_ } @names);
    return %{ProcessArgs(__PACKAGE__, $config, %args)};
}



#CLASS HELPER ROUTINE


sub process_args {

    my ($self, @args) = @_;

    #detect alt call signatures
    my $target = (@args % 2)
        ? shift(@args)
        : {};

    #gather config and process args
    my $result = $self->ProcessArgs(
        $self->_yaarg_fetch_config,
        @args);

    #copy results to target struct
    $target->{$_} = $result->{$_}
        foreach (keys(%{$result}));

    return $target;
}



#CORE ROUTINES


sub ProcessArgs {

    my ($context, $config, %args) = @_;

    my $map = $config->{'map'};
    my $trans = $config->{'trans'};

    my $t_args;
    $t_args = $context->_yaarg_transform_values(\%args, $trans)
        if ($trans);
    $context->_yaarg_transform_keys($t_args, $map, 1)
        if ($map);

    return $t_args || {};
}


sub _yaarg_fetch_config {

    my ($context, $class) = @_;
    $class ||= (ref($context) || $context);

    my @ISA = Class::ISA::self_and_super_path($class);
    my (@map, @trans, @names);
    foreach (@ISA) {
        my ($m, $t, $n) =
            $context->_yaarg_fetch_class_config($_);
        push(@map, $context->_yaarg_to_list($m));
        push(@trans, $context->_yaarg_to_list($t));
        push(@names, $context->_yaarg_to_list($n));
    }
    return {
        map => {@map},
        trans => {@trans},
        names => \@names
    };
}


sub _yaarg_fetch_class_config {

    my $class = $_[1];
    my @return;

    foreach (qw/
        ARG_NAME_MAP
        ARG_VALUE_TRANS
        ARG_NAME_LIST/) {

        push(@return, (($class->can($_))
            ? $class->$_() || undef
            : undef));
    }
    return @return;
}



#UTILITY ROUTINES



sub _yaarg_to_list {

    return %{$_[1]} if (ref($_[1]) eq 'HASH');
    return @{$_[1]} if (ref($_[1]) eq 'ARRAY');
    return ();
}


sub _yaarg_transform_keys {

    my ($self, $hash, $key_map, $no_dup) = @_;
    
    my ($thash, $v) = $no_dup ? $hash : {};
    foreach (keys %$key_map) {
        if (defined($v = $hash->{$_})) {
            $thash->{$key_map->{$_}} = $v;
            delete($thash->{$_}) if ($no_dup);
        }
    }
    return $thash;
}


sub _yaarg_transform_values {
    _yaarg_transform_values_r(@_[0..2], '');
}


sub _yaarg_transform_values_r {

    my ($self, $struct, $type_map, $key) = @_;


    #attempt reading common data structures
    given (ref($struct)) {

        when ('ARRAY') {
            return [ map {
                $self->_yaarg_transform_values_r($_, $type_map, $key);
                } @$struct ];
        }
        default {

            my $target = (defined($key) and $type_map)
                ? $type_map->{$key}
                : undef;
            
            #attempt custom type mapping
            if ($target) {
                given (ref($target)) {
                    when ('CODE') {
                        return $target->($struct);
                    }
                    when ('') {
                        return $target->new($struct) if ($target);
                    }
                }
            } elsif (ref($struct) eq 'HASH') {

                $target = {};
                foreach (keys(%$struct)) {
                    $target->{$_} = $self->_yaarg_transform_values_r(
                        $struct->{$_}, $type_map, $_);
                }
                return $target;
            }
        }
    }
    #otherwise return unchanged
    return $struct;
}





1;