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   4283 use strict;
  14         31  
  14         423  
3 14     14   77 use warnings;
  14         35  
  14         538  
4              
5             our $VERSION = '0.261';
6              
7 14     14   87 use Carp;
  14         26  
  14         802  
8 14     14   5891 use version;
  14         26221  
  14         83  
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 963 my( $class, %args ) = @_;
43              
44 25         187 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         70 $hash{max_widths} = [ (0) x @{ $hash{columns} } ];
  25         101  
54              
55 25         157 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 5682 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 21 sub columns { @{ $_[0]->{columns} } };
  13         55  
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 28 my $self = shift;
102              
103 10         18 my $count = 0;
104 10         20 foreach my $package ( keys %{ $self->{entries} } ) {
  10         627  
105 2896         3760 $count += keys %{ $self->{entries}{$package} };
  2896         6242  
106             }
107              
108 10         158 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 1283 carp "entries is deprecated. Use get_hash instead";
124 7         568 &get_hash;
125             }
126 7     7 1 23 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 2588 $_[0]->{allow_packages_only_once} = !! $_[1] if defined $_[1];
137              
138 1481         4455 $_[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 3 $_[0]->{allow_suspicious_names} = !! $_[1] if defined $_[1];
150              
151 1         5 $_[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 2718 $_[0]->{disallow_alpha_versions} = !! $_[1] if defined $_[1];
163              
164 1499         3662 $_[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   2571 my( $self, $version ) = @_;
183              
184 1493         2122 my $warning;
185 1493     10   8269 local $SIG{__WARN__} = sub { $warning = join "\n", @_ };
  10         74  
186              
187 1493         2936 my( $parsed, $alpha ) = eval {
188 1493 50       2938 die "Version string is undefined\n" unless defined $version;
189 1493 100       2805 die "Version string is empty\n" if '' eq $version;
190 1491         8216 my $v = version->parse($version);
191 1483         2939 map { $v->$_() } qw( numify is_alpha );
  2966         14313  
192             };
193 1493         2573 do {
194 14     14   9396 no warnings 'uninitialized';
  14         55  
  14         20196  
195 1493         2312 my $at = $@;
196 1493         4768 chomp, s/\s+at\s+.*// for ( $at, $warning );
197 1493 100       3351 if( $at ) { ( 0, $alpha, $at ) }
  10 100       66  
198 10         77 elsif( defined $warning ) { ( $parsed, $alpha, $warning ) }
199 1473         7578 else { ( $parsed, $alpha, undef ) }
200             };
201             }
202              
203             sub add_entry {
204 1482     1482 1 4852 my( $self, %args ) = @_;
205              
206 1482         3628 $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       2868 if( exists $args{package_name} ) {
212 18         39 $args{'package name'} = $args{package_name};
213 18         33 delete $args{package_name};
214             }
215              
216 1482         3098 my( $parsed, $alpha, $warning ) = $self->_parse_version( $args{'version'} );
217              
218 1482 100       3523 if( defined $warning ) {
219 12         59 $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         1404 carp( $warning );
221             }
222              
223 1482 100 100     3945 if( $self->disallow_alpha_versions && $alpha ) {
224 3         191 croak "add_entry interprets [$parsed] as an alpha version, and disallow_alpha_versions is on";
225             }
226              
227 1479 50       3073 unless( defined $args{'package name'} ) {
228 0         0 croak "No 'package name' parameter!";
229 0         0 return;
230             }
231              
232 1479 100 66     8148 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         153 croak "Package name [$args{'package name'}] looks suspicious. Not adding it!";
242 0         0 return;
243             }
244              
245 1478 100 100     3139 if( $self->allow_packages_only_once and $self->already_added( $args{'package name'} ) ) {
246 1         160 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         3364 }{$args{'version'}
255             } = $self->entry_class->new( %args );
256              
257 1477         7501 return 1;
258             }
259              
260             sub _mark_as_dirty {
261 1482     1482   2405 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 5572 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 9 my( $self ) = @_;
282              
283 3         4 my $string;
284              
285 3         7 my( $return ) = $self->as_unique_sorted_list;
286              
287 3         8 foreach my $entry ( @$return ) {
288 6         17 $string .= $entry->as_string( $self->columns );
289             }
290              
291 3 100       24 $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 46 my( $self ) = @_;
309              
310 16 100       62 unless( ref $self->{sorted} eq ref [] ) {
311 7         30 $self->{sorted} = [];
312              
313 7         13 my %Seen;
314              
315 7         31 my( $k1, $k2 ) = ( $self->columns )[0,1];
316              
317 7         24 my $e = $self->entries;
318              
319             # We only want the latest versions of everything:
320 7         55 foreach my $package ( sort keys %$e ) {
321 13         25 my $entries = $e->{$package};
322             eval {
323 13 50       21 eval { require version } or die "Could not load version.pm!";
  13         81  
324 13 50       92 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       24 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         39 map { $_->[0] }
337 3         38 sort { $b->[1] <=> $a->[1] } # sort on version objects
338             map {
339 13         32 my $w;
  16         25  
340 16     0   96 local $SIG{__WARN__} = sub { $w = join "\n", @_ };
  0         0  
341 16         30 my $v = eval { version->new( $_ ) };
  16         102  
342 16   33     72 $w = $w || $@;
343 16         40 $w = s/\s+at\s+//;
344 16 50       33 carp "Version [$_] for package [$package] parses with a warning: [$w]. Using [$v] as the version."
345             if $w;
346 16 50 33     37 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         92 else { [ $_, $v ] }
351             }
352             keys %$entries;
353              
354 13         38 push @{ $self->{sorted} }, $entries->{$highest_version};
  13         44  
355             }
356             }
357              
358             my $return = wantarray ?
359             $self->{sorted}
360             :
361 16 100       52 scalar @{ $self->{sorted} };
  4         9  
362              
363 16         58 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 7 my( $self, $package ) = @_;
374              
375             my @entries =
376 3         3 map { values %{$self->{entries}{$package}} }
  3         20  
377 12         27 grep { $_ eq $package }
378 3         5 keys %{ $self->{entries} };
  3         10  
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 553 require CPAN::DistnameInfo;
389 4         1000 my( $self, $distribution ) = @_;
390 4 50       13 croak "You must specify a distribution!" unless defined $distribution;
391              
392             my @entries =
393             grep { # $_ is the entry hash
394 28         261 my $info = CPAN::DistnameInfo->new( $_->{'path'} );
395 28 50       1539 defined $info->dist && $info->dist eq $distribution;
396             }
397             map { # $_ is the package name
398 16         24 values %{ $self->{entries}{$_} }
  16         39  
399             }
400 4         5 keys %{ $self->{entries} };
  4         15  
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