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

use 5.010001;
use strict;
use warnings;
use Log::Any qw($log);

our %SPEC;
require Exporter;
our @ISA       = qw(Exporter);
our @EXPORT_OK = qw(list_prereqs);

our $VERSION = '0.04'; # VERSION

$SPEC{list_prereqs} = {
    v => 1.1,
    summary => 'List prerequisites of a Perl module',
    description => <<'_',

Currently skips prerequisites which are modules already in core (for installed
perl version).

_
    args => {
        module => {
            schema  => ['array*'], # XXX of str*
            summary => 'Perl module(s) to check',
            req     => 1,
            pos     => 0,
            greedy  => 1,
        },
        recursive => {
            schema  => [bool => {default=>0}],
            summary => 'Whether to check recursively',
            cmdline_aliases => { r => {} },
        },
        #cache => {
        #    schema  => [bool => {default=>1}],
        #    summary => 'Whether to cache API results for some time, '.
        #        'for performance',
        #},
        raw => {
            schema  => [bool => {default=>0}],
            summary => 'Return raw result',
        },
        # TODO: arg to set cache root dir
        # TODO: arg to set default cache expire period
    },
};
sub list_prereqs {
    require CHI;
    require MetaCPAN::Client;
    require Module::CoreList;

    my %args = @_;
    # XXX schema
    my $mod = $args{module} or return [400, "Please specify module"];
    my $recursive = $args{recursive};
    #my $do_cache = $args{cache} // 1;
    my $raw = $args{raw};

    # '$cache' is ambiguous between args{cache} and CHI object
    my $chi = CHI->new(driver => "File");

    my $mcpan = MetaCPAN::Client->new;

    my $ce = "24h"; # cache expire period

    my @errs;
    my %mdist; # mentioned dist, for checking circularity
    my %mmod;  # mentioned mod

    $^V =~ /^v(\d+)\.(\d+)\.(\d+)/ or die "Can't parse perl version";
    my $perl_v = $1 + $2/1000 + $3/1000/1000;

    my $do_list;
    $do_list = sub {
        my ($mod, $v, $level) = @_;
        $level //= 0;
        $log->debugf("Listing dependencies for module %s (%s) ...", $mod, $v);

        my @res;

        # if it already looks like a dist, save an API call
        my $dist;
        if ($mod =~ /-/) {
            $dist = $mod;
        } else {
            my $modinfo = $chi->compute(
                "metacpan-mod-$mod", $ce, sub {
                    $log->infof("Querying MetaCPAN for module %s ...", $mod);
                    $mcpan->module($mod);
                });
            $dist = $modinfo->distribution;
        }

        if ($mdist{$dist}++) {
            push @errs, "Circular dependency (dist=$dist)";
            return ();
        }

        my $distinfo = $chi->compute(
            "metacpan-dist-$dist", $ce, sub {
                $log->infof("Querying MetaCPAN for dist %s ...", $dist);
                $mcpan->release($dist);
            });

        for my $dep (@{ $distinfo->dependency }) {
            next unless $dep->{relationship} eq 'requires' &&
                $dep->{phase} eq 'runtime';
            next if $dep->{module} =~ /^(perl)$/;
            next if $mmod{$dep->{module}}++;
            my $v_in_core = Module::CoreList->first_release(
                $dep->{module}, $dep->{version_numified});
            if ($v_in_core && $v_in_core <= $perl_v) {
                $log->debugf("Module %s (%s) is already in core (perl %s), ".
                                 "skipped",
                             $dep->{module}, $dep->{version_numified},
                             $v_in_core);
                next;
            }

            my $res = {
                module=>$dep->{module},
                version=>$dep->{version_numified},
            };
            if ($recursive) {
                $res->{prereqs} = [$do_list->(
                    $res->{module}, $res->{version}, $level+1)];
            }
            if ($raw) {
                push @res, $res;
            } else {
                push @res, join(
                    "",
                    "    " x $level,
                    $res->{module}, " ", ($res->{version} // 0),
                    "\n",
                    join("", @{ $res->{prereqs} // [] }),
                );
            }
        }

        @res;
    };

    my @res;
    for (ref($mod) eq 'ARRAY' ? @$mod : $mod) {
        push @res, $do_list->($_);
    }
    my $res = $raw ? \@res : join("", @res);

    [200, @errs ? "Unsatisfiable dependencies" : "OK", $res,
     {"cmdline.exit_code" => @errs ? 200:0}];
}

1;
#ABSTRACT: List prerequisites of a Perl module

__END__

=pod

=encoding UTF-8

=head1 NAME

App::ListPrereqs - List prerequisites of a Perl module

=head1 VERSION

This document describes version 0.04 of App::ListPrereqs (from Perl distribution App-ListPrereqs), released on 2014-09-04.

=head1 SYNOPSIS

 # Use via list-prereqs CLI script

=head1 DESCRIPTION

Currently uses MetaCPAN API and by default caches API results for 24 hours.

=head1 FUNCTIONS


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

List prerequisites of a Perl module.

Currently skips prerequisites which are modules already in core (for installed
perl version).

Arguments ('*' denotes required arguments):

=over 4

=item * B<module>* => I<array>

Perl module(s) to check.

=item * B<raw> => I<bool> (default: 0)

Return raw result.

=item * B<recursive> => I<bool> (default: 0)

Whether to check recursively.

=back

Return value:

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.

 (any)

=head1 SEE ALSO

http://deps.cpantesters.org/

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/App-ListPrereqs>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-App-ListPrereqs>.

=head1 BUGS

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

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) 2014 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