File Coverage

blib/lib/CPAN/PackageDetails.pm
Criterion Covered Total %
statement 195 238 81.9
branch 42 76 55.2
condition 10 35 28.5
subroutine 35 38 92.1
pod 14 15 93.3
total 296 402 73.6


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