1b39c5158Smillertpackage App::Cpan; 291f110e0Safresh1 3b39c5158Smillertuse strict; 4b39c5158Smillertuse warnings; 5b39c5158Smillertuse vars qw($VERSION); 6b39c5158Smillert 7b8851fccSafresh1use if $] < 5.008 => 'IO::Scalar'; 86fb12b70Safresh1 9*eac174f2Safresh1$VERSION = '1.678'; 10b39c5158Smillert 11b39c5158Smillert=head1 NAME 12b39c5158Smillert 13b39c5158SmillertApp::Cpan - easily interact with CPAN from the command line 14b39c5158Smillert 15b39c5158Smillert=head1 SYNOPSIS 16b39c5158Smillert 17b39c5158Smillert # with arguments and no switches, installs specified modules 18b39c5158Smillert cpan module_name [ module_name ... ] 19b39c5158Smillert 20b39c5158Smillert # with switches, installs modules with extra behavior 2191f110e0Safresh1 cpan [-cfFimtTw] module_name [ module_name ... ] 22b39c5158Smillert 23b39c5158Smillert # use local::lib 2491f110e0Safresh1 cpan -I module_name [ module_name ... ] 25b39c5158Smillert 26b8851fccSafresh1 # one time mirror override for faster mirrors 27b8851fccSafresh1 cpan -p ... 28b8851fccSafresh1 29b39c5158Smillert # with just the dot, install from the distribution in the 30b39c5158Smillert # current directory 31b39c5158Smillert cpan . 32b39c5158Smillert 33b39c5158Smillert # without arguments, starts CPAN.pm shell 34b39c5158Smillert cpan 35b39c5158Smillert 36b39c5158Smillert # without arguments, but some switches 379f11ffb7Safresh1 cpan [-ahpruvACDLOPX] 38b39c5158Smillert 39b39c5158Smillert=head1 DESCRIPTION 40b39c5158Smillert 41b39c5158SmillertThis script provides a command interface (not a shell) to CPAN. At the 42b39c5158Smillertmoment it uses CPAN.pm to do the work, but it is not a one-shot command 43b39c5158Smillertrunner for CPAN.pm. 44b39c5158Smillert 45b39c5158Smillert=head2 Options 46b39c5158Smillert 47b39c5158Smillert=over 4 48b39c5158Smillert 49b39c5158Smillert=item -a 50b39c5158Smillert 51b39c5158SmillertCreates a CPAN.pm autobundle with CPAN::Shell->autobundle. 52b39c5158Smillert 53b39c5158Smillert=item -A module [ module ... ] 54b39c5158Smillert 55b39c5158SmillertShows the primary maintainers for the specified modules. 56b39c5158Smillert 57b39c5158Smillert=item -c module 58b39c5158Smillert 59b39c5158SmillertRuns a `make clean` in the specified module's directories. 60b39c5158Smillert 61b39c5158Smillert=item -C module [ module ... ] 62b39c5158Smillert 63b39c5158SmillertShow the F<Changes> files for the specified modules 64b39c5158Smillert 65b39c5158Smillert=item -D module [ module ... ] 66b39c5158Smillert 67b39c5158SmillertShow the module details. This prints one line for each out-of-date module 68b39c5158Smillert(meaning, modules locally installed but have newer versions on CPAN). 69b39c5158SmillertEach line has three columns: module name, local version, and CPAN 70b39c5158Smillertversion. 71b39c5158Smillert 72b39c5158Smillert=item -f 73b39c5158Smillert 74b39c5158SmillertForce the specified action, when it normally would have failed. Use this 75b39c5158Smillertto install a module even if its tests fail. When you use this option, 76b39c5158Smillert-i is not optional for installing a module when you need to force it: 77b39c5158Smillert 78b39c5158Smillert % cpan -f -i Module::Foo 79b39c5158Smillert 80b39c5158Smillert=item -F 81b39c5158Smillert 82b39c5158SmillertTurn off CPAN.pm's attempts to lock anything. You should be careful with 83b39c5158Smillertthis since you might end up with multiple scripts trying to muck in the 84b39c5158Smillertsame directory. This isn't so much of a concern if you're loading a special 85b39c5158Smillertconfig with C<-j>, and that config sets up its own work directories. 86b39c5158Smillert 87b39c5158Smillert=item -g module [ module ... ] 88b39c5158Smillert 89b39c5158SmillertDownloads to the current directory the latest distribution of the module. 90b39c5158Smillert 91b39c5158Smillert=item -G module [ module ... ] 92b39c5158Smillert 93b39c5158SmillertUNIMPLEMENTED 94b39c5158Smillert 95b39c5158SmillertDownload to the current directory the latest distribution of the 96b39c5158Smillertmodules, unpack each distribution, and create a git repository for each 97b39c5158Smillertdistribution. 98b39c5158Smillert 99b39c5158SmillertIf you want this feature, check out Yanick Champoux's C<Git::CPAN::Patch> 100b39c5158Smillertdistribution. 101b39c5158Smillert 102b39c5158Smillert=item -h 103b39c5158Smillert 104b39c5158SmillertPrint a help message and exit. When you specify C<-h>, it ignores all 105b39c5158Smillertof the other options and arguments. 106b39c5158Smillert 1079f11ffb7Safresh1=item -i module [ module ... ] 108b39c5158Smillert 10991f110e0Safresh1Install the specified modules. With no other switches, this switch 11091f110e0Safresh1is implied. 11191f110e0Safresh1 11291f110e0Safresh1=item -I 11391f110e0Safresh1 11491f110e0Safresh1Load C<local::lib> (think like C<-I> for loading lib paths). Too bad 11591f110e0Safresh1C<-l> was already taken. 116b39c5158Smillert 117b39c5158Smillert=item -j Config.pm 118b39c5158Smillert 119b39c5158SmillertLoad the file that has the CPAN configuration data. This should have the 120b39c5158Smillertsame format as the standard F<CPAN/Config.pm> file, which defines 121b39c5158SmillertC<$CPAN::Config> as an anonymous hash. 122b39c5158Smillert 123b46d8ef2Safresh1If the file does not exist, C<cpan> dies. 124b46d8ef2Safresh1 125b39c5158Smillert=item -J 126b39c5158Smillert 127b39c5158SmillertDump the configuration in the same format that CPAN.pm uses. This is useful 128b39c5158Smillertfor checking the configuration as well as using the dump as a starting point 129b39c5158Smillertfor a new, custom configuration. 130b39c5158Smillert 131b39c5158Smillert=item -l 132b39c5158Smillert 1336fb12b70Safresh1List all installed modules with their versions 134b39c5158Smillert 135b39c5158Smillert=item -L author [ author ... ] 136b39c5158Smillert 137b39c5158SmillertList the modules by the specified authors. 138b39c5158Smillert 139b39c5158Smillert=item -m 140b39c5158Smillert 141b39c5158SmillertMake the specified modules. 142b39c5158Smillert 143b8851fccSafresh1=item -M mirror1,mirror2,... 144b8851fccSafresh1 145b8851fccSafresh1A comma-separated list of mirrors to use for just this run. The C<-P> 146b8851fccSafresh1option can find them for you automatically. 147b8851fccSafresh1 14891f110e0Safresh1=item -n 14991f110e0Safresh1 15091f110e0Safresh1Do a dry run, but don't actually install anything. (unimplemented) 15191f110e0Safresh1 152b39c5158Smillert=item -O 153b39c5158Smillert 154b39c5158SmillertShow the out-of-date modules. 155b39c5158Smillert 15691f110e0Safresh1=item -p 15791f110e0Safresh1 158b8851fccSafresh1Ping the configured mirrors and print a report 15991f110e0Safresh1 16091f110e0Safresh1=item -P 16191f110e0Safresh1 162b8851fccSafresh1Find the best mirrors you could be using and use them for the current 163b8851fccSafresh1session. 16491f110e0Safresh1 16591f110e0Safresh1=item -r 16691f110e0Safresh1 16791f110e0Safresh1Recompiles dynamically loaded modules with CPAN::Shell->recompile. 16891f110e0Safresh1 1699f11ffb7Safresh1=item -s 1709f11ffb7Safresh1 1719f11ffb7Safresh1Drop in the CPAN.pm shell. This command does this automatically if you don't 1729f11ffb7Safresh1specify any arguments. 1739f11ffb7Safresh1 1749f11ffb7Safresh1=item -t module [ module ... ] 175b39c5158Smillert 176b39c5158SmillertRun a `make test` on the specified modules. 177b39c5158Smillert 17891f110e0Safresh1=item -T 179b39c5158Smillert 18091f110e0Safresh1Do not test modules. Simply install them. 181b39c5158Smillert 182b39c5158Smillert=item -u 183b39c5158Smillert 184b39c5158SmillertUpgrade all installed modules. Blindly doing this can really break things, 185b39c5158Smillertso keep a backup. 186b39c5158Smillert 187b39c5158Smillert=item -v 188b39c5158Smillert 189b39c5158SmillertPrint the script version and CPAN.pm version then exit. 190b39c5158Smillert 19191f110e0Safresh1=item -V 19291f110e0Safresh1 19391f110e0Safresh1Print detailed information about the cpan client. 19491f110e0Safresh1 19591f110e0Safresh1=item -w 19691f110e0Safresh1 19791f110e0Safresh1UNIMPLEMENTED 19891f110e0Safresh1 19991f110e0Safresh1Turn on cpan warnings. This checks various things, like directory permissions, 20091f110e0Safresh1and tells you about problems you might have. 20191f110e0Safresh1 2029f11ffb7Safresh1=item -x module [ module ... ] 2039f11ffb7Safresh1 2049f11ffb7Safresh1Find close matches to the named modules that you think you might have 2059f11ffb7Safresh1mistyped. This requires the optional installation of Text::Levenshtein or 2069f11ffb7Safresh1Text::Levenshtein::Damerau. 2079f11ffb7Safresh1 2089f11ffb7Safresh1=item -X 2099f11ffb7Safresh1 2109f11ffb7Safresh1Dump all the namespaces to standard output. 2119f11ffb7Safresh1 212b39c5158Smillert=back 213b39c5158Smillert 214b39c5158Smillert=head2 Examples 215b39c5158Smillert 216b39c5158Smillert # print a help message 217b39c5158Smillert cpan -h 218b39c5158Smillert 219b39c5158Smillert # print the version numbers 220b39c5158Smillert cpan -v 221b39c5158Smillert 222b39c5158Smillert # create an autobundle 223b39c5158Smillert cpan -a 224b39c5158Smillert 225b39c5158Smillert # recompile modules 226b39c5158Smillert cpan -r 227b39c5158Smillert 228b39c5158Smillert # upgrade all installed modules 229b39c5158Smillert cpan -u 230b39c5158Smillert 231b39c5158Smillert # install modules ( sole -i is optional ) 232b39c5158Smillert cpan -i Netscape::Booksmarks Business::ISBN 233b39c5158Smillert 234b39c5158Smillert # force install modules ( must use -i ) 235b39c5158Smillert cpan -fi CGI::Minimal URI 236b39c5158Smillert 237b8851fccSafresh1 # install modules but without testing them 238b8851fccSafresh1 cpan -Ti CGI::Minimal URI 239b8851fccSafresh1 240b8851fccSafresh1=head2 Environment variables 241b8851fccSafresh1 242b8851fccSafresh1There are several components in CPAN.pm that use environment variables. 243b8851fccSafresh1The build tools, L<ExtUtils::MakeMaker> and L<Module::Build> use some, 244b8851fccSafresh1while others matter to the levels above them. Some of these are specified 245b8851fccSafresh1by the Perl Toolchain Gang: 246b8851fccSafresh1 247*eac174f2Safresh1Lancaster Consensus: L<https://github.com/Perl-Toolchain-Gang/toolchain-site/blob/master/lancaster-consensus.md> 248b8851fccSafresh1 249*eac174f2Safresh1Oslo Consensus: L<https://github.com/Perl-Toolchain-Gang/toolchain-site/blob/master/oslo-consensus.md> 250b8851fccSafresh1 251b8851fccSafresh1=over 4 252b8851fccSafresh1 253b8851fccSafresh1=item NONINTERACTIVE_TESTING 254b8851fccSafresh1 255b8851fccSafresh1Assume no one is paying attention and skips prompts for distributions 256b8851fccSafresh1that do that correctly. C<cpan(1)> sets this to C<1> unless it already 257b8851fccSafresh1has a value (even if that value is false). 258b8851fccSafresh1 259b8851fccSafresh1=item PERL_MM_USE_DEFAULT 260b8851fccSafresh1 261b8851fccSafresh1Use the default answer for a prompted questions. C<cpan(1)> sets this 262b8851fccSafresh1to C<1> unless it already has a value (even if that value is false). 263b8851fccSafresh1 264b8851fccSafresh1=item CPAN_OPTS 265b8851fccSafresh1 266b46d8ef2Safresh1As with C<PERL5OPT>, a string of additional C<cpan(1)> options to 267b8851fccSafresh1add to those you specify on the command line. 268b8851fccSafresh1 269b8851fccSafresh1=item CPANSCRIPT_LOGLEVEL 270b8851fccSafresh1 271b8851fccSafresh1The log level to use, with either the embedded, minimal logger or 272b8851fccSafresh1L<Log::Log4perl> if it is installed. Possible values are the same as 273b8851fccSafresh1the C<Log::Log4perl> levels: C<TRACE>, C<DEBUG>, C<INFO>, C<WARN>, 274b8851fccSafresh1C<ERROR>, and C<FATAL>. The default is C<INFO>. 275b8851fccSafresh1 276b8851fccSafresh1=item GIT_COMMAND 277b8851fccSafresh1 278b8851fccSafresh1The path to the C<git> binary to use for the Git features. The default 279b8851fccSafresh1is C</usr/local/bin/git>. 280b8851fccSafresh1 281b8851fccSafresh1=back 282b39c5158Smillert 283b39c5158Smillert=head2 Methods 284b39c5158Smillert 285b39c5158Smillert=over 4 286b39c5158Smillert 287b39c5158Smillert=cut 288b39c5158Smillert 289b39c5158Smillertuse autouse Carp => qw(carp croak cluck); 290b8851fccSafresh1use CPAN 1.80 (); # needs no test 29191f110e0Safresh1use Config; 292b39c5158Smillertuse autouse Cwd => qw(cwd); 293b39c5158Smillertuse autouse 'Data::Dumper' => qw(Dumper); 294b46d8ef2Safresh1use File::Spec::Functions qw(catfile file_name_is_absolute rel2abs); 295b39c5158Smillertuse File::Basename; 296b39c5158Smillertuse Getopt::Std; 297b39c5158Smillert 298b39c5158Smillert# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 299b39c5158Smillert# Internal constants 300b39c5158Smillertuse constant TRUE => 1; 301b39c5158Smillertuse constant FALSE => 0; 302b39c5158Smillert 303b39c5158Smillert 304b39c5158Smillert# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 305b39c5158Smillert# The return values 306b39c5158Smillertuse constant HEY_IT_WORKED => 0; 307b39c5158Smillertuse constant I_DONT_KNOW_WHAT_HAPPENED => 1; # 0b0000_0001 308b39c5158Smillertuse constant ITS_NOT_MY_FAULT => 2; 309b39c5158Smillertuse constant THE_PROGRAMMERS_AN_IDIOT => 4; 310b39c5158Smillertuse constant A_MODULE_FAILED_TO_INSTALL => 8; 311b39c5158Smillert 312b39c5158Smillert 313b39c5158Smillert# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 314b39c5158Smillert# set up the order of options that we layer over CPAN::Shell 315b39c5158SmillertBEGIN { # most of this should be in methods 316b39c5158Smillertuse vars qw( @META_OPTIONS $Default %CPAN_METHODS @CPAN_OPTIONS @option_order 317b39c5158Smillert %Method_table %Method_table_index ); 318b39c5158Smillert 3199f11ffb7Safresh1@META_OPTIONS = qw( h v V I g G M: C A D O l L a r p P j: J w x X ); 320b39c5158Smillert 321b39c5158Smillert$Default = 'default'; 322b39c5158Smillert 323b39c5158Smillert%CPAN_METHODS = ( # map switches to method names in CPAN::Shell 324b39c5158Smillert $Default => 'install', 325b39c5158Smillert 'c' => 'clean', 326b39c5158Smillert 'f' => 'force', 327b39c5158Smillert 'i' => 'install', 328b39c5158Smillert 'm' => 'make', 329b39c5158Smillert 't' => 'test', 330b39c5158Smillert 'u' => 'upgrade', 331b8851fccSafresh1 'T' => 'notest', 3329f11ffb7Safresh1 's' => 'shell', 333b39c5158Smillert ); 334b39c5158Smillert@CPAN_OPTIONS = grep { $_ ne $Default } sort keys %CPAN_METHODS; 335b39c5158Smillert 336b39c5158Smillert@option_order = ( @META_OPTIONS, @CPAN_OPTIONS ); 337b39c5158Smillert 338b39c5158Smillert 339b39c5158Smillert# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 340b39c5158Smillert# map switches to the subroutines in this script, along with other information. 341b39c5158Smillert# use this stuff instead of hard-coded indices and values 342b39c5158Smillertsub NO_ARGS () { 0 } 343b39c5158Smillertsub ARGS () { 1 } 344b39c5158Smillertsub GOOD_EXIT () { 0 } 345b39c5158Smillert 346b39c5158Smillert%Method_table = ( 347b39c5158Smillert# key => [ sub ref, takes args?, exit value, description ] 348b39c5158Smillert 349b39c5158Smillert # options that do their thing first, then exit 350b39c5158Smillert h => [ \&_print_help, NO_ARGS, GOOD_EXIT, 'Printing help' ], 351b39c5158Smillert v => [ \&_print_version, NO_ARGS, GOOD_EXIT, 'Printing version' ], 35291f110e0Safresh1 V => [ \&_print_details, NO_ARGS, GOOD_EXIT, 'Printing detailed version' ], 3539f11ffb7Safresh1 X => [ \&_list_all_namespaces, NO_ARGS, GOOD_EXIT, 'Listing all namespaces' ], 354b39c5158Smillert 355b39c5158Smillert # options that affect other options 356b39c5158Smillert j => [ \&_load_config, ARGS, GOOD_EXIT, 'Use specified config file' ], 357b39c5158Smillert J => [ \&_dump_config, NO_ARGS, GOOD_EXIT, 'Dump configuration to stdout' ], 358b39c5158Smillert F => [ \&_lock_lobotomy, NO_ARGS, GOOD_EXIT, 'Turn off CPAN.pm lock files' ], 35991f110e0Safresh1 I => [ \&_load_local_lib, NO_ARGS, GOOD_EXIT, 'Loading local::lib' ], 360b8851fccSafresh1 M => [ \&_use_these_mirrors, ARGS, GOOD_EXIT, 'Setting per session mirrors' ], 361b8851fccSafresh1 P => [ \&_find_good_mirrors, NO_ARGS, GOOD_EXIT, 'Finding good mirrors' ], 36291f110e0Safresh1 w => [ \&_turn_on_warnings, NO_ARGS, GOOD_EXIT, 'Turning on warnings' ], 363b39c5158Smillert 364b39c5158Smillert # options that do their one thing 3659f11ffb7Safresh1 g => [ \&_download, ARGS, GOOD_EXIT, 'Download the latest distro' ], 3669f11ffb7Safresh1 G => [ \&_gitify, ARGS, GOOD_EXIT, 'Down and gitify the latest distro' ], 367b39c5158Smillert 368b39c5158Smillert C => [ \&_show_Changes, ARGS, GOOD_EXIT, 'Showing Changes file' ], 369b39c5158Smillert A => [ \&_show_Author, ARGS, GOOD_EXIT, 'Showing Author' ], 370b39c5158Smillert D => [ \&_show_Details, ARGS, GOOD_EXIT, 'Showing Details' ], 371b39c5158Smillert O => [ \&_show_out_of_date, NO_ARGS, GOOD_EXIT, 'Showing Out of date' ], 372b39c5158Smillert l => [ \&_list_all_mods, NO_ARGS, GOOD_EXIT, 'Listing all modules' ], 373b39c5158Smillert 374b39c5158Smillert L => [ \&_show_author_mods, ARGS, GOOD_EXIT, 'Showing author mods' ], 375b39c5158Smillert a => [ \&_create_autobundle, NO_ARGS, GOOD_EXIT, 'Creating autobundle' ], 37691f110e0Safresh1 p => [ \&_ping_mirrors, NO_ARGS, GOOD_EXIT, 'Pinging mirrors' ], 37791f110e0Safresh1 378b39c5158Smillert r => [ \&_recompile, NO_ARGS, GOOD_EXIT, 'Recompiling' ], 379b39c5158Smillert u => [ \&_upgrade, NO_ARGS, GOOD_EXIT, 'Running `make test`' ], 380*eac174f2Safresh1 's' => [ \&_shell, NO_ARGS, GOOD_EXIT, 'Drop into the CPAN.pm shell' ], 381b39c5158Smillert 3829f11ffb7Safresh1 'x' => [ \&_guess_namespace, ARGS, GOOD_EXIT, 'Guessing namespaces' ], 383b39c5158Smillert c => [ \&_default, ARGS, GOOD_EXIT, 'Running `make clean`' ], 384b39c5158Smillert f => [ \&_default, ARGS, GOOD_EXIT, 'Installing with force' ], 385b39c5158Smillert i => [ \&_default, ARGS, GOOD_EXIT, 'Running `make install`' ], 386b39c5158Smillert 'm' => [ \&_default, ARGS, GOOD_EXIT, 'Running `make`' ], 387b39c5158Smillert t => [ \&_default, ARGS, GOOD_EXIT, 'Running `make test`' ], 388b8851fccSafresh1 T => [ \&_default, ARGS, GOOD_EXIT, 'Installing with notest' ], 389b39c5158Smillert ); 390b39c5158Smillert 391b39c5158Smillert%Method_table_index = ( 392b39c5158Smillert code => 0, 393b39c5158Smillert takes_args => 1, 394b39c5158Smillert exit_value => 2, 395b39c5158Smillert description => 3, 396b39c5158Smillert ); 397b39c5158Smillert} 398b39c5158Smillert 39991f110e0Safresh1 400b39c5158Smillert# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 401b39c5158Smillert# finally, do some argument processing 402b39c5158Smillert 403b39c5158Smillertsub _stupid_interface_hack_for_non_rtfmers 404b39c5158Smillert { 405b39c5158Smillert no warnings 'uninitialized'; 406b39c5158Smillert shift @ARGV if( $ARGV[0] eq 'install' and @ARGV > 1 ) 407b39c5158Smillert } 408b39c5158Smillert 409b39c5158Smillertsub _process_options 410b39c5158Smillert { 411b39c5158Smillert my %options; 412b39c5158Smillert 41391f110e0Safresh1 push @ARGV, grep $_, split /\s+/, $ENV{CPAN_OPTS} || ''; 41491f110e0Safresh1 415b39c5158Smillert # if no arguments, just drop into the shell 416b39c5158Smillert if( 0 == @ARGV ) { CPAN::shell(); exit 0 } 41756d68f1eSafresh1 elsif (Getopt::Std::getopts( 41856d68f1eSafresh1 join( '', @option_order ), \%options )) 419b39c5158Smillert { 420b39c5158Smillert \%options; 421b39c5158Smillert } 42256d68f1eSafresh1 else { exit 1 } 423b39c5158Smillert} 424b39c5158Smillert 425b39c5158Smillertsub _process_setup_options 426b39c5158Smillert { 427b39c5158Smillert my( $class, $options ) = @_; 428b39c5158Smillert 429b39c5158Smillert if( $options->{j} ) 430b39c5158Smillert { 431b39c5158Smillert $Method_table{j}[ $Method_table_index{code} ]->( $options->{j} ); 432b39c5158Smillert delete $options->{j}; 433b39c5158Smillert } 43456d68f1eSafresh1 elsif ( ! $options->{h} ) { # h "ignores all of the other options and arguments" 435b39c5158Smillert # this is what CPAN.pm would do otherwise 43691f110e0Safresh1 local $CPAN::Be_Silent = 1; 437b39c5158Smillert CPAN::HandleConfig->load( 43891f110e0Safresh1 # be_silent => 1, deprecated 439b39c5158Smillert write_file => 0, 440b39c5158Smillert ); 441b39c5158Smillert } 442b39c5158Smillert 443b8851fccSafresh1 $class->_turn_off_testing if $options->{T}; 444b8851fccSafresh1 445b8851fccSafresh1 foreach my $o ( qw(F I w P M) ) 446b39c5158Smillert { 44791f110e0Safresh1 next unless exists $options->{$o}; 44891f110e0Safresh1 $Method_table{$o}[ $Method_table_index{code} ]->( $options->{$o} ); 44991f110e0Safresh1 delete $options->{$o}; 45091f110e0Safresh1 } 45191f110e0Safresh1 45291f110e0Safresh1 if( $options->{o} ) 45391f110e0Safresh1 { 45491f110e0Safresh1 my @pairs = map { [ split /=/, $_, 2 ] } split /,/, $options->{o}; 45591f110e0Safresh1 foreach my $pair ( @pairs ) 45691f110e0Safresh1 { 45791f110e0Safresh1 my( $setting, $value ) = @$pair; 45891f110e0Safresh1 $CPAN::Config->{$setting} = $value; 45991f110e0Safresh1 # $logger->debug( "Setting [$setting] to [$value]" ); 46091f110e0Safresh1 } 46191f110e0Safresh1 delete $options->{o}; 462b39c5158Smillert } 463b39c5158Smillert 464b39c5158Smillert my $option_count = grep { $options->{$_} } @option_order; 465b39c5158Smillert no warnings 'uninitialized'; 466b8851fccSafresh1 467b8851fccSafresh1 # don't count options that imply installation 468b8851fccSafresh1 foreach my $opt ( qw(f T) ) { # don't count force or notest 469b8851fccSafresh1 $option_count -= $options->{$opt}; 470b8851fccSafresh1 } 471b39c5158Smillert 472b39c5158Smillert # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 473b39c5158Smillert # if there are no options, set -i (this line fixes RT ticket 16915) 474b39c5158Smillert $options->{i}++ unless $option_count; 475b39c5158Smillert } 476b39c5158Smillert 477b8851fccSafresh1sub _setup_environment { 478b8851fccSafresh1# should we override or set defaults? If this were a true interactive 479b8851fccSafresh1# session, we'd be in the CPAN shell. 480b8851fccSafresh1 481b8851fccSafresh1# https://github.com/Perl-Toolchain-Gang/toolchain-site/blob/master/lancaster-consensus.md 482b8851fccSafresh1 $ENV{NONINTERACTIVE_TESTING} = 1 unless defined $ENV{NONINTERACTIVE_TESTING}; 483b8851fccSafresh1 $ENV{PERL_MM_USE_DEFAULT} = 1 unless defined $ENV{PERL_MM_USE_DEFAULT}; 484b8851fccSafresh1 } 485b39c5158Smillert 486*eac174f2Safresh1=item run( ARGS ) 487b39c5158Smillert 488b39c5158SmillertJust do it. 489b39c5158Smillert 4906fb12b70Safresh1The C<run> method returns 0 on success and a positive number on 491b39c5158Smillertfailure. See the section on EXIT CODES for details on the values. 492b39c5158Smillert 493b39c5158Smillert=cut 494b39c5158Smillert 495b39c5158Smillertmy $logger; 496b39c5158Smillert 497b39c5158Smillertsub run 498b39c5158Smillert { 499*eac174f2Safresh1 my( $class, @args ) = @_; 500*eac174f2Safresh1 local @ARGV = @args; 501b39c5158Smillert my $return_value = HEY_IT_WORKED; # assume that things will work 502b39c5158Smillert 503b39c5158Smillert $logger = $class->_init_logger; 504b39c5158Smillert $logger->debug( "Using logger from @{[ref $logger]}" ); 505b39c5158Smillert 506b39c5158Smillert $class->_hook_into_CPANpm_report; 507b39c5158Smillert $logger->debug( "Hooked into output" ); 508b39c5158Smillert 509b39c5158Smillert $class->_stupid_interface_hack_for_non_rtfmers; 510b39c5158Smillert $logger->debug( "Patched cargo culting" ); 511b39c5158Smillert 512b39c5158Smillert my $options = $class->_process_options; 513b39c5158Smillert $logger->debug( "Options are @{[Dumper($options)]}" ); 514b39c5158Smillert 515b39c5158Smillert $class->_process_setup_options( $options ); 516b39c5158Smillert 517b8851fccSafresh1 $class->_setup_environment( $options ); 518b8851fccSafresh1 519b39c5158Smillert OPTION: foreach my $option ( @option_order ) 520b39c5158Smillert { 521b39c5158Smillert next unless $options->{$option}; 522b39c5158Smillert 523b39c5158Smillert my( $sub, $takes_args, $description ) = 524b39c5158Smillert map { $Method_table{$option}[ $Method_table_index{$_} ] } 525b8851fccSafresh1 qw( code takes_args description ); 526b39c5158Smillert 527b39c5158Smillert unless( ref $sub eq ref sub {} ) 528b39c5158Smillert { 529b39c5158Smillert $return_value = THE_PROGRAMMERS_AN_IDIOT; 530b39c5158Smillert last OPTION; 531b39c5158Smillert } 532b39c5158Smillert 5339f11ffb7Safresh1 $logger->info( "[$option] $description -- ignoring other arguments" ) 534b39c5158Smillert if( @ARGV && ! $takes_args ); 535b39c5158Smillert 536b39c5158Smillert $return_value = $sub->( \ @ARGV, $options ); 537b39c5158Smillert 538b39c5158Smillert last; 539b39c5158Smillert } 540b39c5158Smillert 541b39c5158Smillert return $return_value; 542b39c5158Smillert } 543b39c5158Smillert 54456d68f1eSafresh1my $LEVEL; 545b39c5158Smillert{ 54691f110e0Safresh1package 54791f110e0Safresh1 Local::Null::Logger; # hide from PAUSE 548b39c5158Smillert 54956d68f1eSafresh1my @LOGLEVELS = qw(TRACE DEBUG INFO WARN ERROR FATAL); 55056d68f1eSafresh1$LEVEL = uc($ENV{CPANSCRIPT_LOGLEVEL} || 'INFO'); 55156d68f1eSafresh1my %LL = map { $LOGLEVELS[$_] => $_ } 0..$#LOGLEVELS; 55256d68f1eSafresh1unless (defined $LL{$LEVEL}){ 55356d68f1eSafresh1 warn "Unsupported loglevel '$LEVEL', setting to INFO"; 55456d68f1eSafresh1 $LEVEL = 'INFO'; 55556d68f1eSafresh1} 556b39c5158Smillertsub new { bless \ my $x, $_[0] } 5579f11ffb7Safresh1sub AUTOLOAD { 5589f11ffb7Safresh1 my $autoload = our $AUTOLOAD; 5599f11ffb7Safresh1 $autoload =~ s/.*://; 56056d68f1eSafresh1 return if $LL{uc $autoload} < $LL{$LEVEL}; 5619f11ffb7Safresh1 $CPAN::Frontend->mywarn(">($autoload): $_\n") 5629f11ffb7Safresh1 for split /[\r\n]+/, $_[1]; 5639f11ffb7Safresh1} 564b39c5158Smillertsub DESTROY { 1 } 565b39c5158Smillert} 566b39c5158Smillert 5670b7734b3Safresh1# load a module without searching the default entry for the current 5680b7734b3Safresh1# directory 5690b7734b3Safresh1sub _safe_load_module { 5700b7734b3Safresh1 my $name = shift; 5710b7734b3Safresh1 5720b7734b3Safresh1 local @INC = @INC; 5730b7734b3Safresh1 pop @INC if $INC[-1] eq '.'; 5740b7734b3Safresh1 5750b7734b3Safresh1 eval "require $name; 1"; 5760b7734b3Safresh1} 5770b7734b3Safresh1 578b39c5158Smillertsub _init_logger 579b39c5158Smillert { 5800b7734b3Safresh1 my $log4perl_loaded = _safe_load_module("Log::Log4perl"); 581b39c5158Smillert 582b39c5158Smillert unless( $log4perl_loaded ) 583b39c5158Smillert { 5849f11ffb7Safresh1 print STDOUT "Loading internal logger. Log::Log4perl recommended for better logging\n"; 585b39c5158Smillert $logger = Local::Null::Logger->new; 586b39c5158Smillert return $logger; 587b39c5158Smillert } 588b39c5158Smillert 589b39c5158Smillert Log::Log4perl::init( \ <<"HERE" ); 590b39c5158Smillertlog4perl.rootLogger=$LEVEL, A1 591b39c5158Smillertlog4perl.appender.A1=Log::Log4perl::Appender::Screen 592b39c5158Smillertlog4perl.appender.A1.layout=PatternLayout 593b39c5158Smillertlog4perl.appender.A1.layout.ConversionPattern=%m%n 594b39c5158SmillertHERE 595b39c5158Smillert 596b39c5158Smillert $logger = Log::Log4perl->get_logger( 'App::Cpan' ); 597b39c5158Smillert } 598b39c5158Smillert 599b39c5158Smillert# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 600b39c5158Smillert # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 601b39c5158Smillert# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 602b39c5158Smillert 603b39c5158Smillertsub _default 604b39c5158Smillert { 605b39c5158Smillert my( $args, $options ) = @_; 606b39c5158Smillert 607b39c5158Smillert my $switch = ''; 608b39c5158Smillert 609b39c5158Smillert # choose the option that we're going to use 610b39c5158Smillert # we'll deal with 'f' (force) later, so skip it 611b39c5158Smillert foreach my $option ( @CPAN_OPTIONS ) 612b39c5158Smillert { 613b8851fccSafresh1 next if ( $option eq 'f' or $option eq 'T' ); 614b39c5158Smillert next unless $options->{$option}; 615b39c5158Smillert $switch = $option; 616b39c5158Smillert last; 617b39c5158Smillert } 618b39c5158Smillert 619b39c5158Smillert # 1. with no switches, but arguments, use the default switch (install) 620b39c5158Smillert # 2. with no switches and no args, start the shell 621b39c5158Smillert # 3. With a switch but no args, die! These switches need arguments. 622b39c5158Smillert if( not $switch and @$args ) { $switch = $Default; } 623b39c5158Smillert elsif( not $switch and not @$args ) { return CPAN::shell() } 624b39c5158Smillert elsif( $switch and not @$args ) 625b39c5158Smillert { die "Nothing to $CPAN_METHODS{$switch}!\n"; } 626b39c5158Smillert 627b39c5158Smillert # Get and check the method from CPAN::Shell 628b39c5158Smillert my $method = $CPAN_METHODS{$switch}; 629b39c5158Smillert die "CPAN.pm cannot $method!\n" unless CPAN::Shell->can( $method ); 630b39c5158Smillert 631b8851fccSafresh1 # call the CPAN::Shell method, with force or notest if specified 632b39c5158Smillert my $action = do { 633b39c5158Smillert if( $options->{f} ) { sub { CPAN::Shell->force( $method, @_ ) } } 634b8851fccSafresh1 elsif( $options->{T} ) { sub { CPAN::Shell->notest( $method, @_ ) } } 635b39c5158Smillert else { sub { CPAN::Shell->$method( @_ ) } } 636b39c5158Smillert }; 637b39c5158Smillert 638b39c5158Smillert # How do I handle exit codes for multiple arguments? 639b8851fccSafresh1 my @errors = (); 640b39c5158Smillert 6419f11ffb7Safresh1 $options->{x} or _disable_guessers(); 6429f11ffb7Safresh1 643b39c5158Smillert foreach my $arg ( @$args ) 644b39c5158Smillert { 6459f11ffb7Safresh1 # check the argument and perhaps capture typos 6469f11ffb7Safresh1 my $module = _expand_module( $arg ) or do { 6479f11ffb7Safresh1 $logger->error( "Skipping $arg because I couldn't find a matching namespace." ); 6489f11ffb7Safresh1 next; 6499f11ffb7Safresh1 }; 6509f11ffb7Safresh1 651b39c5158Smillert _clear_cpanpm_output(); 652b39c5158Smillert $action->( $arg ); 653b39c5158Smillert 654b8851fccSafresh1 my $error = _cpanpm_output_indicates_failure(); 655b8851fccSafresh1 push @errors, $error if $error; 656b39c5158Smillert } 657b39c5158Smillert 658b8851fccSafresh1 return do { 659b8851fccSafresh1 if( @errors ) { $errors[0] } 660b8851fccSafresh1 else { HEY_IT_WORKED } 661b8851fccSafresh1 }; 662b8851fccSafresh1 663b39c5158Smillert } 664b39c5158Smillert 665b39c5158Smillert# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 666b39c5158Smillert 667b39c5158Smillert=for comment 668b39c5158Smillert 66991f110e0Safresh1CPAN.pm sends all the good stuff either to STDOUT, or to a temp 67091f110e0Safresh1file if $CPAN::Be_Silent is set. I have to intercept that output 67191f110e0Safresh1so I can find out what happened. 672b39c5158Smillert 673b39c5158Smillert=cut 674b39c5158Smillert 67591f110e0Safresh1BEGIN { 676b39c5158Smillertmy $scalar = ''; 677b39c5158Smillert 678b39c5158Smillertsub _hook_into_CPANpm_report 679b39c5158Smillert { 680b39c5158Smillert no warnings 'redefine'; 681b39c5158Smillert 682b39c5158Smillert *CPAN::Shell::myprint = sub { 683b39c5158Smillert my($self,$what) = @_; 68456d68f1eSafresh1 $scalar .= $what if defined $what; 685b39c5158Smillert $self->print_ornamented($what, 686b39c5158Smillert $CPAN::Config->{colorize_print}||'bold blue on_white', 687b39c5158Smillert ); 688b39c5158Smillert }; 689b39c5158Smillert 690b39c5158Smillert *CPAN::Shell::mywarn = sub { 691b39c5158Smillert my($self,$what) = @_; 692*eac174f2Safresh1 $scalar .= $what if defined $what; 693b39c5158Smillert $self->print_ornamented($what, 694b39c5158Smillert $CPAN::Config->{colorize_warn}||'bold red on_white' 695b39c5158Smillert ); 696b39c5158Smillert }; 697b39c5158Smillert 698b39c5158Smillert } 699b39c5158Smillert 700b39c5158Smillertsub _clear_cpanpm_output { $scalar = '' } 701b39c5158Smillert 702b39c5158Smillertsub _get_cpanpm_output { $scalar } 703b39c5158Smillert 704b8851fccSafresh1# These are lines I don't care about in CPAN.pm output. If I can 705b8851fccSafresh1# filter out the informational noise, I have a better chance to 706b8851fccSafresh1# catch the error signal 707b39c5158Smillertmy @skip_lines = ( 708b39c5158Smillert qr/^\QWarning \(usually harmless\)/, 709b39c5158Smillert qr/\bwill not store persistent state\b/, 710b39c5158Smillert qr(//hint//), 711b39c5158Smillert qr/^\s+reports\s+/, 712b8851fccSafresh1 qr/^Try the command/, 713b8851fccSafresh1 qr/^\s+$/, 714b8851fccSafresh1 qr/^to find objects/, 715b8851fccSafresh1 qr/^\s*Database was generated on/, 716b8851fccSafresh1 qr/^Going to read/, 717b8851fccSafresh1 qr|^\s+i\s+/|, # the i /Foo::Whatever/ line when it doesn't know 718b39c5158Smillert ); 719b39c5158Smillert 720b39c5158Smillertsub _get_cpanpm_last_line 721b39c5158Smillert { 7226fb12b70Safresh1 my $fh; 723b8851fccSafresh1 7246fb12b70Safresh1 if( $] < 5.008 ) { 7256fb12b70Safresh1 $fh = IO::Scalar->new( \ $scalar ); 726b8851fccSafresh1 } 727b8851fccSafresh1 else { 728b8851fccSafresh1 eval q{ open $fh, '<', \\ $scalar; }; 7296fb12b70Safresh1 } 730b39c5158Smillert 731b39c5158Smillert my @lines = <$fh>; 732b39c5158Smillert 733b39c5158Smillert # This is a bit ugly. Once we examine a line, we have to 734b39c5158Smillert # examine the line before it and go through all of the same 735b39c5158Smillert # regexes. I could do something fancy, but this works. 736b39c5158Smillert REGEXES: { 737b39c5158Smillert foreach my $regex ( @skip_lines ) 738b39c5158Smillert { 739b39c5158Smillert if( $lines[-1] =~ m/$regex/ ) 740b39c5158Smillert { 741b39c5158Smillert pop @lines; 742b39c5158Smillert redo REGEXES; # we have to go through all of them for every line! 743b39c5158Smillert } 744b39c5158Smillert } 745b39c5158Smillert } 746b39c5158Smillert 747b39c5158Smillert $logger->debug( "Last interesting line of CPAN.pm output is:\n\t$lines[-1]" ); 748b39c5158Smillert 749b39c5158Smillert $lines[-1]; 750b39c5158Smillert } 751b39c5158Smillert} 752b39c5158Smillert 753b39c5158SmillertBEGIN { 754b39c5158Smillertmy $epic_fail_words = join '|', 755b8851fccSafresh1 qw( Error stop(?:ping)? problems force not unsupported 756b8851fccSafresh1 fail(?:ed)? Cannot\s+install ); 757b39c5158Smillert 758b39c5158Smillertsub _cpanpm_output_indicates_failure 759b39c5158Smillert { 760b39c5158Smillert my $last_line = _get_cpanpm_last_line(); 761b39c5158Smillert 762b39c5158Smillert my $result = $last_line =~ /\b(?:$epic_fail_words)\b/i; 763b8851fccSafresh1 return A_MODULE_FAILED_TO_INSTALL if $last_line =~ /\b(?:Cannot\s+install)\b/i; 764b8851fccSafresh1 765b39c5158Smillert $result || (); 766b39c5158Smillert } 767b39c5158Smillert} 768b39c5158Smillert 769b39c5158Smillertsub _cpanpm_output_indicates_success 770b39c5158Smillert { 771b39c5158Smillert my $last_line = _get_cpanpm_last_line(); 772b39c5158Smillert 773b39c5158Smillert my $result = $last_line =~ /\b(?:\s+-- OK|PASS)\b/; 774b39c5158Smillert $result || (); 775b39c5158Smillert } 776b39c5158Smillert 777b39c5158Smillertsub _cpanpm_output_is_vague 778b39c5158Smillert { 779b39c5158Smillert return FALSE if 780b39c5158Smillert _cpanpm_output_indicates_failure() || 781b39c5158Smillert _cpanpm_output_indicates_success(); 782b39c5158Smillert 783b39c5158Smillert return TRUE; 784b39c5158Smillert } 785b39c5158Smillert 78691f110e0Safresh1# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 78791f110e0Safresh1sub _turn_on_warnings { 78891f110e0Safresh1 carp "Warnings are implemented yet"; 78991f110e0Safresh1 return HEY_IT_WORKED; 79091f110e0Safresh1 } 79191f110e0Safresh1 79291f110e0Safresh1sub _turn_off_testing { 79391f110e0Safresh1 $logger->debug( 'Trusting test report history' ); 79491f110e0Safresh1 $CPAN::Config->{trust_test_report_history} = 1; 79591f110e0Safresh1 return HEY_IT_WORKED; 796b39c5158Smillert } 797b39c5158Smillert 798b39c5158Smillert# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 799b39c5158Smillertsub _print_help 800b39c5158Smillert { 801b39c5158Smillert $logger->info( "Use perldoc to read the documentation" ); 80256d68f1eSafresh1 my $HAVE_PERLDOC = eval { require Pod::Perldoc; 1; }; 80356d68f1eSafresh1 if ($HAVE_PERLDOC) { 80456d68f1eSafresh1 system qq{"$^X" -e "require Pod::Perldoc; Pod::Perldoc->run()" $0}; 80556d68f1eSafresh1 exit; 80656d68f1eSafresh1 } else { 80756d68f1eSafresh1 warn "Please install Pod::Perldoc, maybe try 'cpan -i Pod::Perldoc'\n"; 80856d68f1eSafresh1 return HEY_IT_WORKED; 80956d68f1eSafresh1 } 810b39c5158Smillert } 811b39c5158Smillert 81291f110e0Safresh1sub _print_version # -v 813b39c5158Smillert { 814b39c5158Smillert $logger->info( 815b39c5158Smillert "$0 script version $VERSION, CPAN.pm version " . CPAN->VERSION ); 816b39c5158Smillert 817b39c5158Smillert return HEY_IT_WORKED; 818b39c5158Smillert } 819b39c5158Smillert 82091f110e0Safresh1sub _print_details # -V 82191f110e0Safresh1 { 82291f110e0Safresh1 _print_version(); 82391f110e0Safresh1 82491f110e0Safresh1 _check_install_dirs(); 82591f110e0Safresh1 82691f110e0Safresh1 $logger->info( '-' x 50 . "\nChecking configured mirrors..." ); 82791f110e0Safresh1 foreach my $mirror ( @{ $CPAN::Config->{urllist} } ) { 82891f110e0Safresh1 _print_ping_report( $mirror ); 82991f110e0Safresh1 } 83091f110e0Safresh1 83191f110e0Safresh1 $logger->info( '-' x 50 . "\nChecking for faster mirrors..." ); 83291f110e0Safresh1 83391f110e0Safresh1 { 83491f110e0Safresh1 require CPAN::Mirrors; 83591f110e0Safresh1 83691f110e0Safresh1 if ( $CPAN::Config->{connect_to_internet_ok} ) { 83791f110e0Safresh1 $CPAN::Frontend->myprint(qq{Trying to fetch a mirror list from the Internet\n}); 83891f110e0Safresh1 eval { CPAN::FTP->localize('MIRRORED.BY',File::Spec->catfile($CPAN::Config->{keep_source_where},'MIRRORED.BY'),3,1) } 83991f110e0Safresh1 or $CPAN::Frontend->mywarn(<<'HERE'); 84091f110e0Safresh1We failed to get a copy of the mirror list from the Internet. 84191f110e0Safresh1You will need to provide CPAN mirror URLs yourself. 84291f110e0Safresh1HERE 84391f110e0Safresh1 $CPAN::Frontend->myprint("\n"); 84491f110e0Safresh1 } 84591f110e0Safresh1 8469f11ffb7Safresh1 my $mirrors = CPAN::Mirrors->new( _mirror_file() ); 84791f110e0Safresh1 my @continents = $mirrors->find_best_continents; 84891f110e0Safresh1 84991f110e0Safresh1 my @mirrors = $mirrors->get_mirrors_by_continents( $continents[0] ); 85091f110e0Safresh1 my @timings = $mirrors->get_mirrors_timings( \@mirrors ); 85191f110e0Safresh1 85291f110e0Safresh1 foreach my $timing ( @timings ) { 85391f110e0Safresh1 $logger->info( sprintf "%s (%0.2f ms)", 85491f110e0Safresh1 $timing->hostname, $timing->rtt ); 85591f110e0Safresh1 } 85691f110e0Safresh1 } 85791f110e0Safresh1 85891f110e0Safresh1 return HEY_IT_WORKED; 85991f110e0Safresh1 } 86091f110e0Safresh1 86191f110e0Safresh1sub _check_install_dirs 86291f110e0Safresh1 { 86391f110e0Safresh1 my $makepl_arg = $CPAN::Config->{makepl_arg}; 86491f110e0Safresh1 my $mbuildpl_arg = $CPAN::Config->{mbuildpl_arg}; 86591f110e0Safresh1 86691f110e0Safresh1 my @custom_dirs; 86791f110e0Safresh1 # PERL_MM_OPT 86891f110e0Safresh1 push @custom_dirs, 86991f110e0Safresh1 $makepl_arg =~ m/INSTALL_BASE\s*=\s*(\S+)/g, 87091f110e0Safresh1 $mbuildpl_arg =~ m/--install_base\s*=\s*(\S+)/g; 87191f110e0Safresh1 87291f110e0Safresh1 if( @custom_dirs ) { 87391f110e0Safresh1 foreach my $dir ( @custom_dirs ) { 87491f110e0Safresh1 _print_inc_dir_report( $dir ); 87591f110e0Safresh1 } 87691f110e0Safresh1 } 87791f110e0Safresh1 87891f110e0Safresh1 # XXX: also need to check makepl_args, etc 87991f110e0Safresh1 88091f110e0Safresh1 my @checks = ( 88191f110e0Safresh1 [ 'core', [ grep $_, @Config{qw(installprivlib installarchlib)} ] ], 88291f110e0Safresh1 [ 'vendor', [ grep $_, @Config{qw(installvendorlib installvendorarch)} ] ], 88391f110e0Safresh1 [ 'site', [ grep $_, @Config{qw(installsitelib installsitearch)} ] ], 88491f110e0Safresh1 [ 'PERL5LIB', _split_paths( $ENV{PERL5LIB} ) ], 88591f110e0Safresh1 [ 'PERLLIB', _split_paths( $ENV{PERLLIB} ) ], 88691f110e0Safresh1 ); 88791f110e0Safresh1 88891f110e0Safresh1 $logger->info( '-' x 50 . "\nChecking install dirs..." ); 88991f110e0Safresh1 foreach my $tuple ( @checks ) { 89091f110e0Safresh1 my( $label ) = $tuple->[0]; 89191f110e0Safresh1 89291f110e0Safresh1 $logger->info( "Checking $label" ); 89391f110e0Safresh1 $logger->info( "\tno directories for $label" ) unless @{ $tuple->[1] }; 89491f110e0Safresh1 foreach my $dir ( @{ $tuple->[1] } ) { 89591f110e0Safresh1 _print_inc_dir_report( $dir ); 89691f110e0Safresh1 } 89791f110e0Safresh1 } 89891f110e0Safresh1 89991f110e0Safresh1 } 90091f110e0Safresh1 90191f110e0Safresh1sub _split_paths 90291f110e0Safresh1 { 90391f110e0Safresh1 [ map { _expand_filename( $_ ) } split /$Config{path_sep}/, $_[0] || '' ]; 90491f110e0Safresh1 } 90591f110e0Safresh1 90691f110e0Safresh1 90791f110e0Safresh1=pod 90891f110e0Safresh1 90991f110e0Safresh1Stolen from File::Path::Expand 91091f110e0Safresh1 91191f110e0Safresh1=cut 91291f110e0Safresh1 91391f110e0Safresh1sub _expand_filename 91491f110e0Safresh1 { 91591f110e0Safresh1 my( $path ) = @_; 91691f110e0Safresh1 no warnings 'uninitialized'; 91791f110e0Safresh1 $logger->debug( "Expanding path $path\n" ); 91891f110e0Safresh1 $path =~ s{\A~([^/]+)?}{ 91991f110e0Safresh1 _home_of( $1 || $> ) || "~$1" 92091f110e0Safresh1 }e; 92191f110e0Safresh1 return $path; 92291f110e0Safresh1 } 92391f110e0Safresh1 92491f110e0Safresh1sub _home_of 92591f110e0Safresh1 { 92691f110e0Safresh1 require User::pwent; 92791f110e0Safresh1 my( $user ) = @_; 92891f110e0Safresh1 my $ent = User::pwent::getpw($user) or return; 92991f110e0Safresh1 return $ent->dir; 93091f110e0Safresh1 } 93191f110e0Safresh1 93291f110e0Safresh1sub _get_default_inc 93391f110e0Safresh1 { 93491f110e0Safresh1 require Config; 93591f110e0Safresh1 93691f110e0Safresh1 [ @Config::Config{ _vars() }, '.' ]; 93791f110e0Safresh1 } 93891f110e0Safresh1 93991f110e0Safresh1sub _vars { 94091f110e0Safresh1 qw( 94191f110e0Safresh1 installarchlib 94291f110e0Safresh1 installprivlib 94391f110e0Safresh1 installsitearch 94491f110e0Safresh1 installsitelib 94591f110e0Safresh1 ); 94691f110e0Safresh1 } 94791f110e0Safresh1 94891f110e0Safresh1sub _ping_mirrors { 94991f110e0Safresh1 my $urls = $CPAN::Config->{urllist}; 95091f110e0Safresh1 require URI; 95191f110e0Safresh1 95291f110e0Safresh1 foreach my $url ( @$urls ) { 95391f110e0Safresh1 my( $obj ) = URI->new( $url ); 95491f110e0Safresh1 next unless _is_pingable_scheme( $obj ); 95591f110e0Safresh1 my $host = $obj->host; 95691f110e0Safresh1 _print_ping_report( $obj ); 95791f110e0Safresh1 } 95891f110e0Safresh1 95991f110e0Safresh1 } 96091f110e0Safresh1 96191f110e0Safresh1sub _is_pingable_scheme { 96291f110e0Safresh1 my( $uri ) = @_; 96391f110e0Safresh1 96491f110e0Safresh1 $uri->scheme eq 'file' 96591f110e0Safresh1 } 96691f110e0Safresh1 9679f11ffb7Safresh1sub _mirror_file { 96891f110e0Safresh1 my $file = do { 96991f110e0Safresh1 my $file = 'MIRRORED.BY'; 97091f110e0Safresh1 my $local_path = File::Spec->catfile( 97191f110e0Safresh1 $CPAN::Config->{keep_source_where}, $file ); 97291f110e0Safresh1 97391f110e0Safresh1 if( -e $local_path ) { $local_path } 97491f110e0Safresh1 else { 97591f110e0Safresh1 require CPAN::FTP; 97691f110e0Safresh1 CPAN::FTP->localize( $file, $local_path, 3, 1 ); 97791f110e0Safresh1 $local_path; 97891f110e0Safresh1 } 97991f110e0Safresh1 }; 9809f11ffb7Safresh1 } 9819f11ffb7Safresh1 9829f11ffb7Safresh1sub _find_good_mirrors { 9839f11ffb7Safresh1 require CPAN::Mirrors; 9849f11ffb7Safresh1 9859f11ffb7Safresh1 my $mirrors = CPAN::Mirrors->new( _mirror_file() ); 98691f110e0Safresh1 98791f110e0Safresh1 my @mirrors = $mirrors->best_mirrors( 988b8851fccSafresh1 how_many => 5, 98991f110e0Safresh1 verbose => 1, 99091f110e0Safresh1 ); 99191f110e0Safresh1 99291f110e0Safresh1 foreach my $mirror ( @mirrors ) { 99391f110e0Safresh1 next unless eval { $mirror->can( 'http' ) }; 99491f110e0Safresh1 _print_ping_report( $mirror->http ); 99591f110e0Safresh1 } 99691f110e0Safresh1 997b8851fccSafresh1 $CPAN::Config->{urllist} = [ 998b8851fccSafresh1 map { $_->http } @mirrors 999b8851fccSafresh1 ]; 100091f110e0Safresh1 } 100191f110e0Safresh1 100291f110e0Safresh1sub _print_inc_dir_report 100391f110e0Safresh1 { 100491f110e0Safresh1 my( $dir ) = shift; 100591f110e0Safresh1 100691f110e0Safresh1 my $writeable = -w $dir ? '+' : '!!! (not writeable)'; 100791f110e0Safresh1 $logger->info( "\t$writeable $dir" ); 100891f110e0Safresh1 return -w $dir; 100991f110e0Safresh1 } 101091f110e0Safresh1 101191f110e0Safresh1sub _print_ping_report 101291f110e0Safresh1 { 101391f110e0Safresh1 my( $mirror ) = @_; 101491f110e0Safresh1 101591f110e0Safresh1 my $rtt = eval { _get_ping_report( $mirror ) }; 1016b8851fccSafresh1 my $result = $rtt ? sprintf "+ (%4d ms)", $rtt * 1000 : '!'; 101791f110e0Safresh1 101891f110e0Safresh1 $logger->info( 1019b8851fccSafresh1 sprintf "\t%s %s", $result, $mirror 102091f110e0Safresh1 ); 102191f110e0Safresh1 } 102291f110e0Safresh1 102391f110e0Safresh1sub _get_ping_report 102491f110e0Safresh1 { 102591f110e0Safresh1 require URI; 102691f110e0Safresh1 my( $mirror ) = @_; 102791f110e0Safresh1 my( $url ) = ref $mirror ? $mirror : URI->new( $mirror ); #XXX 102891f110e0Safresh1 require Net::Ping; 102991f110e0Safresh1 103091f110e0Safresh1 my $ping = Net::Ping->new( 'tcp', 1 ); 103191f110e0Safresh1 103291f110e0Safresh1 if( $url->scheme eq 'file' ) { 103391f110e0Safresh1 return -e $url->file; 103491f110e0Safresh1 } 103591f110e0Safresh1 103691f110e0Safresh1 my( $port ) = $url->port; 103791f110e0Safresh1 103891f110e0Safresh1 return unless $port; 103991f110e0Safresh1 104091f110e0Safresh1 if ( $ping->can('port_number') ) { 104191f110e0Safresh1 $ping->port_number($port); 104291f110e0Safresh1 } 104391f110e0Safresh1 else { 104491f110e0Safresh1 $ping->{'port_num'} = $port; 104591f110e0Safresh1 } 104691f110e0Safresh1 104791f110e0Safresh1 $ping->hires(1) if $ping->can( 'hires' ); 104891f110e0Safresh1 my( $alive, $rtt ) = eval{ $ping->ping( $url->host ) }; 104991f110e0Safresh1 $alive ? $rtt : undef; 105091f110e0Safresh1 } 105191f110e0Safresh1 105291f110e0Safresh1sub _load_local_lib # -I 105391f110e0Safresh1 { 105491f110e0Safresh1 $logger->debug( "Loading local::lib" ); 105591f110e0Safresh1 10560b7734b3Safresh1 my $rc = _safe_load_module("local::lib"); 105791f110e0Safresh1 unless( $rc ) { 10589f11ffb7Safresh1 $logger->logdie( "Could not load local::lib" ); 105991f110e0Safresh1 } 106091f110e0Safresh1 106191f110e0Safresh1 local::lib->import; 106291f110e0Safresh1 106391f110e0Safresh1 return HEY_IT_WORKED; 106491f110e0Safresh1 } 106591f110e0Safresh1 1066b8851fccSafresh1sub _use_these_mirrors # -M 1067b8851fccSafresh1 { 1068b8851fccSafresh1 $logger->debug( "Setting per session mirrors" ); 1069b8851fccSafresh1 unless( $_[0] ) { 10709f11ffb7Safresh1 $logger->logdie( "The -M switch requires a comma-separated list of mirrors" ); 1071b8851fccSafresh1 } 1072b8851fccSafresh1 1073b8851fccSafresh1 $CPAN::Config->{urllist} = [ split /,/, $_[0] ]; 1074b8851fccSafresh1 1075b8851fccSafresh1 $logger->debug( "Mirrors are @{$CPAN::Config->{urllist}}" ); 1076b8851fccSafresh1 1077b8851fccSafresh1 } 1078b8851fccSafresh1 1079b39c5158Smillertsub _create_autobundle 1080b39c5158Smillert { 1081b39c5158Smillert $logger->info( 1082b39c5158Smillert "Creating autobundle in $CPAN::Config->{cpan_home}/Bundle" ); 1083b39c5158Smillert 1084b39c5158Smillert CPAN::Shell->autobundle; 1085b39c5158Smillert 1086b39c5158Smillert return HEY_IT_WORKED; 1087b39c5158Smillert } 1088b39c5158Smillert 1089b39c5158Smillertsub _recompile 1090b39c5158Smillert { 1091b39c5158Smillert $logger->info( "Recompiling dynamically-loaded extensions" ); 1092b39c5158Smillert 1093b39c5158Smillert CPAN::Shell->recompile; 1094b39c5158Smillert 1095b39c5158Smillert return HEY_IT_WORKED; 1096b39c5158Smillert } 1097b39c5158Smillert 1098b39c5158Smillertsub _upgrade 1099b39c5158Smillert { 1100b39c5158Smillert $logger->info( "Upgrading all modules" ); 1101b39c5158Smillert 1102b39c5158Smillert CPAN::Shell->upgrade(); 1103b39c5158Smillert 1104b39c5158Smillert return HEY_IT_WORKED; 1105b39c5158Smillert } 1106b39c5158Smillert 11079f11ffb7Safresh1sub _shell 11089f11ffb7Safresh1 { 11099f11ffb7Safresh1 $logger->info( "Dropping into shell" ); 11109f11ffb7Safresh1 11119f11ffb7Safresh1 CPAN::shell(); 11129f11ffb7Safresh1 11139f11ffb7Safresh1 return HEY_IT_WORKED; 11149f11ffb7Safresh1 } 11159f11ffb7Safresh1 1116b39c5158Smillertsub _load_config # -j 1117b39c5158Smillert { 1118b46d8ef2Safresh1 my $argument = shift; 1119b46d8ef2Safresh1 1120b46d8ef2Safresh1 my $file = file_name_is_absolute( $argument ) ? $argument : rel2abs( $argument ); 1121b46d8ef2Safresh1 croak( "cpan config file [$file] for -j does not exist!\n" ) unless -e $file; 1122b39c5158Smillert 1123b39c5158Smillert # should I clear out any existing config here? 1124b39c5158Smillert $CPAN::Config = {}; 1125b39c5158Smillert delete $INC{'CPAN/Config.pm'}; 1126b39c5158Smillert 1127b39c5158Smillert my $rc = eval "require '$file'"; 1128b39c5158Smillert 1129b39c5158Smillert # CPAN::HandleConfig::require_myconfig_or_config looks for this 1130b39c5158Smillert $INC{'CPAN/MyConfig.pm'} = 'fake out!'; 1131b39c5158Smillert 1132b39c5158Smillert # CPAN::HandleConfig::load looks for this 1133b39c5158Smillert $CPAN::Config_loaded = 'fake out'; 1134b39c5158Smillert 1135b39c5158Smillert croak( "Could not load [$file]: $@\n") unless $rc; 1136b39c5158Smillert 1137b39c5158Smillert return HEY_IT_WORKED; 1138b39c5158Smillert } 1139b39c5158Smillert 114091f110e0Safresh1sub _dump_config # -J 1141b39c5158Smillert { 1142b39c5158Smillert my $args = shift; 1143b39c5158Smillert require Data::Dumper; 1144b39c5158Smillert 1145b39c5158Smillert my $fh = $args->[0] || \*STDOUT; 1146b39c5158Smillert 11476fb12b70Safresh1 local $Data::Dumper::Sortkeys = 1; 1148b39c5158Smillert my $dd = Data::Dumper->new( 1149b39c5158Smillert [$CPAN::Config], 1150b39c5158Smillert ['$CPAN::Config'] 1151b39c5158Smillert ); 1152b39c5158Smillert 1153b39c5158Smillert print $fh $dd->Dump, "\n1;\n__END__\n"; 1154b39c5158Smillert 1155b39c5158Smillert return HEY_IT_WORKED; 1156b39c5158Smillert } 1157b39c5158Smillert 115891f110e0Safresh1sub _lock_lobotomy # -F 1159b39c5158Smillert { 1160b39c5158Smillert no warnings 'redefine'; 1161b39c5158Smillert 1162b39c5158Smillert *CPAN::_flock = sub { 1 }; 1163b39c5158Smillert *CPAN::checklock = sub { 1 }; 1164b39c5158Smillert 1165b39c5158Smillert return HEY_IT_WORKED; 1166b39c5158Smillert } 1167b39c5158Smillert 1168b39c5158Smillertsub _download 1169b39c5158Smillert { 1170b39c5158Smillert my $args = shift; 1171b39c5158Smillert 1172b39c5158Smillert local $CPAN::DEBUG = 1; 1173b39c5158Smillert 1174b39c5158Smillert my %paths; 1175b39c5158Smillert 11769f11ffb7Safresh1 foreach my $arg ( @$args ) { 11779f11ffb7Safresh1 $logger->info( "Checking $arg" ); 11789f11ffb7Safresh1 11799f11ffb7Safresh1 my $module = _expand_module( $arg ) or next; 11809f11ffb7Safresh1 my $path = $module->cpan_file; 1181b39c5158Smillert 1182b39c5158Smillert $logger->debug( "Inst file would be $path\n" ); 1183b39c5158Smillert 1184b46d8ef2Safresh1 $paths{$module} = _get_file( _make_path( $path ) ); 11859f11ffb7Safresh1 1186b46d8ef2Safresh1 $logger->info( "Downloaded [$arg] to [$paths{$arg}]" ); 1187b39c5158Smillert } 1188b39c5158Smillert 1189b39c5158Smillert return \%paths; 1190b39c5158Smillert } 1191b39c5158Smillert 1192b39c5158Smillertsub _make_path { join "/", qw(authors id), $_[0] } 1193b39c5158Smillert 1194b39c5158Smillertsub _get_file 1195b39c5158Smillert { 1196b39c5158Smillert my $path = shift; 1197b39c5158Smillert 11980b7734b3Safresh1 my $loaded = _safe_load_module("LWP::Simple"); 1199b39c5158Smillert croak "You need LWP::Simple to use features that fetch files from CPAN\n" 1200b39c5158Smillert unless $loaded; 1201b39c5158Smillert 1202b39c5158Smillert my $file = substr $path, rindex( $path, '/' ) + 1; 1203b39c5158Smillert my $store_path = catfile( cwd(), $file ); 1204b39c5158Smillert $logger->debug( "Store path is $store_path" ); 1205b39c5158Smillert 1206b39c5158Smillert foreach my $site ( @{ $CPAN::Config->{urllist} } ) 1207b39c5158Smillert { 1208b39c5158Smillert my $fetch_path = join "/", $site, $path; 1209b39c5158Smillert $logger->debug( "Trying $fetch_path" ); 1210b46d8ef2Safresh1 my $status_code = LWP::Simple::getstore( $fetch_path, $store_path ); 1211b46d8ef2Safresh1 last if( 200 <= $status_code and $status_code <= 300 ); 1212b46d8ef2Safresh1 $logger->warn( "Could not get [$fetch_path]: Status code $status_code" ); 1213b39c5158Smillert } 1214b39c5158Smillert 1215b39c5158Smillert return $store_path; 1216b39c5158Smillert } 1217b39c5158Smillert 1218b39c5158Smillertsub _gitify 1219b39c5158Smillert { 1220b39c5158Smillert my $args = shift; 1221b39c5158Smillert 12220b7734b3Safresh1 my $loaded = _safe_load_module("Archive::Extract"); 1223b39c5158Smillert croak "You need Archive::Extract to use features that gitify distributions\n" 1224b39c5158Smillert unless $loaded; 1225b39c5158Smillert 1226b39c5158Smillert my $starting_dir = cwd(); 1227b39c5158Smillert 12289f11ffb7Safresh1 foreach my $arg ( @$args ) 1229b39c5158Smillert { 12309f11ffb7Safresh1 $logger->info( "Checking $arg" ); 12319f11ffb7Safresh1 my $store_paths = _download( [ $arg ] ); 12329f11ffb7Safresh1 $logger->debug( "gitify Store path is $store_paths->{$arg}" ); 12339f11ffb7Safresh1 my $dirname = dirname( $store_paths->{$arg} ); 1234b39c5158Smillert 12359f11ffb7Safresh1 my $ae = Archive::Extract->new( archive => $store_paths->{$arg} ); 1236b39c5158Smillert $ae->extract( to => $dirname ); 1237b39c5158Smillert 1238b39c5158Smillert chdir $ae->extract_path; 1239b39c5158Smillert 1240b39c5158Smillert my $git = $ENV{GIT_COMMAND} || '/usr/local/bin/git'; 1241b39c5158Smillert croak "Could not find $git" unless -e $git; 1242b39c5158Smillert croak "$git is not executable" unless -x $git; 1243b39c5158Smillert 1244b39c5158Smillert # can we do this in Pure Perl? 1245b39c5158Smillert system( $git, 'init' ); 1246b39c5158Smillert system( $git, qw( add . ) ); 1247b39c5158Smillert system( $git, qw( commit -a -m ), 'initial import' ); 1248b39c5158Smillert } 1249b39c5158Smillert 1250b39c5158Smillert chdir $starting_dir; 1251b39c5158Smillert 1252b39c5158Smillert return HEY_IT_WORKED; 1253b39c5158Smillert } 1254b39c5158Smillert 1255b39c5158Smillertsub _show_Changes 1256b39c5158Smillert { 1257b39c5158Smillert my $args = shift; 1258b39c5158Smillert 1259b39c5158Smillert foreach my $arg ( @$args ) 1260b39c5158Smillert { 1261b39c5158Smillert $logger->info( "Checking $arg\n" ); 1262b39c5158Smillert 12639f11ffb7Safresh1 my $module = _expand_module( $arg ) or next; 12649f11ffb7Safresh1 1265b39c5158Smillert my $out = _get_cpanpm_output(); 1266b39c5158Smillert 1267b39c5158Smillert next unless eval { $module->inst_file }; 1268b39c5158Smillert #next if $module->uptodate; 1269b39c5158Smillert 1270b39c5158Smillert ( my $id = $module->id() ) =~ s/::/\-/; 1271b39c5158Smillert 1272b39c5158Smillert my $url = "http://search.cpan.org/~" . lc( $module->userid ) . "/" . 1273b39c5158Smillert $id . "-" . $module->cpan_version() . "/"; 1274b39c5158Smillert 1275b39c5158Smillert #print "URL: $url\n"; 1276b39c5158Smillert _get_changes_file($url); 1277b39c5158Smillert } 1278b39c5158Smillert 1279b39c5158Smillert return HEY_IT_WORKED; 1280b39c5158Smillert } 1281b39c5158Smillert 1282b39c5158Smillertsub _get_changes_file 1283b39c5158Smillert { 1284b39c5158Smillert croak "Reading Changes files requires LWP::Simple and URI\n" 12850b7734b3Safresh1 unless _safe_load_module("LWP::Simple") && _safe_load_module("URI"); 1286b39c5158Smillert 1287b39c5158Smillert my $url = shift; 1288b39c5158Smillert 1289b39c5158Smillert my $content = LWP::Simple::get( $url ); 1290b39c5158Smillert $logger->info( "Got $url ..." ) if defined $content; 1291b39c5158Smillert #print $content; 1292b39c5158Smillert 1293b39c5158Smillert my( $change_link ) = $content =~ m|<a href="(.*?)">Changes</a>|gi; 1294b39c5158Smillert 1295b39c5158Smillert my $changes_url = URI->new_abs( $change_link, $url ); 1296b39c5158Smillert $logger->debug( "Change link is: $changes_url" ); 1297b39c5158Smillert 1298b39c5158Smillert my $changes = LWP::Simple::get( $changes_url ); 1299b39c5158Smillert 1300b39c5158Smillert print $changes; 1301b39c5158Smillert 1302b39c5158Smillert return HEY_IT_WORKED; 1303b39c5158Smillert } 1304b39c5158Smillert 1305b39c5158Smillertsub _show_Author 1306b39c5158Smillert { 1307b39c5158Smillert my $args = shift; 1308b39c5158Smillert 1309b39c5158Smillert foreach my $arg ( @$args ) 1310b39c5158Smillert { 13119f11ffb7Safresh1 my $module = _expand_module( $arg ) or next; 13129f11ffb7Safresh1 1313b39c5158Smillert unless( $module ) 1314b39c5158Smillert { 1315b39c5158Smillert $logger->info( "Didn't find a $arg module, so no author!" ); 1316b39c5158Smillert next; 1317b39c5158Smillert } 1318b39c5158Smillert 1319b39c5158Smillert my $author = CPAN::Shell->expand( "Author", $module->userid ); 1320b39c5158Smillert 1321b39c5158Smillert next unless $module->userid; 1322b39c5158Smillert 1323b39c5158Smillert printf "%-25s %-8s %-25s %s\n", 132491f110e0Safresh1 $arg, $module->userid, $author->email, $author->name; 1325b39c5158Smillert } 1326b39c5158Smillert 1327b39c5158Smillert return HEY_IT_WORKED; 1328b39c5158Smillert } 1329b39c5158Smillert 1330b39c5158Smillertsub _show_Details 1331b39c5158Smillert { 1332b39c5158Smillert my $args = shift; 1333b39c5158Smillert 1334b39c5158Smillert foreach my $arg ( @$args ) 1335b39c5158Smillert { 13369f11ffb7Safresh1 my $module = _expand_module( $arg ) or next; 1337b39c5158Smillert my $author = CPAN::Shell->expand( "Author", $module->userid ); 1338b39c5158Smillert 1339b39c5158Smillert next unless $module->userid; 1340b39c5158Smillert 1341b39c5158Smillert print "$arg\n", "-" x 73, "\n\t"; 1342b39c5158Smillert print join "\n\t", 1343b39c5158Smillert $module->description ? $module->description : "(no description)", 1344b8851fccSafresh1 $module->cpan_file ? $module->cpan_file : "(no cpanfile)", 1345b8851fccSafresh1 $module->inst_file ? $module->inst_file :"(no installation file)" , 1346b8851fccSafresh1 'Installed: ' . ($module->inst_version ? $module->inst_version : "not installed"), 1347b39c5158Smillert 'CPAN: ' . $module->cpan_version . ' ' . 1348b39c5158Smillert ($module->uptodate ? "" : "Not ") . "up to date", 1349b39c5158Smillert $author->fullname . " (" . $module->userid . ")", 1350b39c5158Smillert $author->email; 1351b39c5158Smillert print "\n\n"; 1352b39c5158Smillert 1353b39c5158Smillert } 1354b39c5158Smillert 1355b39c5158Smillert return HEY_IT_WORKED; 1356b39c5158Smillert } 1357b39c5158Smillert 13589f11ffb7Safresh1BEGIN { 13599f11ffb7Safresh1my $modules; 13609f11ffb7Safresh1sub _get_all_namespaces 13619f11ffb7Safresh1 { 13629f11ffb7Safresh1 return $modules if $modules; 13639f11ffb7Safresh1 $modules = [ map { $_->id } CPAN::Shell->expand( "Module", "/./" ) ]; 13649f11ffb7Safresh1 } 13659f11ffb7Safresh1} 13669f11ffb7Safresh1 1367b39c5158Smillertsub _show_out_of_date 1368b39c5158Smillert { 13699f11ffb7Safresh1 my $modules = _get_all_namespaces(); 1370b39c5158Smillert 1371b39c5158Smillert printf "%-40s %6s %6s\n", "Module Name", "Local", "CPAN"; 1372b39c5158Smillert print "-" x 73, "\n"; 1373b39c5158Smillert 13749f11ffb7Safresh1 foreach my $module ( @$modules ) 1375b39c5158Smillert { 13769f11ffb7Safresh1 next unless $module = _expand_module($module); 1377b39c5158Smillert next unless $module->inst_file; 1378b39c5158Smillert next if $module->uptodate; 1379b39c5158Smillert printf "%-40s %.4f %.4f\n", 1380b39c5158Smillert $module->id, 1381b39c5158Smillert $module->inst_version ? $module->inst_version : '', 1382b39c5158Smillert $module->cpan_version; 1383b39c5158Smillert } 1384b39c5158Smillert 1385b39c5158Smillert return HEY_IT_WORKED; 1386b39c5158Smillert } 1387b39c5158Smillert 1388b39c5158Smillertsub _show_author_mods 1389b39c5158Smillert { 1390b39c5158Smillert my $args = shift; 1391b39c5158Smillert 1392b39c5158Smillert my %hash = map { lc $_, 1 } @$args; 1393b39c5158Smillert 13949f11ffb7Safresh1 my $modules = _get_all_namespaces(); 1395b39c5158Smillert 13969f11ffb7Safresh1 foreach my $module ( @$modules ) { 1397b39c5158Smillert next unless exists $hash{ lc $module->userid }; 1398b39c5158Smillert print $module->id, "\n"; 1399b39c5158Smillert } 1400b39c5158Smillert 1401b39c5158Smillert return HEY_IT_WORKED; 1402b39c5158Smillert } 1403b39c5158Smillert 140491f110e0Safresh1sub _list_all_mods # -l 1405b39c5158Smillert { 1406b39c5158Smillert require File::Find; 1407b39c5158Smillert 1408b39c5158Smillert my $args = shift; 1409b39c5158Smillert 1410b39c5158Smillert 1411b39c5158Smillert my $fh = \*STDOUT; 1412b39c5158Smillert 1413b39c5158Smillert INC: foreach my $inc ( @INC ) 1414b39c5158Smillert { 1415b39c5158Smillert my( $wanted, $reporter ) = _generator(); 1416b39c5158Smillert File::Find::find( { wanted => $wanted }, $inc ); 1417b39c5158Smillert 1418b39c5158Smillert my $count = 0; 1419b39c5158Smillert FILE: foreach my $file ( @{ $reporter->() } ) 1420b39c5158Smillert { 1421b39c5158Smillert my $version = _parse_version_safely( $file ); 1422b39c5158Smillert 1423b39c5158Smillert my $module_name = _path_to_module( $inc, $file ); 1424b39c5158Smillert next FILE unless defined $module_name; 1425b39c5158Smillert 1426b39c5158Smillert print $fh "$module_name\t$version\n"; 1427b39c5158Smillert 1428b39c5158Smillert #last if $count++ > 5; 1429b39c5158Smillert } 1430b39c5158Smillert } 1431b39c5158Smillert 1432b39c5158Smillert return HEY_IT_WORKED; 1433b39c5158Smillert } 1434b39c5158Smillert 1435b39c5158Smillertsub _generator 1436b39c5158Smillert { 1437b39c5158Smillert my @files = (); 1438b39c5158Smillert 1439b39c5158Smillert sub { push @files, 1440b39c5158Smillert File::Spec->canonpath( $File::Find::name ) 1441b39c5158Smillert if m/\A\w+\.pm\z/ }, 1442b39c5158Smillert sub { \@files }, 1443b39c5158Smillert } 1444b39c5158Smillert 1445b39c5158Smillertsub _parse_version_safely # stolen from PAUSE's mldistwatch, but refactored 1446b39c5158Smillert { 1447b39c5158Smillert my( $file ) = @_; 1448b39c5158Smillert 1449b39c5158Smillert local $/ = "\n"; 1450b39c5158Smillert local $_; # don't mess with the $_ in the map calling this 1451b39c5158Smillert 1452b39c5158Smillert return unless open FILE, "<$file"; 1453b39c5158Smillert 1454b39c5158Smillert my $in_pod = 0; 1455b39c5158Smillert my $version; 1456b39c5158Smillert while( <FILE> ) 1457b39c5158Smillert { 1458b39c5158Smillert chomp; 1459b39c5158Smillert $in_pod = /^=(?!cut)/ ? 1 : /^=cut/ ? 0 : $in_pod; 1460b39c5158Smillert next if $in_pod || /^\s*#/; 1461b39c5158Smillert 1462b39c5158Smillert next unless /([\$*])(([\w\:\']*)\bVERSION)\b.*\=/; 1463b39c5158Smillert my( $sigil, $var ) = ( $1, $2 ); 1464b39c5158Smillert 1465b39c5158Smillert $version = _eval_version( $_, $sigil, $var ); 1466b39c5158Smillert last; 1467b39c5158Smillert } 1468b39c5158Smillert close FILE; 1469b39c5158Smillert 1470b39c5158Smillert return 'undef' unless defined $version; 1471b39c5158Smillert 1472b39c5158Smillert return $version; 1473b39c5158Smillert } 1474b39c5158Smillert 1475b39c5158Smillertsub _eval_version 1476b39c5158Smillert { 1477b39c5158Smillert my( $line, $sigil, $var ) = @_; 1478b39c5158Smillert 147991f110e0Safresh1 # split package line to hide from PAUSE 1480b39c5158Smillert my $eval = qq{ 148191f110e0Safresh1 package 148291f110e0Safresh1 ExtUtils::MakeMaker::_version; 1483b39c5158Smillert 1484b39c5158Smillert local $sigil$var; 1485b39c5158Smillert \$$var=undef; do { 1486b39c5158Smillert $line 1487b39c5158Smillert }; \$$var 1488b39c5158Smillert }; 1489b39c5158Smillert 1490b39c5158Smillert my $version = do { 1491b39c5158Smillert local $^W = 0; 1492b39c5158Smillert no strict; 1493b39c5158Smillert eval( $eval ); 1494b39c5158Smillert }; 1495b39c5158Smillert 1496b39c5158Smillert return $version; 1497b39c5158Smillert } 1498b39c5158Smillert 1499b39c5158Smillertsub _path_to_module 1500b39c5158Smillert { 1501b39c5158Smillert my( $inc, $path ) = @_; 1502b39c5158Smillert return if length $path < length $inc; 1503b39c5158Smillert 1504b39c5158Smillert my $module_path = substr( $path, length $inc ); 1505b39c5158Smillert $module_path =~ s/\.pm\z//; 1506b39c5158Smillert 1507b39c5158Smillert # XXX: this is cheating and doesn't handle everything right 1508b39c5158Smillert my @dirs = grep { ! /\W/ } File::Spec->splitdir( $module_path ); 1509b39c5158Smillert shift @dirs; 1510b39c5158Smillert 1511b39c5158Smillert my $module_name = join "::", @dirs; 1512b39c5158Smillert 1513b39c5158Smillert return $module_name; 1514b39c5158Smillert } 1515b39c5158Smillert 15169f11ffb7Safresh1 15179f11ffb7Safresh1sub _expand_module 15189f11ffb7Safresh1 { 15199f11ffb7Safresh1 my( $module ) = @_; 15209f11ffb7Safresh1 15219f11ffb7Safresh1 my $expanded = CPAN::Shell->expandany( $module ); 15229f11ffb7Safresh1 return $expanded if $expanded; 15239f11ffb7Safresh1 $expanded = CPAN::Shell->expand( "Module", $module ); 15249f11ffb7Safresh1 unless( defined $expanded ) { 15259f11ffb7Safresh1 $logger->error( "Could not expand [$module]. Check the module name." ); 15269f11ffb7Safresh1 my $threshold = ( 15279f11ffb7Safresh1 grep { int } 15289f11ffb7Safresh1 sort { length $a <=> length $b } 15299f11ffb7Safresh1 length($module)/4, 4 15309f11ffb7Safresh1 )[0]; 15319f11ffb7Safresh1 15329f11ffb7Safresh1 my $guesses = _guess_at_module_name( $module, $threshold ); 15339f11ffb7Safresh1 if( defined $guesses and @$guesses ) { 15349f11ffb7Safresh1 $logger->info( "Perhaps you meant one of these:" ); 15359f11ffb7Safresh1 foreach my $guess ( @$guesses ) { 15369f11ffb7Safresh1 $logger->info( "\t$guess" ); 15379f11ffb7Safresh1 } 15389f11ffb7Safresh1 } 15399f11ffb7Safresh1 return; 15409f11ffb7Safresh1 } 15419f11ffb7Safresh1 15429f11ffb7Safresh1 return $expanded; 15439f11ffb7Safresh1 } 15449f11ffb7Safresh1 15459f11ffb7Safresh1my $guessers = [ 15469f11ffb7Safresh1 [ qw( Text::Levenshtein::XS distance 7 1 ) ], 15479f11ffb7Safresh1 [ qw( Text::Levenshtein::Damerau::XS xs_edistance 7 1 ) ], 15489f11ffb7Safresh1 15499f11ffb7Safresh1 [ qw( Text::Levenshtein distance 7 1 ) ], 15509f11ffb7Safresh1 [ qw( Text::Levenshtein::Damerau::PP pp_edistance 7 1 ) ], 15519f11ffb7Safresh1 15529f11ffb7Safresh1 ]; 15539f11ffb7Safresh1 15549f11ffb7Safresh1sub _disable_guessers 15559f11ffb7Safresh1 { 15569f11ffb7Safresh1 $_->[-1] = 0 for @$guessers; 15579f11ffb7Safresh1 } 15589f11ffb7Safresh1 15599f11ffb7Safresh1# for -x 15609f11ffb7Safresh1sub _guess_namespace 15619f11ffb7Safresh1 { 15629f11ffb7Safresh1 my $args = shift; 15639f11ffb7Safresh1 15649f11ffb7Safresh1 foreach my $arg ( @$args ) 15659f11ffb7Safresh1 { 15669f11ffb7Safresh1 $logger->debug( "Checking $arg" ); 15679f11ffb7Safresh1 my $guesses = _guess_at_module_name( $arg ); 15689f11ffb7Safresh1 15699f11ffb7Safresh1 foreach my $guess ( @$guesses ) { 15709f11ffb7Safresh1 print $guess, "\n"; 15719f11ffb7Safresh1 } 15729f11ffb7Safresh1 } 15739f11ffb7Safresh1 15749f11ffb7Safresh1 return HEY_IT_WORKED; 15759f11ffb7Safresh1 } 15769f11ffb7Safresh1 15779f11ffb7Safresh1sub _list_all_namespaces { 15789f11ffb7Safresh1 my $modules = _get_all_namespaces(); 15799f11ffb7Safresh1 15809f11ffb7Safresh1 foreach my $module ( @$modules ) { 15819f11ffb7Safresh1 print $module, "\n"; 15829f11ffb7Safresh1 } 15839f11ffb7Safresh1 } 15849f11ffb7Safresh1 15859f11ffb7Safresh1BEGIN { 15869f11ffb7Safresh1my $distance; 15879f11ffb7Safresh1my $_threshold; 15889f11ffb7Safresh1my $can_guess; 15899f11ffb7Safresh1my $shown_help = 0; 15909f11ffb7Safresh1sub _guess_at_module_name 15919f11ffb7Safresh1 { 15929f11ffb7Safresh1 my( $target, $threshold ) = @_; 15939f11ffb7Safresh1 15949f11ffb7Safresh1 unless( defined $distance ) { 15959f11ffb7Safresh1 foreach my $try ( @$guessers ) { 15969f11ffb7Safresh1 $can_guess = eval "require $try->[0]; 1" or next; 15979f11ffb7Safresh1 15989f11ffb7Safresh1 $try->[-1] or next; # disabled 15999f11ffb7Safresh1 no strict 'refs'; 16009f11ffb7Safresh1 $distance = \&{ join "::", @$try[0,1] }; 16019f11ffb7Safresh1 $threshold ||= $try->[2]; 16029f11ffb7Safresh1 } 16039f11ffb7Safresh1 } 16049f11ffb7Safresh1 $_threshold ||= $threshold; 16059f11ffb7Safresh1 16069f11ffb7Safresh1 unless( $distance ) { 16079f11ffb7Safresh1 unless( $shown_help ) { 16089f11ffb7Safresh1 my $modules = join ", ", map { $_->[0] } @$guessers; 16099f11ffb7Safresh1 substr $modules, rindex( $modules, ',' ), 1, ', and'; 16109f11ffb7Safresh1 16119f11ffb7Safresh1 # Should this be colorized? 16129f11ffb7Safresh1 if( $can_guess ) { 16139f11ffb7Safresh1 $logger->info( "I can suggest names if you provide the -x option on invocation." ); 16149f11ffb7Safresh1 } 16159f11ffb7Safresh1 else { 16169f11ffb7Safresh1 $logger->info( "I can suggest names if you install one of $modules" ); 16179f11ffb7Safresh1 $logger->info( "and you provide the -x option on invocation." ); 16189f11ffb7Safresh1 } 16199f11ffb7Safresh1 $shown_help++; 16209f11ffb7Safresh1 } 16219f11ffb7Safresh1 return; 16229f11ffb7Safresh1 } 16239f11ffb7Safresh1 16249f11ffb7Safresh1 my $modules = _get_all_namespaces(); 16259f11ffb7Safresh1 $logger->info( "Checking " . @$modules . " namespaces for close match suggestions" ); 16269f11ffb7Safresh1 16279f11ffb7Safresh1 my %guesses; 16289f11ffb7Safresh1 foreach my $guess ( @$modules ) { 16299f11ffb7Safresh1 my $distance = $distance->( $target, $guess ); 16309f11ffb7Safresh1 next if $distance > $_threshold; 16319f11ffb7Safresh1 $guesses{$guess} = $distance; 16329f11ffb7Safresh1 } 16339f11ffb7Safresh1 16349f11ffb7Safresh1 my @guesses = sort { $guesses{$a} <=> $guesses{$b} } keys %guesses; 16359f11ffb7Safresh1 return [ grep { defined } @guesses[0..9] ]; 16369f11ffb7Safresh1 } 16379f11ffb7Safresh1} 16389f11ffb7Safresh1 1639b39c5158Smillert1; 1640b39c5158Smillert 1641b39c5158Smillert=back 1642b39c5158Smillert 1643b39c5158Smillert=head1 EXIT VALUES 1644b39c5158Smillert 1645b39c5158SmillertThe script exits with zero if it thinks that everything worked, or a 1646b39c5158Smillertpositive number if it thinks that something failed. Note, however, that 1647b39c5158Smillertin some cases it has to divine a failure by the output of things it does 1648b39c5158Smillertnot control. For now, the exit codes are vague: 1649b39c5158Smillert 1650b39c5158Smillert 1 An unknown error 1651b39c5158Smillert 1652b39c5158Smillert 2 The was an external problem 1653b39c5158Smillert 1654b39c5158Smillert 4 There was an internal problem with the script 1655b39c5158Smillert 1656b39c5158Smillert 8 A module failed to install 1657b39c5158Smillert 1658b39c5158Smillert=head1 TO DO 1659b39c5158Smillert 1660b39c5158Smillert* There is initial support for Log4perl if it is available, but I 1661b39c5158Smillerthaven't gone through everything to make the NullLogger work out 1662b39c5158Smillertcorrectly if Log4perl is not installed. 1663b39c5158Smillert 1664b39c5158Smillert* When I capture CPAN.pm output, I need to check for errors and 1665b39c5158Smillertreport them to the user. 1666b39c5158Smillert 166791f110e0Safresh1* Warnings switch 166891f110e0Safresh1 166991f110e0Safresh1* Check then exit 167091f110e0Safresh1 1671b39c5158Smillert=head1 BUGS 1672b39c5158Smillert 1673b39c5158Smillert* none noted 1674b39c5158Smillert 1675b39c5158Smillert=head1 SEE ALSO 1676b39c5158Smillert 1677b8851fccSafresh1L<CPAN>, L<App::cpanminus> 1678b39c5158Smillert 1679b39c5158Smillert=head1 SOURCE AVAILABILITY 1680b39c5158Smillert 1681b8851fccSafresh1This code is in Github in the CPAN.pm repository: 1682b39c5158Smillert 1683b8851fccSafresh1 https://github.com/andk/cpanpm 1684b8851fccSafresh1 1685b8851fccSafresh1The source used to be tracked separately in another GitHub repo, 1686b8851fccSafresh1but the canonical source is now in the above repo. 1687b39c5158Smillert 1688b39c5158Smillert=head1 CREDITS 1689b39c5158Smillert 169091f110e0Safresh1Japheth Cleaver added the bits to allow a forced install (C<-f>). 1691b39c5158Smillert 1692*eac174f2Safresh1Jim Brandt suggested and provided the initial implementation for the 1693b39c5158Smillertup-to-date and Changes features. 1694b39c5158Smillert 169591f110e0Safresh1Adam Kennedy pointed out that C<exit()> causes problems on Windows 1696b39c5158Smillertwhere this script ends up with a .bat extension 1697b39c5158Smillert 169891f110e0Safresh1David Golden helps integrate this into the C<CPAN.pm> repos. 169991f110e0Safresh1 1700b46d8ef2Safresh1Jim Keenan fixed up various issues with _download 1701b46d8ef2Safresh1 1702b39c5158Smillert=head1 AUTHOR 1703b39c5158Smillert 1704b39c5158Smillertbrian d foy, C<< <bdfoy@cpan.org> >> 1705b39c5158Smillert 1706b39c5158Smillert=head1 COPYRIGHT 1707b39c5158Smillert 1708*eac174f2Safresh1Copyright (c) 2001-2021, brian d foy, All Rights Reserved. 1709b39c5158Smillert 1710b39c5158SmillertYou may redistribute this under the same terms as Perl itself. 1711b39c5158Smillert 1712b39c5158Smillert=cut 171356d68f1eSafresh1 171456d68f1eSafresh1# Local Variables: 171556d68f1eSafresh1# mode: cperl 171656d68f1eSafresh1# indent-tabs-mode: t 171756d68f1eSafresh1# cperl-indent-level: 8 171856d68f1eSafresh1# cperl-continued-statement-offset: 8 171956d68f1eSafresh1# End: 1720