File Coverage

blib/lib/CPAN/PackageDetails/Entries.pm
Criterion Covered Total %
statement 125 153 81.7
branch 34 50 68.0
condition 12 18 66.6
subroutine 22 29 75.8
pod 18 18 100.0
total 211 268 78.7


line stmt bran cond sub pod time code
1 14     14   4758 use 5.008;
  14         56  
2              
3             package CPAN::PackageDetails::Entries;
4 14     14   81 use strict;
  14         28  
  14         323  
5 14     14   82 use warnings::register;
  14         28  
  14         1893  
6              
7             our $VERSION = '0.263';
8              
9 14     14   103 use Carp;
  14         29  
  14         947  
10 14     14   6731 use version;
  14         27679  
  14         82  
11              
12       0     sub DESTROY { }
13              
14             =encoding utf8
15              
16             =head1 NAME
17              
18             CPAN::PackageDetails::Entries - Handle the collection of records of 02packages.details.txt.gz
19              
20             =head1 SYNOPSIS
21              
22             Used internally by CPAN::PackageDetails
23              
24             =head1 DESCRIPTION
25              
26             =head2 Methods
27              
28             =over 4
29              
30             =item new
31              
32             Creates a new Entries object. This doesn't do anything fancy. To add
33             to it, use C.
34              
35             entry_class => the class to use for each entry object
36             columns => the column names, in order that you want them in the output
37              
38             If you specify the C option with a true value
39             and you try to add that package twice, the object will die. See C.
40              
41             =cut
42              
43             sub new {
44 25     25 1 1698 my( $class, %args ) = @_;
45              
46 25         200 my %hash = (
47             entry_class => 'CPAN::PackageDetails::Entry',
48             allow_packages_only_once => 1,
49             allow_suspicious_names => 0,
50             columns => [],
51             entries => {},
52             %args
53             );
54              
55 25         72 $hash{max_widths} = [ (0) x @{ $hash{columns} } ];
  25         107  
56              
57 25         145 bless \%hash, $_[0]
58             }
59              
60             =item entry_class
61              
62             Returns the class that Entries uses to make a new Entry object.
63              
64             =cut
65              
66 1477     1477 1 5732 sub entry_class { $_[0]->{entry_class} }
67              
68             =item columns
69              
70             Returns a list of the column names in the entry
71              
72             =cut
73              
74 13     13 1 23 sub columns { @{ $_[0]->{columns} } };
  13         54  
75              
76             =item column_index_for( COLUMN )
77              
78             Returns the list position of the named COLUMN.
79              
80             =cut
81              
82             sub column_index_for {
83 0     0 1 0 my( $self, $column ) = @_;
84              
85              
86             my $index = grep {
87 0         0 $self->{columns}[$_] eq $column
88 0         0 } 0 .. @{ $self->columns };
  0         0  
89              
90 0 0       0 return unless defined $index;
91 0         0 return $index;
92             }
93              
94             =item count
95              
96             Returns the number of entries. This is not the same as the number of
97             lines that would show up in the F<02packages.details.txt> file since
98             this method counts duplicates as well.
99              
100             =cut
101              
102             sub count {
103 10     10 1 27 my $self = shift;
104              
105 10         32 my $count = 0;
106 10         26 foreach my $package ( keys %{ $self->{entries} } ) {
  10         592  
107 2896         3716 $count += keys %{ $self->{entries}{$package} };
  2896         6136  
108             }
109              
110 10         152 return $count;
111             }
112              
113             =item entries
114              
115             DEPRECATED: use C
116              
117             =item get_hash
118              
119             Returns the list of entries as an hash reference. The hash key is the
120             package name.
121              
122             =cut
123              
124             sub entries {
125 0     0 1 0 carp "entries is deprecated. Use get_hash instead";
126 0         0 &get_hash;
127             }
128              
129 7     7 1 18 sub get_hash { $_[0]->{entries} }
130              
131             =item allow_packages_only_once( [ARG] )
132              
133             Set or retrieve the value of the allow_packages_only_once setting. It's
134             a boolean.
135              
136             =cut
137              
138             sub allow_packages_only_once {
139 1481 50   1481 1 2691 $_[0]->{allow_packages_only_once} = !! $_[1] if defined $_[1];
140              
141 1481         4518 $_[0]->{allow_packages_only_once};
142             }
143              
144             =item allow_suspicious_names( [ARG] )
145              
146             Allow an entry to accept an illegal name. Normally you shouldn't use this,
147             but PAUSE has made bad files before.
148              
149             =cut
150              
151             sub allow_suspicious_names {
152 1 50   1 1 4 $_[0]->{allow_suspicious_names} = !! $_[1] if defined $_[1];
153              
154 1         4 $_[0]->{allow_suspicious_names};
155             }
156              
157             =item disallow_alpha_versions( [ARG] )
158              
159             Set or retrieve the value of the disallow_alpha_versions settings. It's
160             a boolean.
161              
162             =cut
163              
164             sub disallow_alpha_versions {
165 1499 50   1499 1 2809 $_[0]->{disallow_alpha_versions} = !! $_[1] if defined $_[1];
166              
167 1499         3470 $_[0]->{disallow_alpha_versions};
168             }
169              
170             =item add_entry
171              
172             Add an entry to the collection. Call this on the C
173             object and it will take care of finding the right handler.
174              
175             If you've set C to a true value (which is the
176             default, too), C will die if you try to add another entry with
177             the same package name even if it has a different or greater version. You can
178             set this to a false value and add as many entries as you like then use
179             C to get just the entries with the highest
180             versions for each package.
181              
182             =cut
183              
184             sub _parse_version {
185 1493     1493   2702 my( $self, $version ) = @_;
186              
187 1493         2076 my $warning;
188 1493     0   8110 local $SIG{__WARN__} = sub { $warning = join "\n", @_ };
  0         0  
189              
190 1493         2937 my( $parsed, $alpha ) = eval {
191 1493 50       3001 die "Version string is undefined\n" unless defined $version;
192 1493 100       2898 die "Version string is empty\n" if '' eq $version;
193 1491         8483 my $v = version->parse($version);
194 1483         3045 map { $v->$_() } qw( numify is_alpha );
  2966         14348  
195             };
196 1493         2521 do {
197 14     14   10086 no warnings 'uninitialized';
  14         39  
  14         21529  
198 1493         2115 my $at = $@;
199 1493         4541 chomp, s/\s+at\s+.*// for ( $at, $warning );
200 1493 50       3326 $warning = undef if $warning =~ m/numify\(\) is lossy/i;
201 1493 100       3116 if( $at ) { ( 0, $alpha, $at ) }
  10 50       65  
202 0         0 elsif( defined $warning ) { ( $parsed, $alpha, $warning ) }
203 1483         7805 else { ( $parsed, $alpha, undef ) }
204             };
205             }
206              
207             sub add_entry {
208 1482     1482 1 5013 my( $self, %args ) = @_;
209              
210 1482         3502 $self->_mark_as_dirty;
211              
212             # The column name has a space in it, but that looks weird in a
213             # hash constructor and I keep doing it wrong. If I type "package_name"
214             # I'll just make it work.
215 1482 100       3163 if( exists $args{package_name} ) {
216 18         33 $args{'package name'} = $args{package_name};
217 18         34 delete $args{package_name};
218             }
219              
220 1482         3198 my( $parsed, $alpha, $warning ) = $self->_parse_version( $args{'version'} );
221              
222 1482 100 66     4149 if( defined $warning and warnings::enabled() ) {
223 5         24 $warning = "add_entry has a problem parsing [$args{'version'}] for package [$args{'package name'}]: [$warning] I'm using [$parsed] as the version for [$args{'package name'}].\n";
224 5         539 warnings::warn( $warning );
225             }
226              
227 1482 100 100     3519 if( $self->disallow_alpha_versions && $alpha ) {
228 3         318 croak "add_entry interprets [$parsed] as an alpha version, and disallow_alpha_versions is on";
229             }
230              
231 1479 50       3057 unless( defined $args{'package name'} ) {
232 0         0 croak "No 'package name' parameter!";
233 0         0 return;
234             }
235              
236 1479 100 66     8520 unless( $args{'package name'} =~ m/
237             ^
238             [A-Za-z0-9_]+
239             (?:
240             (?:\::|')
241             [A-Za-z0-9_]+
242             )*
243             \z
244             /x || $self->allow_suspicious_names ) {
245 1         165 croak "Package name [$args{'package name'}] looks suspicious. Not adding it!";
246 0         0 return;
247             }
248              
249 1478 100 100     2980 if( $self->allow_packages_only_once and $self->already_added( $args{'package name'} ) ) {
250 1         183 croak "$args{'package name'} was already added to CPAN::PackageDetails!";
251 0         0 return;
252             }
253              
254             # should check for allowed columns here
255             # XXX: this part needs to change based on storage
256             $self->{entries}{
257             $args{'package name'}
258 1477         3332 }{$args{'version'}
259             } = $self->entry_class->new( %args );
260              
261 1477         7801 return 1;
262             }
263              
264             sub _mark_as_dirty {
265 1482     1482   2374 delete $_[0]->{sorted};
266             }
267              
268             =item already_added( PACKAGE )
269              
270             Returns true if there is already an entry for PACKAGE.
271              
272             =cut
273              
274             # XXX: this part needs to change based on storage
275 1460     1460 1 5466 sub already_added { exists $_[0]->{entries}{$_[1]} }
276              
277             =item as_string
278              
279             Returns a text version of the Entries object. This calls C
280             on each Entry object, and concatenates the results for all Entry objects.
281              
282             =cut
283              
284             sub as_string {
285 3     3 1 8 my( $self ) = @_;
286              
287 3         6 my $string;
288              
289 3         8 my( $return ) = $self->as_unique_sorted_list;
290              
291 3         8 foreach my $entry ( @$return ) {
292 6         15 $string .= $entry->as_string( $self->columns );
293             }
294              
295 3 100       62 $string || '';
296             }
297              
298             =item as_unique_sorted_list
299              
300             In list context, this returns a list of entries sorted by package name
301             and version. Each package exists exactly once in the list and with the
302             largest version number seen.
303              
304             In scalar context this returns the count of the number of unique entries.
305              
306             Once called, it caches its result until you add more entries.
307              
308             =cut
309              
310             sub VERSION_PM () { 9 }
311             sub as_unique_sorted_list {
312 16     16 1 36 my( $self ) = @_;
313              
314 16 100       72 unless( ref $self->{sorted} eq ref [] ) {
315 7         51 $self->{sorted} = [];
316              
317 7         25 my %Seen;
318              
319 7         33 my( $k1, $k2 ) = ( $self->columns )[0,1];
320              
321 7         27 my $e = $self->get_hash;
322              
323             # We only want the latest versions of everything:
324 7         50 foreach my $package ( sort keys %$e ) {
325 13         25 my $entries = $e->{$package};
326             eval {
327 13 50       17 eval { require version } or die "Could not load version.pm!";
  13         84  
328 13 50       102 die "Your version of the version module doesn't handle the parse method!"
329             unless version->can('parse');
330             } or croak( {
331             message => $@,
332 13 50       23 have_version => eval { version->VERSION },
  0         0  
333             need_version => 0.74,
334             inc => [ @INC ],
335             error => VERSION_PM,
336             }
337             );
338              
339             my( $highest_version ) =
340 16         44 map { $_->[0] }
341 4         43 sort { $b->[1] <=> $a->[1] } # sort on version objects
342             map {
343 13         40 my $w;
  16         35  
344 16     0   118 local $SIG{__WARN__} = sub { $w = join "\n", @_ };
  0         0  
345 16         31 my $v = eval { version->new( $_ ) };
  16         97  
346 16   33     72 $w = $w || $@;
347 16         38 $w = s/\s+at\s+//;
348 16 50       35 carp "Version [$_] for package [$package] parses with a warning: [$w]. Using [$v] as the version."
349             if $w;
350 16 50 33     50 if( $self->disallow_alpha_versions and $v->is_alpha ) {
351 0         0 carp "Skipping alpha version [$v] for [$package] while sorting versions.";
352             ()
353 0         0 }
354 16         96 else { [ $_, $v ] }
355             }
356             keys %$entries;
357              
358 13         38 push @{ $self->{sorted} }, $entries->{$highest_version};
  13         47  
359             }
360             }
361              
362             my $return = wantarray ?
363             $self->{sorted}
364             :
365 16 100       61 scalar @{ $self->{sorted} };
  4         11  
366              
367 16         66 return $return;
368             }
369              
370             =item get_entries_by_distribution( DISTRIBUTION )
371              
372             Returns the entry objects for the named DISTRIBUTION.
373              
374             =cut
375              
376             sub get_entries_by_distribution {
377 4     4 1 515 require CPAN::DistnameInfo;
378 4         1004 my( $self, $distribution ) = @_;
379 4 50       20 croak "You must specify a distribution!" unless defined $distribution;
380              
381             my @entries =
382             grep { # $_ is the entry hash
383 28         257 my $info = CPAN::DistnameInfo->new( $_->{'path'} );
384 28 50       1523 defined $info->dist && $info->dist eq $distribution;
385             }
386             map { # $_ is the package name
387 16         22 values %{ $self->{entries}{$_} }
  16         38  
388             }
389 4         7 keys %{ $self->{entries} };
  4         15  
390             }
391              
392             =item get_entries_by_package( PACKAGE )
393              
394             Returns the entry objects for the named PACKAGE.
395              
396             =cut
397              
398             sub get_entries_by_package {
399 3     3 1 7 my( $self, $package ) = @_;
400              
401             my @entries =
402 3         3 map { values %{$self->{entries}{$package}} }
  3         19  
403 12         26 grep { $_ eq $package }
404 3         5 keys %{ $self->{entries} };
  3         10  
405             }
406              
407             =item get_entries_by_path( PATH )
408              
409             Returns the entry objects for any entries with PATH.
410              
411             =cut
412              
413             sub get_entries_by_path {
414 0     0 1   my( $self, $path ) = @_;
415              
416             my @entries =
417 0           map { $self->{entries}{$_}{$path} }
418 0           grep { exists $self->{entries}{$_}{$path} }
419 0           keys %{ $self->{entries} };
  0            
420             }
421              
422             =item get_entries_by_version( VERSION )
423              
424             Returns the entry objects for any entries with VERSION.
425              
426             =cut
427              
428             sub get_entries_by_version {
429 0     0 1   my( $self, $version ) = @_;
430              
431             my @entries =
432 0           map { $self->{entries}{$_}{$version} }
433 0           grep { exists $self->{entries}{$_}{$version} }
434 0           keys %{ $self->{entries} };
  0            
435             }
436              
437             =back
438              
439             =head1 TO DO
440              
441             =head1 SEE ALSO
442              
443              
444             =head1 SOURCE AVAILABILITY
445              
446             This source is in Github:
447              
448             https://github.com/briandfoy/cpan-packagedetails
449              
450             =head1 AUTHOR
451              
452             brian d foy, C<< >>
453              
454             =head1 COPYRIGHT AND LICENSE
455              
456             Copyright © 2009-2021, brian d foy . All rights reserved.
457              
458             You may redistribute this under the terms of the Artistic License 2.0.
459              
460             =cut
461              
462             1;
463