File Coverage

blib/lib/CPAN/PackageDetails/Entries.pm
Criterion Covered Total %
statement 126 150 84.0
branch 34 48 70.8
condition 10 15 66.6
subroutine 23 28 82.1
pod 18 18 100.0
total 211 259 81.4


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