10 App::Cpan - easily interact with CPAN from the command line
14 # with arguments and no switches, installs specified modules
15 cpan module_name [ module_name ... ]
17 # with switches, installs modules with extra behavior
18 cpan [-cfFimt] module_name [ module_name ... ]
21 cpan -l module_name [ module_name ... ]
23 # with just the dot, install from the distribution in the
27 # without arguments, starts CPAN.pm shell
30 # without arguments, but some switches
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
45 Creates a CPAN.pm autobundle with CPAN::Shell->autobundle.
47 =item -A module [ module ... ]
49 Shows the primary maintainers for the specified modules.
53 Runs a `make clean` in the specified module's directories.
55 =item -C module [ module ... ]
57 Show the F<Changes> files for the specified modules
59 =item -D module [ module ... ]
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
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:
72 % cpan -f -i Module::Foo
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.
81 =item -g module [ module ... ]
83 Downloads to the current directory the latest distribution of the module.
85 =item -G module [ module ... ]
89 Download to the current directory the latest distribution of the
90 modules, unpack each distribution, and create a git repository for each
93 If you want this feature, check out Yanick Champoux's C<Git::CPAN::Patch>
98 Print a help message and exit. When you specify C<-h>, it ignores all
99 of the other options and arguments.
103 Install the specified modules.
107 Load the file that has the CPAN configuration data. This should have the
108 same format as the standard F<CPAN/Config.pm> file, which defines
109 C<$CPAN::Config> as an anonymous hash.
113 Dump the configuration in the same format that CPAN.pm uses. This is useful
114 for checking the configuration as well as using the dump as a starting point
115 for a new, custom configuration.
121 =item -L author [ author ... ]
123 List the modules by the specified authors.
127 Make the specified modules.
131 Show the out-of-date modules.
135 Run a `make test` on the specified modules.
139 Recompiles dynamically loaded modules with CPAN::Shell->recompile.
143 Upgrade all installed modules. Blindly doing this can really break things,
148 Print the script version and CPAN.pm version then exit.
154 # print a help message
157 # print the version numbers
160 # create an autobundle
166 # upgrade all installed modules
169 # install modules ( sole -i is optional )
170 cpan -i Netscape::Booksmarks Business::ISBN
172 # force install modules ( must use -i )
173 cpan -fi CGI::Minimal URI
182 use autouse Carp => qw(carp croak cluck);
184 use autouse Cwd => qw(cwd);
185 use autouse 'Data::Dumper' => qw(Dumper);
186 use File::Spec::Functions;
191 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
193 use constant TRUE => 1;
194 use constant FALSE => 0;
197 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
199 use constant HEY_IT_WORKED => 0;
200 use constant I_DONT_KNOW_WHAT_HAPPENED => 1; # 0b0000_0001
201 use constant ITS_NOT_MY_FAULT => 2;
202 use constant THE_PROGRAMMERS_AN_IDIOT => 4;
203 use constant A_MODULE_FAILED_TO_INSTALL => 8;
206 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
207 # set up the order of options that we layer over CPAN::Shell
208 BEGIN { # most of this should be in methods
209 use vars qw( @META_OPTIONS $Default %CPAN_METHODS @CPAN_OPTIONS @option_order
210 %Method_table %Method_table_index );
212 @META_OPTIONS = qw( h v g G C A D O l L a r j: J );
214 $Default = 'default';
216 %CPAN_METHODS = ( # map switches to method names in CPAN::Shell
217 $Default => 'install',
225 @CPAN_OPTIONS = grep { $_ ne $Default } sort keys %CPAN_METHODS;
227 @option_order = ( @META_OPTIONS, @CPAN_OPTIONS );
230 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
231 # map switches to the subroutines in this script, along with other information.
232 # use this stuff instead of hard-coded indices and values
235 sub GOOD_EXIT () { 0 }
238 # key => [ sub ref, takes args?, exit value, description ]
240 # options that do their thing first, then exit
241 h => [ \&_print_help, NO_ARGS, GOOD_EXIT, 'Printing help' ],
242 v => [ \&_print_version, NO_ARGS, GOOD_EXIT, 'Printing version' ],
244 # options that affect other options
245 j => [ \&_load_config, ARGS, GOOD_EXIT, 'Use specified config file' ],
246 J => [ \&_dump_config, NO_ARGS, GOOD_EXIT, 'Dump configuration to stdout' ],
247 F => [ \&_lock_lobotomy, NO_ARGS, GOOD_EXIT, 'Turn off CPAN.pm lock files' ],
249 # options that do their one thing
250 g => [ \&_download, NO_ARGS, GOOD_EXIT, 'Download the latest distro' ],
251 G => [ \&_gitify, NO_ARGS, GOOD_EXIT, 'Down and gitify the latest distro' ],
253 C => [ \&_show_Changes, ARGS, GOOD_EXIT, 'Showing Changes file' ],
254 A => [ \&_show_Author, ARGS, GOOD_EXIT, 'Showing Author' ],
255 D => [ \&_show_Details, ARGS, GOOD_EXIT, 'Showing Details' ],
256 O => [ \&_show_out_of_date, NO_ARGS, GOOD_EXIT, 'Showing Out of date' ],
258 l => [ \&_list_all_mods, NO_ARGS, GOOD_EXIT, 'Listing all modules' ],
260 L => [ \&_show_author_mods, ARGS, GOOD_EXIT, 'Showing author mods' ],
261 a => [ \&_create_autobundle, NO_ARGS, GOOD_EXIT, 'Creating autobundle' ],
262 r => [ \&_recompile, NO_ARGS, GOOD_EXIT, 'Recompiling' ],
263 u => [ \&_upgrade, NO_ARGS, GOOD_EXIT, 'Running `make test`' ],
265 c => [ \&_default, ARGS, GOOD_EXIT, 'Running `make clean`' ],
266 f => [ \&_default, ARGS, GOOD_EXIT, 'Installing with force' ],
267 i => [ \&_default, ARGS, GOOD_EXIT, 'Running `make install`' ],
268 'm' => [ \&_default, ARGS, GOOD_EXIT, 'Running `make`' ],
269 t => [ \&_default, ARGS, GOOD_EXIT, 'Running `make test`' ],
273 %Method_table_index = (
281 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
282 # finally, do some argument processing
284 sub _stupid_interface_hack_for_non_rtfmers
286 no warnings 'uninitialized';
287 shift @ARGV if( $ARGV[0] eq 'install' and @ARGV > 1 )
294 # if no arguments, just drop into the shell
295 if( 0 == @ARGV ) { CPAN::shell(); exit 0 }
298 Getopt::Std::getopts(
299 join( '', @option_order ), \%options );
304 sub _process_setup_options
306 my( $class, $options ) = @_;
310 $Method_table{j}[ $Method_table_index{code} ]->( $options->{j} );
311 delete $options->{j};
315 # this is what CPAN.pm would do otherwise
316 CPAN::HandleConfig->load(
324 $Method_table{F}[ $Method_table_index{code} ]->( $options->{F} );
325 delete $options->{F};
328 my $option_count = grep { $options->{$_} } @option_order;
329 no warnings 'uninitialized';
330 $option_count -= $options->{'f'}; # don't count force
332 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
333 # if there are no options, set -i (this line fixes RT ticket 16915)
334 $options->{i}++ unless $option_count;
342 The C<run> method returns 0 on success and a postive number on
343 failure. See the section on EXIT CODES for details on the values.
353 my $return_value = HEY_IT_WORKED; # assume that things will work
355 $logger = $class->_init_logger;
356 $logger->debug( "Using logger from @{[ref $logger]}" );
358 $class->_hook_into_CPANpm_report;
359 $logger->debug( "Hooked into output" );
361 $class->_stupid_interface_hack_for_non_rtfmers;
362 $logger->debug( "Patched cargo culting" );
364 my $options = $class->_process_options;
365 $logger->debug( "Options are @{[Dumper($options)]}" );
367 $class->_process_setup_options( $options );
369 OPTION: foreach my $option ( @option_order )
371 next unless $options->{$option};
373 my( $sub, $takes_args, $description ) =
374 map { $Method_table{$option}[ $Method_table_index{$_} ] }
375 qw( code takes_args );
377 unless( ref $sub eq ref sub {} )
379 $return_value = THE_PROGRAMMERS_AN_IDIOT;
383 $logger->info( "$description -- ignoring other arguments" )
384 if( @ARGV && ! $takes_args );
386 $return_value = $sub->( \ @ARGV, $options );
391 return $return_value;
395 package Local::Null::Logger;
397 sub new { bless \ my $x, $_[0] }
398 sub AUTOLOAD { shift; print "NullLogger: ", @_, $/ if $ENV{CPAN_NULL_LOGGER} }
404 my $log4perl_loaded = eval "require Log::Log4perl; 1";
406 unless( $log4perl_loaded )
408 $logger = Local::Null::Logger->new;
412 my $LEVEL = $ENV{CPANSCRIPT_LOGLEVEL} || 'INFO';
414 Log::Log4perl::init( \ <<"HERE" );
415 log4perl.rootLogger=$LEVEL, A1
416 log4perl.appender.A1=Log::Log4perl::Appender::Screen
417 log4perl.appender.A1.layout=PatternLayout
418 log4perl.appender.A1.layout.ConversionPattern=%m%n
421 $logger = Log::Log4perl->get_logger( 'App::Cpan' );
424 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
425 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
426 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
430 my( $args, $options ) = @_;
434 # choose the option that we're going to use
435 # we'll deal with 'f' (force) later, so skip it
436 foreach my $option ( @CPAN_OPTIONS )
438 next if $option eq 'f';
439 next unless $options->{$option};
444 # 1. with no switches, but arguments, use the default switch (install)
445 # 2. with no switches and no args, start the shell
446 # 3. With a switch but no args, die! These switches need arguments.
447 if( not $switch and @$args ) { $switch = $Default; }
448 elsif( not $switch and not @$args ) { return CPAN::shell() }
449 elsif( $switch and not @$args )
450 { die "Nothing to $CPAN_METHODS{$switch}!\n"; }
452 # Get and check the method from CPAN::Shell
453 my $method = $CPAN_METHODS{$switch};
454 die "CPAN.pm cannot $method!\n" unless CPAN::Shell->can( $method );
456 # call the CPAN::Shell method, with force if specified
458 if( $options->{f} ) { sub { CPAN::Shell->force( $method, @_ ) } }
459 else { sub { CPAN::Shell->$method( @_ ) } }
462 # How do I handle exit codes for multiple arguments?
465 foreach my $arg ( @$args )
467 _clear_cpanpm_output();
470 $errors += defined _cpanpm_output_indicates_failure();
473 $errors ? I_DONT_KNOW_WHAT_HAPPENED : HEY_IT_WORKED;
476 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
480 CPAN.pm sends all the good stuff either to STDOUT, or to a temp
481 file if $CPAN::Be_Silent is set. I have to intercept that output
482 so I can find out what happened.
489 sub _hook_into_CPANpm_report
491 no warnings 'redefine';
493 *CPAN::Shell::myprint = sub {
494 my($self,$what) = @_;
495 $scalar .= $what if defined $what;
496 $self->print_ornamented($what,
497 $CPAN::Config->{colorize_print}||'bold blue on_white',
501 *CPAN::Shell::mywarn = sub {
502 my($self,$what) = @_;
503 $scalar .= $what if defined $what;
504 $self->print_ornamented($what,
505 $CPAN::Config->{colorize_warn}||'bold red on_white'
511 sub _clear_cpanpm_output { $scalar = '' }
513 sub _get_cpanpm_output { $scalar }
517 qr/^\QWarning \(usually harmless\)/,
518 qr/\bwill not store persistent state\b/,
523 sub _get_cpanpm_last_line
525 open my($fh), "<", \ $scalar;
529 # This is a bit ugly. Once we examine a line, we have to
530 # examine the line before it and go through all of the same
531 # regexes. I could do something fancy, but this works.
533 foreach my $regex ( @skip_lines )
535 if( $lines[-1] =~ m/$regex/ )
538 redo REGEXES; # we have to go through all of them for every line!
543 $logger->debug( "Last interesting line of CPAN.pm output is:\n\t$lines[-1]" );
550 my $epic_fail_words = join '|',
551 qw( Error stop(?:ping)? problems force not unsupported fail(?:ed)? );
553 sub _cpanpm_output_indicates_failure
555 my $last_line = _get_cpanpm_last_line();
557 my $result = $last_line =~ /\b(?:$epic_fail_words)\b/i;
562 sub _cpanpm_output_indicates_success
564 my $last_line = _get_cpanpm_last_line();
566 my $result = $last_line =~ /\b(?:\s+-- OK|PASS)\b/;
570 sub _cpanpm_output_is_vague
573 _cpanpm_output_indicates_failure() ||
574 _cpanpm_output_indicates_success();
581 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
584 $logger->info( "Use perldoc to read the documentation" );
591 "$0 script version $VERSION, CPAN.pm version " . CPAN->VERSION );
593 return HEY_IT_WORKED;
596 sub _create_autobundle
599 "Creating autobundle in $CPAN::Config->{cpan_home}/Bundle" );
601 CPAN::Shell->autobundle;
603 return HEY_IT_WORKED;
608 $logger->info( "Recompiling dynamically-loaded extensions" );
610 CPAN::Shell->recompile;
612 return HEY_IT_WORKED;
617 $logger->info( "Upgrading all modules" );
619 CPAN::Shell->upgrade();
621 return HEY_IT_WORKED;
624 sub _load_config # -j
626 my $file = shift || '';
628 # should I clear out any existing config here?
630 delete $INC{'CPAN/Config.pm'};
631 croak( "Config file [$file] does not exist!\n" ) unless -e $file;
633 my $rc = eval "require '$file'";
635 # CPAN::HandleConfig::require_myconfig_or_config looks for this
636 $INC{'CPAN/MyConfig.pm'} = 'fake out!';
638 # CPAN::HandleConfig::load looks for this
639 $CPAN::Config_loaded = 'fake out';
641 croak( "Could not load [$file]: $@\n") unless $rc;
643 return HEY_IT_WORKED;
649 require Data::Dumper;
651 my $fh = $args->[0] || \*STDOUT;
653 my $dd = Data::Dumper->new(
658 print $fh $dd->Dump, "\n1;\n__END__\n";
660 return HEY_IT_WORKED;
665 no warnings 'redefine';
667 *CPAN::_flock = sub { 1 };
668 *CPAN::checklock = sub { 1 };
670 return HEY_IT_WORKED;
677 local $CPAN::DEBUG = 1;
681 foreach my $module ( @$args )
683 $logger->info( "Checking $module" );
684 my $path = CPAN::Shell->expand( "Module", $module )->cpan_file;
686 $logger->debug( "Inst file would be $path\n" );
688 $paths{$module} = _get_file( _make_path( $path ) );
694 sub _make_path { join "/", qw(authors id), $_[0] }
700 my $loaded = eval "require LWP::Simple; 1;";
701 croak "You need LWP::Simple to use features that fetch files from CPAN\n"
704 my $file = substr $path, rindex( $path, '/' ) + 1;
705 my $store_path = catfile( cwd(), $file );
706 $logger->debug( "Store path is $store_path" );
708 foreach my $site ( @{ $CPAN::Config->{urllist} } )
710 my $fetch_path = join "/", $site, $path;
711 $logger->debug( "Trying $fetch_path" );
712 last if LWP::Simple::getstore( $fetch_path, $store_path );
722 my $loaded = eval "require Archive::Extract; 1;";
723 croak "You need Archive::Extract to use features that gitify distributions\n"
726 my $starting_dir = cwd();
728 foreach my $module ( @$args )
730 $logger->info( "Checking $module" );
731 my $path = CPAN::Shell->expand( "Module", $module )->cpan_file;
733 my $store_paths = _download( [ $module ] );
734 $logger->debug( "gitify Store path is $store_paths->{$module}" );
735 my $dirname = dirname( $store_paths->{$module} );
737 my $ae = Archive::Extract->new( archive => $store_paths->{$module} );
738 $ae->extract( to => $dirname );
740 chdir $ae->extract_path;
742 my $git = $ENV{GIT_COMMAND} || '/usr/local/bin/git';
743 croak "Could not find $git" unless -e $git;
744 croak "$git is not executable" unless -x $git;
746 # can we do this in Pure Perl?
747 system( $git, 'init' );
748 system( $git, qw( add . ) );
749 system( $git, qw( commit -a -m ), 'initial import' );
754 return HEY_IT_WORKED;
761 foreach my $arg ( @$args )
763 $logger->info( "Checking $arg\n" );
765 my $module = eval { CPAN::Shell->expand( "Module", $arg ) };
766 my $out = _get_cpanpm_output();
768 next unless eval { $module->inst_file };
769 #next if $module->uptodate;
771 ( my $id = $module->id() ) =~ s/::/\-/;
773 my $url = "http://search.cpan.org/~" . lc( $module->userid ) . "/" .
774 $id . "-" . $module->cpan_version() . "/";
776 #print "URL: $url\n";
777 _get_changes_file($url);
780 return HEY_IT_WORKED;
783 sub _get_changes_file
785 croak "Reading Changes files requires LWP::Simple and URI\n"
786 unless eval "require LWP::Simple; require URI; 1";
790 my $content = LWP::Simple::get( $url );
791 $logger->info( "Got $url ..." ) if defined $content;
794 my( $change_link ) = $content =~ m|<a href="(.*?)">Changes</a>|gi;
796 my $changes_url = URI->new_abs( $change_link, $url );
797 $logger->debug( "Change link is: $changes_url" );
799 my $changes = LWP::Simple::get( $changes_url );
803 return HEY_IT_WORKED;
810 foreach my $arg ( @$args )
812 my $module = CPAN::Shell->expand( "Module", $arg );
815 $logger->info( "Didn't find a $arg module, so no author!" );
819 my $author = CPAN::Shell->expand( "Author", $module->userid );
821 next unless $module->userid;
823 printf "%-25s %-8s %-25s %s\n",
824 $arg, $module->userid, $author->email, $author->fullname;
827 return HEY_IT_WORKED;
834 foreach my $arg ( @$args )
836 my $module = CPAN::Shell->expand( "Module", $arg );
837 my $author = CPAN::Shell->expand( "Author", $module->userid );
839 next unless $module->userid;
841 print "$arg\n", "-" x 73, "\n\t";
843 $module->description ? $module->description : "(no description)",
846 'Installed: ' . $module->inst_version,
847 'CPAN: ' . $module->cpan_version . ' ' .
848 ($module->uptodate ? "" : "Not ") . "up to date",
849 $author->fullname . " (" . $module->userid . ")",
855 return HEY_IT_WORKED;
858 sub _show_out_of_date
860 my @modules = CPAN::Shell->expand( "Module", "/./" );
862 printf "%-40s %6s %6s\n", "Module Name", "Local", "CPAN";
863 print "-" x 73, "\n";
865 foreach my $module ( @modules )
867 next unless $module->inst_file;
868 next if $module->uptodate;
869 printf "%-40s %.4f %.4f\n",
871 $module->inst_version ? $module->inst_version : '',
872 $module->cpan_version;
875 return HEY_IT_WORKED;
878 sub _show_author_mods
882 my %hash = map { lc $_, 1 } @$args;
884 my @modules = CPAN::Shell->expand( "Module", "/./" );
886 foreach my $module ( @modules )
888 next unless exists $hash{ lc $module->userid };
889 print $module->id, "\n";
892 return HEY_IT_WORKED;
904 INC: foreach my $inc ( @INC )
906 my( $wanted, $reporter ) = _generator();
907 File::Find::find( { wanted => $wanted }, $inc );
910 FILE: foreach my $file ( @{ $reporter->() } )
912 my $version = _parse_version_safely( $file );
914 my $module_name = _path_to_module( $inc, $file );
915 next FILE unless defined $module_name;
917 print $fh "$module_name\t$version\n";
919 #last if $count++ > 5;
923 return HEY_IT_WORKED;
931 File::Spec->canonpath( $File::Find::name )
936 sub _parse_version_safely # stolen from PAUSE's mldistwatch, but refactored
941 local $_; # don't mess with the $_ in the map calling this
943 return unless open FILE, "<$file";
950 $in_pod = /^=(?!cut)/ ? 1 : /^=cut/ ? 0 : $in_pod;
951 next if $in_pod || /^\s*#/;
953 next unless /([\$*])(([\w\:\']*)\bVERSION)\b.*\=/;
954 my( $sigil, $var ) = ( $1, $2 );
956 $version = _eval_version( $_, $sigil, $var );
961 return 'undef' unless defined $version;
968 my( $line, $sigil, $var ) = @_;
971 package ExtUtils::MakeMaker::_version;
990 my( $inc, $path ) = @_;
991 return if length $path< length $inc;
993 my $module_path = substr( $path, length $inc );
994 $module_path =~ s/\.pm\z//;
996 # XXX: this is cheating and doesn't handle everything right
997 my @dirs = grep { ! /\W/ } File::Spec->splitdir( $module_path );
1000 my $module_name = join "::", @dirs;
1002 return $module_name;
1011 The script exits with zero if it thinks that everything worked, or a
1012 positive number if it thinks that something failed. Note, however, that
1013 in some cases it has to divine a failure by the output of things it does
1014 not control. For now, the exit codes are vague:
1018 2 The was an external problem
1020 4 There was an internal problem with the script
1022 8 A module failed to install
1026 * There is initial support for Log4perl if it is available, but I
1027 haven't gone through everything to make the NullLogger work out
1028 correctly if Log4perl is not installed.
1030 * When I capture CPAN.pm output, I need to check for errors and
1031 report them to the user.
1039 Most behaviour, including environment variables and configuration,
1040 comes directly from CPAN.pm.
1042 =head1 SOURCE AVAILABILITY
1044 This code is in Github:
1046 git://github.com/briandfoy/cpan_script.git
1050 Japheth Cleaver added the bits to allow a forced install (-f).
1052 Jim Brandt suggest and provided the initial implementation for the
1053 up-to-date and Changes features.
1055 Adam Kennedy pointed out that exit() causes problems on Windows
1056 where this script ends up with a .bat extension
1060 brian d foy, C<< <bdfoy@cpan.org> >>
1064 Copyright (c) 2001-2009, brian d foy, All Rights Reserved.
1066 You may redistribute this under the same terms as Perl itself.