xref: /openbsd-src/gnu/usr.bin/perl/cpan/CPAN/lib/App/Cpan.pm (revision eac174f2741a08d8deb8aae59a7f778ef9b5d770)
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