File Coverage

blib/lib/App/RetroPAN.pm
Criterion Covered Total %
statement 30 94 31.9
branch 0 20 0.0
condition 0 11 0.0
subroutine 10 14 71.4
pod 0 4 0.0
total 40 143 27.9


line stmt bran cond sub pod time code
1             package App::RetroPAN;
2             # vim:ts=4:shiftwidth=4:expandtab
3              
4 1     1   96850 use strict;
  1         2  
  1         30  
5 1     1   6 use warnings;
  1         2  
  1         23  
6 1     1   598 use utf8;
  1         14  
  1         5  
7              
8             =encoding utf8
9              
10             =head1 NAME
11              
12             App::RetroPAN - Makes a historic minicpan ⏳
13              
14             =head1 SYNOPSIS
15              
16             use App::RetroCPAN;
17              
18             my ($author, $dist_name, $url) = find_module_on_date("2011-01-01T00:00:00", "Moose");
19              
20             =head1 DESCRIPTION
21              
22             Uses the MetaCPAN API to find releases made prior to a given date to
23             satisfy your modules' dependencies.
24              
25             =head1 SEE ALSO
26              
27             =over
28              
29             =item *
30              
31             L<retropan>
32              
33             =item *
34              
35             L<OrePAN2>
36              
37             =back
38              
39             =head1 LICENSE
40              
41             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
42              
43             =head1 AUTHOR
44              
45             Dave Lambley <dlambley@cpan.org>
46              
47             =cut
48              
49 1     1   479 use HTTP::Request;
  1         22391  
  1         38  
50 1     1   744 use LWP::UserAgent;
  1         25520  
  1         38  
51 1     1   574 use List::MoreUtils qw/ uniq /;
  1         12261  
  1         7  
52 1     1   3925 use Module::CoreList;
  1         104183  
  1         12  
53 1     1   1454 use OrePAN2::Injector;
  1         624138  
  1         41  
54 1     1   464 use OrePAN2::Indexer;
  1         90864  
  1         43  
55              
56 1     1   25 use Cpanel::JSON::XS qw/ encode_json decode_json /;
  1         4  
  1         922  
57              
58             our $VERSION = '0.03';
59              
60             my $ua = LWP::UserAgent->new( keep_alive => 2, agent => "retropan/$VERSION" );
61              
62             sub find_module_dependencies {
63 0     0 0   my ($au, $dist) = @_;
64              
65 0           my $q = {
66             "size" => 1,
67             "query" => {
68             "bool" => {
69             "filter" => [
70             {
71             "match" => {
72             "name" => $dist,
73             }
74             },
75             {
76             "match" => {
77             "author" => $au,
78             }
79             },
80              
81             ],
82             }
83             }
84             };
85              
86 0           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) );
90              
91 0           my $res = $ua->request($req);
92 0 0         die $res->status_line if !$res->is_success;
93 0           my $data = decode_json($res->decoded_content);
94 0           my $hit = $data->{hits}->{hits}->[0];
95 0 0         if (!defined $hit) {
96 0           warn "could not find $au/$dist";
97 0           return;
98             }
99              
100             my @deps =
101 0           grep { !Module::CoreList::is_core($_) }
102 0           grep { $_ ne "perl" }
103 0           map { $_->{module} } @{ $hit->{_source}->{dependency} };
  0            
  0            
104              
105 0           return @deps;
106             }
107             sub find_module_on_date {
108 0     0 0   my ($module, $before) = @_;
109              
110 0 0         return if Module::CoreList::is_core($module);
111              
112             # We prefer authorized modules, but can fall back to unauthorized if none
113             # available.
114 0           my $q = {
115             "size" => 30, # TODO, keep search open.
116             "sort" => [
117             { "module.authorized" => "desc" },
118             { "version_numified" => "desc" },
119             "_score",
120             ],
121             "query" => {
122             "bool" => {
123             "filter" => [
124             {
125             "match" => {
126             "module.name" => $module,
127             }
128             },
129             {
130             "match" => {
131             "maturity" => "released",
132             }
133             },
134             {
135             "range" => { "date" => {"lt" => $before }}
136             },
137             ],
138             }
139             }
140             };
141              
142 0           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) );
146              
147 0           my $res = $ua->request($req);
148 0 0         die $res->status_line if !$res->is_success;
149 0           my $data = decode_json($res->decoded_content);
150              
151              
152 0           my $author;
153 0           my $version = -1;
154 0           my $release;
155             my $url;
156 0           my $authorized;
157              
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 0           foreach my $hit (@{ $data->{hits}->{hits} }) {
  0            
163 0 0         next if $hit->{_source}->{distribution} eq 'perl';
164 0           foreach my $mod (@{ $hit->{_source}->{module} }) {
  0            
165 0 0 0       if (($authorized ? $mod->{authorized} : 1) && $mod->{name} eq $module && $mod->{version_numified} > $version) {
    0 0        
166 0           $author = $hit->{_source}->{author};
167 0           $release = $hit->{_source}->{release};
168 0           $url = $hit->{_source}->{download_url};
169 0           $version = $mod->{version_numified};
170 0           $authorized = $mod->{authorized};
171             }
172             }
173             }
174              
175              
176 0 0         if (!defined $release) {
177 0           warn "could not find $module before $before";
178 0           return;
179             }
180              
181 0           return ($author, $release, $url);
182             }
183              
184             sub find_deps_on_date {
185 0     0 0   my ($before, @modules) = @_;
186              
187 0           my %done_modules;
188             my @dists_required;
189 0           my %dist_to_url;
190              
191 0           while (@modules) {
192 0           my $mod = pop @modules;
193 0 0         next if $done_modules{$mod};
194              
195 0           my ($au, $dist, $url) = find_module_on_date($mod, $before);
196 0           $done_modules{$mod} = 1;
197 0 0 0       next if !defined($au) || !defined($dist);
198 0           $dist_to_url{"$au/$dist"} = $url;
199              
200 0           push @modules, find_module_dependencies($au, $dist);
201 0           unshift @dists_required, "$au/$dist";
202             }
203              
204             return (
205 0           [uniq @dists_required],
206             \%dist_to_url,
207             );
208             }
209              
210             sub make_minicpan {
211 0     0 0   my ($localdir, $dists_required, $dist_to_url) = @_;
212              
213 0           my $injector = OrePAN2::Injector->new(
214             directory => $localdir,
215             author_subdir => 1
216             );
217              
218 0           foreach my $d (@{ $dists_required }) {
  0            
219 0           my ($author, $dist) = split(/\//, $d, 2);
220             $injector->inject(
221 0   0       $dist_to_url->{$d} // die,
222             {
223             author => $author,
224             }
225             );
226             }
227              
228             # XXX undocumented.
229 0           my $orepan = OrePAN2::Indexer->new(
230             directory => $localdir,
231             metacpan => 0,
232             simple => 1,
233             );
234 0           $orepan->make_index(
235             no_compress => 1,
236             );
237 0           return;
238             }