<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package Params::Validate::PP;
{
  $Params::Validate::PP::VERSION = '1.08';
}

use strict;
use warnings;

use Params::Validate::Constants;
use Scalar::Util 1.10 ();

our $options;

# Various internals notes (for me and any future readers of this
# monstrosity):
#
# - A lot of the weirdness is _intentional_, because it optimizes for
#   the _success_ case.  It does not really matter how slow the code is
#   after it enters a path that leads to reporting failure.  But the
#   "success" path should be as fast as possible.
#
# -- We only calculate $called as needed for this reason, even though it
#    means copying code all over.
#
# - All the validation routines need to be careful never to alter the
#   references that are passed.
#
# -- The code assumes that _most_ callers will not be using the
#    skip_leading or ignore_case features.  In order to not alter the
#    references passed in, we copy them wholesale when normalizing them
#    to make these features work.  This is slower but lets us be faster
#    when not using them.

# Matt Sergeant came up with this prototype, which slickly takes the
# first array (which should be the caller's @_), and makes it a
# reference.  Everything after is the parameters for validation.
sub validate_pos (\@@) {
    return if $Params::Validate::NO_VALIDATION &amp;&amp; !defined wantarray;

    my $p = shift;

    my @specs = @_;

    my @p = @$p;
    if ($Params::Validate::NO_VALIDATION) {

        # if the spec is bigger that's where we can start adding
        # defaults
        for ( my $x = $#p + 1 ; $x &lt;= $#specs ; $x++ ) {
            $p[$x] = $specs[$x]-&gt;{default}
                if ref $specs[$x] &amp;&amp; exists $specs[$x]-&gt;{default};
        }

        return wantarray ? @p : \@p;
    }

    # I'm too lazy to pass these around all over the place.
    local $options ||= _get_options( ( caller(0) )[0] )
        unless defined $options;

    my $min = 0;

    while (1) {
        last
            unless (
            ref $specs[$min]
            ? !( exists $specs[$min]-&gt;{default} || $specs[$min]-&gt;{optional} )
            : $specs[$min]
            );

        $min++;
    }

    my $max = scalar @specs;

    my $actual = scalar @p;
    unless ( $actual &gt;= $min
        &amp;&amp; ( $options-&gt;{allow_extra} || $actual &lt;= $max ) ) {
        my $minmax = (
            $options-&gt;{allow_extra}
            ? "at least $min"
            : ( $min != $max ? "$min - $max" : $max )
        );

        my $val = $options-&gt;{allow_extra} ? $min : $max;
        $minmax .= $val != 1 ? ' were' : ' was';

        my $called = _get_called();

        $options-&gt;{on_fail}-&gt;( "$actual parameter"
                . ( $actual != 1 ? 's'    : '' ) . " "
                . ( $actual != 1 ? 'were' : 'was' )
                . " passed to $called but $minmax expected\n" );
    }

    my $bigger = $#p &gt; $#specs ? $#p : $#specs;
    foreach ( 0 .. $bigger ) {
        my $spec = $specs[$_];

        next unless ref $spec;

        if ( $_ &lt;= $#p ) {
            my $value = defined $p[$_] ? qq|"$p[$_]"| : 'undef';
            _validate_one_param(
                $p[$_], \@p, $spec,
                "Parameter #" . ( $_ + 1 ) . " ($value)"
            );
        }

        $p[$_] = $spec-&gt;{default} if $_ &gt; $#p &amp;&amp; exists $spec-&gt;{default};
    }

    _validate_pos_depends( \@p, \@specs );

    foreach (
        grep {
                   defined $p[$_]
                &amp;&amp; !ref $p[$_]
                &amp;&amp; ref $specs[$_]
                &amp;&amp; $specs[$_]{untaint}
        } 0 .. $bigger
        ) {
        ( $p[$_] ) = $p[$_] =~ /(.+)/;
    }

    return wantarray ? @p : \@p;
}

sub _validate_pos_depends {
    my ( $p, $specs ) = @_;

    for my $p_idx ( 0 .. $#$p ) {
        my $spec = $specs-&gt;[$p_idx];

        next
            unless $spec
            &amp;&amp; UNIVERSAL::isa( $spec, 'HASH' )
            &amp;&amp; exists $spec-&gt;{depends};

        my $depends = $spec-&gt;{depends};

        if ( ref $depends ) {
            require Carp;
            local $Carp::CarpLevel = 2;
            Carp::croak(
                "Arguments to 'depends' for validate_pos() must be a scalar");
        }

        my $p_size = scalar @$p;
        if ( $p_size &lt; $depends - 1 ) {
            my $error
                = (   "Parameter #"
                    . ( $p_idx + 1 )
                    . " depends on parameter #"
                    . $depends
                    . ", which was not given" );

            $options-&gt;{on_fail}-&gt;($error);
        }
    }
    return 1;
}

sub _validate_named_depends {
    my ( $p, $specs ) = @_;

    foreach my $pname ( keys %$p ) {
        my $spec = $specs-&gt;{$pname};

        next
            unless $spec
            &amp;&amp; UNIVERSAL::isa( $spec, 'HASH' )
            &amp;&amp; $spec-&gt;{depends};

        unless ( UNIVERSAL::isa( $spec-&gt;{depends}, 'ARRAY' )
            || !ref $spec-&gt;{depends} ) {
            require Carp;
            local $Carp::CarpLevel = 2;
            Carp::croak(
                "Arguments to 'depends' must be a scalar or arrayref");
        }

        foreach my $depends_name (
            ref $spec-&gt;{depends}
            ? @{ $spec-&gt;{depends} }
            : $spec-&gt;{depends}
            ) {
            unless ( exists $p-&gt;{$depends_name} ) {
                my $error
                    = (   "Parameter '$pname' depends on parameter '"
                        . $depends_name
                        . "', which was not given" );

                $options-&gt;{on_fail}-&gt;($error);
            }
        }
    }
}

sub validate (\@$) {
    return if $Params::Validate::NO_VALIDATION &amp;&amp; !defined wantarray;

    my $p = $_[0];

    my $specs = $_[1];
    local $options = _get_options( ( caller(0) )[0] ) unless defined $options;

    if ( ref $p eq 'ARRAY' ) {

        # we were called as validate( @_, ... ) where @_ has a
        # single element, a hash reference
        if ( ref $p-&gt;[0] ) {
            $p = { %{ $p-&gt;[0] } };
        }
        elsif ( @$p % 2 ) {
            my $called = _get_called();

            $options-&gt;{on_fail}
                -&gt;(   "Odd number of parameters in call to $called "
                    . "when named parameters were expected\n" );
        }
        else {
            $p = {@$p};
        }
    }

    if ( $options-&gt;{normalize_keys} ) {
        $specs = _normalize_callback( $specs, $options-&gt;{normalize_keys} );
        $p     = _normalize_callback( $p,     $options-&gt;{normalize_keys} );
    }
    elsif ( $options-&gt;{ignore_case} || $options-&gt;{strip_leading} ) {
        $specs = _normalize_named($specs);
        $p     = _normalize_named($p);
    }

    if ($Params::Validate::NO_VALIDATION) {
        return (
            wantarray
            ? (

                # this is a hash containing just the defaults
                (
                    map { $_ =&gt; $specs-&gt;{$_}-&gt;{default} }
                        grep {
                        ref $specs-&gt;{$_}
                            &amp;&amp; exists $specs-&gt;{$_}-&gt;{default}
                        }
                        keys %$specs
                ),
                (
                    ref $p eq 'ARRAY'
                    ? (
                        ref $p-&gt;[0]
                        ? %{ $p-&gt;[0] }
                        : @$p
                        )
                    : %$p
                )
                )
            : do {
                my $ref = (
                    ref $p eq 'ARRAY'
                    ? (
                        ref $p-&gt;[0]
                        ? $p-&gt;[0]
                        : {@$p}
                        )
                    : $p
                );

                foreach (
                    grep {
                        ref $specs-&gt;{$_}
                            &amp;&amp; exists $specs-&gt;{$_}-&gt;{default}
                    }
                    keys %$specs
                    ) {
                    $ref-&gt;{$_} = $specs-&gt;{$_}-&gt;{default}
                        unless exists $ref-&gt;{$_};
                }

                return $ref;
                }
        );
    }

    _validate_named_depends( $p, $specs );

    unless ( $options-&gt;{allow_extra} ) {
        if ( my @unmentioned = grep { !exists $specs-&gt;{$_} } keys %$p ) {
            my $called = _get_called();

            $options-&gt;{on_fail}-&gt;( "The following parameter"
                    . ( @unmentioned &gt; 1 ? 's were' : ' was' )
                    . " passed in the call to $called but "
                    . ( @unmentioned &gt; 1 ? 'were' : 'was' )
                    . " not listed in the validation options: @unmentioned\n"
            );
        }
    }

    my @missing;

    # the iterator needs to be reset in case the same hashref is being
    # passed to validate() on successive calls, because we may not go
    # through all the hash's elements
    keys %$specs;
OUTER:
    while ( my ( $key, $spec ) = each %$specs ) {
        if (
            !exists $p-&gt;{$key}
            &amp;&amp; (
                ref $spec
                ? !(
                    do {

                        # we want to short circuit the loop here if we
                        # can assign a default, because there's no need
                        # check anything else at all.
                        if ( exists $spec-&gt;{default} ) {
                            $p-&gt;{$key} = $spec-&gt;{default};
                            next OUTER;
                        }
                    }
                    || do {

                        # Similarly, an optional parameter that is
                        # missing needs no additional processing.
                        next OUTER if $spec-&gt;{optional};
                    }
                )
                : $spec
            )
            ) {
            push @missing, $key;
        }

        # Can't validate a non hashref spec beyond the presence or
        # absence of the parameter.
        elsif ( ref $spec ) {
            my $value = defined $p-&gt;{$key} ? qq|"$p-&gt;{$key}"| : 'undef';
            _validate_one_param(
                $p-&gt;{$key}, $p, $spec,
                "The '$key' parameter ($value)"
            );
        }
    }

    if (@missing) {
        my $called = _get_called();

        my $missing = join ', ', map { "'$_'" } @missing;
        $options-&gt;{on_fail}-&gt;( "Mandatory parameter"
                . ( @missing &gt; 1 ? 's' : '' )
                . " $missing missing in call to $called\n" );
    }

    # do untainting after we know everything passed
    foreach my $key (
        grep {
                   defined $p-&gt;{$_}
                &amp;&amp; !ref $p-&gt;{$_}
                &amp;&amp; ref $specs-&gt;{$_}
                &amp;&amp; $specs-&gt;{$_}{untaint}
        }
        keys %$p
        ) {
        ( $p-&gt;{$key} ) = $p-&gt;{$key} =~ /(.+)/;
    }

    return wantarray ? %$p : $p;
}

sub validate_with {
    return if $Params::Validate::NO_VALIDATION &amp;&amp; !defined wantarray;

    my %p = @_;

    local $options = _get_options( ( caller(0) )[0], %p );

    unless ($Params::Validate::NO_VALIDATION) {
        unless ( exists $options-&gt;{called} ) {
            $options-&gt;{called} = ( caller( $options-&gt;{stack_skip} ) )[3];
        }

    }

    if ( UNIVERSAL::isa( $p{spec}, 'ARRAY' ) ) {
        return validate_pos( @{ $p{params} }, @{ $p{spec} } );
    }
    else {

        # intentionally ignore the prototype because this contains
        # either an array or hash reference, and validate() will
        # handle either one properly
        return &amp;validate( $p{params}, $p{spec} );
    }
}

sub _normalize_callback {
    my ( $p, $func ) = @_;

    my %new;

    foreach my $key ( keys %$p ) {
        my $new_key = $func-&gt;($key);

        unless ( defined $new_key ) {
            die
                "The normalize_keys callback did not return a defined value when normalizing the key '$key'";
        }

        if ( exists $new{$new_key} ) {
            die
                "The normalize_keys callback returned a key that already exists, '$new_key', when normalizing the key '$key'";
        }

        $new{$new_key} = $p-&gt;{$key};
    }

    return \%new;
}

sub _normalize_named {

    # intentional copy so we don't destroy original
    my %h = ( ref $_[0] ) =~ /ARRAY/ ? @{ $_[0] } : %{ $_[0] };

    if ( $options-&gt;{ignore_case} ) {
        $h{ lc $_ } = delete $h{$_} for keys %h;
    }

    if ( $options-&gt;{strip_leading} ) {
        foreach my $key ( keys %h ) {
            my $new;
            ( $new = $key ) =~ s/^\Q$options-&gt;{strip_leading}\E//;
            $h{$new} = delete $h{$key};
        }
    }

    return \%h;
}

my %Valid = map { $_ =&gt; 1 }
    qw( callbacks can default depends isa optional regex type untaint  );

sub _validate_one_param {
    my ( $value, $params, $spec, $id ) = @_;

    # for my $key ( keys %{$spec} ) {
    #     unless ( $Valid{$key} ) {
    #         $options-&gt;{on_fail}
    #             -&gt;(qq{"$key" is not an allowed validation spec key});
    #     }
    # }

    if ( exists $spec-&gt;{type} ) {
        unless ( defined $spec-&gt;{type}
            &amp;&amp; Scalar::Util::looks_like_number( $spec-&gt;{type} )
            &amp;&amp; $spec-&gt;{type} &gt; 0 ) {
            my $msg
                = "$id has a type specification which is not a number. It is ";
            if ( defined $spec-&gt;{type} ) {
                $msg .= "a string - $spec-&gt;{type}";
            }
            else {
                $msg .= "undef";
            }

            $msg
                .= ".\n Use the constants exported by Params::Validate to declare types.";

            $options-&gt;{on_fail}-&gt;($msg);
        }

        unless ( _get_type($value) &amp; $spec-&gt;{type} ) {
            my $type = _get_type($value);

            my @is      = _typemask_to_strings($type);
            my @allowed = _typemask_to_strings( $spec-&gt;{type} );
            my $article = $is[0] =~ /^[aeiou]/i ? 'an' : 'a';

            my $called = _get_called(1);

            $options-&gt;{on_fail}-&gt;( "$id to $called was $article '@is', which "
                    . "is not one of the allowed types: @allowed\n" );
        }
    }

    # short-circuit for common case
    return
        unless ( $spec-&gt;{isa}
        || $spec-&gt;{can}
        || $spec-&gt;{callbacks}
        || $spec-&gt;{regex} );

    if ( exists $spec-&gt;{isa} ) {
        foreach ( ref $spec-&gt;{isa} ? @{ $spec-&gt;{isa} } : $spec-&gt;{isa} ) {
            unless (
                do {
                    local $@;
                    eval { $value-&gt;isa($_) };
                }
                ) {
                my $is = ref $value ? ref $value : 'plain scalar';
                my $article1 = $_  =~ /^[aeiou]/i ? 'an' : 'a';
                my $article2 = $is =~ /^[aeiou]/i ? 'an' : 'a';

                my $called = _get_called(1);

                $options-&gt;{on_fail}
                    -&gt;(   "$id to $called was not $article1 '$_' "
                        . "(it is $article2 $is)\n" );
            }
        }
    }

    if ( exists $spec-&gt;{can} ) {
        foreach ( ref $spec-&gt;{can} ? @{ $spec-&gt;{can} } : $spec-&gt;{can} ) {
            unless (
                do {
                    local $@;
                    eval { $value-&gt;can($_) };
                }
                ) {
                my $called = _get_called(1);

                $options-&gt;{on_fail}
                    -&gt;("$id to $called does not have the method: '$_'\n");
            }
        }
    }

    if ( $spec-&gt;{callbacks} ) {
        unless ( UNIVERSAL::isa( $spec-&gt;{callbacks}, 'HASH' ) ) {
            my $called = _get_called(1);

            $options-&gt;{on_fail}-&gt;(
                "'callbacks' validation parameter for $called must be a hash reference\n"
            );
        }

        foreach ( keys %{ $spec-&gt;{callbacks} } ) {
            unless ( UNIVERSAL::isa( $spec-&gt;{callbacks}{$_}, 'CODE' ) ) {
                my $called = _get_called(1);

                $options-&gt;{on_fail}-&gt;(
                    "callback '$_' for $called is not a subroutine reference\n"
                );
            }

            unless ( $spec-&gt;{callbacks}{$_}-&gt;( $value, $params ) ) {
                my $called = _get_called(1);

                $options-&gt;{on_fail}
                    -&gt;("$id to $called did not pass the '$_' callback\n");
            }
        }
    }

    if ( exists $spec-&gt;{regex} ) {
        unless ( ( defined $value ? $value : '' ) =~ /$spec-&gt;{regex}/ ) {
            my $called = _get_called(1);

            $options-&gt;{on_fail}
                -&gt;("$id to $called did not pass regex check\n");
        }
    }
}

{
    # if it UNIVERSAL::isa the string on the left then its the type on
    # the right
    my %isas = (
        'ARRAY'  =&gt; ARRAYREF,
        'HASH'   =&gt; HASHREF,
        'CODE'   =&gt; CODEREF,
        'GLOB'   =&gt; GLOBREF,
        'SCALAR' =&gt; SCALARREF,
        'REGEXP' =&gt; SCALARREF,
    );
    my %simple_refs = map { $_ =&gt; 1 } keys %isas;

    sub _get_type {
        return UNDEF unless defined $_[0];

        my $ref = ref $_[0];
        unless ($ref) {

            # catches things like:  my $fh = do { local *FH; };
            return GLOB if UNIVERSAL::isa( \$_[0], 'GLOB' );
            return SCALAR;
        }

        return $isas{$ref} if $simple_refs{$ref};

        foreach ( keys %isas ) {
            return $isas{$_} | OBJECT if UNIVERSAL::isa( $_[0], $_ );
        }

        # I really hope this never happens.
        return UNKNOWN;
    }
}

{
    my %type_to_string = (
        SCALAR()    =&gt; 'scalar',
        ARRAYREF()  =&gt; 'arrayref',
        HASHREF()   =&gt; 'hashref',
        CODEREF()   =&gt; 'coderef',
        GLOB()      =&gt; 'glob',
        GLOBREF()   =&gt; 'globref',
        SCALARREF() =&gt; 'scalarref',
        UNDEF()     =&gt; 'undef',
        OBJECT()    =&gt; 'object',
        UNKNOWN()   =&gt; 'unknown',
    );

    sub _typemask_to_strings {
        my $mask = shift;

        my @types;
        foreach (
            SCALAR,    ARRAYREF, HASHREF, CODEREF, GLOB, GLOBREF,
            SCALARREF, UNDEF,    OBJECT,  UNKNOWN
            ) {
            push @types, $type_to_string{$_} if $mask &amp; $_;
        }
        return @types ? @types : ('unknown');
    }
}

{
    my %defaults = (
        ignore_case   =&gt; 0,
        strip_leading =&gt; 0,
        allow_extra   =&gt; 0,
        on_fail       =&gt; sub {
            require Carp;
            Carp::confess( $_[0] );
        },
        stack_skip     =&gt; 1,
        normalize_keys =&gt; undef,
    );

    *set_options = \&amp;validation_options;

    sub validation_options {
        my %opts = @_;

        my $caller = caller;

        foreach ( keys %defaults ) {
            $opts{$_} = $defaults{$_} unless exists $opts{$_};
        }

        $Params::Validate::OPTIONS{$caller} = \%opts;
    }

    sub _get_options {
        my $caller = shift;

        if (@_) {

            return (
                $Params::Validate::OPTIONS{$caller}
                ? {
                    %{ $Params::Validate::OPTIONS{$caller} },
                    @_
                    }
                : { %defaults, @_ }
            );
        }
        else {
            return (
                exists $Params::Validate::OPTIONS{$caller}
                ? $Params::Validate::OPTIONS{$caller}
                : \%defaults
            );
        }
    }
}

sub _get_called {
    my $extra_skip = $_[0] || 0;

    # always add one more for this sub
    $extra_skip++;

    my $called = (
        exists $options-&gt;{called}
        ? $options-&gt;{called}
        : ( caller( $options-&gt;{stack_skip} + $extra_skip ) )[3]
    );

    $called = 'N/A' unless defined $called;

    return $called;
}

1;
</pre></body></html>