File Coverage

blib/lib/Parse/CPAN/Packages/Fast.pm
Criterion Covered Total %
statement 164 186 88.1
branch 38 68 55.8
condition 4 12 33.3
subroutine 31 35 88.5
pod 3 10 30.0
total 240 311 77.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             # -*- perl -*-
3              
4             #
5             # Author: Slaven Rezic
6             #
7             # Copyright (C) 2009,2010,2012,2013,2014,2015 Slaven Rezic. All rights reserved.
8             # This program is free software; you can redistribute it and/or
9             # modify it under the same terms as Perl itself.
10             #
11             # Mail: slaven@rezic.de
12             # WWW: http://www.rezic.de/eserte/
13             #
14              
15 3     3   190506 use strict;
  3         8  
  3         109  
16 3     3   18 use warnings;
  3         5  
  3         111  
17              
18 3     3   2725 use CPAN::DistnameInfo ();
  3         3581  
  3         223  
19              
20             ######################################################################
21              
22             {
23             package Parse::CPAN::Packages::Fast;
24              
25             our $VERSION = '0.09';
26             $VERSION =~ s{_}{};
27              
28 3     3   1698 use IO::Uncompress::Gunzip qw($GunzipError);
  3         86276  
  3         346  
29 3     3   2268 use CPAN::Version ();
  3         6354  
  3         161  
30              
31             # Note: this function is possibly interactive, i.e. if CPAN.pm was
32             # never configured, or if CPAN's Config.pm needs reconfiguration.
33             sub _default_packages_file_interactive {
34 0     0   0 my($class) = @_;
35 0         0 require CPAN;
36 3     3   21 no warnings 'once';
  3         6  
  3         350  
37 0         0 local $CPAN::Be_Silent = 1;
38 0         0 CPAN::HandleConfig->load;
39 0         0 my $packages_file = $CPAN::Config->{keep_source_where} . "/modules/02packages.details.txt.gz";
40 0         0 $packages_file;
41             }
42              
43             # Note: this function is guaranteed to be non-interactive, but it
44             # is using just default locations to look at the CPAN config, or
45             # the 02packages files.
46             sub _default_packages_file_batch {
47 2     2   1335 my($class) = @_;
48              
49 2         6 my $home_cpandir = do {
50 3     3   15 no warnings 'uninitialized'; # HOME may be uninitialized on some systems e.g. Windows
  3         7  
  3         2309  
51 2         9 "$ENV{HOME}/.cpan";
52             };
53 2 50       10 if (!$INC{"CPAN/MyConfig.pm"}) {
54 2         6 my $home_myconfig = "$home_cpandir/CPAN/MyConfig.pm";
55 2 50       62 if (-r $home_myconfig) {
56 2         6 local @INC = ($home_cpandir);
57 2         5 eval { require CPAN::MyConfig };
  2         1315  
58             }
59             }
60 2 50 33     378 if ($INC{"CPAN/MyConfig.pm"} && $CPAN::Config->{keep_source_where}) {
61 2         10 my $packages_file = $CPAN::Config->{keep_source_where} . "/modules/02packages.details.txt.gz";
62 2 50 33     94 if (-r $packages_file && -s $packages_file) {
63 2         26 return $packages_file;
64             }
65             }
66              
67             # Cannot find a usable CPAN::MyConfig, try a default location
68 0         0 my $packages_file = "$home_cpandir/sources/modules/02packages.details.txt.gz";
69 0 0 0     0 if (-r $packages_file && -s $packages_file) {
70 0         0 return $packages_file;
71             }
72              
73 0         0 undef;
74             }
75              
76             *_default_packages_file = \&_default_packages_file_interactive;
77              
78             sub new {
79 3     3 1 16463 my($class, $packages_file) = @_;
80              
81 3 50       17 if (!$packages_file) {
82 0         0 $packages_file = $class->_default_packages_file;
83 0 0       0 if (!$packages_file) {
84 0         0 die "packages file not specified and cannot be determined from CPAN.pm configuration";
85             }
86             }
87              
88 3         8 my %pkg_to_dist;
89             my %dist_to_pkgs;
90 0         0 my %pkg_ver;
91              
92 0         0 my $FH;
93 3 50       28 if ($packages_file !~ m{\.gz$}) { # assume uncompressed file
94 0 0       0 open $FH, '<', $packages_file
95             or die "Can't open $packages_file: $!";
96             } else {
97 3 50       37 $FH = IO::Uncompress::Gunzip->new($packages_file)
98             or die "Can't open $packages_file: $GunzipError";
99             }
100             # overread header
101 3         6875 while(<$FH>) {
102 20 100       1141 last if /^$/;
103             }
104             # read payload
105 3         10 while(<$FH>) {
106 331124         17631483 my($pkg, $ver, $dist) = split;
107 331124         1089196 $pkg_to_dist{$pkg} = $dist;
108 331124         720299 $pkg_ver{$pkg} = $ver;
109 331124         327024 push @{ $dist_to_pkgs{$dist} }, $pkg;
  331124         1304495  
110             }
111            
112 3         110 bless { pkg_to_dist => \%pkg_to_dist,
113             dist_to_pkgs => \%dist_to_pkgs,
114             pkg_ver => \%pkg_ver,
115             }, $class;
116             }
117              
118             sub package {
119 1     1 0 3 my($self, $package_name) = @_;
120 1 50       11 return undef if !exists $self->{pkg_ver}{$package_name};
121 1         15 Parse::CPAN::Packages::Fast::Package->new($package_name, $self);
122             }
123              
124             sub packages {
125 1     1 1 230 my $self = shift;
126 1         3 keys %{ $self->{pkg_ver} };
  1         91562  
127             }
128              
129             sub package_count {
130 1     1 0 1101 my $self = shift;
131 1         2 scalar keys %{ $self->{pkg_ver} };
  1         11  
132             }
133              
134             sub distribution {
135 0     0 1 0 my($self, $distribution_name) = @_;
136 0 0       0 die "Distribution $distribution_name does not exist" if !exists $self->{dist_to_pkgs}{$distribution_name}; # XXX die or not?
137 0         0 Parse::CPAN::Packages::Fast::Distribution->new($distribution_name, $self);
138             }
139              
140             sub distributions {
141 1     1 0 3 my $self = shift;
142 1         2 map { Parse::CPAN::Packages::Fast::Distribution->new($_, $self) } keys %{ $self->{dist_to_pkgs} };
  33282         64696  
  1         18920  
143             }
144              
145             sub distribution_count {
146 1     1 0 2 my $self = shift;
147 1         5 my @dists = $self->distributions;
148 1         9764 scalar @dists;
149             }
150              
151             sub latest_distribution {
152 3     3 0 1233 my($self, $distribution_name) = @_;
153 3         8 my @candidates;
154 3         7 for my $candidate (keys %{ $self->{dist_to_pkgs} }) {
  3         33539  
155 66566 100       202102 if ($candidate =~ m{^./../.*/\Q$distribution_name}) {
156             # Possibly pure CPAN::DistnameInfo is somewhat faster
157             # than Parse::CPAN::Packages::Fast::Distribution (no
158             # inside-out handling, no additional DESTROY)
159 6         57 my $d = CPAN::DistnameInfo->new($candidate);
160 3     3   19 no warnings 'uninitialized'; # Some distributions have no parseable dist name
  3         5  
  3         2477  
161 6 50       568 if ($d->dist eq $distribution_name) {
162 6         64 push @candidates, $d;
163             }
164             }
165             }
166 3 50       13684 return if !@candidates; # XXX die or not?
167 3         17 my $best_candidate = pop @candidates;
168 3         41 my $best_candidate_version = $best_candidate->version;
169 3         40 for my $candidate (@candidates) {
170 3         100 my $this_version = $candidate->version;
171 3 100       43 if (CPAN::Version->vlt($best_candidate_version, $this_version)) {
172 1         64 $best_candidate = $candidate;
173 1         11 $best_candidate_version = $this_version;
174             }
175             }
176 3         50 Parse::CPAN::Packages::Fast::Distribution->new($best_candidate->pathname, $self);
177             }
178              
179             sub latest_distributions {
180 2     2 0 6 my $self = shift;
181 2         7 my %latest_dist;
182 2         5 for my $pathname (keys %{ $self->{dist_to_pkgs} }) {
  2         33739  
183 66564         125686 my $d = Parse::CPAN::Packages::Fast::Distribution->new($pathname, $self);
184 66564         157447 my $dist = $d->dist;
185 66564 100       252684 next if !defined $dist;
186 66496 100       161400 if (!exists $latest_dist{$dist}) {
187 62558         209387 $latest_dist{$dist} = $d;
188             } else {
189 3938 100       11903 if (CPAN::Version->vlt($latest_dist{$dist}->version, $d->version)) {
190 1758         72180 $latest_dist{$dist} = $d;
191             }
192             }
193             }
194 2         124803 values %latest_dist;
195             }
196              
197             sub latest_distribution_count {
198 1     1 0 3 my $self = shift;
199 1         6 my @dists = $self->latest_distributions;
200 1         7174 scalar @dists;
201             }
202              
203             # Addition: fast module lookup without loading whole packages file
204             # Not yet official!
205             sub _module_lookup {
206 13 50   13   43856 die "Usage?" if @_ != 4;
207 13         43 my($class, $module, $orig_packages_file, $cache_file) = @_;
208 13         4158 require Search::Dict;
209 13         1517 my $pwhfh = $class->_get_plain_packages_fh($orig_packages_file, $cache_file);
210 13         33 my $skey = "$module ";
211 13 50       45 return if Search::Dict::look($pwhfh, $skey, 0, 1) == -1;
212 13         6812 while () {
213 13         84 my $got = <$pwhfh>;
214 13 100       45 if (index($got, $skey) == 0) {
215 12         13 chomp $got;
216 12         72 my($pkg, $ver, $dist) = split /\s+/, $got;
217             return {
218 12         165 package => $pkg,
219             version => $ver,
220             dist => $dist,
221             };
222             }
223 1 50       58 return if lc(substr($got, 0, length($skey))) gt lc($skey);
224 0 0       0 return if eof($pwhfh);
225             }
226             }
227            
228             sub _get_plain_packages_fh {
229 13 50   13   37 die "Usage?" if @_ != 3;
230 13         29 my(undef, $orig_packages_file, $cache_file) = @_;
231 13 50       436 die "$orig_packages_file does not exist" if !-e $orig_packages_file;
232 13 100 66     584 if (!-e $cache_file || -M $cache_file > -M $orig_packages_file) {
233 1 50       16 my $ifh = IO::Uncompress::Gunzip->new($orig_packages_file)
234             or die "Can't open $orig_packages_file: $GunzipError";
235 1         2601 require File::Temp;
236 1         9 require File::Basename;
237 1 50       78 my($tmpfh,$tmpfile) = File::Temp::tempfile(DIR => File::Basename::dirname($cache_file))
238             or die "Can't create temporary file: $!";
239 1         541 while (<$ifh>) {
240 9 100       595 last if /^$/;
241             }
242             {
243 1         4 local $/ = \8192;
  1         6  
244 1         5 while (<$ifh>) {
245 1720         918324 print $tmpfh $_;
246             }
247             }
248 1 50       171 close $tmpfh
249             or die "Error while writing temporary file $tmpfile: $!";
250 1 50       243 rename $tmpfile, $cache_file
251             or die "While renaming $tmpfile to $cache_file: $!";
252             }
253 13 50       703 open my $fh, $cache_file
254             or die "Can't open $cache_file: $!";
255 13         38 $fh;
256             }
257            
258             }
259              
260             ######################################################################
261              
262             {
263              
264             package Parse::CPAN::Packages::Fast::Package;
265              
266             our $VERSION = $Parse::CPAN::Packages::Fast::VERSION;
267              
268             # Use inside-out technique for this member, to hide it in dumps etc.
269             my %obj_to_packages;
270              
271             sub new {
272 4     4   9 my($class, $package_name, $packages) = @_;
273             my $self = bless { package => $package_name,
274 4         21 version => $packages->{pkg_ver}{$package_name},
275             }, 'Parse::CPAN::Packages::Fast::Package';
276 4         25 $obj_to_packages{$self} = $packages;
277 4         13 $self;
278             }
279              
280             for my $method (qw(package version)) {
281 3     3   17 no strict 'refs';
  3         6  
  3         587  
282 6     6   923 *{$method} = sub { shift->{$method} };
283             }
284              
285             sub distribution {
286 2     2   4 my $self = shift;
287 2         7 my $packages = $obj_to_packages{$self};
288 2         9 my $dist = $packages->{pkg_to_dist}->{$self->package};
289 2         10 Parse::CPAN::Packages::Fast::Distribution->new($dist, $packages);
290             }
291              
292             sub prefix {
293 1     1   4 my $self = shift;
294 1         5 $self->distribution->prefix;
295             }
296              
297             sub DESTROY {
298 4     4   55879 my $self = shift;
299 4         380881 delete $obj_to_packages{$self};
300             }
301             }
302              
303             ######################################################################
304              
305             {
306             package Parse::CPAN::Packages::Fast::Distribution;
307              
308             our $VERSION = $Parse::CPAN::Packages::Fast::VERSION;
309              
310 3     3   37 use base qw(CPAN::DistnameInfo);
  3         5  
  3         1376  
311              
312             # Use inside-out technique for this member, to hide it in dumps etc.
313             my %obj_to_packages;
314              
315             sub new {
316 99851     99851   166803 my($class, $pathname, $packages) = @_;
317 99851         228113 my $self = $class->SUPER::new($pathname);
318 99851         5026607 $obj_to_packages{$self} = $packages;
319 99851         195997 $self;
320             }
321            
322             sub prefix {
323 2     2   866 my $self = shift;
324 2         15 my $prefix = $self->pathname;
325 2         9 $prefix =~ s{^authors/id/}{};
326 2         18 $prefix;
327             }
328              
329             sub contains {
330 1     1   2 my $self = shift;
331 1         5 my $packages = $obj_to_packages{$self};
332 1         3 map { Parse::CPAN::Packages::Fast::Package->new($_, $packages) } @{ $packages->{dist_to_pkgs}{$self->pathname} };
  3         13  
  1         5  
333             }
334              
335             sub DESTROY {
336 99851     99851   331558 my $self = shift;
337 99851         622601 delete $obj_to_packages{$self};
338             }
339              
340             # Methods found in original Parse::CPAN::Packages::Distribution
341             sub add_package {
342 0     0     die "NYI";
343             }
344              
345             # Would be nice to have:
346             sub is_latest_distribution {
347 0     0     die "NYI";
348             }
349             }
350              
351             ######################################################################
352              
353             1;
354              
355             __END__