File Coverage

blib/lib/App/Cpan.pm
Criterion Covered Total %
statement 123 499 24.6
branch 10 136 7.3
condition 8 40 20.0
subroutine 41 92 44.5
pod 1 1 100.0
total 183 768 23.8


line stmt bran cond sub pod time code
1             package App::Cpan;
2 3     3   29735 use strict;
  3         6  
  3         122  
3 3     3   17 use warnings;
  3         7  
  3         122  
4 3     3   17 use vars qw($VERSION);
  3         5  
  3         358  
5              
6             $VERSION = '1.58';
7              
8             =head1 NAME
9              
10             App::Cpan - easily interact with CPAN from the command line
11              
12             =head1 SYNOPSIS
13              
14             # with arguments and no switches, installs specified modules
15             cpan module_name [ module_name ... ]
16              
17             # with switches, installs modules with extra behavior
18             cpan [-cfFimtTw] module_name [ module_name ... ]
19              
20             # use local::lib
21             cpan -I module_name [ module_name ... ]
22              
23             # with just the dot, install from the distribution in the
24             # current directory
25             cpan .
26              
27             # without arguments, starts CPAN.pm shell
28             cpan
29              
30             # without arguments, but some switches
31             cpan [-ahpruvACDLOP]
32              
33             =head1 DESCRIPTION
34              
35             This script provides a command interface (not a shell) to CPAN. At the
36             moment it uses CPAN.pm to do the work, but it is not a one-shot command
37             runner for CPAN.pm.
38              
39             =head2 Options
40              
41             =over 4
42              
43             =item -a
44              
45             Creates a CPAN.pm autobundle with CPAN::Shell->autobundle.
46              
47             =item -A module [ module ... ]
48              
49             Shows the primary maintainers for the specified modules.
50              
51             =item -c module
52              
53             Runs a `make clean` in the specified module's directories.
54              
55             =item -C module [ module ... ]
56              
57             Show the F files for the specified modules
58              
59             =item -D module [ module ... ]
60              
61             Show the module details. This prints one line for each out-of-date module
62             (meaning, modules locally installed but have newer versions on CPAN).
63             Each line has three columns: module name, local version, and CPAN
64             version.
65              
66             =item -f
67              
68             Force the specified action, when it normally would have failed. Use this
69             to install a module even if its tests fail. When you use this option,
70             -i is not optional for installing a module when you need to force it:
71              
72             % cpan -f -i Module::Foo
73              
74             =item -F
75              
76             Turn off CPAN.pm's attempts to lock anything. You should be careful with
77             this since you might end up with multiple scripts trying to muck in the
78             same directory. This isn't so much of a concern if you're loading a special
79             config with C<-j>, and that config sets up its own work directories.
80              
81             =item -g module [ module ... ]
82              
83             Downloads to the current directory the latest distribution of the module.
84              
85             =item -G module [ module ... ]
86              
87             UNIMPLEMENTED
88              
89             Download to the current directory the latest distribution of the
90             modules, unpack each distribution, and create a git repository for each
91             distribution.
92              
93             If you want this feature, check out Yanick Champoux's C
94             distribution.
95              
96             =item -h
97              
98             Print a help message and exit. When you specify C<-h>, it ignores all
99             of the other options and arguments.
100              
101             =item -i
102              
103             Install the specified modules. With no other switches, this switch
104             is implied.
105              
106             =item -I
107              
108             Load C (think like C<-I> for loading lib paths). Too bad
109             C<-l> was already taken.
110              
111             =item -j Config.pm
112              
113             Load the file that has the CPAN configuration data. This should have the
114             same format as the standard F file, which defines
115             C<$CPAN::Config> as an anonymous hash.
116              
117             =item -J
118              
119             Dump the configuration in the same format that CPAN.pm uses. This is useful
120             for checking the configuration as well as using the dump as a starting point
121             for a new, custom configuration.
122              
123             =item -l
124              
125             List all installed modules wth their versions
126              
127             =item -L author [ author ... ]
128              
129             List the modules by the specified authors.
130              
131             =item -m
132              
133             Make the specified modules.
134              
135             =item -n
136              
137             Do a dry run, but don't actually install anything. (unimplemented)
138              
139             =item -O
140              
141             Show the out-of-date modules.
142              
143             =item -p
144              
145             Ping the configured mirrors
146              
147             =item -P
148              
149             Find the best mirrors you could be using (but doesn't configure them just yet)
150              
151             =item -r
152              
153             Recompiles dynamically loaded modules with CPAN::Shell->recompile.
154              
155             =item -t
156              
157             Run a `make test` on the specified modules.
158              
159             =item -T
160              
161             Do not test modules. Simply install them.
162              
163             =item -u
164              
165             Upgrade all installed modules. Blindly doing this can really break things,
166             so keep a backup.
167              
168             =item -v
169              
170             Print the script version and CPAN.pm version then exit.
171              
172             =item -V
173              
174             Print detailed information about the cpan client.
175              
176             =item -w
177              
178             UNIMPLEMENTED
179              
180             Turn on cpan warnings. This checks various things, like directory permissions,
181             and tells you about problems you might have.
182              
183             =back
184              
185             =head2 Examples
186              
187             # print a help message
188             cpan -h
189              
190             # print the version numbers
191             cpan -v
192              
193             # create an autobundle
194             cpan -a
195              
196             # recompile modules
197             cpan -r
198              
199             # upgrade all installed modules
200             cpan -u
201              
202             # install modules ( sole -i is optional )
203             cpan -i Netscape::Booksmarks Business::ISBN
204              
205             # force install modules ( must use -i )
206             cpan -fi CGI::Minimal URI
207              
208              
209             =head2 Methods
210              
211             =over 4
212              
213             =cut
214              
215 3     3   2835 use autouse Carp => qw(carp croak cluck);
  3         2623  
  3         18  
216 3     3   423976 use CPAN ();
  3         1283177  
  3         367  
217 3     3   105 use Config;
  3         17  
  3         289  
218 3     3   27 use autouse Cwd => qw(cwd);
  3         18  
  3         53  
219 3     3   14345 use autouse 'Data::Dumper' => qw(Dumper);
  3         7  
  3         37  
220 3     3   4568 use File::Spec::Functions;
  3         3890  
  3         407  
221 3     3   43 use File::Basename;
  3         21  
  3         338  
222 3     3   23117 use Getopt::Std;
  3         218  
  3         262  
223              
224             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
225             # Internal constants
226 3     3   20 use constant TRUE => 1;
  3         16  
  3         354  
227 3     3   18 use constant FALSE => 0;
  3         5  
  3         195  
228              
229              
230             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
231             # The return values
232 3     3   19 use constant HEY_IT_WORKED => 0;
  3         15  
  3         161  
233 3     3   18 use constant I_DONT_KNOW_WHAT_HAPPENED => 1; # 0b0000_0001
  3         8  
  3         143  
234 3     3   21 use constant ITS_NOT_MY_FAULT => 2;
  3         7  
  3         158  
235 3     3   25 use constant THE_PROGRAMMERS_AN_IDIOT => 4;
  3         10  
  3         157  
236 3     3   24 use constant A_MODULE_FAILED_TO_INSTALL => 8;
  3         6  
  3         225  
237              
238              
239             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
240             # set up the order of options that we layer over CPAN::Shell
241             BEGIN { # most of this should be in methods
242 3         2645 use vars qw( @META_OPTIONS $Default %CPAN_METHODS @CPAN_OPTIONS @option_order
243 3     3   22 %Method_table %Method_table_index );
  3         7  
244              
245 3     3   22 @META_OPTIONS = qw( h v V I g G C A D O l L a r p P j: J w T);
246              
247 3         69 $Default = 'default';
248              
249 3         79 %CPAN_METHODS = ( # map switches to method names in CPAN::Shell
250             $Default => 'install',
251             'c' => 'clean',
252             'f' => 'force',
253             'i' => 'install',
254             'm' => 'make',
255             't' => 'test',
256             'u' => 'upgrade',
257             );
258 3         30 @CPAN_OPTIONS = grep { $_ ne $Default } sort keys %CPAN_METHODS;
  21         53  
259              
260 3         23 @option_order = ( @META_OPTIONS, @CPAN_OPTIONS );
261              
262              
263             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
264             # map switches to the subroutines in this script, along with other information.
265             # use this stuff instead of hard-coded indices and values
266             sub NO_ARGS () { 0 }
267             sub ARGS () { 1 }
268             sub GOOD_EXIT () { 0 }
269              
270 3         273 %Method_table = (
271             # key => [ sub ref, takes args?, exit value, description ]
272              
273             # options that do their thing first, then exit
274             h => [ \&_print_help, NO_ARGS, GOOD_EXIT, 'Printing help' ],
275             v => [ \&_print_version, NO_ARGS, GOOD_EXIT, 'Printing version' ],
276             V => [ \&_print_details, NO_ARGS, GOOD_EXIT, 'Printing detailed version' ],
277              
278             # options that affect other options
279             j => [ \&_load_config, ARGS, GOOD_EXIT, 'Use specified config file' ],
280             J => [ \&_dump_config, NO_ARGS, GOOD_EXIT, 'Dump configuration to stdout' ],
281             F => [ \&_lock_lobotomy, NO_ARGS, GOOD_EXIT, 'Turn off CPAN.pm lock files' ],
282             I => [ \&_load_local_lib, NO_ARGS, GOOD_EXIT, 'Loading local::lib' ],
283             w => [ \&_turn_on_warnings, NO_ARGS, GOOD_EXIT, 'Turning on warnings' ],
284             T => [ \&_turn_off_testing, NO_ARGS, GOOD_EXIT, 'Turning off testing' ],
285              
286             # options that do their one thing
287             g => [ \&_download, NO_ARGS, GOOD_EXIT, 'Download the latest distro' ],
288             G => [ \&_gitify, NO_ARGS, GOOD_EXIT, 'Down and gitify the latest distro' ],
289              
290             C => [ \&_show_Changes, ARGS, GOOD_EXIT, 'Showing Changes file' ],
291             A => [ \&_show_Author, ARGS, GOOD_EXIT, 'Showing Author' ],
292             D => [ \&_show_Details, ARGS, GOOD_EXIT, 'Showing Details' ],
293             O => [ \&_show_out_of_date, NO_ARGS, GOOD_EXIT, 'Showing Out of date' ],
294             l => [ \&_list_all_mods, NO_ARGS, GOOD_EXIT, 'Listing all modules' ],
295              
296             L => [ \&_show_author_mods, ARGS, GOOD_EXIT, 'Showing author mods' ],
297             a => [ \&_create_autobundle, NO_ARGS, GOOD_EXIT, 'Creating autobundle' ],
298             p => [ \&_ping_mirrors, NO_ARGS, GOOD_EXIT, 'Pinging mirrors' ],
299             P => [ \&_find_good_mirrors, NO_ARGS, GOOD_EXIT, 'Finding good mirrors' ],
300              
301             r => [ \&_recompile, NO_ARGS, GOOD_EXIT, 'Recompiling' ],
302             u => [ \&_upgrade, NO_ARGS, GOOD_EXIT, 'Running `make test`' ],
303              
304             c => [ \&_default, ARGS, GOOD_EXIT, 'Running `make clean`' ],
305             f => [ \&_default, ARGS, GOOD_EXIT, 'Installing with force' ],
306             i => [ \&_default, ARGS, GOOD_EXIT, 'Running `make install`' ],
307             'm' => [ \&_default, ARGS, GOOD_EXIT, 'Running `make`' ],
308             t => [ \&_default, ARGS, GOOD_EXIT, 'Running `make test`' ],
309             );
310              
311 3         176 %Method_table_index = (
312             code => 0,
313             takes_args => 1,
314             exit_value => 2,
315             description => 3,
316             );
317             }
318              
319              
320             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
321             # finally, do some argument processing
322              
323             sub _stupid_interface_hack_for_non_rtfmers
324             {
325 3     3   28 no warnings 'uninitialized';
  3         8  
  3         2054  
326 4 100 100 4   4853 shift @ARGV if( $ARGV[0] eq 'install' and @ARGV > 1 )
327             }
328              
329             sub _process_options
330             {
331 1     1   36 my %options;
332              
333 1   50     10 push @ARGV, grep $_, split /\s+/, $ENV{CPAN_OPTS} || '';
334              
335             # if no arguments, just drop into the shell
336 1 50       5 if( 0 == @ARGV ) { CPAN::shell(); exit 0 }
  1         7  
  1         43  
337             else
338             {
339 0         0 Getopt::Std::getopts(
340             join( '', @option_order ), \%options );
341 0         0 \%options;
342             }
343             }
344              
345             sub _process_setup_options
346             {
347 0     0   0 my( $class, $options ) = @_;
348              
349 0 0       0 if( $options->{j} )
350             {
351 0         0 $Method_table{j}[ $Method_table_index{code} ]->( $options->{j} );
352 0         0 delete $options->{j};
353             }
354             else
355             {
356             # this is what CPAN.pm would do otherwise
357 0         0 CPAN::HandleConfig->load(
358             be_silent => 1,
359             write_file => 0,
360             );
361             }
362              
363 0         0 foreach my $o ( qw(F I w T) )
364             {
365 0 0       0 next unless exists $options->{$o};
366 0         0 $Method_table{$o}[ $Method_table_index{code} ]->( $options->{$o} );
367 0         0 delete $options->{$o};
368             }
369              
370 0 0       0 if( $options->{o} )
371             {
372 0         0 my @pairs = map { [ split /=/, $_, 2 ] } split /,/, $options->{o};
  0         0  
373 0         0 foreach my $pair ( @pairs )
374             {
375 0         0 my( $setting, $value ) = @$pair;
376 0         0 $CPAN::Config->{$setting} = $value;
377             # $logger->debug( "Setting [$setting] to [$value]" );
378             }
379 0         0 delete $options->{o};
380             }
381              
382 0         0 my $option_count = grep { $options->{$_} } @option_order;
  0         0  
383 3     3   21 no warnings 'uninitialized';
  3         5  
  3         3419  
384 0         0 $option_count -= $options->{'f'}; # don't count force
385              
386             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
387             # if there are no options, set -i (this line fixes RT ticket 16915)
388 0 0       0 $options->{i}++ unless $option_count;
389             }
390              
391              
392             =item run()
393              
394             Just do it.
395              
396             The C method returns 0 on success and a postive number on
397             failure. See the section on EXIT CODES for details on the values.
398              
399             =cut
400              
401             my $logger;
402              
403             sub run
404             {
405 0     0 1 0 my $class = shift;
406              
407 0         0 my $return_value = HEY_IT_WORKED; # assume that things will work
408              
409 0         0 $logger = $class->_init_logger;
410 0         0 $logger->debug( "Using logger from @{[ref $logger]}" );
  0         0  
411              
412 0         0 $class->_hook_into_CPANpm_report;
413 0         0 $logger->debug( "Hooked into output" );
414              
415 0         0 $class->_stupid_interface_hack_for_non_rtfmers;
416 0         0 $logger->debug( "Patched cargo culting" );
417              
418 0         0 my $options = $class->_process_options;
419 0         0 $logger->debug( "Options are @{[Dumper($options)]}" );
  0         0  
420              
421 0         0 $class->_process_setup_options( $options );
422              
423 0         0 OPTION: foreach my $option ( @option_order )
424             {
425 0 0       0 next unless $options->{$option};
426              
427 0         0 my( $sub, $takes_args, $description ) =
428 0         0 map { $Method_table{$option}[ $Method_table_index{$_} ] }
429             qw( code takes_args );
430              
431 0 0   0   0 unless( ref $sub eq ref sub {} )
  0         0  
432             {
433 0         0 $return_value = THE_PROGRAMMERS_AN_IDIOT;
434 0         0 last OPTION;
435             }
436              
437 0 0 0     0 $logger->info( "$description -- ignoring other arguments" )
438             if( @ARGV && ! $takes_args );
439              
440 0         0 $return_value = $sub->( \ @ARGV, $options );
441              
442 0         0 last;
443             }
444              
445 0         0 return $return_value;
446             }
447              
448             {
449             package Local::Null::Logger;
450              
451 1     1   5 sub new { bless \ my $x, $_[0] }
452 29     29   51 sub AUTOLOAD { 1 }
453 0     0   0 sub DESTROY { 1 }
454             }
455              
456             sub _init_logger
457             {
458 1     1   111 my $log4perl_loaded = eval "require Log::Log4perl; 1";
459              
460 1 50       13 unless( $log4perl_loaded )
461             {
462 1         11 $logger = Local::Null::Logger->new;
463 1         4 return $logger;
464             }
465              
466 0   0     0 my $LEVEL = $ENV{CPANSCRIPT_LOGLEVEL} || 'INFO';
467              
468 0         0 Log::Log4perl::init( \ <<"HERE" );
469             log4perl.rootLogger=$LEVEL, A1
470             log4perl.appender.A1=Log::Log4perl::Appender::Screen
471             log4perl.appender.A1.layout=PatternLayout
472             log4perl.appender.A1.layout.ConversionPattern=%m%n
473             HERE
474              
475 0         0 $logger = Log::Log4perl->get_logger( 'App::Cpan' );
476             }
477              
478             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
479             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
480             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
481              
482             sub _default
483             {
484 0     0   0 my( $args, $options ) = @_;
485              
486 0         0 my $switch = '';
487              
488             # choose the option that we're going to use
489             # we'll deal with 'f' (force) later, so skip it
490 0         0 foreach my $option ( @CPAN_OPTIONS )
491             {
492 0 0       0 next if $option eq 'f';
493 0 0       0 next unless $options->{$option};
494 0         0 $switch = $option;
495 0         0 last;
496             }
497              
498             # 1. with no switches, but arguments, use the default switch (install)
499             # 2. with no switches and no args, start the shell
500             # 3. With a switch but no args, die! These switches need arguments.
501 0 0 0     0 if( not $switch and @$args ) { $switch = $Default; }
  0 0 0     0  
    0 0        
502 0         0 elsif( not $switch and not @$args ) { return CPAN::shell() }
503             elsif( $switch and not @$args )
504 0         0 { die "Nothing to $CPAN_METHODS{$switch}!\n"; }
505              
506             # Get and check the method from CPAN::Shell
507 0         0 my $method = $CPAN_METHODS{$switch};
508 0 0       0 die "CPAN.pm cannot $method!\n" unless CPAN::Shell->can( $method );
509              
510             # call the CPAN::Shell method, with force if specified
511 0         0 my $action = do {
512 0 0   0   0 if( $options->{f} ) { sub { CPAN::Shell->force( $method, @_ ) } }
  0         0  
  0         0  
513 0     0   0 else { sub { CPAN::Shell->$method( @_ ) } }
  0         0  
514             };
515              
516             # How do I handle exit codes for multiple arguments?
517 0         0 my $errors = 0;
518              
519 0         0 foreach my $arg ( @$args )
520             {
521 0         0 _clear_cpanpm_output();
522 0         0 $action->( $arg );
523              
524 0         0 $errors += defined _cpanpm_output_indicates_failure();
525             }
526              
527 0 0       0 $errors ? I_DONT_KNOW_WHAT_HAPPENED : HEY_IT_WORKED;
528             }
529              
530             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
531              
532             =for comment
533              
534             CPAN.pm sends all the good stuff either to STDOUT, or to a temp
535             file if $CPAN::Be_Silent is set. I have to intercept that output
536             so I can find out what happened.
537              
538             =cut
539              
540             BEGIN {
541 3     3   11 my $scalar = '';
542              
543             sub _hook_into_CPANpm_report
544             {
545 3     3   22 no warnings 'redefine';
  3         7  
  3         1475  
546              
547             *CPAN::Shell::myprint = sub {
548 18     18   1737 my($self,$what) = @_;
549 18         32 $scalar .= $what;
550 18   100     99 $self->print_ornamented($what,
551             $CPAN::Config->{colorize_print}||'bold blue on_white',
552             );
553 1     1   16150 };
554              
555             *CPAN::Shell::mywarn = sub {
556 14     14   6151 my($self,$what) = @_;
557 14         25 $scalar .= $what;
558 14   100     92 $self->print_ornamented($what,
559             $CPAN::Config->{colorize_warn}||'bold red on_white'
560             );
561 1         25 };
562              
563             }
564              
565 8     8   5378 sub _clear_cpanpm_output { $scalar = '' }
566              
567 11     11   55 sub _get_cpanpm_output { $scalar }
568              
569 3         280 my @skip_lines = (
570             qr/^\QWarning \(usually harmless\)/,
571             qr/\bwill not store persistent state\b/,
572             qr(//hint//),
573             qr/^\s+reports\s+/,
574             );
575              
576             sub _get_cpanpm_last_line
577             {
578 1     1   7 open my($fh), "<", \ $scalar;
  1     29   3  
  1         8  
  29         425  
579              
580 29         2120 my @lines = <$fh>;
581              
582             # This is a bit ugly. Once we examine a line, we have to
583             # examine the line before it and go through all of the same
584             # regexes. I could do something fancy, but this works.
585             REGEXES: {
586 29         50 foreach my $regex ( @skip_lines )
  38         53  
587             {
588 140 100       644 if( $lines[-1] =~ m/$regex/ )
589             {
590 9         12 pop @lines;
591 9         18 redo REGEXES; # we have to go through all of them for every line!
592             }
593             }
594             }
595              
596 29         175 $logger->debug( "Last interesting line of CPAN.pm output is:\n\t$lines[-1]" );
597              
598 29         168 $lines[-1];
599             }
600             }
601              
602             BEGIN {
603 3     3   8423 my $epic_fail_words = join '|',
604             qw( Error stop(?:ping)? problems force not unsupported fail(?:ed)? );
605              
606             sub _cpanpm_output_indicates_failure
607             {
608 9     9   5107 my $last_line = _get_cpanpm_last_line();
609              
610 9         143 my $result = $last_line =~ /\b(?:$epic_fail_words)\b/i;
611 9 100       44 $result || ();
612             }
613             }
614              
615             sub _cpanpm_output_indicates_success
616             {
617 9     9   10004 my $last_line = _get_cpanpm_last_line();
618              
619 9         60 my $result = $last_line =~ /\b(?:\s+-- OK|PASS)\b/;
620 9 100       45 $result || ();
621             }
622              
623             sub _cpanpm_output_is_vague
624             {
625 0 0 0 0     return FALSE if
626             _cpanpm_output_indicates_failure() ||
627             _cpanpm_output_indicates_success();
628              
629 0           return TRUE;
630             }
631              
632             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
633             sub _turn_on_warnings {
634 0     0     carp "Warnings are implemented yet";
635 0           return HEY_IT_WORKED;
636             }
637              
638             sub _turn_off_testing {
639 0     0     $logger->debug( 'Trusting test report history' );
640 0           $CPAN::Config->{trust_test_report_history} = 1;
641 0           return HEY_IT_WORKED;
642             }
643              
644             # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
645             sub _print_help
646             {
647 0     0     $logger->info( "Use perldoc to read the documentation" );
648 0           exec "perldoc $0";
649             }
650              
651             sub _print_version # -v
652             {
653 0     0     $logger->info(
654             "$0 script version $VERSION, CPAN.pm version " . CPAN->VERSION );
655              
656 0           return HEY_IT_WORKED;
657             }
658              
659             sub _print_details # -V
660             {
661 0     0     _print_version();
662              
663 0           _check_install_dirs();
664              
665 0           $logger->info( '-' x 50 . "\nChecking configured mirrors..." );
666 0           foreach my $mirror ( @{ $CPAN::Config->{urllist} } ) {
  0            
667 0           _print_ping_report( $mirror );
668             }
669              
670 0           $logger->info( '-' x 50 . "\nChecking for faster mirrors..." );
671              
672             {
673 0           require CPAN::Mirrors;
  0            
674              
675 0 0         if ( $CPAN::Config->{connect_to_internet_ok} ) {
676 0           $CPAN::Frontend->myprint(qq{Trying to fetch a mirror list from the Internet\n});
677 0 0         eval { CPAN::FTP->localize('MIRRORED.BY',File::Spec->catfile($CPAN::Config->{keep_source_where},'MIRRORED.BY'),3,1) }
  0            
678             or $CPAN::Frontend->mywarn(<<'HERE');
679             We failed to get a copy of the mirror list from the Internet.
680             You will need to provide CPAN mirror URLs yourself.
681             HERE
682 0           $CPAN::Frontend->myprint("\n");
683             }
684              
685 0           my $mirrors = CPAN::Mirrors->new( );
686 0           $mirrors->parse_mirrored_by( File::Spec->catfile($CPAN::Config->{keep_source_where},'MIRRORED.BY') );
687 0           my @continents = $mirrors->find_best_continents;
688              
689 0           my @mirrors = $mirrors->get_mirrors_by_continents( $continents[0] );
690 0           my @timings = $mirrors->get_mirrors_timings( \@mirrors );
691              
692 0           foreach my $timing ( @timings ) {
693 0           $logger->info( sprintf "%s (%0.2f ms)",
694             $timing->hostname, $timing->rtt );
695             }
696             }
697              
698 0           return HEY_IT_WORKED;
699             }
700              
701             sub _check_install_dirs
702             {
703 0     0     my $makepl_arg = $CPAN::Config->{makepl_arg};
704 0           my $mbuildpl_arg = $CPAN::Config->{mbuildpl_arg};
705              
706 0           my @custom_dirs;
707             # PERL_MM_OPT
708 0           push @custom_dirs,
709             $makepl_arg =~ m/INSTALL_BASE\s*=\s*(\S+)/g,
710             $mbuildpl_arg =~ m/--install_base\s*=\s*(\S+)/g;
711              
712 0 0         if( @custom_dirs ) {
713 0           foreach my $dir ( @custom_dirs ) {
714 0           _print_inc_dir_report( $dir );
715             }
716             }
717              
718             # XXX: also need to check makepl_args, etc
719              
720 0           my @checks = (
721             [ 'core', [ grep $_, @Config{qw(installprivlib installarchlib)} ] ],
722             [ 'vendor', [ grep $_, @Config{qw(installvendorlib installvendorarch)} ] ],
723             [ 'site', [ grep $_, @Config{qw(installsitelib installsitearch)} ] ],
724             [ 'PERL5LIB', _split_paths( $ENV{PERL5LIB} ) ],
725             [ 'PERLLIB', _split_paths( $ENV{PERLLIB} ) ],
726             );
727              
728 0           $logger->info( '-' x 50 . "\nChecking install dirs..." );
729 0           foreach my $tuple ( @checks ) {
730 0           my( $label ) = $tuple->[0];
731              
732 0           $logger->info( "Checking $label" );
733 0 0         $logger->info( "\tno directories for $label" ) unless @{ $tuple->[1] };
  0            
734 0           foreach my $dir ( @{ $tuple->[1] } ) {
  0            
735 0           _print_inc_dir_report( $dir );
736             }
737             }
738              
739             }
740              
741             sub _split_paths
742             {
743 0   0 0     [ map { _expand_filename( $_ ) } split /$Config{path_sep}/, $_[0] || '' ];
  0            
744             }
745              
746              
747             =pod
748              
749             Stolen from File::Path::Expand
750              
751             =cut
752              
753             sub _expand_filename
754             {
755 0     0     my( $path )= @_;
756 0           print STDERR "\tExpanding $path\n";
757 0           $path =~ s{\A~([^/]+)?}{
758 0 0 0       _home_of( $1 || $> ) || ${^MATCH}
759             }pe;
760 0           return $path;
761             }
762              
763             sub _home_of
764             {
765 0     0     require User::pwent;
766 0           my( $user ) = @_;
767 0 0         my $ent = User::pwent::getpw($user) or return;
768 0           return $ent->dir;
769             }
770              
771             sub _get_default_inc
772             {
773 0     0     require Config;
774              
775 0           [ @Config::Config{ _vars() }, '.' ];
776             }
777              
778             sub _vars {
779 0     0     qw(
780             installarchlib
781             installprivlib
782             installsitearch
783             installsitelib
784             );
785             }
786              
787             sub _ping_mirrors {
788 0     0     my $urls = $CPAN::Config->{urllist};
789 0           require URI;
790              
791 0           foreach my $url ( @$urls ) {
792 0           my( $obj ) = URI->new( $url );
793 0 0         next unless _is_pingable_scheme( $obj );
794 0           my $host = $obj->host;
795 0           _print_ping_report( $obj );
796             }
797              
798             }
799              
800             sub _is_pingable_scheme {
801 0     0     my( $uri ) = @_;
802              
803 0           $uri->scheme eq 'file'
804             }
805              
806             sub _find_good_mirrors {
807 0     0     require CPAN::Mirrors;
808              
809 0           my $mirrors = CPAN::Mirrors->new;
810 0           my $file = do {
811 0           my $file = 'MIRRORED.BY';
812 0           my $local_path = File::Spec->catfile(
813             $CPAN::Config->{keep_source_where}, $file );
814              
815 0 0         if( -e $local_path ) { $local_path }
  0            
816             else {
817 0           require CPAN::FTP;
818 0           CPAN::FTP->localize( $file, $local_path, 3, 1 );
819 0           $local_path;
820             }
821             };
822              
823 0           $mirrors->parse_mirrored_by( $file );
824              
825 0           my @mirrors = $mirrors->best_mirrors(
826             how_many => 3,
827             verbose => 1,
828             );
829              
830 0           foreach my $mirror ( @mirrors ) {
831 0 0         next unless eval { $mirror->can( 'http' ) };
  0            
832 0           _print_ping_report( $mirror->http );
833             }
834              
835             }
836              
837             sub _print_inc_dir_report
838             {
839 0     0     my( $dir ) = shift;
840              
841 0 0         my $writeable = -w $dir ? '+' : '!!! (not writeable)';
842 0           $logger->info( "\t$writeable $dir" );
843 0           return -w $dir;
844             }
845              
846             sub _print_ping_report
847             {
848 0     0     my( $mirror ) = @_;
849              
850 0           my $rtt = eval { _get_ping_report( $mirror ) };
  0            
851              
852 0 0         $logger->info(
853             sprintf "\t%s (%4d ms) %s", $rtt ? '+' : '!', $rtt * 1000, $mirror
854             );
855             }
856              
857             sub _get_ping_report
858             {
859 0     0     require URI;
860 0           my( $mirror ) = @_;
861 0 0         my( $url ) = ref $mirror ? $mirror : URI->new( $mirror ); #XXX
862 0           require Net::Ping;
863              
864 0           my $ping = Net::Ping->new( 'tcp', 1 );
865              
866 0 0         if( $url->scheme eq 'file' ) {
867 0           return -e $url->file;
868             }
869              
870 0           my( $port ) = $url->port;
871              
872 0 0         return unless $port;
873              
874 0 0         if ( $ping->can('port_number') ) {
875 0           $ping->port_number($port);
876             }
877             else {
878 0           $ping->{'port_num'} = $port;
879             }
880              
881 0 0         $ping->hires(1) if $ping->can( 'hires' );
882 0           my( $alive, $rtt ) = eval{ $ping->ping( $url->host ) };
  0            
883 0 0         $alive ? $rtt : undef;
884             }
885              
886             sub _load_local_lib # -I
887             {
888 0     0     $logger->debug( "Loading local::lib" );
889              
890 0           my $rc = eval { require local::lib; 1; };
  0            
  0            
891 0 0         unless( $rc ) {
892 0           $logger->warn( "Could not load local::lib" );
893             }
894              
895 0           local::lib->import;
896              
897 0           return HEY_IT_WORKED;
898             }
899              
900             sub _create_autobundle
901             {
902 0     0     $logger->info(
903             "Creating autobundle in $CPAN::Config->{cpan_home}/Bundle" );
904              
905 0           CPAN::Shell->autobundle;
906              
907 0           return HEY_IT_WORKED;
908             }
909              
910             sub _recompile
911             {
912 0     0     $logger->info( "Recompiling dynamically-loaded extensions" );
913              
914 0           CPAN::Shell->recompile;
915              
916 0           return HEY_IT_WORKED;
917             }
918              
919             sub _upgrade
920             {
921 0     0     $logger->info( "Upgrading all modules" );
922              
923 0           CPAN::Shell->upgrade();
924              
925 0           return HEY_IT_WORKED;
926             }
927              
928             sub _load_config # -j
929             {
930 0   0 0     my $file = shift || '';
931              
932             # should I clear out any existing config here?
933 0           $CPAN::Config = {};
934 0           delete $INC{'CPAN/Config.pm'};
935 0 0         croak( "Config file [$file] does not exist!\n" ) unless -e $file;
936              
937 0           my $rc = eval "require '$file'";
938              
939             # CPAN::HandleConfig::require_myconfig_or_config looks for this
940 0           $INC{'CPAN/MyConfig.pm'} = 'fake out!';
941              
942             # CPAN::HandleConfig::load looks for this
943 0           $CPAN::Config_loaded = 'fake out';
944              
945 0 0         croak( "Could not load [$file]: $@\n") unless $rc;
946              
947 0           return HEY_IT_WORKED;
948             }
949              
950             sub _dump_config # -J
951             {
952 0     0     my $args = shift;
953 0           require Data::Dumper;
954              
955 0   0       my $fh = $args->[0] || \*STDOUT;
956              
957 0           my $dd = Data::Dumper->new(
958             [$CPAN::Config],
959             ['$CPAN::Config']
960             );
961              
962 0           print $fh $dd->Dump, "\n1;\n__END__\n";
963              
964 0           return HEY_IT_WORKED;
965             }
966              
967             sub _lock_lobotomy # -F
968             {
969 3     3   29 no warnings 'redefine';
  3         9  
  3         8688  
970              
971 0     0     *CPAN::_flock = sub { 1 };
  0     0      
972 0     0     *CPAN::checklock = sub { 1 };
  0            
973              
974 0           return HEY_IT_WORKED;
975             }
976              
977             sub _download
978             {
979 0     0     my $args = shift;
980              
981 0           local $CPAN::DEBUG = 1;
982              
983 0           my %paths;
984              
985 0           foreach my $module ( @$args )
986             {
987 0           $logger->info( "Checking $module" );
988 0           my $path = CPAN::Shell->expand( "Module", $module )->cpan_file;
989              
990 0           $logger->debug( "Inst file would be $path\n" );
991              
992 0           $paths{$module} = _get_file( _make_path( $path ) );
993             }
994              
995 0           return \%paths;
996             }
997              
998 0     0     sub _make_path { join "/", qw(authors id), $_[0] }
999              
1000             sub _get_file
1001             {
1002 0     0     my $path = shift;
1003              
1004 0           my $loaded = eval "require LWP::Simple; 1;";
1005 0 0         croak "You need LWP::Simple to use features that fetch files from CPAN\n"
1006             unless $loaded;
1007              
1008 0           my $file = substr $path, rindex( $path, '/' ) + 1;
1009 0           my $store_path = catfile( cwd(), $file );
1010 0           $logger->debug( "Store path is $store_path" );
1011              
1012 0           foreach my $site ( @{ $CPAN::Config->{urllist} } )
  0            
1013             {
1014 0           my $fetch_path = join "/", $site, $path;
1015 0           $logger->debug( "Trying $fetch_path" );
1016 0 0         last if LWP::Simple::getstore( $fetch_path, $store_path );
1017             }
1018              
1019 0           return $store_path;
1020             }
1021              
1022             sub _gitify
1023             {
1024 0     0     my $args = shift;
1025              
1026 0           my $loaded = eval "require Archive::Extract; 1;";
1027 0 0         croak "You need Archive::Extract to use features that gitify distributions\n"
1028             unless $loaded;
1029              
1030 0           my $starting_dir = cwd();
1031              
1032 0           foreach my $module ( @$args )
1033             {
1034 0           $logger->info( "Checking $module" );
1035 0           my $path = CPAN::Shell->expand( "Module", $module )->cpan_file;
1036              
1037 0           my $store_paths = _download( [ $module ] );
1038 0           $logger->debug( "gitify Store path is $store_paths->{$module}" );
1039 0           my $dirname = dirname( $store_paths->{$module} );
1040              
1041 0           my $ae = Archive::Extract->new( archive => $store_paths->{$module} );
1042 0           $ae->extract( to => $dirname );
1043              
1044 0           chdir $ae->extract_path;
1045              
1046 0   0       my $git = $ENV{GIT_COMMAND} || '/usr/local/bin/git';
1047 0 0         croak "Could not find $git" unless -e $git;
1048 0 0         croak "$git is not executable" unless -x $git;
1049              
1050             # can we do this in Pure Perl?
1051 0           system( $git, 'init' );
1052 0           system( $git, qw( add . ) );
1053 0           system( $git, qw( commit -a -m ), 'initial import' );
1054             }
1055              
1056 0           chdir $starting_dir;
1057              
1058 0           return HEY_IT_WORKED;
1059             }
1060              
1061             sub _show_Changes
1062             {
1063 0     0     my $args = shift;
1064              
1065 0           foreach my $arg ( @$args )
1066             {
1067 0           $logger->info( "Checking $arg\n" );
1068              
1069 0           my $module = eval { CPAN::Shell->expand( "Module", $arg ) };
  0            
1070 0           my $out = _get_cpanpm_output();
1071              
1072 0 0         next unless eval { $module->inst_file };
  0            
1073             #next if $module->uptodate;
1074              
1075 0           ( my $id = $module->id() ) =~ s/::/\-/;
1076              
1077 0           my $url = "http://search.cpan.org/~" . lc( $module->userid ) . "/" .
1078             $id . "-" . $module->cpan_version() . "/";
1079              
1080             #print "URL: $url\n";
1081 0           _get_changes_file($url);
1082             }
1083              
1084 0           return HEY_IT_WORKED;
1085             }
1086              
1087             sub _get_changes_file
1088             {
1089 0 0   0     croak "Reading Changes files requires LWP::Simple and URI\n"
1090             unless eval "require LWP::Simple; require URI; 1";
1091              
1092 0           my $url = shift;
1093              
1094 0           my $content = LWP::Simple::get( $url );
1095 0 0         $logger->info( "Got $url ..." ) if defined $content;
1096             #print $content;
1097              
1098 0           my( $change_link ) = $content =~ m|Changes|gi;
1099              
1100 0           my $changes_url = URI->new_abs( $change_link, $url );
1101 0           $logger->debug( "Change link is: $changes_url" );
1102              
1103 0           my $changes = LWP::Simple::get( $changes_url );
1104              
1105 0           print $changes;
1106              
1107 0           return HEY_IT_WORKED;
1108             }
1109              
1110             sub _show_Author
1111             {
1112 0     0     my $args = shift;
1113              
1114 0           foreach my $arg ( @$args )
1115             {
1116 0           my $module = CPAN::Shell->expand( "Module", $arg );
1117 0 0         unless( $module )
1118             {
1119 0           $logger->info( "Didn't find a $arg module, so no author!" );
1120 0           next;
1121             }
1122              
1123 0           my $author = CPAN::Shell->expand( "Author", $module->userid );
1124              
1125 0 0         next unless $module->userid;
1126              
1127 0           printf "%-25s %-8s %-25s %s\n",
1128             $arg, $module->userid, $author->email, $author->name;
1129             }
1130              
1131 0           return HEY_IT_WORKED;
1132             }
1133              
1134             sub _show_Details
1135             {
1136 0     0     my $args = shift;
1137              
1138 0           foreach my $arg ( @$args )
1139             {
1140 0           my $module = CPAN::Shell->expand( "Module", $arg );
1141 0           my $author = CPAN::Shell->expand( "Author", $module->userid );
1142              
1143 0 0         next unless $module->userid;
1144              
1145 0           print "$arg\n", "-" x 73, "\n\t";
1146 0 0         print join "\n\t",
    0          
1147             $module->description ? $module->description : "(no description)",
1148             $module->cpan_file,
1149             $module->inst_file,
1150             'Installed: ' . $module->inst_version,
1151             'CPAN: ' . $module->cpan_version . ' ' .
1152             ($module->uptodate ? "" : "Not ") . "up to date",
1153             $author->fullname . " (" . $module->userid . ")",
1154             $author->email;
1155 0           print "\n\n";
1156              
1157             }
1158              
1159 0           return HEY_IT_WORKED;
1160             }
1161              
1162             sub _show_out_of_date
1163             {
1164 0     0     my @modules = CPAN::Shell->expand( "Module", "/./" );
1165              
1166 0           printf "%-40s %6s %6s\n", "Module Name", "Local", "CPAN";
1167 0           print "-" x 73, "\n";
1168              
1169 0           foreach my $module ( @modules )
1170             {
1171 0 0         next unless $module->inst_file;
1172 0 0         next if $module->uptodate;
1173 0 0         printf "%-40s %.4f %.4f\n",
1174             $module->id,
1175             $module->inst_version ? $module->inst_version : '',
1176             $module->cpan_version;
1177             }
1178              
1179 0           return HEY_IT_WORKED;
1180             }
1181              
1182             sub _show_author_mods
1183             {
1184 0     0     my $args = shift;
1185              
1186 0           my %hash = map { lc $_, 1 } @$args;
  0            
1187              
1188 0           my @modules = CPAN::Shell->expand( "Module", "/./" );
1189              
1190 0           foreach my $module ( @modules )
1191             {
1192 0 0         next unless exists $hash{ lc $module->userid };
1193 0           print $module->id, "\n";
1194             }
1195              
1196 0           return HEY_IT_WORKED;
1197             }
1198              
1199             sub _list_all_mods # -l
1200             {
1201 0     0     require File::Find;
1202              
1203 0           my $args = shift;
1204              
1205              
1206 0           my $fh = \*STDOUT;
1207              
1208 0           INC: foreach my $inc ( @INC )
1209             {
1210 0           my( $wanted, $reporter ) = _generator();
1211 0           File::Find::find( { wanted => $wanted }, $inc );
1212              
1213 0           my $count = 0;
1214 0           FILE: foreach my $file ( @{ $reporter->() } )
  0            
1215             {
1216 0           my $version = _parse_version_safely( $file );
1217              
1218 0           my $module_name = _path_to_module( $inc, $file );
1219 0 0         next FILE unless defined $module_name;
1220              
1221 0           print $fh "$module_name\t$version\n";
1222              
1223             #last if $count++ > 5;
1224             }
1225             }
1226              
1227 0           return HEY_IT_WORKED;
1228             }
1229              
1230             sub _generator
1231             {
1232 0     0     my @files = ();
1233              
1234 0 0   0     sub { push @files,
1235             File::Spec->canonpath( $File::Find::name )
1236             if m/\A\w+\.pm\z/ },
1237 0     0     sub { \@files },
1238 0           }
1239              
1240             sub _parse_version_safely # stolen from PAUSE's mldistwatch, but refactored
1241             {
1242 0     0     my( $file ) = @_;
1243              
1244 0           local $/ = "\n";
1245 0           local $_; # don't mess with the $_ in the map calling this
1246              
1247 0 0         return unless open FILE, "<$file";
1248              
1249 0           my $in_pod = 0;
1250 0           my $version;
1251 0           while( )
1252             {
1253 0           chomp;
1254 0 0         $in_pod = /^=(?!cut)/ ? 1 : /^=cut/ ? 0 : $in_pod;
    0          
1255 0 0 0       next if $in_pod || /^\s*#/;
1256              
1257 0 0         next unless /([\$*])(([\w\:\']*)\bVERSION)\b.*\=/;
1258 0           my( $sigil, $var ) = ( $1, $2 );
1259              
1260 0           $version = _eval_version( $_, $sigil, $var );
1261 0           last;
1262             }
1263 0           close FILE;
1264              
1265 0 0         return 'undef' unless defined $version;
1266              
1267 0           return $version;
1268             }
1269              
1270             sub _eval_version
1271             {
1272 0     0     my( $line, $sigil, $var ) = @_;
1273              
1274 0           my $eval = qq{
1275             package ExtUtils::MakeMaker::_version;
1276              
1277             local $sigil$var;
1278             \$$var=undef; do {
1279             $line
1280             }; \$$var
1281             };
1282              
1283 0           my $version = do {
1284 0           local $^W = 0;
1285 3     3   28 no strict;
  3         7  
  3         736  
1286 0           eval( $eval );
1287             };
1288              
1289 0           return $version;
1290             }
1291              
1292             sub _path_to_module
1293             {
1294 0     0     my( $inc, $path ) = @_;
1295 0 0         return if length $path< length $inc;
1296              
1297 0           my $module_path = substr( $path, length $inc );
1298 0           $module_path =~ s/\.pm\z//;
1299              
1300             # XXX: this is cheating and doesn't handle everything right
1301 0           my @dirs = grep { ! /\W/ } File::Spec->splitdir( $module_path );
  0            
1302 0           shift @dirs;
1303              
1304 0           my $module_name = join "::", @dirs;
1305              
1306 0           return $module_name;
1307             }
1308              
1309             1;
1310              
1311             =back
1312              
1313             =head1 EXIT VALUES
1314              
1315             The script exits with zero if it thinks that everything worked, or a
1316             positive number if it thinks that something failed. Note, however, that
1317             in some cases it has to divine a failure by the output of things it does
1318             not control. For now, the exit codes are vague:
1319              
1320             1 An unknown error
1321              
1322             2 The was an external problem
1323              
1324             4 There was an internal problem with the script
1325              
1326             8 A module failed to install
1327              
1328             =head1 TO DO
1329              
1330             * There is initial support for Log4perl if it is available, but I
1331             haven't gone through everything to make the NullLogger work out
1332             correctly if Log4perl is not installed.
1333              
1334             * When I capture CPAN.pm output, I need to check for errors and
1335             report them to the user.
1336              
1337             * Support local::lib
1338              
1339             * Warnings switch
1340              
1341             * Check then exit
1342              
1343             * ping mirrors support
1344              
1345             * no test option
1346              
1347             =head1 BUGS
1348              
1349             * none noted
1350              
1351             =head1 SEE ALSO
1352              
1353             Most behaviour, including environment variables and configuration,
1354             comes directly from CPAN.pm.
1355              
1356             =head1 SOURCE AVAILABILITY
1357              
1358             This code is in Github:
1359              
1360             git://github.com/briandfoy/cpan_script.git
1361              
1362             =head1 CREDITS
1363              
1364             Japheth Cleaver added the bits to allow a forced install (-f).
1365              
1366             Jim Brandt suggest and provided the initial implementation for the
1367             up-to-date and Changes features.
1368              
1369             Adam Kennedy pointed out that exit() causes problems on Windows
1370             where this script ends up with a .bat extension
1371              
1372             =head1 AUTHOR
1373              
1374             brian d foy, C<< >>
1375              
1376             =head1 COPYRIGHT
1377              
1378             Copyright (c) 2001-2009, brian d foy, All Rights Reserved.
1379              
1380             You may redistribute this under the same terms as Perl itself.
1381              
1382             =cut