package WWW::PAUSE::Simple;

our $DATE = '2015-03-25'; # DATE
our $VERSION = '0.14'; # VERSION

use 5.010001;
use strict;
use warnings;
use Log::Any '$log';
use Exporter qw(import);
our @EXPORT_OK = qw(
                       upload_file
                       list_files
                       delete_files
                       undelete_files
                       reindex_files
                       list_dists
                       delete_old_releases
                       set_password
                       set_account_info
               );

use Perinci::Object;

our %SPEC;

our $re_archive_ext = qr/(?:tar|tar\.(?:Z|gz|bz2|xz)|zip|rar)/;

our %common_args = (
    username => {
        summary => 'PAUSE ID',
        schema  => ['str*', match=>'\A\w{2,9}\z', max_len=>9],
        req     => 1,
        tags    => ['common'],
    },
    password => {
        summary => 'PAUSE password',
        schema  => 'str*',
        is_password => 1,
        req     => 1,
        tags    => ['common'],
    },
);

our %detail_arg = (
    detail => {
        summary => 'Whether to return detailed records',
        schema  => 'bool',
    },
);

our %detail_l_arg = (
    detail => {
        summary => 'Whether to return detailed records',
        schema  => 'bool',
        cmdline_aliases => {l=>{}},
    },
);

our %file_arg = (
    file => {
        summary => 'File name/wildcard pattern',
        schema  => ['array*', of=>'str*', min_len=>1],
        req => 1,
        pos => 0,
        greedy => 1,
    },
);

our %file_opt_arg = (
    file => {
        summary => 'File name/wildcard pattern',
        schema  => ['array*', of=>'str*'],
        pos => 0,
        greedy => 1,
    },
);

$SPEC{':package'} = {
    v => 1.1,
    summary => 'An API for PAUSE',
};

sub _common_args {
    my $args = shift;
    (username=>$args->{username}, password=>$args->{password});
}

sub _request {
    require HTTP::Request::Common;

    my %args = @_;

    state $ua = do {
        require LWP::UserAgent;
        LWP::UserAgent->new;
    };
    my $req = HTTP::Request::Common::POST(
        "https://pause.perl.org/pause/authenquery",
        @{ $args{post_data} });
    $req->authorization_basic($args{username}, $args{password});

    $ua->request($req);
}

sub _htres2envres {
    my $res = shift;
    [$res->code, $res->message, $res->content];
}

$SPEC{upload_file} = {
    v => 1.1,
    summary => 'Upload file(s) to your PAUSE account',
    args => {
        %common_args,
        %file_arg,
        subdir => {
            summary => 'Subdirectory to put the file(s) into',
            schema  => 'str*',
            default => '',
        },
    },
};
use experimental 'smartmatch'; no warnings 'void'; require List::Util;no warnings 'void';require Scalar::Util::Numeric; $SPEC{upload_file} = {args=>{file=>{greedy=>1, pos=>0, req=>1, schema=>["array", {min_len=>1, of=>"str*", req=>1}, {}], summary=>"File name/wildcard pattern"}, password=>{is_password=>1, req=>1, schema=>["str", {req=>1}, {}], summary=>"PAUSE password", tags=>["common"]}, subdir=>{default=>"", schema=>["str", {req=>1}, {}], summary=>"Subdirectory to put the file(s) into"}, username=>{req=>1, schema=>["str", {match=>"\\A\\w{2,9}\\z", max_len=>9, req=>1}, {}], summary=>"PAUSE ID", tags=>["common"]}}, args_as=>"hash", summary=>"Upload file(s) to your PAUSE account", v=>1.1, "x.perinci.sub.wrapper.logs"=>[{normalize_schema=>1, validate_args=>1, validate_result=>1}]}; sub upload_file { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap
    require File::Basename;

    my %args = @_; my $_sahv_dpath = []; my $_w_res = undef; for (sort keys %args) { if (!/\A(-?)\w+(\.\w+)*\z/o) { return [400, "Invalid argument name (please use letters/numbers/underscores only)'$_'"]; } if (!($1 || $_ ~~ ['file','password','subdir','username'])) { return [400, "Unknown argument '$_'"]; } } if (exists($args{'file'})) { my $err_file; ((defined($args{'file'})) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((ref($args{'file'}) eq 'ARRAY') ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type array"),0)) && ((@{$args{'file'}} >= 1) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at least 1"),0)) && ([push(@{$_sahv_dpath}, undef), ((!defined(List::Util::first(sub {!( ($_sahv_dpath->[-1] = defined($_sahv_dpath->[-1]) ? $_sahv_dpath->[-1]+1 : 0), ((defined($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) )}, @{$args{'file'}}))) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)), pop(@{$_sahv_dpath})]->[1]); if ($err_file) { return [400, "Argument 'file' fails validation: $err_file"]; } }  if (!exists($args{'file'})) { return [400, "Missing required argument: file"]; } if (exists($args{'password'})) { my $err_password; ((defined($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_password) { return [400, "Argument 'password' fails validation: $err_password"]; } }  if (!exists($args{'password'})) { return [400, "Missing required argument: password"]; } if (exists($args{'subdir'})) { my $err_subdir; ((defined($args{'subdir'})) ? 1 : (($err_subdir //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'subdir'})) ? 1 : (($err_subdir //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_subdir) { return [400, "Argument 'subdir' fails validation: $err_subdir"]; } } else { $args{'subdir'} //= ''; }  if (exists($args{'username'})) { my $err_username; ((defined($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) && (($args{'username'} =~ qr((?:(?-)\A\w{2,9}\z))) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must match regex pattern \\A\\w{2,9}\\z"),0)) && ((length($args{'username'}) <= 9) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at most 9"),0)); if ($err_username) { return [400, "Argument 'username' fails validation: $err_username"]; } }  if (!exists($args{'username'})) { return [400, "Missing required argument: username"]; }    $_w_res = do { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap
    my $files  = $args{file}
        or return [400, "Please specify at least one file"];
    my $subdir = $args{subdir} // '';

    my $envres = envresmulti();

    for my $file (@$files) {
        my $res;
        {
            unless (-f $file) {
                $res = [404, "No such file"];
                last;
            }

            $log->tracef("Uploading %s ...", $file);
            my $httpres = _request(
                %args,
                post_data => [
                    Content_Type => 'form-data',
                    Content => {
                        HIDDENNAME                        => $args{username},
                        CAN_MULTIPART                     => 0,
                        pause99_add_uri_upload            => File::Basename::basename($file),
                        SUBMIT_pause99_add_uri_httpupload => " Upload this file from my disk ",
                        pause99_add_uri_uri               => "",
                        pause99_add_uri_httpupload        => [$file],
                        (length($subdir) ? (pause99_add_uri_subdirtext => $subdir) : ()),
                    },
                ]
            );
            if (!$httpres->is_success) {
                $res = _htres2envres($httpres);
                last;
            }
            if ($httpres->content !~ m!<h3>Submitting query</h3>\s*<p>(.+?)</p>!s) {
                $res = [543, "Can't scrape upload status from response", $httpres->content];
                last;
            }
            my $str = $1;
            if ($str =~ /Query succeeded/) {
                $res = [200, "OK", undef, {"func.raw_status" => $str}];
            } else {
                $res = [500, "Failed: $str"];
            }
        }
        $res->[3] //= {};
        $res->[3]{item_id} = $file;
        $log->tracef("Result of upload: %s", $res);
        $envres->add_result($res->[0], $res->[1], $res->[3]);
    }
    $envres->as_struct;
};      unless (ref($_w_res) eq "ARRAY" && $_w_res->[0]) { return [500, 'BUG: Sub WWW::PAUSE::Simple::upload_file does not produce envelope']; } return $_w_res; } ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

$SPEC{list_files} = {
    v => 1.1,
    summary => 'List files on your PAUSE account',
    args => {
        %common_args,
        %detail_l_arg,
        %file_opt_arg,
        del => {
            summary => 'Only list files which are scheduled for deletion',
            'summary.alt.bool.not' => 'Only list files which are not scheduled for deletion',
            schema => 'bool',
            tags => ['category:filtering'],
        },
    },
};
$SPEC{list_files} = {args=>{del=>{schema=>["bool", {}, {}], summary=>"Only list files which are scheduled for deletion", "summary.alt.bool.not"=>"Only list files which are not scheduled for deletion", tags=>["category:filtering"]}, detail=>{cmdline_aliases=>{l=>{}}, schema=>["bool", {}, {}], summary=>"Whether to return detailed records"}, file=>{greedy=>1, pos=>0, schema=>["array", {of=>"str*", req=>1}, {}], summary=>"File name/wildcard pattern"}, password=>{is_password=>1, req=>1, schema=>["str", {req=>1}, {}], summary=>"PAUSE password", tags=>["common"]}, username=>{req=>1, schema=>["str", {match=>"\\A\\w{2,9}\\z", max_len=>9, req=>1}, {}], summary=>"PAUSE ID", tags=>["common"]}}, args_as=>"hash", summary=>"List files on your PAUSE account", v=>1.1, "x.perinci.sub.wrapper.logs"=>[{normalize_schema=>1, validate_args=>1, validate_result=>1}]}; sub list_files {
    require DateTime::Format::DateParse; # XXX any better module?
    require Regexp::Wildcards;
    require String::Wildcard::Bash;

    my %args  = @_; my $_sahv_dpath = []; my $_w_res = undef; for (sort keys %args) { if (!/\A(-?)\w+(\.\w+)*\z/o) { return [400, "Invalid argument name (please use letters/numbers/underscores only)'$_'"]; } if (!($1 || $_ ~~ ['del','detail','file','password','username'])) { return [400, "Unknown argument '$_'"]; } } if (exists($args{'del'})) { my $err_del; (!defined($args{'del'}) ? 1 :  ((!ref($args{'del'})) ? 1 : (($err_del //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type boolean value"),0))); if ($err_del) { return [400, "Argument 'del' fails validation: $err_del"]; } }  if (exists($args{'detail'})) { my $err_detail; (!defined($args{'detail'}) ? 1 :  ((!ref($args{'detail'})) ? 1 : (($err_detail //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type boolean value"),0))); if ($err_detail) { return [400, "Argument 'detail' fails validation: $err_detail"]; } }  if (exists($args{'file'})) { my $err_file; ((defined($args{'file'})) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((ref($args{'file'}) eq 'ARRAY') ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type array"),0)) && ([push(@{$_sahv_dpath}, undef), ((!defined(List::Util::first(sub {!( ($_sahv_dpath->[-1] = defined($_sahv_dpath->[-1]) ? $_sahv_dpath->[-1]+1 : 0), ((defined($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) )}, @{$args{'file'}}))) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)), pop(@{$_sahv_dpath})]->[1]); if ($err_file) { return [400, "Argument 'file' fails validation: $err_file"]; } }  if (exists($args{'password'})) { my $err_password; ((defined($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_password) { return [400, "Argument 'password' fails validation: $err_password"]; } }  if (!exists($args{'password'})) { return [400, "Missing required argument: password"]; } if (exists($args{'username'})) { my $err_username; ((defined($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) && (($args{'username'} =~ qr((?:(?-)\A\w{2,9}\z))) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must match regex pattern \\A\\w{2,9}\\z"),0)) && ((length($args{'username'}) <= 9) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at most 9"),0)); if ($err_username) { return [400, "Argument 'username' fails validation: $err_username"]; } }  if (!exists($args{'username'})) { return [400, "Missing required argument: username"]; }    $_w_res = do { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap
    my $q   = $args{file} // [];
    my $del = $args{del};

    my $httpres = _request(
        %args,
        post_data => [{ACTION=>'show_files'}],
    );

    # convert wildcard patterns in arguments to regexp
    $q = [@$q];
    for (@$q) {
        next unless String::Wildcard::Bash::contains_wildcard($_);
        my $re = Regexp::Wildcards->new(type=>'unix')->convert($_);
        $re = qr/\A($re)\z/;
        $_ = $re;
    }

    return _htres2envres($httpres) unless $httpres->is_success;
    return [543, "Can't scrape list of files from response",
            $httpres->content]
        unless $httpres->content =~ m!<h3>Files in directory.+</h3><pre>(.+)</pre>!s;
    my $str = $1;
    my @files;
  REC:
    while ($str =~ m!(?:\A |<br/> )(.+?)\s+(\d+)\s+(Scheduled for deletion \(due at )?(\w+, \d\d \w+ \d{4} \d\d:\d\d:\d\d GMT)!g) {

        my $time = DateTime::Format::DateParse->parse_datetime($4);
        if ($time) {
            $time = $time->epoch;
        } else {
            $time = 0;
        }

        my $rec = {
            name  => $1,
            size  => $2,
            is_scheduled_for_deletion => $3 ? 1:0,
        };
        if ($3) {
            $rec->{deletion_time} = $time;
        } else {
            $rec->{mtime} = $time;
        }

        # filter by requested file/wildcard
        for (@$q) {
            if (ref($_) eq 'Regexp') {
                next REC unless $rec->{name} =~ $_;
            } else {
                next REC unless $rec->{name} eq $_;
            }
        }
        if (defined $del) {
            next REC if $del xor $rec->{is_scheduled_for_deletion};
        }

        push @files, $args{detail} ? $rec : $rec->{name};

    }
    my %resmeta;
    if ($args{detail}) {
        $resmeta{format_options} = {
            any => {
                table_column_orders => [[qw/name size mtime is_scheduled_for_deletion deletion_time/]],
            },
        };
    }
    [200, "OK", \@files, \%resmeta];
};      unless (ref($_w_res) eq "ARRAY" && $_w_res->[0]) { return [500, 'BUG: Sub WWW::PAUSE::Simple::list_files does not produce envelope']; } return $_w_res; } ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

$SPEC{list_dists} = {
    v => 1.1,
    summary => 'List distributions on your PAUSE account',
    description => <<'_',

Distribution names will be extracted from tarball/zip filenames.

Unknown/unparseable filenames will be skipped.

_
    args => {
        %common_args,
        %detail_l_arg,
        newest => {
            schema => 'bool',
            summary => 'Only show newest non-dev version',
            description => <<'_',

Dev versions will be skipped.

_
        },
        newest_n => {
            schema => ['int*', min=>1],
            summary => 'Only show this number of newest non-dev versions',
            description => <<'_',

Dev versions will be skipped.

_
        },
    },
};
$SPEC{list_dists} = {args=>{detail=>{cmdline_aliases=>{l=>{}}, schema=>["bool", {}, {}], summary=>"Whether to return detailed records"}, newest=>{description=>"\nDev versions will be skipped.\n\n", schema=>["bool", {}, {}], summary=>"Only show newest non-dev version"}, newest_n=>{description=>"\nDev versions will be skipped.\n\n", schema=>["int", {min=>1, req=>1}, {}], summary=>"Only show this number of newest non-dev versions"}, password=>{is_password=>1, req=>1, schema=>["str", {req=>1}, {}], summary=>"PAUSE password", tags=>["common"]}, username=>{req=>1, schema=>["str", {match=>"\\A\\w{2,9}\\z", max_len=>9, req=>1}, {}], summary=>"PAUSE ID", tags=>["common"]}}, args_as=>"hash", description=>"\nDistribution names will be extracted from tarball/zip filenames.\n\nUnknown/unparseable filenames will be skipped.\n\n", summary=>"List distributions on your PAUSE account", v=>1.1, "x.perinci.sub.wrapper.logs"=>[{normalize_schema=>1, validate_args=>1, validate_result=>1}]}; sub list_dists {
    require List::MoreUtils;
    require Version::Util;
    use experimental 'smartmatch';

    my %args  = @_; my $_sahv_dpath = []; my $_w_res = undef; for (sort keys %args) { if (!/\A(-?)\w+(\.\w+)*\z/o) { return [400, "Invalid argument name (please use letters/numbers/underscores only)'$_'"]; } if (!($1 || $_ ~~ ['detail','newest','newest_n','password','username'])) { return [400, "Unknown argument '$_'"]; } } if (exists($args{'detail'})) { my $err_detail; (!defined($args{'detail'}) ? 1 :  ((!ref($args{'detail'})) ? 1 : (($err_detail //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type boolean value"),0))); if ($err_detail) { return [400, "Argument 'detail' fails validation: $err_detail"]; } }  if (exists($args{'newest'})) { my $err_newest; (!defined($args{'newest'}) ? 1 :  ((!ref($args{'newest'})) ? 1 : (($err_newest //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type boolean value"),0))); if ($err_newest) { return [400, "Argument 'newest' fails validation: $err_newest"]; } }  if (exists($args{'newest_n'})) { my $err_newest_n; ((defined($args{'newest_n'})) ? 1 : (($err_newest_n //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((Scalar::Util::Numeric::isint($args{'newest_n'})) ? 1 : (($err_newest_n //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type integer"),0)) && (($args{'newest_n'} >= 1) ? 1 : (($err_newest_n //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must be at least 1"),0)); if ($err_newest_n) { return [400, "Argument 'newest_n' fails validation: $err_newest_n"]; } }  if (exists($args{'password'})) { my $err_password; ((defined($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_password) { return [400, "Argument 'password' fails validation: $err_password"]; } }  if (!exists($args{'password'})) { return [400, "Missing required argument: password"]; } if (exists($args{'username'})) { my $err_username; ((defined($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) && (($args{'username'} =~ qr((?:(?-)\A\w{2,9}\z))) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must match regex pattern \\A\\w{2,9}\\z"),0)) && ((length($args{'username'}) <= 9) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at most 9"),0)); if ($err_username) { return [400, "Argument 'username' fails validation: $err_username"]; } }  if (!exists($args{'username'})) { return [400, "Missing required argument: username"]; }    $_w_res = do { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

    my $res = list_files(_common_args(\%args), del=>0);
    return [500, "Can't list files: $res->[0] - $res->[1]"] if $res->[0] != 200;

    my $newest_n;
    if ($args{newest_n}) {
        $newest_n = $args{newest_n};
    } elsif ($args{newest}) {
        $newest_n = 1;
    }

    my @dists;
    for my $file (@{$res->[2]}) {
        if ($file =~ m!/!) {
            $log->debugf("Skipping %s: under a subdirectory", $file);
            next;
        }
        unless ($file =~ /\A
                          (\w+(?:-\w+)*)
                          -v?(\d+(?:\.\d+){0,2}(_\d+|-TRIAL)?)
                          \.$re_archive_ext
                          \z/ix) {
            $log->debugf("Skipping %s: doesn't match release regex", $file);
            next;
        }
        my ($dist, $version, $is_dev) = ($1, $2, $3);
        next if $is_dev && $newest_n;
        push @dists, {
            name => $dist,
            file => $file,
            version => $version,
            is_dev_version => $is_dev ? 1:0,
        };
    }

    my @old_files;
    if ($newest_n) {
        my %dist_versions;
        for my $dist (@dists) {
            push @{ $dist_versions{$dist->{name}} }, $dist->{version};
        }
        for my $dist (keys %dist_versions) {
            $dist_versions{$dist} = [
                sort { -Version::Util::cmp_version($a, $b) }
                    @{ $dist_versions{$dist} }];
            if (@{ $dist_versions{$dist} } > $newest_n) {
                $dist_versions{$dist} = [splice(
                    @{ $dist_versions{$dist} }, 0, $newest_n)];
            }
        }
        my @old_dists = @dists;
        @dists = ();
        for my $dist (@old_dists) {
            if ($dist->{version} ~~ @{ $dist_versions{$dist->{name}} }) {
                push @dists, $dist;
            } else {
                push @old_files, $dist->{file};
            }
        }
    }

    unless ($args{detail}) {
        @dists = List::MoreUtils::uniq(map { $_->{name} } @dists);
    }

    my %resmeta;
    if ($newest_n) {
        $resmeta{"func.old_files"} = \@old_files;
    }
    if ($args{detail}) {
        $resmeta{format_options} = {
            any => {
                table_column_orders => [[qw/name version is_dev_version file/]],
            },
        };
    }
    [200, "OK", \@dists, \%resmeta];
};      unless (ref($_w_res) eq "ARRAY" && $_w_res->[0]) { return [500, 'BUG: Sub WWW::PAUSE::Simple::list_dists does not produce envelope']; } return $_w_res; } ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

$SPEC{delete_old_releases} = {
    v => 1.1,
    summary => 'Delete older versions of distributions on your PAUSE account',
    description => <<'_',

Developer releases will not be deleted.

_
    args => {
        %common_args,
        %detail_l_arg,
        num_keep => {
            schema => ['int*', min=>1],
            default => 1,
            summary => 'Number of new versions (including newest) to keep',
            cmdline_aliases => { n=>{} },
            description => <<'_',

1 means to only keep the newest version, 2 means to keep the newest and the
second newest, and so on.

_
        },
    },
    features => {dry_run=>1},
};
$SPEC{delete_old_releases} = {args=>{detail=>{cmdline_aliases=>{l=>{}}, schema=>["bool", {}, {}], summary=>"Whether to return detailed records"}, num_keep=>{cmdline_aliases=>{n=>{}}, default=>1, description=>"\n1 means to only keep the newest version, 2 means to keep the newest and the\nsecond newest, and so on.\n\n", schema=>["int", {min=>1, req=>1}, {}], summary=>"Number of new versions (including newest) to keep"}, password=>{is_password=>1, req=>1, schema=>["str", {req=>1}, {}], summary=>"PAUSE password", tags=>["common"]}, username=>{req=>1, schema=>["str", {match=>"\\A\\w{2,9}\\z", max_len=>9, req=>1}, {}], summary=>"PAUSE ID", tags=>["common"]}}, args_as=>"hash", description=>"\nDeveloper releases will not be deleted.\n\n", features=>{dry_run=>1}, summary=>"Delete older versions of distributions on your PAUSE account", v=>1.1, "x.perinci.sub.wrapper.logs"=>[{normalize_schema=>1, validate_args=>1, validate_result=>1}]}; sub delete_old_releases {
    my %args = @_; my $_sahv_dpath = []; my $_w_res = undef; for (sort keys %args) { if (!/\A(-?)\w+(\.\w+)*\z/o) { return [400, "Invalid argument name (please use letters/numbers/underscores only)'$_'"]; } if (!($1 || $_ ~~ ['detail','num_keep','password','username'])) { return [400, "Unknown argument '$_'"]; } } if (exists($args{'detail'})) { my $err_detail; (!defined($args{'detail'}) ? 1 :  ((!ref($args{'detail'})) ? 1 : (($err_detail //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type boolean value"),0))); if ($err_detail) { return [400, "Argument 'detail' fails validation: $err_detail"]; } }  if (exists($args{'num_keep'})) { my $err_num_keep; ((defined($args{'num_keep'})) ? 1 : (($err_num_keep //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((Scalar::Util::Numeric::isint($args{'num_keep'})) ? 1 : (($err_num_keep //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type integer"),0)) && (($args{'num_keep'} >= 1) ? 1 : (($err_num_keep //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must be at least 1"),0)); if ($err_num_keep) { return [400, "Argument 'num_keep' fails validation: $err_num_keep"]; } } else { $args{'num_keep'} //= 1; }  if (exists($args{'password'})) { my $err_password; ((defined($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_password) { return [400, "Argument 'password' fails validation: $err_password"]; } }  if (!exists($args{'password'})) { return [400, "Missing required argument: password"]; } if (exists($args{'username'})) { my $err_username; ((defined($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) && (($args{'username'} =~ qr((?:(?-)\A\w{2,9}\z))) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must match regex pattern \\A\\w{2,9}\\z"),0)) && ((length($args{'username'}) <= 9) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at most 9"),0)); if ($err_username) { return [400, "Argument 'username' fails validation: $err_username"]; } }  if (!exists($args{'username'})) { return [400, "Missing required argument: username"]; }    $_w_res = do { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

    my $res = list_dists(_common_args(\%args), newest_n=>$args{num_keep}//1);
    return [500, "Can't list dists: $res->[0] - $res->[1]"] if $res->[0] != 200;
    my $old_files = $res->[3]{'func.old_files'};

    return [304, "No older releases", undef,
            {'cmdline.result'=>'There are no older releases to delete'}]
        unless @$old_files;
    my @to_delete;
    for my $file (@$old_files) {
        $file =~ s/\.$re_archive_ext\z//;
        push @to_delete, "$file.*";
    }
    $res = delete_files(_common_args(\%args),
                        file=>\@to_delete, -dry_run=>$args{-dry_run});
    return $res if $res->[0] != 200 || $args{-dry_run};
    my $deleted_files = $res->[3]{'func.files'} // [];
    if (@$deleted_files) {
        $res->[3]{'cmdline.result'} = $deleted_files;
    } else {
        $res->[3]{'cmdline.result'} = 'Deleted 0 files';
    }
    $res;
};      unless (ref($_w_res) eq "ARRAY" && $_w_res->[0]) { return [500, 'BUG: Sub WWW::PAUSE::Simple::delete_old_releases does not produce envelope']; } return $_w_res; } ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

sub _delete_or_undelete_or_reindex_files {
    use experimental 'smartmatch';
    require Regexp::Wildcards;
    require String::Wildcard::Bash;

    my $which = shift;
    my %args = @_;

    my $files0 = $args{file} // [];
    return [400, "Please specify at least one file"] unless @$files0;

    my @files;
    {
        my $listres;
        for my $file (@$files0) {
            if (String::Wildcard::Bash::contains_wildcard($file)) {
                unless ($listres) {
                    $listres = list_files(_common_args(\%args));
                    return [500, "Can't list files: $listres->[0] - $listres->[1]"]
                        unless $listres->[0] == 200;
                }
                my $re = Regexp::Wildcards->new(type=>'unix')->convert($file);
                $re = qr/\A($re)\z/;
                for my $f (@{$listres->[2]}) {
                    push @files, $f if $f =~ $re && !($f ~~ @files);
                }
            } else {
                push @files, $file;
            }
        }
    }

    unless (@files) {
        return [304, "No files to process"];
    }

    if ($args{-dry_run}) {
        $log->warnf("[dry-run] %s %s", $which, \@files);
        return [200, "OK (dry-run)"];
    } else {
        $log->infof("%s %s ...", $which, \@files);
    }

    my $httpres = _request(
        %args,
        post_data => [
            [
                HIDDENNAME                => $args{username},
                ($which eq 'delete'   ? (SUBMIT_pause99_delete_files_delete   => "Delete"  ) : ()),
                ($which eq 'undelete' ? (SUBMIT_pause99_delete_files_undelete => "Undelete") : ()),
                ($which eq 'reindex'  ? (SUBMIT_pause99_reindex_delete        => "Reindex" ) : ()),
                ($which =~ /delete/   ? (pause99_delete_files_FILE => \@files) : ()),
                ($which eq 'reindex'  ? (pause99_reindex_FILE => \@files) : ()),
            ],
        ],
    );
    return _htres2envres($httpres) unless $httpres->is_success;
    return [543, "Can't scrape $which status from response", $httpres->content]
        unless $httpres->content =~ m!<h3>Files in directory!s;
    [200,"OK", undef, {'func.files'=>\@files}];
}

$SPEC{delete_files} = {
    v => 1.1,
    summary => 'Delete files',
    description => <<'_',

When a file is deleted, it is not immediately deleted but has
scheduled_for_deletion status for 72 hours, then deleted. During that time, the
file can be undeleted.

_
    args => {
        %common_args,
        %file_arg,
    },
    features => {dry_run=>1},
};
use experimental 'smartmatch'; no warnings 'void'; require List::Util;no warnings 'void';require Scalar::Util::Numeric; $SPEC{delete_files} = {args=>{file=>{greedy=>1, pos=>0, req=>1, schema=>["array", {min_len=>1, of=>"str*", req=>1}, {}], summary=>"File name/wildcard pattern"}, password=>{is_password=>1, req=>1, schema=>["str", {req=>1}, {}], summary=>"PAUSE password", tags=>["common"]}, username=>{req=>1, schema=>["str", {match=>"\\A\\w{2,9}\\z", max_len=>9, req=>1}, {}], summary=>"PAUSE ID", tags=>["common"]}}, args_as=>"hash", description=>"\nWhen a file is deleted, it is not immediately deleted but has\nscheduled_for_deletion status for 72 hours, then deleted. During that time, the\nfile can be undeleted.\n\n", features=>{dry_run=>1}, summary=>"Delete files", v=>1.1, "x.perinci.sub.wrapper.logs"=>[{normalize_schema=>1, validate_args=>1, validate_result=>1}]}; sub delete_files { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap
    my %args = @_;  my $_sahv_dpath = []; my $_w_res = undef; for (sort keys %args) { if (!/\A(-?)\w+(\.\w+)*\z/o) { return [400, "Invalid argument name (please use letters/numbers/underscores only)'$_'"]; } if (!($1 || $_ ~~ ['file','password','username'])) { return [400, "Unknown argument '$_'"]; } } if (exists($args{'file'})) { my $err_file; ((defined($args{'file'})) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((ref($args{'file'}) eq 'ARRAY') ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type array"),0)) && ((@{$args{'file'}} >= 1) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at least 1"),0)) && ([push(@{$_sahv_dpath}, undef), ((!defined(List::Util::first(sub {!( ($_sahv_dpath->[-1] = defined($_sahv_dpath->[-1]) ? $_sahv_dpath->[-1]+1 : 0), ((defined($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) )}, @{$args{'file'}}))) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)), pop(@{$_sahv_dpath})]->[1]); if ($err_file) { return [400, "Argument 'file' fails validation: $err_file"]; } }  if (!exists($args{'file'})) { return [400, "Missing required argument: file"]; } if (exists($args{'password'})) { my $err_password; ((defined($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_password) { return [400, "Argument 'password' fails validation: $err_password"]; } }  if (!exists($args{'password'})) { return [400, "Missing required argument: password"]; } if (exists($args{'username'})) { my $err_username; ((defined($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) && (($args{'username'} =~ qr((?:(?-)\A\w{2,9}\z))) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must match regex pattern \\A\\w{2,9}\\z"),0)) && ((length($args{'username'}) <= 9) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at most 9"),0)); if ($err_username) { return [400, "Argument 'username' fails validation: $err_username"]; } }  if (!exists($args{'username'})) { return [400, "Missing required argument: username"]; }    $_w_res = do { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap
    _delete_or_undelete_or_reindex_files('delete', @_);
};      unless (ref($_w_res) eq "ARRAY" && $_w_res->[0]) { return [500, 'BUG: Sub WWW::PAUSE::Simple::delete_files does not produce envelope']; } return $_w_res; } ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

$SPEC{undelete_files} = {
    v => 1.1,
    summary => 'Undelete files',
    description => <<'_',

When a file is deleted, it is not immediately deleted but has
scheduled_for_deletion status for 72 hours, then deleted. During that time, the
file can be undeleted.

_
    args => {
        %common_args,
        %file_arg,
    },
    features => {dry_run=>1},
};
$SPEC{undelete_files} = {args=>{file=>{greedy=>1, pos=>0, req=>1, schema=>["array", {min_len=>1, of=>"str*", req=>1}, {}], summary=>"File name/wildcard pattern"}, password=>{is_password=>1, req=>1, schema=>["str", {req=>1}, {}], summary=>"PAUSE password", tags=>["common"]}, username=>{req=>1, schema=>["str", {match=>"\\A\\w{2,9}\\z", max_len=>9, req=>1}, {}], summary=>"PAUSE ID", tags=>["common"]}}, args_as=>"hash", description=>"\nWhen a file is deleted, it is not immediately deleted but has\nscheduled_for_deletion status for 72 hours, then deleted. During that time, the\nfile can be undeleted.\n\n", features=>{dry_run=>1}, summary=>"Undelete files", v=>1.1, "x.perinci.sub.wrapper.logs"=>[{normalize_schema=>1, validate_args=>1, validate_result=>1}]}; sub undelete_files {
    my %args = @_;  my $_sahv_dpath = []; my $_w_res = undef; for (sort keys %args) { if (!/\A(-?)\w+(\.\w+)*\z/o) { return [400, "Invalid argument name (please use letters/numbers/underscores only)'$_'"]; } if (!($1 || $_ ~~ ['file','password','username'])) { return [400, "Unknown argument '$_'"]; } } if (exists($args{'file'})) { my $err_file; ((defined($args{'file'})) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((ref($args{'file'}) eq 'ARRAY') ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type array"),0)) && ((@{$args{'file'}} >= 1) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at least 1"),0)) && ([push(@{$_sahv_dpath}, undef), ((!defined(List::Util::first(sub {!( ($_sahv_dpath->[-1] = defined($_sahv_dpath->[-1]) ? $_sahv_dpath->[-1]+1 : 0), ((defined($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) )}, @{$args{'file'}}))) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)), pop(@{$_sahv_dpath})]->[1]); if ($err_file) { return [400, "Argument 'file' fails validation: $err_file"]; } }  if (!exists($args{'file'})) { return [400, "Missing required argument: file"]; } if (exists($args{'password'})) { my $err_password; ((defined($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_password) { return [400, "Argument 'password' fails validation: $err_password"]; } }  if (!exists($args{'password'})) { return [400, "Missing required argument: password"]; } if (exists($args{'username'})) { my $err_username; ((defined($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) && (($args{'username'} =~ qr((?:(?-)\A\w{2,9}\z))) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must match regex pattern \\A\\w{2,9}\\z"),0)) && ((length($args{'username'}) <= 9) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at most 9"),0)); if ($err_username) { return [400, "Argument 'username' fails validation: $err_username"]; } }  if (!exists($args{'username'})) { return [400, "Missing required argument: username"]; }    $_w_res = do { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap
    _delete_or_undelete_or_reindex_files('undelete', @_);
};      unless (ref($_w_res) eq "ARRAY" && $_w_res->[0]) { return [500, 'BUG: Sub WWW::PAUSE::Simple::undelete_files does not produce envelope']; } return $_w_res; } ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

$SPEC{reindex_files} = {
    v => 1.1,
    summary => 'Force reindexing',
    args => {
        %common_args,
        %file_arg,
    },
    features => {dry_run=>1},
};
$SPEC{reindex_files} = {args=>{file=>{greedy=>1, pos=>0, req=>1, schema=>["array", {min_len=>1, of=>"str*", req=>1}, {}], summary=>"File name/wildcard pattern"}, password=>{is_password=>1, req=>1, schema=>["str", {req=>1}, {}], summary=>"PAUSE password", tags=>["common"]}, username=>{req=>1, schema=>["str", {match=>"\\A\\w{2,9}\\z", max_len=>9, req=>1}, {}], summary=>"PAUSE ID", tags=>["common"]}}, args_as=>"hash", features=>{dry_run=>1}, summary=>"Force reindexing", v=>1.1, "x.perinci.sub.wrapper.logs"=>[{normalize_schema=>1, validate_args=>1, validate_result=>1}]}; sub reindex_files {
    my %args = @_;  my $_sahv_dpath = []; my $_w_res = undef; for (sort keys %args) { if (!/\A(-?)\w+(\.\w+)*\z/o) { return [400, "Invalid argument name (please use letters/numbers/underscores only)'$_'"]; } if (!($1 || $_ ~~ ['file','password','username'])) { return [400, "Unknown argument '$_'"]; } } if (exists($args{'file'})) { my $err_file; ((defined($args{'file'})) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((ref($args{'file'}) eq 'ARRAY') ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type array"),0)) && ((@{$args{'file'}} >= 1) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at least 1"),0)) && ([push(@{$_sahv_dpath}, undef), ((!defined(List::Util::first(sub {!( ($_sahv_dpath->[-1] = defined($_sahv_dpath->[-1]) ? $_sahv_dpath->[-1]+1 : 0), ((defined($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($_)) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) )}, @{$args{'file'}}))) ? 1 : (($err_file //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)), pop(@{$_sahv_dpath})]->[1]); if ($err_file) { return [400, "Argument 'file' fails validation: $err_file"]; } }  if (!exists($args{'file'})) { return [400, "Missing required argument: file"]; } if (exists($args{'password'})) { my $err_password; ((defined($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_password) { return [400, "Argument 'password' fails validation: $err_password"]; } }  if (!exists($args{'password'})) { return [400, "Missing required argument: password"]; } if (exists($args{'username'})) { my $err_username; ((defined($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) && (($args{'username'} =~ qr((?:(?-)\A\w{2,9}\z))) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must match regex pattern \\A\\w{2,9}\\z"),0)) && ((length($args{'username'}) <= 9) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at most 9"),0)); if ($err_username) { return [400, "Argument 'username' fails validation: $err_username"]; } }  if (!exists($args{'username'})) { return [400, "Missing required argument: username"]; }    $_w_res = do { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap
    _delete_or_undelete_or_reindex_files('reindex', @_);
};      unless (ref($_w_res) eq "ARRAY" && $_w_res->[0]) { return [500, 'BUG: Sub WWW::PAUSE::Simple::reindex_files does not produce envelope']; } return $_w_res; } ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

$SPEC{set_password} = {
    v => 1.1,
    args => {
        %common_args,
    },
};
$SPEC{set_password} = {args=>{password=>{is_password=>1, req=>1, schema=>["str", {req=>1}, {}], summary=>"PAUSE password", tags=>["common"]}, username=>{req=>1, schema=>["str", {match=>"\\A\\w{2,9}\\z", max_len=>9, req=>1}, {}], summary=>"PAUSE ID", tags=>["common"]}}, args_as=>"hash", v=>1.1, "x.perinci.sub.wrapper.logs"=>[{normalize_schema=>1, validate_args=>1, validate_result=>1}]}; sub set_password {
    my %args = @_; my $_sahv_dpath = []; my $_w_res = undef; for (sort keys %args) { if (!/\A(-?)\w+(\.\w+)*\z/o) { return [400, "Invalid argument name (please use letters/numbers/underscores only)'$_'"]; } if (!($1 || $_ ~~ ['password','username'])) { return [400, "Unknown argument '$_'"]; } } if (exists($args{'password'})) { my $err_password; ((defined($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_password) { return [400, "Argument 'password' fails validation: $err_password"]; } }  if (!exists($args{'password'})) { return [400, "Missing required argument: password"]; } if (exists($args{'username'})) { my $err_username; ((defined($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) && (($args{'username'} =~ qr((?:(?-)\A\w{2,9}\z))) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must match regex pattern \\A\\w{2,9}\\z"),0)) && ((length($args{'username'}) <= 9) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at most 9"),0)); if ($err_username) { return [400, "Argument 'username' fails validation: $err_username"]; } }  if (!exists($args{'username'})) { return [400, "Missing required argument: username"]; }    $_w_res = do { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap
    [501, "Not yet implemented"];
};      unless (ref($_w_res) eq "ARRAY" && $_w_res->[0]) { return [500, 'BUG: Sub WWW::PAUSE::Simple::set_password does not produce envelope']; } return $_w_res; } ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap

$SPEC{set_account_info} = {
    v => 1.1,
    args => {
        %common_args,
    },
};
$SPEC{set_account_info} = {args=>{password=>{is_password=>1, req=>1, schema=>["str", {req=>1}, {}], summary=>"PAUSE password", tags=>["common"]}, username=>{req=>1, schema=>["str", {match=>"\\A\\w{2,9}\\z", max_len=>9, req=>1}, {}], summary=>"PAUSE ID", tags=>["common"]}}, args_as=>"hash", v=>1.1, "x.perinci.sub.wrapper.logs"=>[{normalize_schema=>1, validate_args=>1, validate_result=>1}]}; sub set_account_info {
    my %args = @_; my $_sahv_dpath = []; my $_w_res = undef; for (sort keys %args) { if (!/\A(-?)\w+(\.\w+)*\z/o) { return [400, "Invalid argument name (please use letters/numbers/underscores only)'$_'"]; } if (!($1 || $_ ~~ ['password','username'])) { return [400, "Unknown argument '$_'"]; } } if (exists($args{'password'})) { my $err_password; ((defined($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'password'})) ? 1 : (($err_password //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($err_password) { return [400, "Argument 'password' fails validation: $err_password"]; } }  if (!exists($args{'password'})) { return [400, "Missing required argument: password"]; } if (exists($args{'username'})) { my $err_username; ((defined($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($args{'username'})) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)) && (($args{'username'} =~ qr((?:(?-)\A\w{2,9}\z))) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Must match regex pattern \\A\\w{2,9}\\z"),0)) && ((length($args{'username'}) <= 9) ? 1 : (($err_username //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Length must be at most 9"),0)); if ($err_username) { return [400, "Argument 'username' fails validation: $err_username"]; } }  if (!exists($args{'username'})) { return [400, "Missing required argument: username"]; }    $_w_res = do { ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap
    [501, "Not yet implemented"];
};      unless (ref($_w_res) eq "ARRAY" && $_w_res->[0]) { return [500, 'BUG: Sub WWW::PAUSE::Simple::set_account_info does not produce envelope']; } return $_w_res; } ## this line is put by Dist::Zilla::Plugin::Rinci::Wrap


1;
# ABSTRACT: An API for PAUSE

__END__

=pod

=encoding UTF-8

=head1 NAME

WWW::PAUSE::Simple - An API for PAUSE

=head1 VERSION

This document describes version 0.14 of WWW::PAUSE::Simple (from Perl distribution WWW-PAUSE-Simple), released on 2015-03-25.

=head1 SYNOPSIS

=head1 DESCRIPTION

B<STATUS: Experimental.>

This module provides several API functions for performing common tasks on PAUSE.
It comes with a CLI script L<pause>.

=head1 FUNCTIONS


=head2 delete_files(%args) -> [status, msg, result, meta]

Delete files.

When a file is deleted, it is not immediately deleted but has
scheduled_for_deletion status for 72 hours, then deleted. During that time, the
file can be undeleted.

This function supports dry-run operation.


Arguments ('*' denotes required arguments):

=over 4

=item * B<file>* => I<array[str]>

File name/wildcard pattern.

=item * B<password>* => I<str>

PAUSE password.

=item * B<username>* => I<str>

PAUSE ID.

=back

Special arguments:

=over 4

=item * B<-dry_run> => I<bool>

Pass -dry_run=>1 to enable simulation mode.

=back

Returns an enveloped result (an array).

First element (status) is an integer containing HTTP status code
(200 means OK, 4xx caller error, 5xx function error). Second element
(msg) is a string containing error message, or 'OK' if status is
200. Third element (result) is optional, the actual result. Fourth
element (meta) is called result metadata and is optional, a hash
that contains extra information.

Return value:  (any)


=head2 delete_old_releases(%args) -> [status, msg, result, meta]

Delete older versions of distributions on your PAUSE account.

Developer releases will not be deleted.

This function supports dry-run operation.


Arguments ('*' denotes required arguments):

=over 4

=item * B<detail> => I<bool>

Whether to return detailed records.

=item * B<num_keep> => I<int> (default: 1)

Number of new versions (including newest) to keep.

1 means to only keep the newest version, 2 means to keep the newest and the
second newest, and so on.

=item * B<password>* => I<str>

PAUSE password.

=item * B<username>* => I<str>

PAUSE ID.

=back

Special arguments:

=over 4

=item * B<-dry_run> => I<bool>

Pass -dry_run=>1 to enable simulation mode.

=back

Returns an enveloped result (an array).

First element (status) is an integer containing HTTP status code
(200 means OK, 4xx caller error, 5xx function error). Second element
(msg) is a string containing error message, or 'OK' if status is
200. Third element (result) is optional, the actual result. Fourth
element (meta) is called result metadata and is optional, a hash
that contains extra information.

Return value:  (any)


=head2 list_dists(%args) -> [status, msg, result, meta]

List distributions on your PAUSE account.

Distribution names will be extracted from tarball/zip filenames.

Unknown/unparseable filenames will be skipped.

Arguments ('*' denotes required arguments):

=over 4

=item * B<detail> => I<bool>

Whether to return detailed records.

=item * B<newest> => I<bool>

Only show newest non-dev version.

Dev versions will be skipped.

=item * B<newest_n> => I<int>

Only show this number of newest non-dev versions.

Dev versions will be skipped.

=item * B<password>* => I<str>

PAUSE password.

=item * B<username>* => I<str>

PAUSE ID.

=back

Returns an enveloped result (an array).

First element (status) is an integer containing HTTP status code
(200 means OK, 4xx caller error, 5xx function error). Second element
(msg) is a string containing error message, or 'OK' if status is
200. Third element (result) is optional, the actual result. Fourth
element (meta) is called result metadata and is optional, a hash
that contains extra information.

Return value:  (any)


=head2 list_files(%args) -> [status, msg, result, meta]

List files on your PAUSE account.

Arguments ('*' denotes required arguments):

=over 4

=item * B<del> => I<bool>

Only list files which are scheduled for deletion.

=item * B<detail> => I<bool>

Whether to return detailed records.

=item * B<file> => I<array[str]>

File name/wildcard pattern.

=item * B<password>* => I<str>

PAUSE password.

=item * B<username>* => I<str>

PAUSE ID.

=back

Returns an enveloped result (an array).

First element (status) is an integer containing HTTP status code
(200 means OK, 4xx caller error, 5xx function error). Second element
(msg) is a string containing error message, or 'OK' if status is
200. Third element (result) is optional, the actual result. Fourth
element (meta) is called result metadata and is optional, a hash
that contains extra information.

Return value:  (any)


=head2 reindex_files(%args) -> [status, msg, result, meta]

Force reindexing.

This function supports dry-run operation.


Arguments ('*' denotes required arguments):

=over 4

=item * B<file>* => I<array[str]>

File name/wildcard pattern.

=item * B<password>* => I<str>

PAUSE password.

=item * B<username>* => I<str>

PAUSE ID.

=back

Special arguments:

=over 4

=item * B<-dry_run> => I<bool>

Pass -dry_run=>1 to enable simulation mode.

=back

Returns an enveloped result (an array).

First element (status) is an integer containing HTTP status code
(200 means OK, 4xx caller error, 5xx function error). Second element
(msg) is a string containing error message, or 'OK' if status is
200. Third element (result) is optional, the actual result. Fourth
element (meta) is called result metadata and is optional, a hash
that contains extra information.

Return value:  (any)


=head2 set_account_info(%args) -> [status, msg, result, meta]

Arguments ('*' denotes required arguments):

=over 4

=item * B<password>* => I<str>

PAUSE password.

=item * B<username>* => I<str>

PAUSE ID.

=back

Returns an enveloped result (an array).

First element (status) is an integer containing HTTP status code
(200 means OK, 4xx caller error, 5xx function error). Second element
(msg) is a string containing error message, or 'OK' if status is
200. Third element (result) is optional, the actual result. Fourth
element (meta) is called result metadata and is optional, a hash
that contains extra information.

Return value:  (any)


=head2 set_password(%args) -> [status, msg, result, meta]

Arguments ('*' denotes required arguments):

=over 4

=item * B<password>* => I<str>

PAUSE password.

=item * B<username>* => I<str>

PAUSE ID.

=back

Returns an enveloped result (an array).

First element (status) is an integer containing HTTP status code
(200 means OK, 4xx caller error, 5xx function error). Second element
(msg) is a string containing error message, or 'OK' if status is
200. Third element (result) is optional, the actual result. Fourth
element (meta) is called result metadata and is optional, a hash
that contains extra information.

Return value:  (any)


=head2 undelete_files(%args) -> [status, msg, result, meta]

Undelete files.

When a file is deleted, it is not immediately deleted but has
scheduled_for_deletion status for 72 hours, then deleted. During that time, the
file can be undeleted.

This function supports dry-run operation.


Arguments ('*' denotes required arguments):

=over 4

=item * B<file>* => I<array[str]>

File name/wildcard pattern.

=item * B<password>* => I<str>

PAUSE password.

=item * B<username>* => I<str>

PAUSE ID.

=back

Special arguments:

=over 4

=item * B<-dry_run> => I<bool>

Pass -dry_run=>1 to enable simulation mode.

=back

Returns an enveloped result (an array).

First element (status) is an integer containing HTTP status code
(200 means OK, 4xx caller error, 5xx function error). Second element
(msg) is a string containing error message, or 'OK' if status is
200. Third element (result) is optional, the actual result. Fourth
element (meta) is called result metadata and is optional, a hash
that contains extra information.

Return value:  (any)


=head2 upload_file(%args) -> [status, msg, result, meta]

Upload file(s) to your PAUSE account.

Arguments ('*' denotes required arguments):

=over 4

=item * B<file>* => I<array[str]>

File name/wildcard pattern.

=item * B<password>* => I<str>

PAUSE password.

=item * B<subdir> => I<str> (default: "")

Subdirectory to put the file(s) into.

=item * B<username>* => I<str>

PAUSE ID.

=back

Returns an enveloped result (an array).

First element (status) is an integer containing HTTP status code
(200 means OK, 4xx caller error, 5xx function error). Second element
(msg) is a string containing error message, or 'OK' if status is
200. Third element (result) is optional, the actual result. Fourth
element (meta) is called result metadata and is optional, a hash
that contains extra information.

Return value:  (any)

=head1 SEE ALSO

L<CPAN::Uploader> which also does uploading from CLI.

L<WWW::PAUSE::CleanUpHomeDir> which can clean old releases from your PAUSE
account (but no CLI). A similar subcommand will be added to C<pause> (e.g.
C<pause clean>), which will provide an option to specify the number of old
releases to keep.

L<https://perlancar.wordpress.com/2015/03/25/interacting-with-pause-using-cli/>

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/WWW-PAUSE-Simple>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-WWW-PAUSE-Simple>.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=WWW-PAUSE-Simple>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2015 by perlancar@cpan.org.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
