This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Fix a CPANPLUS test that fails when run on a read-only source tree
[perl5.git] / lib / CPANPLUS / Shell / Classic.pm
1 ##################################################
2 ###            CPANPLUS/Shell/Classic.pm       ###
3 ###    Backwards compatible shell for CPAN++   ###
4 ###      Written 08-04-2002 by Jos Boumans     ###
5 ##################################################
6
7 package CPANPLUS::Shell::Classic;
8
9 use strict;
10
11
12 use CPANPLUS::Error;
13 use CPANPLUS::Backend;
14 use CPANPLUS::Configure::Setup;
15 use CPANPLUS::Internals::Constants;
16
17 use Cwd;
18 use IPC::Cmd;
19 use Term::UI;
20 use Data::Dumper;
21 use Term::ReadLine;
22
23 use Module::Load                qw[load];
24 use Params::Check               qw[check];
25 use Module::Load::Conditional   qw[can_load];
26
27 $Params::Check::VERBOSE       = 1;
28 $Params::Check::ALLOW_UNKNOWN = 1;
29
30 BEGIN {
31     use vars        qw[ $VERSION @ISA ];
32     @ISA        =   qw[ CPANPLUS::Shell::_Base::ReadLine ];
33     $VERSION    =   '0.0562';
34 }
35
36 load CPANPLUS::Shell;
37
38
39 ### our command set ###
40 my $map = {
41     a           => '_author',
42     b           => '_bundle',
43     d           => '_distribution',
44     'm'         => '_module',
45     i           => '_find_all',
46     r           => '_uptodate',
47     u           => '_not_supported',
48     ls          => '_ls',
49     get         => '_fetch',
50     make        => '_install',
51     test        => '_install',
52     install     => '_install',
53     clean       => '_not_supported',
54     look        => '_shell',
55     readme      => '_readme',
56     h           => '_help',
57     '?'         => '_help',
58     o           => '_set_conf',
59     reload      => '_reload',
60     autobundle  => '_autobundle',
61     '!'         => '_bang',
62     #'q'         => '_quit', # done it the loop itself
63 };
64
65 ### the shell object, scoped to the file ###
66 my $Shell;
67 my $Brand   = 'cpan';
68 my $Prompt  = $Brand . '> ';
69
70 sub new {
71     my $class   = shift;
72
73     my $cb      = new CPANPLUS::Backend;
74     my $self    = $class->SUPER::_init(
75                             brand   => $Brand,
76                             term    => Term::ReadLine->new( $Brand ),
77                             prompt  => $Prompt,
78                             backend => $cb,
79                             format  => "%5s %-50s %8s %-10s\n",
80                         );
81     ### make it available package wide ###
82     $Shell = $self;
83
84     ### enable verbose, it's the cpan.pm way
85     $cb->configure_object->set_conf( verbose => 1 );
86
87
88     ### register install callback ###
89     $cb->_register_callback(
90             name    => 'install_prerequisite',
91             code    => \&__ask_about_install,
92     );
93
94     ### register test report callback ###
95     $cb->_register_callback(
96             name    => 'edit_test_report',
97             code    => \&__ask_about_test_report,
98     );
99
100     return $self;
101 }
102
103 sub shell {
104     my $self = shift;
105     my $term = $self->term;
106
107     $self->_show_banner;
108     $self->_input_loop && print "\n";
109     $self->_quit;
110 }
111
112 sub _input_loop {
113     my $self    = shift;
114     my $term    = $self->term;
115     my $cb      = $self->backend;
116
117     my $normal_quit = 0;
118     while (
119         defined (my $input = eval { $term->readline($self->prompt) } )
120         or $self->_signals->{INT}{count} == 1
121     ) {
122         ### re-initiate all signal handlers
123         while (my ($sig, $entry) = each %{$self->_signals} ) {
124             $SIG{$sig} = $entry->{handler} if exists($entry->{handler});
125         }
126
127         last if $self->_dispatch_on_input( input => $input );
128
129         ### flush the lib cache ###
130         $cb->_flush( list => [qw|lib load|] );
131
132     } continue {
133         $self->_signals->{INT}{count}--
134             if $self->_signals->{INT}{count}; # clear the sigint count
135     }
136
137     return 1;
138 }
139
140 sub _dispatch_on_input {
141     my $self = shift;
142     my $conf = $self->backend->configure_object();
143     my $term = $self->term;
144     my %hash = @_;
145
146     my $string;
147     my $tmpl = {
148         input   => { required => 1, store => \$string }
149     };
150
151     check( $tmpl, \%hash ) or return;
152
153     ### the original force setting;
154     my $force_store = $conf->get_conf( 'force' );
155
156     ### parse the input: the first part before the space
157     ### is the command, followed by arguments.
158     ### see the usage below
159     my $key;
160     PARSE_INPUT: {
161         $string =~ s|^\s*([\w\?\!]+)\s*||;
162         chomp $string;
163         $key = lc($1);
164     }
165
166     ### you prefixed the input with 'force'
167     ### that means we set the force flag, and
168     ### reparse the input...
169     ### YAY goto block :)
170     if( $key eq 'force' ) {
171         $conf->set_conf( force => 1 );
172         goto PARSE_INPUT;
173     }
174
175     ### you want to quit
176     return 1 if $key =~ /^q/;
177
178     my $method = $map->{$key};
179     unless( $self->can( $method ) ) {
180         print "Unknown command '$key'. Type ? for help.\n";
181         return;
182     }
183
184     ### dispatch the method call
185     eval { $self->$method(
186                     command => $key,
187                     result  => [ split /\s+/, $string ],
188                     input   => $string );
189     };
190     warn $@ if $@;
191
192     return;
193 }
194
195 ### displays quit message
196 sub _quit {
197
198     ### well, that's what CPAN.pm says...
199     print "Lockfile removed\n";
200 }
201
202 sub _not_supported {
203     my $self = shift;
204     my %hash = @_;
205
206     my $cmd;
207     my $tmpl = {
208         command => { required => 1, store => \$cmd }
209     };
210
211     check( $tmpl, \%hash ) or return;
212
213     print "Sorry, the command '$cmd' is not supported\n";
214
215     return;
216 }
217
218 sub _fetch {
219     my $self = shift;
220     my $cb   = $self->backend;
221     my %hash = @_;
222
223     my($aref, $input);
224     my $tmpl = {
225         result  => { store => \$aref, default => [] },
226         input   => { default => 'all', store => \$input },
227     };
228
229     check( $tmpl, \%hash ) or return;
230
231     for my $mod (@$aref) {
232         my $obj;
233
234         unless( $obj = $cb->module_tree($mod) ) {
235             print "Warning: Cannot get $input, don't know what it is\n";
236             print "Try the command\n\n";
237             print "\ti /$mod/\n\n";
238             print "to find objects with matching identifiers.\n";
239
240             next;
241         }
242
243         $obj->fetch && $obj->extract;
244     }
245
246     return $aref;
247 }
248
249 sub _install {
250     my $self = shift;
251     my $cb   = $self->backend;
252     my %hash = @_;
253
254     my $mapping = {
255         make        => { target => TARGET_CREATE, skiptest => 1 },
256         test        => { target => TARGET_CREATE },
257         install     => { target => TARGET_INSTALL },
258     };
259
260     my($aref,$cmd);
261     my $tmpl = {
262         result  => { store => \$aref, default => [] },
263         command => { required => 1, store => \$cmd, allow => [keys %$mapping] },
264     };
265
266     check( $tmpl, \%hash ) or return;
267
268     for my $mod (@$aref) {
269         my $obj = $cb->module_tree( $mod );
270
271         unless( $obj ) {
272             print "No such module '$mod'\n";
273             next;
274         }
275
276         my $opts = $mapping->{$cmd};
277         $obj->install( %$opts );
278     }
279
280     return $aref;
281 }
282
283 sub _shell {
284     my $self    = shift;
285     my $cb      = $self->backend;
286     my $conf    = $cb->configure_object;
287     my %hash    = @_;
288
289     my($aref, $cmd);
290     my $tmpl = {
291         result  => { store => \$aref, default => [] },
292         command => { required => 1, store => \$cmd },
293
294     };
295
296     check( $tmpl, \%hash ) or return;
297
298
299     my $shell = $conf->get_program('shell');
300     unless( $shell ) {
301         print "Your configuration does not define a value for subshells.\n".
302               qq[Please define it with "o conf shell <your shell>"\n];
303         return;
304     }
305
306     my $cwd = Cwd::cwd();
307
308     for my $mod (@$aref) {
309         print "Running $cmd for $mod\n";
310
311         my $obj = $cb->module_tree( $mod )  or next;
312         $obj->fetch                         or next;
313         $obj->extract                       or next;
314
315         $cb->_chdir( dir => $obj->status->extract )   or next;
316
317         local $ENV{PERL5OPT} = CPANPLUS::inc->original_perl5opt;
318         if( system($shell) and $! ) {
319             print "Error executing your subshell '$shell': $!\n";
320             next;
321         }
322     }
323     $cb->_chdir( dir => $cwd );
324
325     return $aref;
326 }
327
328 sub _readme {
329     my $self    = shift;
330     my $cb      = $self->backend;
331     my $conf    = $cb->configure_object;
332     my %hash    = @_;
333
334     my($aref, $cmd);
335     my $tmpl = {
336         result  => { store => \$aref, default => [] },
337         command => { required => 1, store => \$cmd },
338
339     };
340
341     check( $tmpl, \%hash ) or return;
342
343     for my $mod (@$aref) {
344         my $obj = $cb->module_tree( $mod ) or next;
345
346         if( my $readme = $obj->readme ) {
347
348             $self->_pager_open;
349             print $readme;
350             $self->_pager_close;
351         }
352     }
353
354     return 1;
355 }
356
357 sub _reload {
358     my $self    = shift;
359     my $cb      = $self->backend;
360     my $conf    = $cb->configure_object;
361     my %hash    = @_;
362
363     my($input, $cmd);
364     my $tmpl = {
365         input   => { default => 'all', store => \$input },
366         command => { required => 1, store => \$cmd },
367
368     };
369
370     check( $tmpl, \%hash ) or return;
371
372     if ( $input =~ /cpan/i ) {
373         print qq[You want to reload the CPAN code\n];
374         print qq[Just type 'q' and then restart... ] .
375               qq[Trust me, it is MUCH safer\n];
376
377     } elsif ( $input =~ /index/i ) {
378         $cb->reload_indices(update_source => 1);
379
380     } else {
381         print qq[cpan     re-evals the CPANPLUS.pm file\n];
382         print qq[index    re-reads the index files\n];
383     }
384
385     return 1;
386 }
387
388 sub _autobundle {
389     my $self    = shift;
390     my $cb      = $self->backend;
391
392     print qq[Writing bundle file... This may take a while\n];
393
394     my $where = $cb->autobundle();
395
396     print $where
397         ? qq[\nWrote autobundle to $where\n]
398         : qq[\nCould not create autobundle\n];
399
400     return 1;
401 }
402
403 sub _set_conf {
404     my $self = shift;
405     my $cb   = $self->backend;
406     my $conf = $cb->configure_object;
407     my %hash = @_;
408
409     my($aref, $input);
410     my $tmpl = {
411         result  => { store => \$aref, default => [] },
412         input   => { default => 'all', store => \$input },
413     };
414
415     check( $tmpl, \%hash ) or return;
416
417     my $type = shift @$aref;
418
419     if( $type eq 'debug' ) {
420         print   qq[Sorry you cannot set debug options through ] .
421                 qq[this shell in CPANPLUS\n];
422         return;
423
424     } elsif ( $type eq 'conf' ) {
425
426         ### from CPAN.pm :o)
427         # CPAN::Shell::o and CPAN::Config::edit are closely related. 'o conf'
428         # should have been called set and 'o debug' maybe 'set debug'
429
430         #    commit             Commit changes to disk
431         #    defaults           Reload defaults from disk
432         #    init               Interactive setting of all options
433
434         my $name    = shift @$aref;
435         my $value   = "@$aref";
436
437         if( $name eq 'init' ) {
438             my $setup = CPANPLUS::Configure::Setup->new(
439                         conf    => $cb->configure_object,
440                         term    => $self->term,
441                         backend => $cb,
442                     );
443             return $setup->init;
444
445         } elsif ($name eq 'commit' ) {;
446             $cb->configure_object->save;
447             print "Your CPAN++ configuration info has been saved!\n\n";
448             return;
449
450         } elsif ($name eq 'defaults' ) {
451             print   qq[Sorry, CPANPLUS cannot restore default for you.\n] .
452                     qq[Perhaps you should run the interactive setup again.\n] .
453                     qq[\ttry running 'o conf init'\n];
454             return;
455
456         ### we're just supplying things in the 'conf' section now,
457         ### not the program section.. it's a bit of a hassle to make that
458         ### work cleanly with the original CPAN.pm interface, so we'll fix
459         ### it when people start complaining, which is hopefully never.
460         } else {
461             unless( $name ) {
462                 my @list =  grep { $_ ne 'hosts' }
463                             $conf->options( type => $type );
464
465                 my $method = 'get_' . $type;
466
467                 local $Data::Dumper::Indent = 0;
468                 for my $name ( @list ) {
469                     my $val = $conf->$method($name);
470                     ($val)  = ref($val)
471                                 ? (Data::Dumper::Dumper($val) =~ /= (.*);$/)
472                                 : "'$val'";
473                     printf  "    %-25s %s\n", $name, $val;
474                 }
475
476             } elsif ( $name eq 'hosts' ) {
477                 print   "Setting hosts is not trivial.\n" .
478                         "It is suggested you edit the " .
479                         "configuration file manually";
480
481             } else {
482                 my $method = 'set_' . $type;
483                 if( $conf->$method($name => defined $value ? $value : '') ) {
484                     my $set_to = defined $value ? $value : 'EMPTY STRING';
485                     print "Key '$name' was set to '$set_to'\n";
486                 }
487             }
488         }
489     } else {
490         print   qq[Known options:\n] .
491                 qq[  conf    set or get configuration variables\n] .
492                 qq[  debug   set or get debugging options\n];
493     }
494
495     return;
496 }
497
498 ########################
499 ### search functions ###
500 ########################
501
502 sub _author {
503     my $self = shift;
504     my $cb   = $self->backend;
505     my %hash = @_;
506
507     my($aref, $short, $input, $class);
508     my $tmpl = {
509         result  => { store => \$aref, default => ['/./'] },
510         short   => { default => 0, store => \$short },
511         input   => { default => 'all', store => \$input },
512         class   => { default => 'Author', no_override => 1,
513                     store => \$class },
514     };
515
516     check( $tmpl, \%hash ) or return;
517
518     my @regexes = map { m|/(.+)/| ? qr/$1/ : $_ } @$aref;
519
520
521     my @rv;
522     for my $type (qw[author cpanid]) {
523         push @rv, $cb->search( type => $type, allow => \@regexes );
524     }
525
526     unless( @rv ) {
527         print "No object of type $class found for argument $input\n"
528             unless $short;
529         return;
530     }
531
532     return $self->_pp_author(
533                 result  => \@rv,
534                 class   => $class,
535                 short   => $short,
536                 input   => $input );
537
538 }
539
540 ### find all modules matching a query ###
541 sub _module {
542     my $self = shift;
543     my $cb   = $self->backend;
544     my %hash = @_;
545
546     my($aref, $short, $input, $class);
547     my $tmpl = {
548         result  => { store => \$aref, default => ['/./'] },
549         short   => { default => 0, store => \$short },
550         input   => { default => 'all', store => \$input },
551         class   => { default => 'Module', no_override => 1,
552                     store => \$class },
553     };
554
555     check( $tmpl, \%hash ) or return;
556
557     my @rv;
558     for my $module (@$aref) {
559         if( $module =~ m|/(.+)/| ) {
560             push @rv, $cb->search(  type    => 'module',
561                                     allow   => [qr/$1/i] );
562         } else {
563             my $obj = $cb->module_tree( $module ) or next;
564             push @rv, $obj;
565         }
566     }
567
568     return $self->_pp_module(
569                 result  => \@rv,
570                 class   => $class,
571                 short   => $short,
572                 input   => $input );
573 }
574
575 ### find all bundles matching a query ###
576 sub _bundle {
577     my $self = shift;
578     my $cb   = $self->backend;
579     my %hash = @_;
580
581     my($aref, $short, $input, $class);
582     my $tmpl = {
583         result  => { store => \$aref, default => ['/./'] },
584         short   => { default => 0, store => \$short },
585         input   => { default => 'all', store => \$input },
586         class   => { default => 'Bundle', no_override => 1,
587                     store => \$class },
588     };
589
590     check( $tmpl, \%hash ) or return;
591
592     my @rv;
593     for my $bundle (@$aref) {
594         if( $bundle =~ m|/(.+)/| ) {
595             push @rv, $cb->search(  type    => 'module',
596                                     allow   => [qr/Bundle::.*?$1/i] );
597         } else {
598             my $obj = $cb->module_tree( "Bundle::${bundle}" ) or next;
599             push @rv, $obj;
600         }
601     }
602
603     return $self->_pp_module(
604                 result  => \@rv,
605                 class   => $class,
606                 short   => $short,
607                 input   => $input );
608 }
609
610 sub _distribution {
611     my $self = shift;
612     my $cb   = $self->backend;
613     my %hash = @_;
614
615     my($aref, $short, $input, $class);
616     my $tmpl = {
617         result  => { store => \$aref, default => ['/./'] },
618         short   => { default => 0, store => \$short },
619         input   => { default => 'all', store => \$input },
620         class   => { default => 'Distribution', no_override => 1,
621                     store => \$class },
622     };
623
624     check( $tmpl, \%hash ) or return;
625
626     my @rv;
627     for my $module (@$aref) {
628         ### if it's a regex... ###
629         if ( my ($match) = $module =~ m|^/(.+)/$|) {
630
631             ### something like /FOO/Bar.tar.gz/ was entered
632             if (my ($path,$package) = $match =~ m|^/?(.+)/(.+)$|) {
633                 my $seen;
634
635                 my @data = $cb->search( type    => 'package',
636                                         allow   => [qr/$package/i] );
637
638                 my @list = $cb->search( type    => 'path',
639                                         allow   => [qr/$path/i],
640                                         data    => \@data );
641
642                 ### make sure we dont list the same dist twice
643                 for my $val ( @list ) {
644                     next if $seen->{$val->package}++;
645
646                     push @rv, $val;
647                 }
648
649             ### something like /FOO/ or /Bar.tgz/ was entered
650             ### so we look both in the path, as well as in the package name
651             } else {
652                 my $seen;
653                 {   my @list = $cb->search( type    => 'package',
654                                             allow   => [qr/$match/i] );
655
656                     ### make sure we dont list the same dist twice
657                     for my $val ( @list ) {
658                         next if $seen->{$val->package}++;
659
660                         push @rv, $val;
661                     }
662                 }
663
664                 {   my @list = $cb->search( type    => 'path',
665                                             allow   => [qr/$match/i] );
666
667                     ### make sure we dont list the same dist twice
668                     for my $val ( @list ) {
669                         next if $seen->{$val->package}++;
670
671                         push @rv, $val;
672                     }
673
674                 }
675             }
676
677         } else {
678
679             ### user entered a full dist, like: R/RC/RCAPUTO/POE-0.19.tar.gz
680             if (my ($path,$package) = $module =~ m|^/?(.+)/(.+)$|) {
681                 my @data = $cb->search( type    => 'package',
682                                         allow   => [qr/^$package$/] );
683                 my @list = $cb->search( type    => 'path',
684                                         allow   => [qr/$path$/i],
685                                         data    => \@data);
686
687                 ### make sure we dont list the same dist twice
688                 my $seen;
689                 for my $val ( @list ) {
690                     next if $seen->{$val->package}++;
691
692                     push @rv, $val;
693                 }
694             }
695         }
696     }
697
698     return $self->_pp_distribution(
699                 result  => \@rv,
700                 class   => $class,
701                 short   => $short,
702                 input   => $input );
703 }
704
705 sub _find_all {
706     my $self = shift;
707
708     my @rv;
709     for my $method (qw[_author _bundle _module _distribution]) {
710         my $aref = $self->$method( @_, short => 1 );
711
712         push @rv, @$aref if $aref;
713     }
714
715     print scalar(@rv). " items found\n"
716 }
717
718 sub _uptodate {
719     my $self = shift;
720     my $cb   = $self->backend;
721     my %hash = @_;
722
723     my($aref, $short, $input, $class);
724     my $tmpl = {
725         result  => { store => \$aref, default => ['/./'] },
726         short   => { default => 0, store => \$short },
727         input   => { default => 'all', store => \$input },
728         class   => { default => 'Uptodate', no_override => 1,
729                     store => \$class },
730     };
731
732     check( $tmpl, \%hash ) or return;
733
734
735     my @rv;
736     if( @$aref) {
737         for my $module (@$aref) {
738             if( $module =~ m|/(.+)/| ) {
739                 my @list = $cb->search( type    => 'module',
740                                         allow   => [qr/$1/i] );
741
742                 ### only add those that are installed and not core
743                 push @rv, grep { not $_->package_is_perl_core }
744                           grep { $_->installed_file }
745                           @list;
746
747             } else {
748                 my $obj = $cb->module_tree( $module ) or next;
749                 push @rv, $obj;
750             }
751         }
752     } else {
753         @rv = @{$cb->_all_installed};
754     }
755
756     return $self->_pp_uptodate(
757             result  => \@rv,
758             class   => $class,
759             short   => $short,
760             input   => $input );
761 }
762
763 sub _ls {
764     my $self = shift;
765     my $cb   = $self->backend;
766     my %hash = @_;
767
768     my($aref, $short, $input, $class);
769     my $tmpl = {
770         result  => { store => \$aref, default => [] },
771         short   => { default => 0, store => \$short },
772         input   => { default => 'all', store => \$input },
773         class   => { default => 'Uptodate', no_override => 1,
774                     store => \$class },
775     };
776
777     check( $tmpl, \%hash ) or return;
778
779     my @rv;
780     for my $name (@$aref) {
781         my $auth = $cb->author_tree( uc $name );
782
783         unless( $auth ) {
784             print qq[ls command rejects argument $name: not an author\n];
785             next;
786         }
787
788         push @rv, $auth->distributions;
789     }
790
791     return $self->_pp_ls(
792             result  => \@rv,
793             class   => $class,
794             short   => $short,
795             input   => $input );
796 }
797
798 ############################
799 ### pretty printing subs ###
800 ############################
801
802
803 sub _pp_author {
804     my $self = shift;
805     my %hash = @_;
806
807     my( $aref, $short, $class, $input );
808     my $tmpl = {
809         result  => { required => 1, default => [], strict_type => 1,
810                         store => \$aref },
811         short   => { default => 0, store => \$short },
812         class   => { required => 1, store => \$class },
813         input   => { required => 1, store => \$input },
814     };
815
816     check( $tmpl, \%hash ) or return;
817
818     ### no results
819     if( !@$aref ) {
820         print "No objects of type $class found for argument $input\n"
821             unless $short;
822
823     ### one result, long output desired;
824     } elsif( @$aref == 1 and !$short ) {
825
826         ### should look like this:
827         #cpan> a KANE
828         #Author id = KANE
829         #    EMAIL        boumans@frg.eur.nl
830         #    FULLNAME     Jos Boumans
831
832         my $obj = shift @$aref;
833
834         print "$class id = ",                   $obj->cpanid(), "\n";
835         printf "    %-12s %s\n", 'EMAIL',       $obj->email();
836         printf "    %-12s %s%s\n", 'FULLNAME',  $obj->author();
837
838     } else {
839
840         ### should look like this:
841         #Author          KANE (Jos Boumans)
842         #Author          LBROCARD (Leon Brocard)
843         #2 items found
844
845         for my $obj ( @$aref ) {
846             printf qq[%-15s %s ("%s" (%s))\n],
847                 $class, $obj->cpanid, $obj->author, $obj->email;
848         }
849         print scalar(@$aref)." items found\n" unless $short;
850     }
851
852     return $aref;
853 }
854
855 sub _pp_module {
856     my $self = shift;
857     my %hash = @_;
858
859     my( $aref, $short, $class, $input );
860     my $tmpl = {
861         result  => { required => 1, default => [], strict_type => 1,
862                         store => \$aref },
863         short   => { default => 0, store => \$short },
864         class   => { required => 1, store => \$class },
865         input   => { required => 1, store => \$input },
866     };
867
868     check( $tmpl, \%hash ) or return;
869
870
871     ### no results
872     if( !@$aref ) {
873         print "No objects of type $class found for argument $input\n"
874             unless $short;
875
876     ### one result, long output desired;
877     } elsif( @$aref == 1 and !$short ) {
878
879
880         ### should look like this:
881         #Module id = LWP
882         #    DESCRIPTION  Libwww-perl
883         #    CPAN_USERID  GAAS (Gisle Aas <gisle@ActiveState.com>)
884         #    CPAN_VERSION 5.64
885         #    CPAN_FILE    G/GA/GAAS/libwww-perl-5.64.tar.gz
886         #    DSLI_STATUS  RmpO (released,mailing-list,perl,object-oriented)
887         #    MANPAGE      LWP - The World-Wide Web library for Perl
888         #    INST_FILE    C:\Perl\site\lib\LWP.pm
889         #    INST_VERSION 5.62
890
891         my $obj     = shift @$aref;
892         my $aut_obj = $obj->author;
893         my $format  = "    %-12s %s%s\n";
894
895         print "$class id = ",           $obj->module(), "\n";
896         printf $format, 'DESCRIPTION',  $obj->description()
897             if $obj->description();
898
899         printf $format, 'CPAN_USERID',  $aut_obj->cpanid() . " (" .
900             $aut_obj->author() . " <" . $aut_obj->email() . ">)";
901
902         printf $format, 'CPAN_VERSION', $obj->version();
903         printf $format, 'CPAN_FILE',    $obj->path() . '/' . $obj->package();
904
905         printf $format, 'DSLI_STATUS',  $self->_pp_dslip($obj->dslip)
906             if $obj->dslip() =~ /\w/;
907
908         #printf $format, 'MANPAGE',      $obj->foo();
909         ### this is for bundles... CPAN.pm downloads them,
910         #printf $format, 'CONATAINS,
911         # parses and goes from there...
912
913         printf $format, 'INST_FILE',    $obj->installed_file ||
914             '(not installed)';
915         printf $format, 'INST_VERSION', $obj->installed_version;
916
917
918
919     } else {
920
921         ### should look like this:
922         #Module          LWP             (G/GA/GAAS/libwww-perl-5.64.tar.gz)
923         #Module          POE             (R/RC/RCAPUTO/POE-0.19.tar.gz)
924         #2 items found
925
926         for my $obj ( @$aref ) {
927             printf "%-15s %-15s (%s)\n", $class, $obj->module(),
928                 $obj->path() .'/'. $obj->package();
929         }
930         print scalar(@$aref). " items found\n" unless $short;
931     }
932
933     return $aref;
934 }
935
936 sub _pp_dslip {
937     my $self    = shift;
938     my $dslip   = shift or return;
939
940     my (%_statusD, %_statusS, %_statusL, %_statusI);
941
942     @_statusD{qw(? i c a b R M S)} =
943         qw(unknown idea pre-alpha alpha beta released mature standard);
944
945     @_statusS{qw(? m d u n)}       =
946         qw(unknown mailing-list developer comp.lang.perl.* none);
947
948     @_statusL{qw(? p c + o h)}     = qw(unknown perl C C++ other hybrid);
949     @_statusI{qw(? f r O h)}       =
950         qw(unknown functions references+ties object-oriented hybrid);
951
952     my @status = split("", $dslip);
953
954     my $results = sprintf( "%s (%s,%s,%s,%s)",
955         $dslip,
956         $_statusD{$status[0]},
957         $_statusS{$status[1]},
958         $_statusL{$status[2]},
959         $_statusI{$status[3]}
960     );
961
962     return $results;
963 }
964
965 sub _pp_distribution {
966     my $self = shift;
967     my $cb   = $self->backend;
968     my %hash = @_;
969
970     my( $aref, $short, $class, $input );
971     my $tmpl = {
972         result  => { required => 1, default => [], strict_type => 1,
973                         store => \$aref },
974         short   => { default => 0, store => \$short },
975         class   => { required => 1, store => \$class },
976         input   => { required => 1, store => \$input },
977     };
978
979     check( $tmpl, \%hash ) or return;
980
981
982     ### no results
983     if( !@$aref ) {
984         print "No objects of type $class found for argument $input\n"
985             unless $short;
986
987     ### one result, long output desired;
988     } elsif( @$aref == 1 and !$short ) {
989
990
991         ### should look like this:
992         #Distribution id = S/SA/SABECK/POE-Component-Client-POP3-0.02.tar.gz
993         #    CPAN_USERID  SABECK (Scott Beck <scott@gossamer-threads.com>)
994         #    CONTAINSMODS POE::Component::Client::POP3
995
996         my $obj     = shift @$aref;
997         my $aut_obj = $obj->author;
998         my $pkg     = $obj->package;
999         my $format  = "    %-12s %s\n";
1000
1001         my @list    = $cb->search(  type    => 'package',
1002                                     allow   => [qr/^$pkg$/] );
1003
1004
1005         print "$class id = ", $obj->path(), '/', $obj->package(), "\n";
1006         printf $format, 'CPAN_USERID',
1007                     $aut_obj->cpanid .' ('. $aut_obj->author .
1008                     ' '. $aut_obj->email .')';
1009
1010         ### yes i know it's ugly, but it's what cpan.pm does
1011         printf $format, 'CONTAINSMODS', join (' ', map { $_->module } @list);
1012
1013     } else {
1014
1015         ### should look like this:
1016         #Distribution    LWP             (G/GA/GAAS/libwww-perl-5.64.tar.gz)
1017         #Distribution    POE             (R/RC/RCAPUTO/POE-0.19.tar.gz)
1018         #2 items found
1019
1020         for my $obj ( @$aref ) {
1021             printf "%-15s %s\n", $class, $obj->path() .'/'. $obj->package();
1022         }
1023
1024         print scalar(@$aref). " items found\n" unless $short;
1025     }
1026
1027     return $aref;
1028 }
1029
1030 sub _pp_uptodate {
1031     my $self = shift;
1032     my $cb   = $self->backend;
1033     my %hash = @_;
1034
1035     my( $aref, $short, $class, $input );
1036     my $tmpl = {
1037         result  => { required => 1, default => [], strict_type => 1,
1038                         store => \$aref },
1039         short   => { default => 0, store => \$short },
1040         class   => { required => 1, store => \$class },
1041         input   => { required => 1, store => \$input },
1042     };
1043
1044     check( $tmpl, \%hash ) or return;
1045
1046     my $format  = "%-25s %9s %9s  %s\n";
1047
1048     my @not_uptodate;
1049     my $no_version;
1050
1051     my %seen;
1052     for my $mod (@$aref) {
1053         next if $mod->package_is_perl_core;
1054         next if $seen{ $mod->package }++;
1055
1056
1057         if( $mod->installed_file and not $mod->installed_version ) {
1058             $no_version++;
1059             next;
1060         }
1061
1062         push @not_uptodate, $mod unless $mod->is_uptodate;
1063     }
1064
1065     unless( @not_uptodate ) {
1066         my $string = $input
1067                         ? "for $input"
1068                         : '';
1069         print "All modules are up to date $string\n";
1070         return;
1071
1072     } else {
1073         printf $format, (   'Package namespace',
1074                             'installed',
1075                             'latest',
1076                             'in CPAN file'
1077                         );
1078     }
1079
1080     for my $mod ( sort { $a->module cmp $b->module } @not_uptodate ) {
1081         printf $format, (   $mod->module,
1082                             $mod->installed_version,
1083                             $mod->version,
1084                             $mod->path .'/'. $mod->package,
1085                         );
1086     }
1087
1088     print "$no_version installed modules have no (parsable) version number\n"
1089         if $no_version;
1090
1091     return \@not_uptodate;
1092 }
1093
1094 sub _pp_ls {
1095     my $self = shift;
1096     my $cb   = $self->backend;
1097     my %hash = @_;
1098
1099     my( $aref, $short, $class, $input );
1100     my $tmpl = {
1101         result  => { required => 1, default => [], strict_type => 1,
1102                         store => \$aref },
1103         short   => { default => 0, store => \$short },
1104         class   => { required => 1, store => \$class },
1105         input   => { required => 1, store => \$input },
1106     };
1107
1108     check( $tmpl, \%hash ) or return;
1109
1110     ### should look something like this:
1111     #6272 2002-05-12 KANE/Acme-Comment-1.00.tar.gz
1112     #8171 2002-08-13 KANE/Acme-Comment-1.01.zip
1113     #7110 2002-09-04 KANE/Acme-Comment-1.02.tar.gz
1114     #7571 2002-09-08 KANE/Acme-Intraweb-1.01.tar.gz
1115     #6625 2001-08-23 KANE/Acme-POE-Knee-1.10.zip
1116     #3058 2003-10-05 KANE/Acme-Test-0.02.tar.gz
1117
1118     ### don't know size or mtime
1119     #my $format = "%8d %10s %s/%s\n";
1120
1121     for my $mod ( sort { $a->package cmp $b->package } @$aref ) {
1122         print "\t" . $mod->package . "\n";
1123     }
1124
1125     return $aref;
1126 }
1127
1128
1129 #############################
1130 ### end pretty print subs ###
1131 #############################
1132
1133
1134 sub _bang {
1135     my $self = shift;
1136     my %hash = @_;
1137
1138     my( $input );
1139     my $tmpl = {
1140         input   => { required => 1, store => \$input },
1141     };
1142
1143     check( $tmpl, \%hash ) or return;
1144
1145     eval $input;
1146     warn $@ if $@;
1147
1148     print "\n";
1149
1150     return;
1151 }
1152
1153 sub _help {
1154     print qq[
1155 Display Information
1156  a                                    authors
1157  b         string           display   bundles
1158  d         or               info      distributions
1159  m         /regex/          about     modules
1160  i         or                         anything of above
1161  r         none             reinstall recommendations
1162  u                          uninstalled distributions
1163
1164 Download, Test, Make, Install...
1165  get                        download
1166  make                       make (implies get)
1167  test      modules,         make test (implies make)
1168  install   dists, bundles   make install (implies test)
1169  clean                      make clean
1170  look                       open subshell in these dists' directories
1171  readme                     display these dists' README files
1172
1173 Other
1174  h,?           display this menu       ! perl-code   eval a perl command
1175  o conf [opt]  set and query options   q             quit the cpan shell
1176  reload cpan   load CPAN.pm again      reload index  load newer indices
1177  autobundle    Snapshot                force cmd     unconditionally do cmd
1178 ];
1179
1180 }
1181
1182
1183
1184 1;
1185 __END__
1186
1187 =pod
1188
1189 =head1 NAME
1190
1191 CPANPLUS::Shell::Classic - CPAN.pm emulation for CPANPLUS
1192
1193 =head1 DESCRIPTION
1194
1195 The Classic shell is designed to provide the feel of the CPAN.pm shell
1196 using CPANPLUS underneath.
1197
1198 For detailed documentation, refer to L<CPAN>.
1199
1200 =head1 BUG REPORTS
1201
1202 Please report bugs or other issues to E<lt>bug-cpanplus@rt.cpan.org<gt>.
1203
1204 =head1 AUTHOR
1205
1206 This module by Jos Boumans E<lt>kane@cpan.orgE<gt>.
1207
1208 =head1 COPYRIGHT
1209
1210 The CPAN++ interface (of which this module is a part of) is copyright (c) 
1211 2001 - 2007, Jos Boumans E<lt>kane@cpan.orgE<gt>. All rights reserved.
1212
1213 This library is free software; you may redistribute and/or modify it 
1214 under the same terms as Perl itself.
1215
1216 =head1 SEE ALSO
1217
1218 L<CPANPLUS::Configure>, L<CPANPLUS::Module>, L<CPANPLUS::Module::Author>
1219
1220 =cut
1221
1222
1223 =head1 SEE ALSO
1224
1225 L<CPAN>
1226
1227 =cut
1228
1229
1230
1231 # Local variables:
1232 # c-indentation-style: bsd
1233 # c-basic-offset: 4
1234 # indent-tabs-mode: nil
1235 # End:
1236 # vim: expandtab shiftwidth=4: