File Coverage

blib/lib/CPAN/PackageDetails/PerlHash.pm
Criterion Covered Total %
statement 17 150 11.3
branch 0 48 0.0
condition 0 15 0.0
subroutine 6 28 21.4
pod 17 17 100.0
total 40 258 15.5


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