File Coverage

blib/lib/CPAN/PackageDetails.pm
Criterion Covered Total %
statement 194 237 81.8
branch 42 76 55.2
condition 10 35 28.5
subroutine 34 37 91.8
pod 14 15 93.3
total 294 400 73.5


line stmt bran cond sub pod time code
1             package CPAN::PackageDetails;
2 14     14   8175 use strict;
  14         22  
  14         294  
3 14     14   49 use warnings;
  14         19  
  14         255  
4              
5 14     14   5587 use subs qw();
  14         244  
  14         294  
6              
7 14     14   53 use Carp qw(carp croak cluck confess);
  14         24  
  14         607  
8 14     14   59 use Cwd;
  14         17  
  14         501  
9 14     14   52 use File::Basename;
  14         23  
  14         578  
10 14     14   2574 use File::Spec::Functions;
  14         4123  
  14         2333  
11              
12             our $VERSION = '0.261';
13              
14             =encoding utf8
15              
16             =head1 NAME
17              
18             CPAN::PackageDetails - Create or read 02packages.details.txt.gz
19              
20             =head1 SYNOPSIS
21              
22             use CPAN::PackageDetails;
23              
24             # read an existing file #####################
25             my $package_details = CPAN::PackageDetails->read( $filename );
26              
27             my $count = $package_details->count;
28              
29             my $records = $package_details->entries->get_hash;
30              
31             foreach my $record ( @$records )
32             {
33             # See CPAN::PackageDetails::Entry too
34             # print join "\t", map { $record->$_() } ('package name', 'version', 'path')
35             print join "\t", map { $record->$_() } $package_details->columns_as_list;
36             }
37              
38             # not yet implemented, but would be really, really cool eh?
39             my $records = $package_details->entries(
40             logic => 'OR', # but that could be AND, which is the default
41             package => qr/^Test::/, # or a string
42             author => 'OVID', # case insenstive
43             path => qr/foo/,
44             );
45              
46             # create a new file #####################
47             my $package_details = CPAN::PackageDetails->new(
48             file => "02packages.details.txt",
49             url => "http://example.com/MyCPAN/modules/02packages.details.txt",
50             description => "Package names for my private CPAN",
51             columns => "package name, version, path",
52             intended_for => "My private CPAN",
53             written_by => "$0 using CPAN::PackageDetails $CPAN::PackageDetails::VERSION",
54             last_updated => CPAN::PackageDetails->format_date,
55             allow_packages_only_once => 1,
56             disallow_alpha_versions => 1,
57             );
58              
59             $package_details->add_entry(
60             package_name => $package,
61             version => $package->VERSION;
62             path => $path,
63             );
64              
65             print "About to write ", $package_details->count, " entries\n";
66              
67             $package_details->write_file( $file );
68              
69             # OR ...
70              
71             $package_details->write_fh( \*STDOUT )
72              
73             =head1 DESCRIPTION
74              
75             CPAN uses an index file, F<02packages.details.txt.gz>, to map package names to
76             distribution files. Using this module, you can get a data structure of that
77             file, or create your own.
78              
79             There are two parts to the F<02packages.details.txt.g>z: a header and the index.
80             This module uses a top-level C object to control
81             everything and comprise an C and
82             C object. The C
83             object is a collection of C objects.
84              
85             For the most common uses, you don't need to worry about the insides
86             of what class is doing what. You'll call most of the methods on
87             the top-level C object and it will make sure
88             that it gets to the right place.
89              
90             =head2 Methods
91              
92             These methods are in the top-level object, and there are more methods
93             for this class in the sections that cover the Header, Entries, and
94             Entry objects.
95              
96             =over 4
97              
98             =item new
99              
100             Create a new F<02packages.details.txt.gz> file. The C
101             method shows you which values you can pass to C. For instance:
102              
103             my $package_details = CPAN::PackageDetails->new(
104             url => $url,
105             columns => 'author, package name, version, path',
106             )
107              
108             If you specify the C option with a true value
109             and you try to add that package twice, the object will die. See C
110             in C.
111              
112             If you specify the C option with a true value
113             and you try to add that package twice, the object will die. See C
114             in C.
115              
116             =cut
117              
118 0         0 BEGIN {
119 14     14   3928 my $class_counter = 0;
120             sub new {
121 24     24 1 13452 my( $class, %args ) = @_;
122              
123 24         34 my( $ref, $bless_class ) = do {
124 24 50       59 if( exists $args{dbmdeep} ) {
125 0         0 eval { require DBM::Deep };
  0         0  
126 0 0       0 if( $@ ) {
127 0         0 croak "You must have DBM::Deep installed and discoverable to use the dbmdeep feature";
128             }
129             my $ref = DBM::Deep->new(
130             file => $args{dbmdeep},
131 0         0 autoflush => 1,
132             );
133 0 0       0 croak "Could not create DBM::Deep object" unless ref $ref;
134 0         0 my $single_class = sprintf "${class}::DBM%03d", $class_counter++;
135              
136 14     14   76 no strict 'refs';
  14         21  
  14         1261  
137 0         0 @{"${single_class}::ISA"} = ( $class , 'DBM::Deep' );
  0         0  
138 0         0 ( $ref, $single_class );
139             }
140             else {
141 24         54 ( {}, $class );
142             }
143             };
144              
145 24         64 my $self = bless $ref, $bless_class;
146              
147 24         85 $self->init( %args );
148              
149 24         63 $self;
150             }
151             }
152              
153             =item init
154              
155             Sets up the object. C calls this automatically for you.
156              
157             =item default_headers
158              
159             Returns the hash of header fields and their default values:
160              
161             file "02packages.details.txt"
162             url "http://example.com/MyCPAN/modules/02packages.details.txt"
163             description "Package names for my private CPAN"
164             columns "package name, version, path"
165             intended_for "My private CPAN"
166             written_by "$0 using CPAN::PackageDetails $CPAN::PackageDetails::VERSION"
167             last_updated format_date()
168              
169             In the header, these fields show up with the underscores turned into hyphens,
170             and the letters at the beginning or after a hyphen are uppercase.
171              
172             =cut
173              
174 0         0 BEGIN {
175             # These methods live in the top level and delegate interfaces
176             # so I need to intercept them at the top-level and redirect
177             # them to the right delegate
178             my %Dispatch = (
179 70         136 header => { map { $_, 1 } qw(
180             default_headers get_header set_header header_exists
181             columns_as_list
182             ) },
183 14     14   57 entries => { map { $_, 1 } qw(
  168         315  
184             add_entry count as_unique_sorted_list already_added
185             allow_packages_only_once disallow_alpha_versions
186             get_entries_by_package get_entries_by_version
187             get_entries_by_path get_entries_by_distribution
188             allow_suspicious_names get_hash
189             ) },
190             # entry => { map { $_, 1 } qw() },
191             );
192              
193             my %Dispatchable = map { #inverts %Dispatch
194 14         54 my $class = $_;
  28         37  
195 28         31 map { $_, $class } keys %{$Dispatch{$class}}
  238         4186  
  28         75  
196             } keys %Dispatch;
197              
198             sub can {
199 39     39 0 12126 my( $self, @methods ) = @_;
200              
201 39   66     123 my $class = ref $self || $self; # class or instance
202              
203 39         63 foreach my $method ( @methods ) {
204             next if
205 39         308 defined &{"${class}::$method"} ||
206 39 100 100     47 exists $Dispatchable{$method} ||
      100        
207             $self->header_exists( $method );
208 5         57 return 0;
209             }
210              
211 34         78 return 1;
212             }
213              
214             sub AUTOLOAD {
215 1616     1616   18283 my $self = shift;
216              
217              
218 1616         1474 our $AUTOLOAD;
219 1616 50       2510 carp "There are no AUTOLOADable class methods: $AUTOLOAD" unless ref $self;
220 1616         4948 ( my $method = $AUTOLOAD ) =~ s/.*:://;
221              
222 1616 100       2775 if( exists $Dispatchable{$method} ) {
    100          
223 1602         1769 my $delegate = $Dispatchable{$method};
224 1602         2440 return $self->$delegate()->$method(@_)
225             }
226             elsif( $self->header_exists( $method ) ) {
227 13         27 return $self->header->get_header( $method );
228             }
229             else {
230 1         126 carp "No such method as $method!";
231 1         70 return;
232             }
233             }
234             }
235              
236 0         0 BEGIN {
237 14     14   1858 my %defaults = (
238             file => "02packages.details.txt",
239             url => "http://example.com/MyCPAN/modules/02packages.details.txt",
240             description => "Package names for my private CPAN",
241             columns => "package name, version, path",
242             intended_for => "My private CPAN",
243             written_by => "$0 using CPAN::PackageDetails $CPAN::PackageDetails::VERSION",
244              
245             header_class => 'CPAN::PackageDetails::Header',
246             entries_class => 'CPAN::PackageDetails::Entries',
247             entry_class => 'CPAN::PackageDetails::Entry',
248              
249             allow_packages_only_once => 1,
250             disallow_alpha_versions => 0,
251             allow_suspicious_names => 0,
252             );
253              
254             sub init
255             {
256 24     24 1 42 my( $self, %args ) = @_;
257              
258 24         191 my %config = ( %defaults, %args );
259              
260             # we'll delegate everything, but also try to hide the mess from the user
261 24         66 foreach my $key ( map { "${_}_class" } qw(header entries entry) ) {
  72         147  
262 72         127 $self->{$key} = $config{$key};
263 72         109 delete $config{$key};
264             }
265              
266 24         43 foreach my $class ( map { $self->$_ } qw(header_class entries_class entry_class) ) {
  72         167  
267 72         3077 eval "require $class";
268             }
269              
270             # don't initialize things if they are already there. For instance,
271             # if we read an existing DBM::Deep file
272             $self->{entries} = $self->entries_class->new(
273             entry_class => $self->entry_class,
274             columns => [ split /,\s+/, $config{columns} ],
275             allow_packages_only_once => $config{allow_packages_only_once},
276             allow_suspicious_names => $config{allow_suspicious_names},
277             disallow_alpha_versions => $config{disallow_alpha_versions},
278 24 50       135 ) unless exists $self->{entries};
279              
280             $self->{header} = $self->header_class->new(
281             _entries => $self->entries,
282 24 50       77 ) unless exists $self->{header};
283              
284              
285 24         74 foreach my $key ( keys %config )
286             {
287 217         260 $self->header->set_header( $key, $config{$key} );
288             }
289              
290             $self->header->set_header(
291 24         45 'last_updated',
292             $self->header->format_date
293             );
294              
295             }
296              
297             }
298              
299             =item read( FILE )
300              
301             Read an existing 02packages.details.txt.gz file.
302              
303             While parsing, it modifies the field names to map them to Perly
304             identifiers. The field is lowercased, and then hyphens become
305             underscores. For instance:
306              
307             Written-By ---> written_by
308              
309             =cut
310              
311             sub read {
312 7     7 1 4697 my( $class, $file, %args ) = @_;
313              
314 7 100       21 unless( defined $file ) {
315 1         415 carp "Missing argument!";
316 1         8 return;
317             }
318              
319 6         1122 require IO::Uncompress::Gunzip;
320              
321 6 100       58095 my $fh = IO::Uncompress::Gunzip->new( $file ) or do {
322 14     14   99 no warnings;
  14         19  
  14         11844  
323 1         555 carp "Could not open $file: $IO::Compress::Gunzip::GunzipError\n";
324 1         7 return;
325             };
326              
327 5         7871 my $self = $class->_parse( $fh, %args );
328              
329 5         10 $self->{source_file} = $file;
330              
331 5         39 $self;
332             }
333              
334             =item source_file
335              
336             Returns the original file path for objects created through the
337             C method.
338              
339             =cut
340              
341 1     1 1 871 sub source_file { $_[0]->{source_file} }
342              
343             sub _parse {
344 5     5   14 my( $class, $fh, %args ) = @_;
345              
346 5         43 my $package_details = $class->new( %args );
347              
348 5         26 while( <$fh> ) { # header processing
349 44 100       2056 last if /\A\s*\Z/;
350 40         51 chomp;
351 40         133 my( $field, $value ) = split /\s*:\s*/, $_, 2;
352              
353 40   50     77 $field = lc( $field || '' );
354 40         50 $field =~ tr/-/_/;
355              
356 40         33 carp "Unknown field value [$field] at line $.! Skipping..."
357             unless 1; # XXX should there be field name restrictions?
358 40         117 $package_details->set_header( $field, $value );
359             }
360              
361 5         24 my @columns = $package_details->columns_as_list;
362 5         16 while( <$fh> ) { # entry processing
363 1445         76222 chomp;
364 1445         2811 my @values = split; # this could be in any order based on columns field.
365             $package_details->add_entry(
366 1445         2194 map { $columns[$_], $values[$_] } 0 .. $#columns,
  4335         8461  
367             )
368             }
369              
370 5         55 $package_details;
371             }
372              
373             =item write_file( OUTPUT_FILE )
374              
375             Formats the object as a string and writes it to a temporary file and
376             gzips the output. When everything is complete, it renames the temporary
377             file to its final name.
378              
379             C carps and returns nothing if you pass it no arguments, if
380             it cannot open OUTPUT_FILE for writing, or if it cannot rename the file.
381              
382             =cut
383              
384             sub write_file {
385 4     4 1 2452 my( $self, $output_file ) = @_;
386              
387 4 100       17 unless( defined $output_file ) {
388 1         60 carp "Missing argument!";
389 1         53 return;
390             }
391              
392 3         508 require IO::Compress::Gzip;
393              
394 3 100       28457 my $fh = IO::Compress::Gzip->new( "$output_file.$$" ) or do {
395 1         1213 carp "Could not open $output_file.$$ for writing: $IO::Compress::Gzip::GzipError";
396 1         58 return;
397             };
398              
399 2         2758 $self->write_fh( $fh );
400 2         265 $fh->close;
401              
402 2 50       530 unless( rename "$output_file.$$", $output_file ) {
403 0         0 carp "Could not rename temporary file to $output_file!\n";
404 0         0 return;
405             }
406              
407 2         13 return 1;
408             }
409              
410             =item write_fh( FILEHANDLE )
411              
412             Formats the object as a string and writes it to FILEHANDLE
413              
414             =cut
415              
416             sub write_fh {
417 3     3 1 2807 my( $self, $fh ) = @_;
418              
419 3         20 print $fh $self->header->as_string, $self->entries->as_string;
420             }
421              
422             =item check_file( FILE, CPAN_PATH )
423              
424             This method takes an existing F<02packages.details.txt.gz> named in FILE and
425             the the CPAN root at CPAN_PATH (to append to the relative paths in the
426             index), then checks the file for several things:
427              
428             1. That there are entries in the file
429             2. The number of entries matches those declared in the Line-Count header
430             3. All paths listed in the file exist under CPAN_PATH
431             4. All distributions under CPAN_PATH have an entry (not counting older versions)
432              
433             If any of these checks fail, C croaks with a hash reference
434             with these keys:
435              
436             # present in every error object
437             filename the FILE you passed in
438             cpan_path the CPAN_PATH you passed in
439             cwd the current working directory
440             error_count
441              
442             # if FILE is missing
443             missing_file exists and true if FILE doesn't exist
444              
445             # if the entry count in the file is wrong
446             # that is, the actual line count and header disagree
447             entry_count_mismatch true
448             line_count the line count declared in the header
449             entry_count the actual count
450              
451             # if some distros in CPAN_HOME are missing in FILE
452             missing_in_file anonymous array of missing paths
453              
454             # if some entries in FILE are missing the file in CPAN_HOME
455             missing_in_repo anonymous array of missing paths
456              
457             =cut
458              
459             sub ENTRY_COUNT_MISMATCH () { 1 }
460             sub MISSING_IN_REPO () { 2 }
461             sub MISSING_IN_FILE () { 3 }
462              
463             sub check_file {
464 4     4 1 4016 my( $either, $file, $cpan_path ) = @_;
465              
466             # works with a class or an instance. We have to create a new
467             # instance, so we need the class. However, I'm concerned about
468             # subclasses, so if the higher level application just has the
469             # object, and maybe from a class I don't know about, they should
470             # be able to call this method and have it end up here if they
471             # didn't override it. That is, don't encourage them to hard code
472             # a class name
473 4   33     15 my $class = ref $either || $either;
474              
475             # file exists
476 4         7575 my $error = {
477             error_count => 0,
478             cpan_path => $cpan_path,
479             filename => $file,
480             cwd => cwd(),
481             };
482 4 50       102 unless( -e $file ) {
483 0         0 $error->{missing_file} = 1;
484 0         0 $error->{error_count} += 1;
485             }
486              
487             # file is gzipped
488              
489             # check header # # # # # # # # # # # # # # # # # # #
490 4         50 my $packages = $class->read( $file );
491              
492             # count of entries in non-zero # # # # # # # # # # # # # # # # # # #
493              
494 4         315 my $header_count = $packages->get_header( 'line_count' );
495 4         14 my $entries_count = $packages->count;
496              
497 4 50       8 unless( $header_count ) {
498 0         0 $error->{entry_count_mismatch} = 1;
499 0         0 $error->{line_count} = $header_count;
500 0         0 $error->{entry_count} = $entries_count;
501 0         0 $error->{error_count} += 1;
502             }
503              
504 4 100       8 unless( $header_count == $entries_count ) {
505 1         2 $error->{entry_count_mismatch} = 1;
506 1         2 $error->{line_count} = $header_count;
507 1         2 $error->{entry_count} = $entries_count;
508 1         5 $error->{error_count} += 1;
509             }
510              
511 4 50       7 if( $cpan_path ) {
512 4         17 my $missing_in_file = $packages->check_for_missing_dists_in_file( $cpan_path );
513 4         7 my $missing_in_repo = $packages->check_for_missing_dists_in_repo( $cpan_path );
514              
515 4 100       16 $error->{missing_in_file} = $missing_in_file if @$missing_in_file;
516 4 100       6 $error->{missing_in_repo} = $missing_in_repo if @$missing_in_repo;
517 4         8 $error->{error_count} += @$missing_in_file + @$missing_in_repo;
518             }
519              
520 4 100       142 croak $error if $error->{error_count};
521              
522 1         18 return 1;
523             }
524              
525              
526              
527             =item check_for_missing_dists_in_repo( CPAN_PATH )
528              
529             Given an object and a CPAN_PATH, return an anonymous array of the
530             distributions in the object that are not in CPAN_PATH. That is,
531             complain when the object has extra distributions.
532              
533             C calls this for you and adds the result to its
534             error output.
535              
536             =cut
537              
538             sub check_for_missing_dists_in_repo {
539 4     4 1 8 my( $packages, $cpan_path ) = @_;
540              
541 4         4 my @missing;
542 4         12 my( $entries ) = $packages->as_unique_sorted_list;
543 4         32 foreach my $entry ( @$entries ) {
544 7         30 my $path = $entry->path;
545              
546 7         32 my $native_path = catfile( $cpan_path, split m|/|, $path );
547              
548 7 100       89 push @missing, $path unless -e $native_path;
549             }
550              
551 4         9 return \@missing;
552             }
553              
554             =item check_for_missing_dists_in_file( CPAN_PATH )
555              
556             Given an object and a CPAN_PATH, return an anonymous array of the
557             distributions in CPAN_PATH that do not show up in the object. That is,
558             complain when the object doesn't have all the dists.
559              
560             C calls this for you and adds the result to its
561             error output.
562              
563             =cut
564              
565             sub check_for_missing_dists_in_file {
566 4     4 1 6 my( $packages, $cpan_path ) = @_;
567              
568 4         16 my $dists = $packages->_get_repo_dists( $cpan_path );
569              
570 4         12 $packages->_filter_older_dists( $dists );
571              
572 4         5 my %files = map { $_, 1 } @$dists;
  8         20  
573 14     14   6357 use Data::Dumper;
  14         65005  
  14         8886  
574              
575 4         17 my( $entries ) = $packages->as_unique_sorted_list;
576              
577 4         30 foreach my $entry ( @$entries ) {
578 7         17 my $path = $entry->path;
579 7         39 my $native_path = catfile( $cpan_path, split m|/|, $path );
580 7         15 delete $files{$native_path};
581             }
582              
583 4         14 [ keys %files ];
584             }
585              
586             sub _filter_older_dists {
587 7     7   1390 my( $self, $array ) = @_;
588              
589 7         11 my %Seen;
590             my @order;
591 7         675 require CPAN::DistnameInfo;
592 7         1209 foreach my $path ( @$array ) {
593 25         893 my( $basename, $directory, $suffix ) = fileparse( $path, qw(.tar.gz .tgz .zip .tar.bz2) );
594 25         64 my( $name, $version, $developer ) = CPAN::DistnameInfo::distname_info( $basename );
595 25         608 my $tuple = [ $path, $name, $version ];
596 25         31 push @order, $name;
597              
598             # first branch, haven't seen the distro yet
599 25 100       60 if( ! exists $Seen{ $name } ) { $Seen{ $name } = $tuple }
  15 100       37  
600             # second branch, the version we see now is greater than before
601 9         19 elsif( $Seen{ $name }[2] lt $version ) { $Seen{ $name } = $tuple }
602             # third branch, nothing. Really? Are you sure there's not another case?
603 1         3 else { () }
604             }
605              
606             @$array = map {
607 7 100       9 if( exists $Seen{$_} ) {
  25         35  
608 15         16 my $dist = $Seen{$_}[0];
609 15         20 delete $Seen{$_};
610 15         35 $dist;
611             }
612             else {
613             ()
614 10         15 }
615             } @order;
616              
617 7         14 return 1;
618             }
619              
620              
621             sub _distname_info {
622 0 0   0   0 my $file = shift or return;
623              
624 0 0       0 my ($dist, $version) = $file =~ /^
625             ( # start of dist name
626             (?:
627             [-+.]*
628              
629             (?:
630             [A-Za-z0-9]+
631             |
632             (?<=\D)_
633             |
634             _(?=\D)
635             )*
636              
637             (?:
638             [A-Za-z]
639             (?=
640             [^A-Za-z]
641             |
642             $
643             )
644             |
645             \d
646             (?=-)
647             )
648              
649             (?
650             [._-][vV]
651             )
652             )+
653             ) # end of dist name
654              
655             ( # start of version
656             .*
657             ) # end of version
658             $/xs or return ($file, undef, undef );
659              
660 0 0 0     0 $dist =~ s/-undef\z// if ($dist =~ /-undef\z/ and ! length $version);
661              
662             # Catch names like Unicode-Collate-Standard-V3_1_1-0.1
663             # where the V3_1_1 is part of the distname
664 0 0       0 if ($version =~ /^(-[Vv].*)-(\d.*)/) {
665 0         0 $dist .= $1;
666 0         0 $version = $2;
667             }
668              
669 0 0 0     0 $version = $1 if !length $version and $dist =~ s/-(\d+\w)$//;
670              
671 0 0 0     0 $version = $1 . $version if $version =~ /^\d+$/ and $dist =~ s/-(\w+)$//;
672              
673 0 0       0 if( $version =~ /\d\.\d/ ) { $version =~ s/^[-_.]+// }
  0         0  
674 0         0 else { $version =~ s/^[-_]+// }
675              
676             # deal with versions with extra information
677 0         0 $version =~ s/-build\d+.*//;
678 0         0 $version =~ s/-DRW.*//;
679              
680             # deal with perl versions, merely to see if it is a dev version
681 0         0 my $dev;
682 0 0       0 if( length $version ) {
683 0         0 $dev = do {
684 0 0       0 if ($file =~ /^perl-?\d+\.(\d+)(?:\D(\d+))?(-(?:TRIAL|RC)\d+)?$/) {
    0          
685 0 0 0     0 1 if (($1 > 6 and $1 & 1) or ($2 and $2 >= 50)) or $3;
      0        
      0        
      0        
686             }
687             elsif ($version =~ /\d\D\d+_\d/) {
688 0         0 1;
689             }
690             };
691             }
692             else {
693 0         0 $version = undef;
694             }
695              
696 0         0 ($dist, $version, $dev);
697             }
698              
699             sub _get_repo_dists {
700 4     4   6 my( $self, $cpan_home ) = @_;
701              
702 4         7 my @files = ();
703              
704 14     14   101 use File::Find;
  14         26  
  14         3407  
705              
706             my $wanted = sub {
707 32 100   32   1133 push @files,
708             File::Spec::Functions::canonpath( $File::Find::name )
709             if m/\.(?:tar\.gz|tgz|zip)\z/
710 4         28 };
711              
712 4         390 find( $wanted, $cpan_home );
713              
714 4         31 return \@files;
715             }
716              
717       0     sub DESTROY {}
718              
719             =back
720              
721              
722             =head3 Methods in CPAN::PackageDetails
723              
724             =over 4
725              
726             =item header_class
727              
728             Returns the class that C uses to create
729             the header object.
730              
731             =cut
732              
733 50     50 1 108 sub header_class { $_[0]->{header_class} }
734              
735             =item header
736              
737             Returns the header object.
738              
739             =cut
740              
741 374     374 1 2987 sub header { $_[0]->{header} }
742              
743             =back
744              
745             =head3 Methods in CPAN::PackageDetails::Header
746              
747             =over 4
748              
749             =cut
750              
751             =back
752              
753             =head2 Entries
754              
755             Entries are the collection of the items describing the package details.
756             It comprises all of the Entry object.
757              
758             =head3 Methods is CPAN::PackageDetails
759              
760             =over 4
761              
762             =item entries_class
763              
764             Returns the class to use for the Entries object.
765              
766             To use a different Entries class, tell C which class you want to use
767             by passing the C option:
768              
769             CPAN::PackageDetails->new(
770             ...,
771             entries_class => $class,
772             );
773              
774             Note that you are responsible for loading the right class yourself.
775              
776             =item count
777              
778             Returns the number of entries.
779              
780             This dispatches to the C in CPAN::PackageDetails::Entries. These
781             are the same:
782              
783             $package_details->count;
784              
785             $package_details->entries->count;
786              
787             =cut
788              
789 49     49 1 111 sub entries_class { $_[0]->{entries_class} }
790              
791             =item entries
792              
793             Returns the entries object.
794              
795             =cut
796              
797 1561     1561 1 7139 sub entries { $_[0]->{entries} }
798              
799             =item entry_class
800              
801             Returns the class to use for each Entry object.
802              
803             To use a different Entry class, tell C which class you want to use
804             by passing the C option:
805              
806             CPAN::PackageDetails->new(
807             ...,
808             entry_class => $class,
809             )
810              
811             Note that you are responsible for loading the right class yourself.
812              
813             =cut
814              
815 48     48 1 279 sub entry_class { $_[0]->{entry_class} }
816              
817 0     0     sub _entries { $_[0]->{_entries} }
818              
819             =back
820              
821             =head1 TO DO
822              
823              
824             =head1 SEE ALSO
825              
826              
827             =head1 SOURCE AVAILABILITY
828              
829             This source is in Github:
830              
831             https://github.com/briandfoy/cpan-packagedetails
832              
833             =head1 AUTHOR
834              
835             brian d foy, C<< >>
836              
837             =head1 COPYRIGHT AND LICENSE
838              
839             Copyright © 2009-2018, brian d foy . All rights reserved.
840              
841             You may redistribute this under the terms of the Artistic License 2.0.
842              
843             =cut
844              
845             1;