File Coverage

blib/lib/CPAN/PackageDetails.pm
Criterion Covered Total %
statement 197 240 82.0
branch 42 76 55.2
condition 10 35 28.5
subroutine 36 39 92.3
pod 14 15 93.3
total 299 405 73.8


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