New dev release.
[perl-App-RetroPAN.git] / lib / App / RetroPAN.pm
blob8a2cde19f10d3e368ee329b963784f64b0f7b01a
1 package App::RetroPAN;
2 # vim:ts=4:shiftwidth=4:expandtab
4 use strict;
5 use warnings;
6 use utf8;
8 =encoding utf8
10 =head1 NAME
12 App::RetroPAN - Makes a historic minicpan ⏳
14 =head1 SYNOPSIS
16 use App::RetroCPAN;
18 my ($author, $dist_name, $url) = find_module_on_date("2011-01-01T00:00:00", "Moose");
20 =head1 DESCRIPTION
22 Uses the MetaCPAN API to find releases made prior to a given date to
23 satisfy your modules' dependencies.
25 =head1 SEE ALSO
27 =over
29 =item *
31 L<retropan>
33 =item *
35 L<OrePAN2>
37 =back
39 =head1 LICENSE
41 This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
43 =head1 AUTHOR
45 Dave Lambley <dlambley@cpan.org>
47 =cut
49 use HTTP::Request;
50 use LWP::UserAgent;
51 use List::Util qw/ uniq /;
52 use Module::CoreList;
53 use OrePAN2::Injector;
54 use OrePAN2::Indexer;
56 use Cpanel::JSON::XS qw/ encode_json decode_json /;
58 our $VERSION = '0.01_02';
60 my $ua = LWP::UserAgent->new( keep_alive => 2, agent => "retropan/$VERSION" );
62 sub find_module_dependencies {
63 my ($au, $dist) = @_;
65 my $q = {
66 "size" => 1,
67 "query" => {
68 "bool" => {
69 "filter" => [
71 "match" => {
72 "name" => $dist,
76 "match" => {
77 "author" => $au,
86 my $req = HTTP::Request->new( POST => 'https://fastapi.metacpan.org/v1/release/_search', [
87 "Content-Type" => "text/json",
88 "Accept" => "text/json"
89 ], encode_json($q) );
91 my $res = $ua->request($req);
92 die $res->status_line if !$res->is_success;
93 my $data = decode_json($res->decoded_content);
94 my $hit = $data->{hits}->{hits}->[0];
95 if (!defined $hit) {
96 warn "could not find $au/$dist";
97 return;
100 my @deps =
101 grep { !Module::CoreList::is_core($_) }
102 grep { $_ ne "perl" }
103 map { $_->{module} } @{ $hit->{_source}->{dependency} };
105 return @deps;
107 sub find_module_on_date {
108 my ($module, $before) = @_;
110 return if Module::CoreList::is_core($module);
112 # We prefer authorized modules, but can fall back to unauthorized if none
113 # available.
114 my $q = {
115 "size" => 30, # TODO, keep search open.
116 "sort" => [
117 { "module.authorized" => "desc" },
118 { "version_numified" => "desc" },
119 "_score",
121 "query" => {
122 "bool" => {
123 "filter" => [
125 "match" => {
126 "module.name" => $module,
130 "match" => {
131 "maturity" => "released",
135 "range" => { "date" => {"lt" => $before }}
142 my $req = HTTP::Request->new( POST => 'https://fastapi.metacpan.org/v1/module/_search', [
143 "Content-Type" => "text/json",
144 "Accept" => "text/json"
145 ], encode_json($q) );
147 my $res = $ua->request($req);
148 die $res->status_line if !$res->is_success;
149 my $data = decode_json($res->decoded_content);
152 my $author;
153 my $version = -1;
154 my $release;
155 my $url;
156 my $authorized;
158 # Some distributions re-release existing modules outside their own
159 # distribution, eg., perl-5.005-minimal-bin-0-arm-linux
160 # We therefore iterate through all modules returned to find the newest
161 # version.
162 foreach my $hit (@{ $data->{hits}->{hits} }) {
163 foreach my $mod (@{ $hit->{_source}->{module} }) {
164 if (($authorized ? $mod->{authorized} : 1) && $mod->{name} eq $module && $mod->{version_numified} > $version) {
165 $author = $hit->{_source}->{author};
166 $release = $hit->{_source}->{release};
167 $url = $hit->{_source}->{download_url};
168 $version = $mod->{version_numified};
169 $authorized = $mod->{authorized};
175 if (!defined $release) {
176 warn "could not find $module before $before";
177 return;
180 return ($author, $release, $url);
183 sub find_deps_on_date {
184 my ($before, @modules) = @_;
186 my %done_modules;
187 my @dists_required;
188 my %dist_to_url;
190 while (@modules) {
191 my $mod = pop @modules;
192 next if $done_modules{$mod};
194 my ($au, $dist, $url) = find_module_on_date($mod, $before);
195 $done_modules{$mod} = 1;
196 next if !defined($au) || !defined($dist);
197 $dist_to_url{"$au/$dist"} = $url;
199 push @modules, find_module_dependencies($au, $dist);
200 unshift @dists_required, "$au/$dist";
203 return (
204 [uniq @dists_required],
205 \%dist_to_url,
209 sub make_minicpan {
210 my ($localdir, $dists_required, $dist_to_url) = @_;
212 my $injector = OrePAN2::Injector->new(
213 directory => $localdir,
214 author_subdir => 1
217 foreach my $d (@{ $dists_required }) {
218 my ($author, $dist) = split(/\//, $d, 2);
219 $injector->inject(
220 $dist_to_url->{$d} // die,
222 author => $author,
227 # XXX undocumented.
228 my $orepan = OrePAN2::Indexer->new(
229 directory => $localdir,
230 metacpan => 0,
231 simple => 1,
233 $orepan->make_index(
234 no_compress => 1,
236 return;