Look at module list to find correct module.
[perl-App-RetroPAN.git] / lib / App / RetroPAN.pm
blob05e6239ee2050b1e652da99b1764cde0a5c40843
1 package App::RetroPAN;
2 # vim:ts=4:shiftwidth=4:expandtab
4 =head1 NAME
6 App::RetroPAN - Makes a historic minicpan E<9203>
8 =head1 SYNOPSIS
10 use App::RetroCPAN;
12 my ($author, $dist_name, $url) = find_module_on_date("2011-01-01T00:00:00", "Moose");
14 =head1 DESCRIPTION
16 Uses the MetaCPAN API to find releases made prior to a given date to
17 satisfy your modules' dependencies.
19 =head1 SEE ALSO
21 =over
23 =item L<retropan>
25 =item L<OrePAN2>
27 =back
29 =head1 LICENSE
31 This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
33 =head1 AUTHOR
35 Dave Lambley <dlambley@cpan.org>
37 =cut
39 use strict;
40 use warnings;
42 use HTTP::Request;
43 use LWP::UserAgent;
44 use List::Util qw/ uniq /;
45 use Module::CoreList;
46 use OrePAN2::Injector;
47 use OrePAN2::Indexer;
49 use Cpanel::JSON::XS qw/ encode_json decode_json /;
51 our $VERSION = 0.01;
53 my $ua = LWP::UserAgent->new( keep_alive => 2, agent => "retropan/$VERSION" );
55 sub find_module_dependencies {
56 my ($au, $dist) = @_;
58 my $q = {
59 "size" => 1,
60 "query" => {
61 "bool" => {
62 "filter" => [
64 "match" => {
65 "name" => $dist,
69 "match" => {
70 "author" => $au,
79 my $req = HTTP::Request->new( POST => 'https://fastapi.metacpan.org/v1/release/_search', [
80 "Content-Type" => "text/json",
81 "Accept" => "text/json"
82 ], encode_json($q) );
84 my $res = $ua->request($req);
85 die $res->status_line if !$res->is_success;
86 my $data = decode_json($res->decoded_content);
87 my $hit = $data->{hits}->{hits}->[0];
88 if (!defined $hit) {
89 warn "could not find $au/$dist";
90 return;
93 my @deps =
94 grep { !Module::CoreList::is_core($_) }
95 grep { $_ ne "perl" }
96 map { $_->{module} } @{ $hit->{_source}->{dependency} };
98 return @deps;
100 sub find_module_on_date {
101 my ($module, $before) = @_;
103 return if Module::CoreList::is_core($module);
105 # We prefer authorized modules, but can fall back to unauthorized if none
106 # available.
107 my $q = {
108 "size" => 30, # TODO, keep search open.
109 "sort" => [
110 { "module.authorized" => "desc" },
111 { "version_numified" => "desc" },
112 "_score",
114 "query" => {
115 "bool" => {
116 "filter" => [
118 "match" => {
119 "module.name" => $module,
123 "match" => {
124 "maturity" => "released",
128 "range" => { "date" => {"lt" => $before }}
135 my $req = HTTP::Request->new( POST => 'https://fastapi.metacpan.org/v1/module/_search', [
136 "Content-Type" => "text/json",
137 "Accept" => "text/json"
138 ], encode_json($q) );
140 my $res = $ua->request($req);
141 die $res->status_line if !$res->is_success;
142 my $data = decode_json($res->decoded_content);
145 my $author;
146 my $version = -1;
147 my $release;
148 my $url;
149 my $authorized;
151 # Some distributions re-release existing modules outside their own
152 # distribution, eg., perl-5.005-minimal-bin-0-arm-linux
153 # We therefore iterate through all modules returned to find the newest
154 # version.
155 foreach my $hit (@{ $data->{hits}->{hits} }) {
156 foreach my $mod (@{ $hit->{_source}->{module} }) {
157 if (($authorized ? $mod->{authorized} : 1) && $mod->{name} eq $module && $mod->{version_numified} > $version) {
158 $author = $hit->{_source}->{author};
159 $release = $hit->{_source}->{release};
160 $url = $hit->{_source}->{download_url};
161 $version = $mod->{version_numified};
162 $authorized = $mod->{authorized};
168 if (!defined $release) {
169 warn "could not find $module before $before";
170 return;
173 return ($author, $release, $url);
176 sub find_deps_on_date {
177 my ($before, @modules) = @_;
179 my %done_modules;
180 my @dists_required;
181 my %dist_to_url;
183 while (@modules) {
184 my $mod = pop @modules;
185 next if $done_modules{$mod};
187 my ($au, $dist, $url) = find_module_on_date($mod, $before);
188 $done_modules{$mod} = 1;
189 next if !defined($au) || !defined($dist);
190 $dist_to_url{"$au/$dist"} = $url;
192 push @modules, find_module_dependencies($au, $dist);
193 unshift @dists_required, "$au/$dist";
196 return (
197 [uniq @dists_required],
198 \%dist_to_url,
202 sub make_minicpan {
203 my ($localdir, $dists_required, $dist_to_url) = @_;
205 my $injector = OrePAN2::Injector->new(
206 directory => $localdir,
207 author_subdir => 1
210 foreach my $d (@{ $dists_required }) {
211 my ($author, $dist) = split(/\//, $d, 2);
212 $injector->inject(
213 $dist_to_url->{$d} // die,
215 author => $author,
220 # XXX undocumented.
221 my $orepan = OrePAN2::Indexer->new(
222 directory => $localdir,
223 metacpan => 0,
224 simple => 1,
226 $orepan->make_index(
227 no_compress => 1,
229 return;