| 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; |