The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Sepia::CPAN;
use CPAN ();

sub init
{
      CPAN::HandleConfig->load;
      CPAN::Shell::setup_output;
      CPAN::Index->reload;
}

sub interesting_parts
{
    my $mod = shift;
    # XXX: stupid CPAN.pm functions die for some modules...
    +{ map {
        $_ => scalar eval { $mod->$_ }
    } qw(id cpan_version inst_version fullname cpan_file)};
}

# Only list the "root" module of each package, meaning either (1) the
# module matching the dist name or (2) the module with the shortest
# name, whichever comes first.

# XXX: this is hacky.
sub group_by_dist
{
    my %h;
    for (@_) {
        my $cf = $_->{cpan_file};
        if (!exists $h{$cf}) {
            $h{$_->{cpan_file}} = $_;
        } else {
            (my $tmp = $cf) =~ s/-/::/g;
            if ($tmp =~ /^\Q$h{$cf}{id}\E/) {
                next;           # already perfect
            } elsif ($tmp =~ /^\Q$_->{id}\E/) {
                $h{$cf} = $_;   # perfect
            } # elsif (length $h{$cf}{id} > length $_->{id}) {
            #     $h{$cf} = $_;   # short, at least...
            # }
        }
    }
    sort { $a->{id} cmp $b->{id} } values %h;
}

sub _list
{
    CPAN::Shell->expand('Module', shift || '/./');
}

sub list
{
    group_by_dist map { interesting_parts $_ } _list @_
}

sub _ls
{
    my $want = shift;
    grep {
        # XXX: key to test in this order, because inst_file is slow.
        $_->userid eq $want
    } CPAN::Shell->expand('Module', '/./')
}

sub ls
{
    group_by_dist map { interesting_parts $_ } _ls @_
}

sub _desc
{
    my $pat = qr/$_[0]/i;
    grep {
        $_->description &&
        ($_->description =~ /$pat/ || $_->id =~ /$pat/)
    } CPAN::Shell->expand('Module', '/./');
}

sub desc
{
    group_by_dist map { interesting_parts $_ } _desc @_;
}

sub outdated
{
    grep !$_->uptodate, list @_;
}

## stolen from CPAN::Shell...
sub readme
{
    my $dist = CPAN::Shell->expand('Module', shift);
    return unless $dist;
    my $wantfile = shift;
    $dist = $dist->cpan_file;
    # my ($dist) = $self->id;
    my ($sans, $suffix) = $dist =~ /(.+)\.(tgz|tar[\._-]gz|tar\.Z|zip)$/;
    my ($local_file);
    my ($local_wanted) = File::Spec->catfile(
        $CPAN::Config->{keep_source_where}, "authors", "id",
        split(/\//,"$sans.readme"));
    $local_file = CPAN::FTP->localize("authors/id/$sans.readme", $local_wanted);
    ## Return filename rather than contents to avoid Elisp reader issues...
    if ($wantfile) {
        $local_file;
    } else {
        local (*IN, $/);
        open IN, $local_wanted;
        my $ret = <IN>;
        close IN;
        $ret;
    }
}

sub perldoc
{
    eval q{ use LWP::Simple; };
    if ($@) {
        print STDERR "Can't get perldocs: LWP::Simple not installed.\n";
        "Can't get perldocs: LWP::Simple not installed.\n";
    } else {
        *perldoc = sub { get($CPAN::Defaultdocs . shift) };
        goto &perldoc;
    }
}

sub install
{
    my $dist = CPAN::Shell->expand('Module', shift);
    $dist->install if $dist;
}

# Based on CPAN::Shell::_u_r_common
sub _recommend
{
    my $pat = shift || '/./';
    my (@result, %seen, %need);
    $version_undefs = $version_zeroes = 0;
    for my $module (CPAN::Shell->expand('Module',$pat)) {
        my $file  = $module->cpan_file;
        next unless defined $file && $module->inst_file;
        $file =~ s!^./../!!;
        my $latest = $module->cpan_version;
        my $have = $module->inst_version;
        local ($^W) = 0;
        next unless CPAN::Version->vgt($latest, $have);
        push @result, $module;
        next if $seen{$file}++;
        $need{$module->id}++;
    }
    @result;
}

sub recommend
{
    group_by_dist map { interesting_parts $_ } _recommend @_;
}

1;