Group
Extension

App-RetroPAN/lib/App/RetroPAN.pm

package App::RetroPAN;
# vim:ts=4:shiftwidth=4:expandtab

use strict;
use warnings;
use utf8;

=encoding utf8

=head1 NAME

App::RetroPAN - Makes a historic minicpan ⏳

=head1 SYNOPSIS

  use App::RetroCPAN;

  my ($author, $dist_name, $url) = find_module_on_date("2011-01-01T00:00:00", "Moose");

  my @dependencies = find_deps_on_date_with_perl("2011-01-01T00:00:00", $], "Moose", "Mouse", "Moo", ...);

  my @dependencies = find_modules_dependencies($author, $dist_name, $]);

=head1 DESCRIPTION

Uses the MetaCPAN API to find releases made prior to a given date to
satisfy your modules' dependencies.

=head2 Core modules

Newer versions of modules existing in core are currently ignored. This may be
desirable as users may be reluctant to upgrade core modules from CPAN.

=head1 SEE ALSO

=over

=item *

L<retropan>

=item *

L<OrePAN2>

=back

=head1 LICENSE

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

=head1 AUTHOR

Dave Lambley <dlambley@cpan.org>

=cut

use HTTP::Request;
use LWP::UserAgent;
use List::MoreUtils qw/ uniq /;
use Module::CoreList;
use OrePAN2::Injector;
use OrePAN2::Indexer;

use Cpanel::JSON::XS qw/ encode_json decode_json /;

our $VERSION = '0.03_01';

my $ua = LWP::UserAgent->new( keep_alive => 2, agent => "retropan/$VERSION" );

sub find_module_dependencies {
    my ($au, $dist, $perl_version) = @_;

    $perl_version ||= $];

    my $q = {
        "size" => 1,
        "query" => {
            "bool" => {
                "filter" => [
                    {
                        "match" => {
                            "name" => $dist,
                        }
                    },
                    {
                        "match" => {
                            "author" => $au,
                        }
                    },

                ],
            }
        }
    };

    my $req = HTTP::Request->new( POST => 'https://fastapi.metacpan.org/v1/release/_search', [
        "Content-Type" => "text/json",
        "Accept" => "text/json"
    ], encode_json($q) );

    my $res = $ua->request($req);
    die $res->status_line if !$res->is_success;
    my $data = decode_json($res->decoded_content);
    my $hit = $data->{hits}->{hits}->[0];
    if (!defined $hit) {
        warn "could not find $au/$dist";
        return;
    }

    my @deps =
        grep { !Module::CoreList::is_core($_, undef, $perl_version) }
        grep { $_ ne "perl" }
        map { $_->{module} } @{ $hit->{_source}->{dependency} };

    return @deps;
}

sub find_module_on_date {
    my ($module, $before, $perl_version) = @_;

    $perl_version ||= $];
    return if Module::CoreList::is_core($module, undef, $perl_version);

    # We prefer authorized modules, but can fall back to unauthorized if none
    # available.
    my $q = {
        "size" => 30, # TODO, keep search open.
        "sort" => [
            { "module.authorized" => "desc" },
            { "version_numified" => "desc" },
            "_score",
        ],
        "query" => {
            "bool" => {
                "filter" => [
                    {
                        "match" => {
                            "module.name" => $module,
                        }
                    },
                    {
                        "match" => {
                            "maturity" => "released",
                        }
                    },
                    {
                        "range" => { "date" => {"lt" => $before }}
                    },
                ],
            }
        }
    };

    my $req = HTTP::Request->new( POST => 'https://fastapi.metacpan.org/v1/module/_search', [
        "Content-Type" => "text/json",
        "Accept" => "text/json"
    ], encode_json($q) );

    my $res = $ua->request($req);
    die $res->status_line if !$res->is_success;
    my $data = decode_json($res->decoded_content);


    my $author;
    my $version = -1;
    my $release;
    my $url;
    my $authorized;

    # Some distributions re-release  existing modules outside their own
    # distribution, eg., perl-5.005-minimal-bin-0-arm-linux
    # We therefore iterate through all modules returned to find the newest
    # version.
    foreach my $hit (@{ $data->{hits}->{hits} }) {
	next if $hit->{_source}->{distribution} eq 'perl';
        foreach my $mod (@{ $hit->{_source}->{module} }) {
            if (($authorized ? $mod->{authorized} : 1) && $mod->{name} eq $module && $mod->{version_numified} > $version) {
                $author     = $hit->{_source}->{author};
                $release    = $hit->{_source}->{release};
                $url        = $hit->{_source}->{download_url};
                $version    = $mod->{version_numified};
                $authorized = $mod->{authorized};
            }
        }
    }


    if (!defined $release) {
        warn "could not find $module before $before";
        return;
    }

    return ($author, $release, $url);
}

sub find_deps_on_date {
    my ($before, @modules) = @_;
    return find_deps_on_date_with_perl($before, $], @modules);
}

sub find_deps_on_date_with_perl {
    my ($before, $perl_version, @modules) = @_;

    my %done_modules;
    my @dists_required;
    my %dist_to_url;

    while (@modules) {
        my $mod = pop @modules;
        next if $done_modules{$mod};

        my ($au, $dist, $url) = find_module_on_date($mod, $before, $perl_version);
        $done_modules{$mod} = 1;
        next if !defined($au) || !defined($dist);
        $dist_to_url{"$au/$dist"} = $url;

        push @modules, find_module_dependencies($au, $dist, $perl_version);
        unshift @dists_required, "$au/$dist";
    }

    return (
        [uniq @dists_required],
        \%dist_to_url,
    );
}

sub make_minicpan {
    my ($localdir, $dists_required, $dist_to_url) = @_;

    my $injector = OrePAN2::Injector->new(
        directory => $localdir,
        author_subdir => 1
    );

    foreach my $d (@{ $dists_required }) {
        my ($author, $dist) = split(/\//, $d, 2);
        $injector->inject(
            $dist_to_url->{$d} // die,
            {
                author => $author,
            }
        );
    }

    # XXX undocumented.
    my $orepan = OrePAN2::Indexer->new(
        directory => $localdir,
        metacpan  => 0,
        simple    => 1,
    );
    $orepan->make_index(
        no_compress => 1,
    );
    return;
}


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.