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   152840 use strict;
  3         7  
  3         94  
16 3     3   16 use warnings;
  3         6  
  3         95  
17              
18 3     3   923942 use CPAN::DistnameInfo ();
  3         2840  
  3         208  
19              
20             ######################################################################
21              
22             {
23             package Parse::CPAN::Packages::Fast;
24              
25             our $VERSION = '0.08_50';
26             $VERSION =~ s{_}{};
27              
28 3     3   1976 use IO::Uncompress::Gunzip qw($GunzipError);
  3         92681  
  3         336  
29 3     3   2194 use CPAN::Version ();
  3         5740  
  3         156  
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   22 no warnings 'once';
  3         6  
  3         344  
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   1321 my($class) = @_;
48              
49 2         5 my $home_cpandir = do {
50 3     3   15 no warnings 'uninitialized'; # HOME may be uninitialized on some systems e.g. Windows
  3         6  
  3         2127  
51 2         8 "$ENV{HOME}/.cpan";
52             };
53 2 50       11 if (!$INC{"CPAN/MyConfig.pm"}) {
54 2         7 my $home_myconfig = "$home_cpandir/CPAN/MyConfig.pm";
55 2 50       63 if (-r $home_myconfig) {
56 2         7 local @INC = ($home_cpandir);
57 2         4 eval { require CPAN::MyConfig };
  2         1250  
58             }
59             }
60 2 50 33     25 if ($INC{"CPAN/MyConfig.pm"} && $CPAN::Config->{keep_source_where}) {
61 2         6 my $packages_file = $CPAN::Config->{keep_source_where} . "/modules/02packages.details.txt.gz";
62 2 50 33     93 if (-r $packages_file && -s $packages_file) {
63 2         25 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 3842 my($class, $packages_file) = @_;
80              
81 3 50       14 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         6 my %pkg_to_dist;
89             my %dist_to_pkgs;
90 0         0 my %pkg_ver;
91              
92 0         0 my $FH;
93 3 50       20 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       31 $FH = IO::Uncompress::Gunzip->new($packages_file)
98             or die "Can't open $packages_file: $GunzipError";
99             }
100             # overread header
101 3         6097 while(<$FH>) {
102 20 100       1137 last if /^$/;
103             }
104             # read payload
105 3         11 while(<$FH>) {
106 329828         17588160 my($pkg, $ver, $dist) = split;
107 329828         1137640 $pkg_to_dist{$pkg} = $dist;
108 329828         729057 $pkg_ver{$pkg} = $ver;
109 329828         354816 push @{ $dist_to_pkgs{$dist} }, $pkg;
  329828         1397532  
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       10 return undef if !exists $self->{pkg_ver}{$package_name};
121 1         17 Parse::CPAN::Packages::Fast::Package->new($package_name, $self);
122             }
123              
124             sub packages {
125 1     1 1 272 my $self = shift;
126 1         2 keys %{ $self->{pkg_ver} };
  1         85846  
127             }
128              
129             sub package_count {
130 1     1 0 1105 my $self = shift;
131 1         2 scalar keys %{ $self->{pkg_ver} };
  1         14  
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} };
  33133         75771  
  1         19110  
143             }
144              
145             sub distribution_count {
146 1     1 0 4 my $self = shift;
147 1         8 my @dists = $self->distributions;
148 1         11512 scalar @dists;
149             }
150              
151             sub latest_distribution {
152 3     3 0 1537 my($self, $distribution_name) = @_;
153 3         5 my @candidates;
154 3         10 for my $candidate (keys %{ $self->{dist_to_pkgs} }) {
  3         46976  
155 66268 100       228704 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         74 my $d = CPAN::DistnameInfo->new($candidate);
160 3     3   17 no warnings 'uninitialized'; # Some distributions have no parseable dist name
  3         6  
  3         2379  
161 6 50       624 if ($d->dist eq $distribution_name) {
162 6         68 push @candidates, $d;
163             }
164             }
165             }
166 3 50       14731 return if !@candidates; # XXX die or not?
167 3         20 my $best_candidate = pop @candidates;
168 3         45 my $best_candidate_version = $best_candidate->version;
169 3         47 for my $candidate (@candidates) {
170 3         22 my $this_version = $candidate->version;
171 3 100       51 if (CPAN::Version->vlt($best_candidate_version, $this_version)) {
172 2         139 $best_candidate = $candidate;
173 2         16 $best_candidate_version = $this_version;
174             }
175             }
176 3         90 Parse::CPAN::Packages::Fast::Distribution->new($best_candidate->pathname, $self);
177             }
178              
179             sub latest_distributions {
180 2     2 0 7 my $self = shift;
181 2         4 my %latest_dist;
182 2         6 for my $pathname (keys %{ $self->{dist_to_pkgs} }) {
  2         34203  
183 66266         139551 my $d = Parse::CPAN::Packages::Fast::Distribution->new($pathname, $self);
184 66266         171506 my $dist = $d->dist;
185 66266 100       262504 next if !defined $dist;
186 66198 100       171615 if (!exists $latest_dist{$dist}) {
187 62286         237412 $latest_dist{$dist} = $d;
188             } else {
189 3912 100       13348 if (CPAN::Version->vlt($latest_dist{$dist}->version, $d->version)) {
190 1718         77851 $latest_dist{$dist} = $d;
191             }
192             }
193             }
194 2         136494 values %latest_dist;
195             }
196              
197             sub latest_distribution_count {
198 1     1 0 4 my $self = shift;
199 1         8 my @dists = $self->latest_distributions;
200 1         8077 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   40527 die "Usage?" if @_ != 4;
207 13         39 my($class, $module, $orig_packages_file, $cache_file) = @_;
208 13         11329 require Search::Dict;
209 13         2105 my $pwhfh = $class->_get_plain_packages_fh($orig_packages_file, $cache_file);
210 13         29 my $skey = "$module ";
211 13 50       41 return if Search::Dict::look($pwhfh, $skey, 0, 1) == -1;
212 13         5798 while () {
213 13         87 my $got = <$pwhfh>;
214 13 100       44 if (index($got, $skey) == 0) {
215 12         15 chomp $got;
216 12         70 my($pkg, $ver, $dist) = split /\s+/, $got;
217             return {
218 12         160 package => $pkg,
219             version => $ver,
220             dist => $dist,
221             };
222             }
223 1 50       55 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   42 die "Usage?" if @_ != 3;
230 13         29 my(undef, $orig_packages_file, $cache_file) = @_;
231 13 50       409 die "$orig_packages_file does not exist" if !-e $orig_packages_file;
232 13 100 66     593 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         3653 require File::Temp;
236 1         9 require File::Basename;
237 1 50       89 my($tmpfh,$tmpfile) = File::Temp::tempfile(DIR => File::Basename::dirname($cache_file))
238             or die "Can't create temporary file: $!";
239 1         679 while (<$ifh>) {
240 9 100       857 last if /^$/;
241             }
242             {
243 1         3 local $/ = \8192;
  1         7  
244 1         6 while (<$ifh>) {
245 1713         889307 print $tmpfh $_;
246             }
247             }
248 1 50       245 close $tmpfh
249             or die "Error while writing temporary file $tmpfile: $!";
250 1 50       175 rename $tmpfile, $cache_file
251             or die "While renaming $tmpfile to $cache_file: $!";
252             }
253 13 50       606 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   7 my($class, $package_name, $packages) = @_;
273             my $self = bless { package => $package_name,
274 4         25 version => $packages->{pkg_ver}{$package_name},
275             }, 'Parse::CPAN::Packages::Fast::Package';
276 4         25 $obj_to_packages{$self} = $packages;
277 4         16 $self;
278             }
279              
280             for my $method (qw(package version)) {
281 3     3   15 no strict 'refs';
  3         6  
  3         599  
282 6     6   879 *{$method} = sub { shift->{$method} };
283             }
284              
285             sub distribution {
286 2     2   4 my $self = shift;
287 2         5 my $packages = $obj_to_packages{$self};
288 2         199 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   3 my $self = shift;
294 1         5 $self->distribution->prefix;
295             }
296              
297             sub DESTROY {
298 4     4   56949 my $self = shift;
299 4         401681 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         6  
  3         939  
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 99404     99404   182447 my($class, $pathname, $packages) = @_;
317 99404         246009 my $self = $class->SUPER::new($pathname);
318 99404         5524454 $obj_to_packages{$self} = $packages;
319 99404         216051 $self;
320             }
321            
322             sub prefix {
323 2     2   807 my $self = shift;
324 2         70 my $prefix = $self->pathname;
325 2         10 $prefix =~ s{^authors/id/}{};
326 2         17 $prefix;
327             }
328              
329             sub contains {
330 1     1   4 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         14  
  1         5  
333             }
334              
335             sub DESTROY {
336 99404     99404   332798 my $self = shift;
337 99404         673228 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__