File Coverage

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