| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Dist::Surveyor; | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | =head1 NAME | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | Dist::Surveyor - Survey installed modules and determine the specific distribution versions they came from | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | my $options = { | 
| 10 |  |  |  |  |  |  | opt_match => $opt_match, | 
| 11 |  |  |  |  |  |  | opt_perlver => $opt_perlver, | 
| 12 |  |  |  |  |  |  | opt_remnants => $opt_remnants, | 
| 13 |  |  |  |  |  |  | distro_key_mod_names => $distro_key_mod_names, | 
| 14 |  |  |  |  |  |  | }; | 
| 15 |  |  |  |  |  |  | my @installed_releases = determine_installed_releases($options, \@libdirs); | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | Surveys your huge ball of Perl modules, jammed together inside a directory, | 
| 20 |  |  |  |  |  |  | and tells you exactly which module is installed there. | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | For quick start, and a fine example of this module usage, see L. | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | This module have one exported function - determine_installed_releases | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | =cut | 
| 27 |  |  |  |  |  |  |  | 
| 28 | 1 |  |  | 1 |  | 380 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 22 |  | 
| 29 | 1 |  |  | 1 |  | 4 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 18 |  | 
| 30 |  |  |  |  |  |  |  | 
| 31 | 1 |  |  | 1 |  | 355 | use version; | 
|  | 1 |  |  |  |  | 1578 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 32 | 1 |  |  | 1 |  | 63 | use Carp; # core | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 56 |  | 
| 33 | 1 |  |  | 1 |  | 484 | use Data::Dumper; # core | 
|  | 1 |  |  |  |  | 6586 |  | 
|  | 1 |  |  |  |  | 55 |  | 
| 34 | 1 |  |  | 1 |  | 8 | use File::Find;  # core | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 75 |  | 
| 35 | 1 |  |  | 1 |  | 7 | use File::Spec; # core | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 34 |  | 
| 36 | 1 |  |  | 1 |  | 6 | use List::Util qw(max sum); # core | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 79 |  | 
| 37 | 1 |  |  | 1 |  | 395 | use Dist::Surveyor::Inquiry; # internal | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 63 |  | 
| 38 | 1 |  |  | 1 |  | 2108 | use Module::CoreList; | 
|  | 1 |  |  |  |  | 41455 |  | 
|  | 1 |  |  |  |  | 11 |  | 
| 39 | 1 |  |  | 1 |  | 1307 | use Module::Metadata; | 
|  | 1 |  |  |  |  | 4119 |  | 
|  | 1 |  |  |  |  | 56 |  | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | our $VERSION = '0.019'; | 
| 42 |  |  |  |  |  |  |  | 
| 43 | 1 |  |  | 1 |  | 9 | use constant ON_WIN32 => $^O eq 'MSWin32'; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 79 |  | 
| 44 | 1 |  |  | 1 |  | 6 | use constant ON_VMS   => $^O eq 'VMS'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 2165 |  | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | if (ON_VMS) { | 
| 47 |  |  |  |  |  |  | require File::Spec::Unix; | 
| 48 |  |  |  |  |  |  | } | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | our ($DEBUG, $VERBOSE); | 
| 51 |  |  |  |  |  |  | *DEBUG = \$::DEBUG; | 
| 52 |  |  |  |  |  |  | *VERBOSE = \$::VERBOSE; | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | require Exporter; | 
| 55 |  |  |  |  |  |  | our @ISA = qw{Exporter}; | 
| 56 |  |  |  |  |  |  | our @EXPORT = qw{determine_installed_releases}; | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | =head1 determine_installed_releases($options, $search_dirs) | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | $options includes: | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | =over | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | =item opt_match | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | A regex qr//. If exists, will ignore modules that doesn't match this regex | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | =item opt_perlver | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | Skip modules that are included as core in this Perl version | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | =item opt_remnants | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | If true, output will include old distribution versions that have left old modules behind | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | =item distro_key_mod_names | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | A hash-ref, with a list of irregular named releases. i.e. 'libwww-perl' => 'LWP'. | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | =back | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | $search_dirs is an array-ref containing the list of directories to survey. | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | Returns a list, where each element is a hashref representing one installed distibution. | 
| 85 |  |  |  |  |  |  | This hashref is what MetaCPAN returns for C, | 
| 86 |  |  |  |  |  |  | with two additional keys: | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | =over | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | =item * | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | 'url' - that same as 'download_url', but without the hostname. can be used to | 
| 93 |  |  |  |  |  |  | download the file for your favorite mirror | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | =item * | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | 'dist_data' - Hashref containing info about the release, i.e. percent_installed. | 
| 98 |  |  |  |  |  |  | (fully installed releases will have '100.00') | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | =back | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | =cut | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | sub determine_installed_releases { | 
| 105 | 1 |  |  | 1 | 0 | 15708 | my ($options, $search_dirs) = @_; | 
| 106 | 1 |  | 33 |  |  | 27 | $options->{opt_perlver} ||= version->parse( $] )->numify; | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 1 |  |  |  |  | 7 | my %installed_mod_info; | 
| 109 |  |  |  |  |  |  |  | 
| 110 | 1 |  |  |  |  | 76 | warn "Finding modules in @$search_dirs\n"; | 
| 111 | 1 |  |  |  |  | 9 | my ($installed_mod_files, $installed_meta) = find_installed_modules(@$search_dirs); | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | # get the installed version of each installed module and related info | 
| 114 | 1 |  |  |  |  | 50 | warn "Finding candidate releases for the ".keys(%$installed_mod_files)." installed modules\n"; | 
| 115 | 1 |  |  |  |  | 12 | foreach my $module ( sort keys %$installed_mod_files ) { | 
| 116 | 6 |  |  |  |  | 26 | my $mod_file = $installed_mod_files->{$module}; | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 6 | 50 |  |  |  | 32 | if (my $opt_match = $options->{opt_match}) { | 
| 119 | 0 | 0 |  |  |  | 0 | if ($module !~ m/$opt_match/o) { | 
| 120 | 0 |  |  |  |  | 0 | delete $installed_mod_files->{$module}; | 
| 121 | 0 |  |  |  |  | 0 | next; | 
| 122 |  |  |  |  |  |  | } | 
| 123 |  |  |  |  |  |  | } | 
| 124 |  |  |  |  |  |  |  | 
| 125 | 6 | 50 |  |  |  | 54 | module_progress_indicator($module) unless $VERBOSE; | 
| 126 | 6 |  |  |  |  | 26 | my $mi = get_installed_mod_info($options, $module, $mod_file); | 
| 127 | 6 | 50 |  |  |  | 36 | $installed_mod_info{$module} = $mi if $mi; | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | # Map modules to dists using the accumulated %installed_mod_info info | 
| 132 |  |  |  |  |  |  |  | 
| 133 | 1 |  |  |  |  | 104 | warn "*** Mapping modules to releases\n"; | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 1 |  |  |  |  | 6 | my %best_dist; | 
| 136 | 1 |  |  |  |  | 12 | foreach my $mod ( sort keys %installed_mod_info ) { | 
| 137 | 6 |  |  |  |  | 21 | my $mi = $installed_mod_info{$mod}; | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 6 | 50 |  |  |  | 43 | module_progress_indicator($mod) unless $VERBOSE; | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | # find best match among the cpan releases that included this module | 
| 142 |  |  |  |  |  |  | my $ccdr = $installed_mod_info{$mod}{candidate_cpan_dist_releases} | 
| 143 | 6 | 50 |  |  |  | 25 | or next; # no candidates, warned about above (for mods with a version) | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 6 |  |  |  |  | 61 | my $best_dist_cache_key = join " ", sort keys %$ccdr; | 
| 146 | 6 |  |  |  |  | 15 | our %best_dist_cache; | 
| 147 | 6 |  | 66 |  |  | 38 | my $best = $best_dist_cache{$best_dist_cache_key} | 
| 148 |  |  |  |  |  |  | ||= pick_best_cpan_dist_release($ccdr, \%installed_mod_info); | 
| 149 |  |  |  |  |  |  |  | 
| 150 | 6 |  |  |  |  | 16 | my $note = ""; | 
| 151 | 6 | 0 | 33 |  |  | 22 | if ((@$best > 1) and $installed_meta->{perllocalpod}) { | 
| 152 |  |  |  |  |  |  | # try using perllocal.pod to narrow the options, if there is one | 
| 153 |  |  |  |  |  |  | # XXX TODO move this logic into the per-candidate-distro loop below | 
| 154 |  |  |  |  |  |  | # it doesn't make much sense to be here at the per-module level | 
| 155 |  |  |  |  |  |  | my @in_perllocal = grep { | 
| 156 | 0 |  |  |  |  | 0 | my $distname = $_->{distribution}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 157 |  |  |  |  |  |  | my ($v, $dist_mod_name) = perllocal_distro_mod_version( | 
| 158 | 0 |  |  |  |  | 0 | $options->{distro_key_mod_names}, $distname, $installed_meta->{perllocalpod}); | 
| 159 | 0 | 0 |  |  |  | 0 | warn "$dist_mod_name in perllocal.pod: ".($v ? "YES" : "NO")."\n" | 
|  |  | 0 |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | if $DEBUG; | 
| 161 | 0 |  |  |  |  | 0 | $v; | 
| 162 |  |  |  |  |  |  | } @$best; | 
| 163 | 0 | 0 | 0 |  |  | 0 | if (@in_perllocal && @in_perllocal < @$best) { | 
| 164 | 0 |  |  |  |  | 0 | $note = sprintf "narrowed from %d via perllocal", scalar @$best; | 
| 165 | 0 |  |  |  |  | 0 | $best = \@in_perllocal; | 
| 166 |  |  |  |  |  |  | } | 
| 167 |  |  |  |  |  |  | } | 
| 168 |  |  |  |  |  |  |  | 
| 169 | 6 | 50 | 33 |  |  | 50 | if (@$best > 1 or $note) { # note the poor match for this module | 
| 170 |  |  |  |  |  |  | # but not if there's no version (as that's common) | 
| 171 | 0 |  |  |  |  | 0 | my $best_desc = join " or ", map { $_->{release} } @$best; | 
|  | 0 |  |  |  |  | 0 |  | 
| 172 | 0 |  |  |  |  | 0 | my $pct = sprintf "%.2f%%", $best->[0]{fraction_installed} * 100; | 
| 173 |  |  |  |  |  |  | warn "$mod $mi->{version} odd best match: $best_desc $note ($best->[0]{fraction_installed})\n" | 
| 174 | 0 | 0 | 0 |  |  | 0 | if $note or $VERBOSE or ($mi->{version} and $best->[0]{fraction_installed} < 0.3); | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 175 |  |  |  |  |  |  | # if the module has no version and multiple best matches | 
| 176 |  |  |  |  |  |  | # then it's unlikely make a useful contribution, so ignore it | 
| 177 |  |  |  |  |  |  | # XXX there's a risk that we'd ignore all the modules of a release | 
| 178 |  |  |  |  |  |  | # where none of the modules has a version, but that seems unlikely. | 
| 179 | 0 | 0 |  |  |  | 0 | next if not $mi->{version}; | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 | 6 |  |  |  |  | 18 | for my $dist (@$best) { | 
| 183 |  |  |  |  |  |  | # two level hash to make it easier to handle versions | 
| 184 | 6 |  | 100 |  |  | 47 | my $di = $best_dist{ $dist->{distribution} }{ $dist->{release} } ||= { dist => $dist }; | 
| 185 | 6 |  |  |  |  | 13 | push @{ $di->{modules} }, $mi; | 
|  | 6 |  |  |  |  | 22 |  | 
| 186 | 6 |  |  |  |  | 19 | $di->{or}{$_->{release}}++ for grep { $_ != $dist } @$best; | 
|  | 6 |  |  |  |  | 40 |  | 
| 187 |  |  |  |  |  |  | } | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 | 1 |  |  |  |  | 176 | warn "*** Refining releases\n"; | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | # $best_dist{ Foo }{ Foo-1.23 }{ dist=>$dist_struct, modules=>, or=>{ Foo-1.22 => $dist_struct } } | 
| 194 |  |  |  |  |  |  |  | 
| 195 | 1 |  |  |  |  | 8 | my @installed_releases;    # Dist-Name => { ... } | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 1 |  |  |  |  | 14 | for my $distname ( sort keys %best_dist ) { | 
| 198 | 3 |  |  |  |  | 14 | my $releases = $best_dist{$distname}; | 
| 199 | 3 |  |  |  |  | 20 | push @installed_releases, refine_releases($options, $distname, $releases); | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | # sorting into dependency order could be added later, maybe | 
| 203 |  |  |  |  |  |  |  | 
| 204 | 1 |  |  |  |  | 57 | return @installed_releases; | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | sub refine_releases { | 
| 208 | 3 |  |  | 3 | 0 | 16 | my ($options, $distname, $releases) = @_; | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | my @dist_by_version  = sort { | 
| 211 | 3 |  |  |  |  | 24 | $a->{dist}{version_obj}        <=> $b->{dist}{version_obj} or | 
| 212 |  |  |  |  |  |  | $a->{dist}{fraction_installed} <=> $b->{dist}{fraction_installed} | 
| 213 | 0 | 0 |  |  |  | 0 | } values %$releases; | 
| 214 |  |  |  |  |  |  | my @dist_by_fraction = sort { | 
| 215 | 3 |  |  |  |  | 15 | $a->{dist}{fraction_installed} <=> $b->{dist}{fraction_installed} or | 
| 216 |  |  |  |  |  |  | $a->{dist}{version_obj}        <=> $b->{dist}{version_obj} | 
| 217 | 0 | 0 |  |  |  | 0 | } values %$releases; | 
| 218 |  |  |  |  |  |  |  | 
| 219 | 3 |  |  |  |  | 11 | my @remnant_dists  = @dist_by_version; | 
| 220 | 3 |  |  |  |  | 8 | my $installed_dist = pop @remnant_dists; | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | # is the most recent candidate dist version also the one with the | 
| 223 |  |  |  |  |  |  | # highest fraction_installed? | 
| 224 | 3 | 50 |  |  |  | 20 | if ($dist_by_version[-1] == $dist_by_fraction[-1]) { | 
|  |  | 0 |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | # this is the common case: we'll assume that's installed and the | 
| 226 |  |  |  |  |  |  | # rest are remnants of earlier versions | 
| 227 |  |  |  |  |  |  | } | 
| 228 |  |  |  |  |  |  | elsif ($dist_by_fraction[-1]{dist}{fraction_installed} == 100) { | 
| 229 | 0 |  |  |  |  | 0 | warn "Unsure which $distname is installed from among @{[ keys %$releases ]}\n"; | 
|  | 0 |  |  |  |  | 0 |  | 
| 230 | 0 |  |  |  |  | 0 | @remnant_dists  = @dist_by_fraction; | 
| 231 | 0 |  |  |  |  | 0 | $installed_dist = pop @remnant_dists; | 
| 232 | 0 |  |  |  |  | 0 | warn "Selecting the one that apprears to be 100% installed\n"; | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  | else { | 
| 235 |  |  |  |  |  |  | # else grumble so the user knows to ponder the possibilities | 
| 236 | 0 |  |  |  |  | 0 | warn "Can't determine which $distname is installed from among @{[ keys %$releases ]}\n"; | 
|  | 0 |  |  |  |  | 0 |  | 
| 237 | 0 |  |  |  |  | 0 | warn Dumper([\@dist_by_version, \@dist_by_fraction]); | 
| 238 | 0 |  |  |  |  | 0 | warn "\tSelecting based on latest version\n"; | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  |  | 
| 241 | 3 | 50 | 33 |  |  | 30 | if (@remnant_dists or $DEBUG) { | 
| 242 | 0 | 0 |  |  |  | 0 | warn "Distributions with remnants (chosen release is first):\n" | 
| 243 |  |  |  |  |  |  | unless our $dist_with_remnants_warning++; | 
| 244 | 0 |  |  |  |  | 0 | warn "@{[ map { $_->{dist}{release} } reverse @dist_by_fraction ]}\n"; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 245 | 0 |  |  |  |  | 0 | for ($installed_dist, @remnant_dists) { | 
| 246 | 0 |  |  |  |  | 0 | my $fi = $_->{dist}{fraction_installed}; | 
| 247 | 0 |  |  |  |  | 0 | my $modules = $_->{modules}; | 
| 248 | 0 |  |  |  |  | 0 | my $mv_desc = join(", ", map { "$_->{module} $_->{version}" } @$modules); | 
|  | 0 |  |  |  |  | 0 |  | 
| 249 |  |  |  |  |  |  | warn sprintf "\t%s\t%s%% installed: %s\n", | 
| 250 |  |  |  |  |  |  | $_->{dist}{release}, | 
| 251 |  |  |  |  |  |  | $_->{dist}{percent_installed}, | 
| 252 | 0 | 0 |  |  |  | 0 | (@$modules > 4 ? "(".@$modules." modules)" : $mv_desc), | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 | 3 |  |  |  |  | 9 | my @installed_releases; | 
| 257 |  |  |  |  |  |  | # note ordering: remnants first | 
| 258 | 3 | 50 |  |  |  | 14 | for (($options->{opt_remnants} ? @remnant_dists : ()), $installed_dist) { | 
| 259 |  |  |  |  |  |  | my ($author, $release) | 
| 260 | 3 |  |  |  |  | 8 | = @{$_->{dist}}{qw(author release)}; | 
|  | 3 |  |  |  |  | 20 |  | 
| 261 |  |  |  |  |  |  |  | 
| 262 | 3 |  |  |  |  | 132 | my $release_data = get_release_info($author, $release); | 
| 263 | 3 | 50 |  |  |  | 58 | next unless $release_data; | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | # shortcuts | 
| 266 | 3 |  |  |  |  | 41 | (my $url = $release_data->{download_url}) =~ s{ .*? \b authors/ }{authors/}x; | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | push @installed_releases, { | 
| 269 |  |  |  |  |  |  | # | 
| 270 |  |  |  |  |  |  | %$release_data, | 
| 271 |  |  |  |  |  |  | # extra items mushed inhandy shortcuts | 
| 272 |  |  |  |  |  |  | url => $url, | 
| 273 |  |  |  |  |  |  | # raw data structures | 
| 274 |  |  |  |  |  |  | dist_data => $_->{dist}, | 
| 275 | 3 |  |  |  |  | 66 | }; | 
| 276 |  |  |  |  |  |  | } | 
| 277 |  |  |  |  |  |  | #die Dumper(\@installed_releases); | 
| 278 | 3 |  |  |  |  | 18 | return @installed_releases; | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | # for each installed module, get the list of releases that it exists in | 
| 282 |  |  |  |  |  |  | # Parameters: | 
| 283 |  |  |  |  |  |  | #   $options - uses only opt_perlver | 
| 284 |  |  |  |  |  |  | #   $module - module name (i.e. 'Dist::Surveyor') | 
| 285 |  |  |  |  |  |  | #   $mod_file - the location of this module on the filesystem | 
| 286 |  |  |  |  |  |  | # Return: | 
| 287 |  |  |  |  |  |  | #   undef if this module should be skipped | 
| 288 |  |  |  |  |  |  | #   otherwise, a hashref containing: | 
| 289 |  |  |  |  |  |  | #       file => $mod_file, | 
| 290 |  |  |  |  |  |  | #       module => $module, | 
| 291 |  |  |  |  |  |  | #       version => $mod_version, | 
| 292 |  |  |  |  |  |  | #       version_obj => same as version, but as an object, | 
| 293 |  |  |  |  |  |  | #       size => $mod_file_size, | 
| 294 |  |  |  |  |  |  | #       # optional flags: | 
| 295 |  |  |  |  |  |  | #       file_size_mismatch => 1, | 
| 296 |  |  |  |  |  |  | #       cpan_dist_fallback => 1, # could not find this module/version on cpan, | 
| 297 |  |  |  |  |  |  | #           # but found a release with that version, containing such module | 
| 298 |  |  |  |  |  |  | #       version_not_on_cpan> 1, # can not find this file on CPAN. | 
| 299 |  |  |  |  |  |  | #       # releases info | 
| 300 |  |  |  |  |  |  | #       candidate_cpan_dist_releases => hashref, | 
| 301 |  |  |  |  |  |  | # | 
| 302 |  |  |  |  |  |  | #   candidate_cpan_dist_releases hashref contain a map of all the releases | 
| 303 |  |  |  |  |  |  | #   that this module exists in. see get_candidate_cpan_dist_releases for more | 
| 304 |  |  |  |  |  |  | #   info. | 
| 305 |  |  |  |  |  |  | sub get_installed_mod_info { | 
| 306 | 6 |  |  | 6 | 0 | 24 | my ($options, $module, $mod_file) = @_; | 
| 307 |  |  |  |  |  |  |  | 
| 308 | 6 |  |  |  |  | 13 | my $mod_version = do { | 
| 309 |  |  |  |  |  |  | # silence warnings about duplicate VERSION declarations | 
| 310 |  |  |  |  |  |  | # eg Catalyst::Controller::DBIC::API* 2.002001 | 
| 311 | 6 | 0 |  | 0 |  | 75 | local $SIG{__WARN__} = sub { warn @_ if $_[0] !~ /already declared with version/ }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 312 | 6 |  |  |  |  | 73 | my $mm = Module::Metadata->new_from_file($mod_file); | 
| 313 | 6 |  |  |  |  | 48603 | $mm->version; # only one version for one package in file | 
| 314 |  |  |  |  |  |  | }; | 
| 315 | 6 |  | 100 |  |  | 274 | $mod_version ||= 0; # XXX | 
| 316 | 6 |  |  |  |  | 180 | my $mod_file_size = -s $mod_file; | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | # Eliminate modules that will be supplied by the target perl version | 
| 319 | 6 | 50 |  |  |  | 85 | if ( my $cv = $Module::CoreList::version{ $options->{opt_perlver} }->{$module} ) { | 
| 320 | 0 |  |  |  |  | 0 | $cv =~ s/ //g; | 
| 321 | 0 | 0 |  |  |  | 0 | if (version->parse($cv) >= version->parse($mod_version)) { | 
| 322 | 0 |  |  |  |  | 0 | warn "$module is core in perl $options->{opt_perlver} (lib: $mod_version, core: $cv) - skipped\n"; | 
| 323 | 0 |  |  |  |  | 0 | return; | 
| 324 |  |  |  |  |  |  | } | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  |  | 
| 327 | 6 |  |  |  |  | 6458 | my $mi = { | 
| 328 |  |  |  |  |  |  | file => $mod_file, | 
| 329 |  |  |  |  |  |  | module => $module, | 
| 330 |  |  |  |  |  |  | version => $mod_version, | 
| 331 |  |  |  |  |  |  | version_obj => version->parse($mod_version), | 
| 332 |  |  |  |  |  |  | size => $mod_file_size, | 
| 333 |  |  |  |  |  |  | }; | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | # ignore modules we know aren't indexed | 
| 336 | 6 | 50 |  |  |  | 26 | return $mi if $module =~ /^Moose::Meta::Method::Accessor::Native::/; | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | # XXX could also consider file mtime: releases newer than the mtime | 
| 339 |  |  |  |  |  |  | # of the module file can't be the origin of that module file. | 
| 340 |  |  |  |  |  |  | # (assuming clocks and file times haven't been messed with) | 
| 341 |  |  |  |  |  |  |  | 
| 342 | 6 |  |  |  |  | 17 | eval { | 
| 343 | 6 |  |  |  |  | 200 | my $ccdr = get_candidate_cpan_dist_releases($module, $mod_version, $mod_file_size); | 
| 344 | 6 | 50 |  |  |  | 125 | if (not %$ccdr) { | 
| 345 | 0 |  |  |  |  | 0 | $ccdr = get_candidate_cpan_dist_releases($module, $mod_version, 0); | 
| 346 | 0 | 0 | 0 |  |  | 0 | if (%$ccdr) { | 
|  |  | 0 |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | # probably either a local change/patch or installed direct from repo | 
| 348 |  |  |  |  |  |  | # but with a version number that matches a release | 
| 349 | 0 | 0 | 0 |  |  | 0 | warn "$module $mod_version on CPAN but with different file size (not $mod_file_size)\n" | 
| 350 |  |  |  |  |  |  | if $mod_version or $VERBOSE; | 
| 351 | 0 |  |  |  |  | 0 | $mi->{file_size_mismatch}++; | 
| 352 |  |  |  |  |  |  | } | 
| 353 |  |  |  |  |  |  | elsif ($ccdr = get_candidate_cpan_dist_releases_fallback($module, $mod_version) and %$ccdr) { | 
| 354 | 0 | 0 | 0 |  |  | 0 | warn "$module $mod_version not on CPAN but assumed to be from @{[ sort keys %$ccdr ]}\n" | 
|  | 0 |  |  |  |  | 0 |  | 
| 355 |  |  |  |  |  |  | if $mod_version or $VERBOSE; | 
| 356 | 0 |  |  |  |  | 0 | $mi->{cpan_dist_fallback}++; | 
| 357 |  |  |  |  |  |  | } | 
| 358 |  |  |  |  |  |  | else { | 
| 359 | 0 |  |  |  |  | 0 | $mi->{version_not_on_cpan}++; | 
| 360 |  |  |  |  |  |  | # Possibly: | 
| 361 |  |  |  |  |  |  | # - a local change/patch or installed direct from repo | 
| 362 |  |  |  |  |  |  | #   with a version number that was never released. | 
| 363 |  |  |  |  |  |  | # - a private module never released on cpan. | 
| 364 |  |  |  |  |  |  | # - a build-time create module eg common/sense.pm.PL | 
| 365 |  |  |  |  |  |  | warn "$module $mod_version not found on CPAN\n" | 
| 366 |  |  |  |  |  |  | if $mi->{version} # no version implies uninteresting | 
| 367 | 0 | 0 | 0 |  |  | 0 | or $VERBOSE; | 
| 368 |  |  |  |  |  |  | # XXX could try finding the module with *any* version on cpan | 
| 369 |  |  |  |  |  |  | # to help with later advice. ie could select as candidates | 
| 370 |  |  |  |  |  |  | # the version above and the version below the number we have, | 
| 371 |  |  |  |  |  |  | # and set a flag to inform later logic. | 
| 372 |  |  |  |  |  |  | } | 
| 373 |  |  |  |  |  |  | } | 
| 374 | 6 | 50 |  |  |  | 40 | $mi->{candidate_cpan_dist_releases} = $ccdr if %$ccdr; | 
| 375 |  |  |  |  |  |  | }; | 
| 376 | 6 | 50 |  |  |  | 22 | if ($@) { | 
| 377 | 0 |  |  |  |  | 0 | warn "Failed get_candidate_cpan_dist_releases($module, $mod_version, $mod_file_size): $@"; | 
| 378 |  |  |  |  |  |  | } | 
| 379 | 6 |  |  |  |  | 28 | return $mi; | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | # pick_best_cpan_dist_release - memoized | 
| 383 |  |  |  |  |  |  | # for each %$ccdr adds a fraction_installed based on %$installed_mod_info | 
| 384 |  |  |  |  |  |  | # returns ref to array of %$ccdr values that have the max fraction_installed | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | sub pick_best_cpan_dist_release { | 
| 387 | 5 |  |  | 5 | 0 | 15 | my ($ccdr, $installed_mod_info) = @_; | 
| 388 |  |  |  |  |  |  |  | 
| 389 | 5 |  |  |  |  | 24 | for my $release (sort keys %$ccdr) { | 
| 390 | 48 |  |  |  |  | 178 | my $release_info = $ccdr->{$release}; | 
| 391 |  |  |  |  |  |  | $release_info->{fraction_installed} | 
| 392 | 48 |  |  |  |  | 212 | = dist_fraction_installed($release_info->{author}, $release, $installed_mod_info); | 
| 393 |  |  |  |  |  |  | $release_info->{percent_installed} # for informal use | 
| 394 | 48 |  |  |  |  | 971 | = sprintf "%.2f", $release_info->{fraction_installed} * 100; | 
| 395 |  |  |  |  |  |  | } | 
| 396 |  |  |  |  |  |  |  | 
| 397 | 5 |  |  |  |  | 33 | my $max_fraction_installed = max( map { $_->{fraction_installed} } values %$ccdr ); | 
|  | 48 |  |  |  |  | 112 |  | 
| 398 | 5 |  |  |  |  | 16 | my @best = grep { $_->{fraction_installed} == $max_fraction_installed } values %$ccdr; | 
|  | 48 |  |  |  |  | 91 |  | 
| 399 |  |  |  |  |  |  |  | 
| 400 | 5 |  |  |  |  | 30 | return \@best; | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | # returns a number from 0 to 1 representing the fraction of the modules | 
| 405 |  |  |  |  |  |  | # in a particular release match the coresponding modules in %$installed_mod_info | 
| 406 |  |  |  |  |  |  | sub dist_fraction_installed { | 
| 407 | 48 |  |  | 48 | 0 | 181 | my ($author, $release, $installed_mod_info) = @_; | 
| 408 |  |  |  |  |  |  |  | 
| 409 | 48 |  |  |  |  | 182 | my $tag = "$author/$release"; | 
| 410 | 48 |  |  |  |  | 1970 | my $mods_in_rel = get_module_versions_in_release($author, $release); | 
| 411 | 48 |  |  |  |  | 951 | my $mods_in_rel_count = keys %$mods_in_rel; | 
| 412 |  |  |  |  |  |  | my $mods_inst_count = sum( map { | 
| 413 | 48 |  | 50 |  |  | 288 | my $mi = $installed_mod_info->{ $_->{name} }; | 
| 414 |  |  |  |  |  |  | # XXX we stash the version_obj into the mods_in_rel hash | 
| 415 |  |  |  |  |  |  | # (though with little/no caching effect with current setup) | 
| 416 |  |  |  |  |  |  | $_->{version_obj} ||= eval { version->parse($_->{version}) }; | 
| 417 |  |  |  |  |  |  | my $hit = ($mi && $mi->{version_obj} == $_->{version_obj}) ? 1 : 0; | 
| 418 |  |  |  |  |  |  | # demote to a low-scoring partial match if the file size differs | 
| 419 |  |  |  |  |  |  | # XXX this isn't good as the effect varies with the number of modules | 
| 420 |  |  |  |  |  |  | $hit = 0.1 if $mi && $mi->{size} != $_->{size}; | 
| 421 |  |  |  |  |  |  | warn sprintf "%s %s %s %s: %s\n", $tag, $_->{name}, $_->{version_obj}, $_->{size}, | 
| 422 |  |  |  |  |  |  | ($hit == 1) ? "matches" | 
| 423 |  |  |  |  |  |  | : ($mi) ? "differs ($mi->{version_obj}, $mi->{size})" | 
| 424 |  |  |  |  |  |  | : "not installed", | 
| 425 |  |  |  |  |  |  | if $DEBUG; | 
| 426 |  |  |  |  |  |  | $hit; | 
| 427 |  |  |  |  |  |  | } values %$mods_in_rel) || 0; | 
| 428 |  |  |  |  |  |  |  | 
| 429 | 48 | 50 |  |  |  | 288 | my $fraction_installed = ($mods_in_rel_count) ? $mods_inst_count/$mods_in_rel_count : 0; | 
| 430 | 48 | 50 | 33 |  |  | 304 | warn "$author/$release:\tfraction_installed $fraction_installed ($mods_inst_count/$mods_in_rel_count)\n" | 
| 431 |  |  |  |  |  |  | if $VERBOSE or !$mods_in_rel_count; | 
| 432 |  |  |  |  |  |  |  | 
| 433 | 48 |  |  |  |  | 288 | return $fraction_installed; | 
| 434 |  |  |  |  |  |  | } | 
| 435 |  |  |  |  |  |  |  | 
| 436 |  |  |  |  |  |  | sub get_file_mtime { | 
| 437 | 0 |  |  | 0 | 0 | 0 | my ($file) = @_; | 
| 438 |  |  |  |  |  |  | # try to find the time the file was 'installed' | 
| 439 |  |  |  |  |  |  | # by looking for the commit date in svn or git | 
| 440 |  |  |  |  |  |  | # else fallback to the file modification time | 
| 441 | 0 |  |  |  |  | 0 | return (stat($file))[9]; | 
| 442 |  |  |  |  |  |  | } | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  | sub find_installed_modules { | 
| 446 | 1 |  |  | 1 | 0 | 4 | my (@dirs) = @_; | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | ### File::Find uses follow_skip => 1 by default, which doesn't die | 
| 449 |  |  |  |  |  |  | ### on duplicates, unless they are directories or symlinks. | 
| 450 |  |  |  |  |  |  | ### Ticket #29796 shows this code dying on Alien::WxWidgets, | 
| 451 |  |  |  |  |  |  | ### which uses symlinks. | 
| 452 |  |  |  |  |  |  | ### File::Find doc says to use follow_skip => 2 to ignore duplicates | 
| 453 |  |  |  |  |  |  | ### so this will stop it from dying. | 
| 454 | 1 |  |  |  |  | 5 | my %find_args = ( follow_skip => 2 ); | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | ### File::Find uses lstat, which quietly becomes stat on win32 | 
| 457 |  |  |  |  |  |  | ### it then uses -l _ which is not allowed by the statbuffer because | 
| 458 |  |  |  |  |  |  | ### you did a stat, not an lstat (duh!). so don't tell win32 to | 
| 459 |  |  |  |  |  |  | ### follow symlinks, as that will break badly | 
| 460 |  |  |  |  |  |  | # XXX disabled because we want the postprocess hook to work | 
| 461 |  |  |  |  |  |  | #$find_args{'follow_fast'} = 1 unless ON_WIN32; | 
| 462 |  |  |  |  |  |  |  | 
| 463 |  |  |  |  |  |  | ### never use the @INC hooks to find installed versions of | 
| 464 |  |  |  |  |  |  | ### modules -- they're just there in case they're not on the | 
| 465 |  |  |  |  |  |  | ### perl install, but the user shouldn't trust them for *other* | 
| 466 |  |  |  |  |  |  | ### modules! | 
| 467 |  |  |  |  |  |  | ### XXX CPANPLUS::inc is now obsolete, remove the calls | 
| 468 |  |  |  |  |  |  | #local @INC = CPANPLUS::inc->original_inc; | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | # sort @dirs to put longest first to make it easy to handle | 
| 471 |  |  |  |  |  |  | # elements that are within other elements (e.g., an archdir) | 
| 472 | 1 |  |  |  |  | 5 | my @dirs_ordered = sort { length $b <=> length $a } @dirs; | 
|  | 0 |  |  |  |  | 0 |  | 
| 473 |  |  |  |  |  |  |  | 
| 474 | 1 |  |  |  |  | 4 | my %seen_mod; | 
| 475 |  |  |  |  |  |  | my %dir_done; | 
| 476 | 1 |  |  |  |  | 0 | my %meta; # return metadata about the search | 
| 477 | 1 |  |  |  |  | 4 | for my $dir (@dirs_ordered) { | 
| 478 | 1 | 50 |  |  |  | 5 | next if $dir eq '.'; | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | ### not a directory after all | 
| 481 |  |  |  |  |  |  | ### may be coderef or some such | 
| 482 | 1 | 50 |  |  |  | 17 | next unless -d $dir; | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | ### make sure to clean up the directories just in case, | 
| 485 |  |  |  |  |  |  | ### as we're making assumptions about the length | 
| 486 |  |  |  |  |  |  | ### This solves rt.cpan issue #19738 | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | ### John M. notes: On VMS cannonpath can not currently handle | 
| 489 |  |  |  |  |  |  | ### the $dir values that are in UNIX format. | 
| 490 | 1 |  |  |  |  | 7 | $dir = File::Spec->canonpath($dir) unless ON_VMS; | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | ### have to use F::S::Unix on VMS, or things will break | 
| 493 | 1 |  |  |  |  | 3 | my $file_spec = ON_VMS ? 'File::Spec::Unix' : 'File::Spec'; | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | ### XXX in some cases File::Find can actually die! | 
| 496 |  |  |  |  |  |  | ### so be safe and wrap it in an eval. | 
| 497 | 1 | 50 |  |  |  | 3 | eval { | 
| 498 |  |  |  |  |  |  | File::Find::find( | 
| 499 |  |  |  |  |  |  | {   %find_args, | 
| 500 |  |  |  |  |  |  | postprocess => sub { | 
| 501 | 6 |  |  | 6 |  | 65 | $dir_done{$File::Find::dir}++; | 
| 502 |  |  |  |  |  |  | }, | 
| 503 |  |  |  |  |  |  | wanted => sub { | 
| 504 |  |  |  |  |  |  |  | 
| 505 | 12 | 100 |  | 12 |  | 51 | unless (/\.pm$/i) { | 
| 506 |  |  |  |  |  |  | # skip all dot-dirs (eg .git .svn) | 
| 507 | 6 | 50 | 33 |  |  | 89 | $File::Find::prune = 1 | 
| 508 |  |  |  |  |  |  | if -d $File::Find::name and /^\.\w/; | 
| 509 |  |  |  |  |  |  | # don't reenter a dir we've already done | 
| 510 |  |  |  |  |  |  | $File::Find::prune = 1 | 
| 511 | 6 | 50 |  |  |  | 16 | if $dir_done{$File::Find::name}; | 
| 512 |  |  |  |  |  |  | # remember perllocal.pod if we see it | 
| 513 | 6 | 50 |  |  |  | 14 | push @{$meta{perllocalpod}}, $File::Find::name | 
|  | 0 |  |  |  |  | 0 |  | 
| 514 |  |  |  |  |  |  | if $_ eq 'perllocal.pod'; | 
| 515 | 6 |  |  |  |  | 310 | return; | 
| 516 |  |  |  |  |  |  | } | 
| 517 | 6 |  |  |  |  | 12 | my $mod = $File::Find::name; | 
| 518 |  |  |  |  |  |  |  | 
| 519 |  |  |  |  |  |  | ### make sure it's in Unix format, as it | 
| 520 |  |  |  |  |  |  | ### may be in VMS format on VMS; | 
| 521 | 6 |  |  |  |  | 8 | $mod = VMS::Filespec::unixify($mod) if ON_VMS; | 
| 522 |  |  |  |  |  |  |  | 
| 523 | 6 |  |  |  |  | 16 | $mod = substr( $mod, length($dir) + 1, -3 ); | 
| 524 | 6 |  |  |  |  | 29 | $mod = join '::', $file_spec->splitdir($mod); | 
| 525 |  |  |  |  |  |  |  | 
| 526 | 6 | 50 |  |  |  | 17 | return if $seen_mod{$mod}; | 
| 527 | 6 |  |  |  |  | 64 | $seen_mod{$mod} = $File::Find::name; | 
| 528 |  |  |  |  |  |  |  | 
| 529 |  |  |  |  |  |  | ### ignore files that don't contain a matching package declaration | 
| 530 |  |  |  |  |  |  | ### warn about those that do contain some kind of package declaration | 
| 531 |  |  |  |  |  |  | #use File::Slurp; | 
| 532 |  |  |  |  |  |  | #my $content = read_file($File::Find::name); | 
| 533 |  |  |  |  |  |  | #unless ( $content =~ m/^ \s* package \s+ (\#.*\n\s*)? $mod \b/xm ) { | 
| 534 |  |  |  |  |  |  | #warn "No 'package $mod' seen in $File::Find::name\n" | 
| 535 |  |  |  |  |  |  | #if $VERBOSE && $content =~ /\b package \b/x; | 
| 536 |  |  |  |  |  |  | #return; | 
| 537 |  |  |  |  |  |  | #} | 
| 538 |  |  |  |  |  |  |  | 
| 539 |  |  |  |  |  |  | }, | 
| 540 |  |  |  |  |  |  | }, | 
| 541 | 1 |  |  |  |  | 116 | $dir | 
| 542 |  |  |  |  |  |  | ); | 
| 543 | 1 |  |  |  |  | 11 | 1; | 
| 544 |  |  |  |  |  |  | } | 
| 545 |  |  |  |  |  |  | or die "File::Find died: $@"; | 
| 546 |  |  |  |  |  |  |  | 
| 547 |  |  |  |  |  |  | } | 
| 548 |  |  |  |  |  |  |  | 
| 549 | 1 |  |  |  |  | 8 | return (\%seen_mod, \%meta); | 
| 550 |  |  |  |  |  |  | } | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  |  | 
| 553 |  |  |  |  |  |  | sub perllocal_distro_mod_version { | 
| 554 | 0 |  |  | 0 | 0 | 0 | my ($distro_key_mod_names, $distname, $perllocalpod) = @_; | 
| 555 |  |  |  |  |  |  |  | 
| 556 | 0 |  |  |  |  | 0 | ( my $dist_mod_name = $distname ) =~ s/-/::/g; | 
| 557 | 0 |  | 0 |  |  | 0 | my $key_mod_name = $distro_key_mod_names->{$distname} || $dist_mod_name; | 
| 558 |  |  |  |  |  |  |  | 
| 559 | 0 |  |  |  |  | 0 | our $perllocal_distro_mod_version; | 
| 560 | 0 | 0 |  |  |  | 0 | if (not $perllocal_distro_mod_version) { # initial setup | 
| 561 | 0 | 0 | 0 |  |  | 0 | warn "Only first perllocal.pod file will be processed: @$perllocalpod\n" | 
| 562 |  |  |  |  |  |  | if ref $perllocalpod eq 'ARRAY' and @$perllocalpod > 1; | 
| 563 |  |  |  |  |  |  |  | 
| 564 | 0 |  |  |  |  | 0 | $perllocal_distro_mod_version = {}; | 
| 565 |  |  |  |  |  |  | # extract data from perllocal.pod | 
| 566 | 0 | 0 |  |  |  | 0 | if (my $plp = shift @$perllocalpod) { | 
| 567 |  |  |  |  |  |  | # The VERSION isn't always the same as that in the distro file | 
| 568 | 0 | 0 |  |  |  | 0 | if (eval { require ExtUtils::Perllocal::Parser }) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 569 | 0 |  |  |  |  | 0 | my $p = ExtUtils::Perllocal::Parser->new; | 
| 570 |  |  |  |  |  |  | $perllocal_distro_mod_version = { map { | 
| 571 | 0 |  |  |  |  | 0 | $_->name => $_->{data}{VERSION} | 
| 572 | 0 |  |  |  |  | 0 | } $p->parse_from_file($plp) }; | 
| 573 | 0 |  |  |  |  | 0 | warn "Details of ".keys(%$perllocal_distro_mod_version)." distributions found in $plp\n"; | 
| 574 |  |  |  |  |  |  | } | 
| 575 |  |  |  |  |  |  | else { | 
| 576 | 0 |  |  |  |  | 0 | warn "Wanted to use perllocal.pod but can't because ExtUtils::Perllocal::Parser isn't available\n"; | 
| 577 |  |  |  |  |  |  | } | 
| 578 |  |  |  |  |  |  | } | 
| 579 |  |  |  |  |  |  | else { | 
| 580 | 0 |  |  |  |  | 0 | warn "No perllocal.pod found to aid disambiguation\n"; | 
| 581 |  |  |  |  |  |  | } | 
| 582 |  |  |  |  |  |  | } | 
| 583 |  |  |  |  |  |  |  | 
| 584 | 0 |  |  |  |  | 0 | return $perllocal_distro_mod_version->{$key_mod_name}; | 
| 585 |  |  |  |  |  |  | } | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  |  | 
| 588 |  |  |  |  |  |  | sub module_progress_indicator { | 
| 589 | 12 |  |  | 12 | 0 | 45 | my ($module) = @_; | 
| 590 | 12 |  |  |  |  | 83 | my $crnt = (split /::/, $module)[0]; | 
| 591 | 12 |  | 100 |  |  | 62 | our $last ||= ''; | 
| 592 | 12 | 100 |  |  |  | 58 | if ($last ne $crnt) { | 
| 593 | 4 |  |  |  |  | 289 | warn "\t$crnt...\n"; | 
| 594 | 4 |  |  |  |  | 24 | $last = $crnt; | 
| 595 |  |  |  |  |  |  | } | 
| 596 |  |  |  |  |  |  | } | 
| 597 |  |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  | =head1 OTHERS | 
| 599 |  |  |  |  |  |  |  | 
| 600 |  |  |  |  |  |  | This module checks $::DEBUG and $::VERBOSE for obvious proposes. | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | This module uses L to communicate with MetaCPAN. | 
| 603 |  |  |  |  |  |  | Check that module's documentation for options and caching. | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | You can use L to take the list of releases | 
| 606 |  |  |  |  |  |  | and create a mini-cpan containing them. | 
| 607 |  |  |  |  |  |  |  | 
| 608 |  |  |  |  |  |  | =head1 AUTHOR | 
| 609 |  |  |  |  |  |  |  | 
| 610 |  |  |  |  |  |  | Written by Tim Bunce ETim.Bunce@pobox.comE | 
| 611 |  |  |  |  |  |  |  | 
| 612 |  |  |  |  |  |  | Maintained by Fomberg Shmuel Eshmuelfomberg@gmail.comE, Dan Book Edbook@cpan.orgE | 
| 613 |  |  |  |  |  |  |  | 
| 614 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 615 |  |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  | Copyright 2011-2013 by Tim Bunce. | 
| 617 |  |  |  |  |  |  |  | 
| 618 |  |  |  |  |  |  | This library is free software; you can redistribute it and/or modify | 
| 619 |  |  |  |  |  |  | it under the same terms as Perl itself. | 
| 620 |  |  |  |  |  |  |  | 
| 621 |  |  |  |  |  |  | =cut | 
| 622 |  |  |  |  |  |  |  | 
| 623 |  |  |  |  |  |  | 1; |