This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Be sure to find the vmsish pragma for one-liners in exit.t.
[perl5.git] / lib / CPAN / HandleConfig.pm
1 package CPAN::HandleConfig;
2 use strict;
3 use vars qw(%can %keys $loading $VERSION);
4
5 $VERSION = "5.5";
6
7 %can = (
8         commit   => "Commit changes to disk",
9         defaults => "Reload defaults from disk",
10         help     => "Short help about 'o conf' usage",
11         init     => "Interactive setting of all options",
12 );
13
14 # Q: where is the "How do I add a new config option" HOWTO?
15 # A1: svn diff -r 757:758 # where dagolden added test_report
16 # A2: svn diff -r 985:986 # where andk added yaml_module
17 # A3: 1. add new config option to %keys below
18 #     2. add a Pod description in CPAN::FirstTime; it should include a
19 #        prompt line; see others for examples
20 #     3. add a "matcher" section in CPAN::FirstTime::init that includes
21 #        a prompt function; see others for examples
22 #     4. add config option to documentation section in CPAN.pm
23
24 %keys = map { $_ => undef }
25     (
26      "applypatch",
27      "auto_commit",
28      "build_cache",
29      "build_dir",
30      "build_dir_reuse",
31      "build_requires_install_policy",
32      "bzip2",
33      "cache_metadata",
34      "check_sigs",
35      "colorize_debug",
36      "colorize_output",
37      "colorize_print",
38      "colorize_warn",
39      "commandnumber_in_prompt",
40      "commands_quote",
41      "connect_to_internet_ok",
42      "cpan_home",
43      "curl",
44      "dontload_hash", # deprecated after 1.83_68 (rev. 581)
45      "dontload_list",
46      "ftp",
47      "ftp_passive",
48      "ftp_proxy",
49      "ftpstats_size",
50      "ftpstats_period",
51      "getcwd",
52      "gpg",
53      "gzip",
54      "halt_on_failure",
55      "histfile",
56      "histsize",
57      "http_proxy",
58      "inactivity_timeout",
59      "index_expire",
60      "inhibit_startup_message",
61      "keep_source_where",
62      "load_module_verbosity",
63      "lynx",
64      "make",
65      "make_arg",
66      "make_install_arg",
67      "make_install_make_command",
68      "makepl_arg",
69      "mbuild_arg",
70      "mbuild_install_arg",
71      "mbuild_install_build_command",
72      "mbuildpl_arg",
73      "ncftp",
74      "ncftpget",
75      "no_proxy",
76      "pager",
77      "password",
78      "patch",
79      "patches_dir",
80      "perl5lib_verbosity",
81      "prefer_installer",
82      "prefs_dir",
83      "prerequisites_policy",
84      "proxy_pass",
85      "proxy_user",
86      "randomize_urllist",
87      "scan_cache",
88      "shell",
89      "show_unparsable_versions",
90      "show_upload_date",
91      "show_zero_versions",
92      "tar",
93      "tar_verbosity",
94      "term_is_latin",
95      "term_ornaments",
96      "test_report",
97      "trust_test_report_history",
98      "unzip",
99      "urllist",
100      "use_sqlite",
101      "username",
102      "wait_list",
103      "wget",
104      "yaml_load_code",
105      "yaml_module",
106     );
107
108 my %prefssupport = map { $_ => 1 }
109     (
110      "build_requires_install_policy",
111      "check_sigs",
112      "make",
113      "make_install_make_command",
114      "prefer_installer",
115      "test_report",
116     );
117
118 # returns true on successful action
119 sub edit {
120     my($self,@args) = @_;
121     return unless @args;
122     CPAN->debug("self[$self]args[".join(" | ",@args)."]");
123     my($o,$str,$func,$args,$key_exists);
124     $o = shift @args;
125     if($can{$o}) {
126         my $success = $self->$o(args => \@args); # o conf init => sub init => sub load
127         unless ($success) {
128             die "Panic: could not configure CPAN.pm for args [@args]. Giving up.";
129         }
130     } else {
131         CPAN->debug("o[$o]") if $CPAN::DEBUG;
132         unless (exists $keys{$o}) {
133             $CPAN::Frontend->mywarn("Warning: unknown configuration variable '$o'\n");
134         }
135         my $changed;
136
137
138         # one day I used randomize_urllist for a boolean, so we must
139         # list them explicitly --ak
140         if (0) {
141         } elsif ($o =~ /^(wait_list|urllist|dontload_list)$/) {
142
143             #
144             # ARRAYS
145             #
146
147             $func = shift @args;
148             $func ||= "";
149             CPAN->debug("func[$func]args[@args]") if $CPAN::DEBUG;
150             # Let's avoid eval, it's easier to comprehend without.
151             if ($func eq "push") {
152                 push @{$CPAN::Config->{$o}}, @args;
153                 $changed = 1;
154             } elsif ($func eq "pop") {
155                 pop @{$CPAN::Config->{$o}};
156                 $changed = 1;
157             } elsif ($func eq "shift") {
158                 shift @{$CPAN::Config->{$o}};
159                 $changed = 1;
160             } elsif ($func eq "unshift") {
161                 unshift @{$CPAN::Config->{$o}}, @args;
162                 $changed = 1;
163             } elsif ($func eq "splice") {
164                 my $offset = shift @args || 0;
165                 my $length = shift @args || 0;
166                 splice @{$CPAN::Config->{$o}}, $offset, $length, @args; # may warn
167                 $changed = 1;
168             } elsif ($func) {
169                 $CPAN::Config->{$o} = [$func, @args];
170                 $changed = 1;
171             } else {
172                 $self->prettyprint($o);
173             }
174             if ($changed) {
175                 if ($o eq "urllist") {
176                     # reset the cached values
177                     undef $CPAN::FTP::Thesite;
178                     undef $CPAN::FTP::Themethod;
179                     $CPAN::Index::LAST_TIME = 0;
180                 } elsif ($o eq "dontload_list") {
181                     # empty it, it will be built up again
182                     $CPAN::META->{dontload_hash} = {};
183                 }
184             }
185         } elsif ($o =~ /_hash$/) {
186
187             #
188             # HASHES
189             #
190
191             if (@args==1 && $args[0] eq "") {
192                 @args = ();
193             } elsif (@args % 2) {
194                 push @args, "";
195             }
196             $CPAN::Config->{$o} = { @args };
197             $changed = 1;
198         } else {
199
200             #
201             # SCALARS
202             #
203
204             if (defined $args[0]) {
205                 $CPAN::CONFIG_DIRTY = 1;
206                 $CPAN::Config->{$o} = $args[0];
207                 $changed = 1;
208             }
209             $self->prettyprint($o)
210                 if exists $keys{$o} or defined $CPAN::Config->{$o};
211         }
212         if ($changed) {
213             if ($CPAN::Config->{auto_commit}) {
214                 $self->commit;
215             } else {
216                 $CPAN::CONFIG_DIRTY = 1;
217                 $CPAN::Frontend->myprint("Please use 'o conf commit' to ".
218                                          "make the config permanent!\n\n");
219             }
220         }
221     }
222 }
223
224 sub prettyprint {
225     my($self,$k) = @_;
226     my $v = $CPAN::Config->{$k};
227     if (ref $v) {
228         my(@report);
229         if (ref $v eq "ARRAY") {
230             @report = map {"\t$_ \[$v->[$_]]\n"} 0..$#$v;
231         } else {
232             @report = map
233                 {
234                     sprintf "\t%-18s => %s\n",
235                                "[$_]",
236                                         defined $v->{$_} ? "[$v->{$_}]" : "undef"
237                 } keys %$v;
238         }
239         $CPAN::Frontend->myprint(
240                                  join(
241                                       "",
242                                       sprintf(
243                                               "    %-18s\n",
244                                               $k
245                                              ),
246                                       @report
247                                      )
248                                 );
249     } elsif (defined $v) {
250         $CPAN::Frontend->myprint(sprintf "    %-18s [%s]\n", $k, $v);
251     } else {
252         $CPAN::Frontend->myprint(sprintf "    %-18s undef\n", $k);
253     }
254 }
255
256 sub commit {
257     my($self,@args) = @_;
258     CPAN->debug("args[@args]") if $CPAN::DEBUG;
259     if ($CPAN::RUN_DEGRADED) {
260                              $CPAN::Frontend->mydie(
261                                                     "'o conf commit' disabled in ".
262                                                     "degraded mode. Maybe try\n".
263                                                     " !undef \$CPAN::RUN_DEGRADED\n"
264                                                    );
265     }
266     my $configpm;
267     if (@args) {
268       if ($args[0] eq "args") {
269         # we have not signed that contract
270       } else {
271         $configpm = $args[0];
272       }
273     }
274     unless (defined $configpm) {
275         $configpm ||= $INC{"CPAN/MyConfig.pm"};
276         $configpm ||= $INC{"CPAN/Config.pm"};
277         $configpm || Carp::confess(q{
278 CPAN::Config::commit called without an argument.
279 Please specify a filename where to save the configuration or try
280 "o conf init" to have an interactive course through configing.
281 });
282     }
283     my($mode);
284     if (-f $configpm) {
285         $mode = (stat $configpm)[2];
286         if ($mode && ! -w _) {
287             Carp::confess("$configpm is not writable");
288         }
289     }
290
291     my $msg;
292     my $home = home();
293     $msg = <<EOF unless $configpm =~ /MyConfig/;
294
295 # This is CPAN.pm's systemwide configuration file. This file provides
296 # defaults for users, and the values can be changed in a per-user
297 # configuration file. The user-config file is being looked for as
298 # $home/.cpan/CPAN/MyConfig.pm.
299
300 EOF
301     $msg ||= "\n";
302     my($fh) = FileHandle->new;
303     rename $configpm, "$configpm~" if -f $configpm;
304     open $fh, ">$configpm" or
305         $CPAN::Frontend->mydie("Couldn't open >$configpm: $!");
306     $fh->print(qq[$msg\$CPAN::Config = \{\n]);
307     foreach (sort keys %$CPAN::Config) {
308         unless (exists $keys{$_}) {
309             # do not drop them: forward compatibility!
310             $CPAN::Frontend->mywarn("Unknown config variable '$_'\n");
311             next;
312         }
313         $fh->print(
314             "  '$_' => ",
315             $self->neatvalue($CPAN::Config->{$_}),
316             ",\n"
317         );
318     }
319
320     $fh->print("};\n1;\n__END__\n");
321     close $fh;
322
323     #$mode = 0444 | ( $mode & 0111 ? 0111 : 0 );
324     #chmod $mode, $configpm;
325 ###why was that so?    $self->defaults;
326     $CPAN::Frontend->myprint("commit: wrote '$configpm'\n");
327     $CPAN::CONFIG_DIRTY = 0;
328     1;
329 }
330
331 # stolen from MakeMaker; not taking the original because it is buggy;
332 # bugreport will have to say: keys of hashes remain unquoted and can
333 # produce syntax errors
334 sub neatvalue {
335     my($self, $v) = @_;
336     return "undef" unless defined $v;
337     my($t) = ref $v;
338     unless ($t) {
339         $v =~ s/\\/\\\\/g;
340         return "q[$v]";
341     }
342     if ($t eq 'ARRAY') {
343         my(@m, @neat);
344         push @m, "[";
345         foreach my $elem (@$v) {
346             push @neat, "q[$elem]";
347         }
348         push @m, join ", ", @neat;
349         push @m, "]";
350         return join "", @m;
351     }
352     return "$v" unless $t eq 'HASH';
353     my(@m, $key, $val);
354     while (($key,$val) = each %$v) {
355         last unless defined $key; # cautious programming in case (undef,undef) is true
356         push(@m,"q[$key]=>".$self->neatvalue($val)) ;
357     }
358     return "{ ".join(', ',@m)." }";
359 }
360
361 sub defaults {
362     my($self) = @_;
363     if ($CPAN::RUN_DEGRADED) {
364                              $CPAN::Frontend->mydie(
365                                                     "'o conf defaults' disabled in ".
366                                                     "degraded mode. Maybe try\n".
367                                                     " !undef \$CPAN::RUN_DEGRADED\n"
368                                                    );
369     }
370     my $done;
371     for my $config (qw(CPAN/MyConfig.pm CPAN/Config.pm)) {
372         if ($INC{$config}) {
373             CPAN->debug("INC{'$config'}[$INC{$config}]") if $CPAN::DEBUG;
374             CPAN::Shell->_reload_this($config,{reloforce => 1});
375             $CPAN::Frontend->myprint("'$INC{$config}' reread\n");
376             last;
377         }
378     }
379     $CPAN::CONFIG_DIRTY = 0;
380     1;
381 }
382
383 =head2 C<< CLASS->safe_quote ITEM >>
384
385 Quotes an item to become safe against spaces
386 in shell interpolation. An item is enclosed
387 in double quotes if:
388
389   - the item contains spaces in the middle
390   - the item does not start with a quote
391
392 This happens to avoid shell interpolation
393 problems when whitespace is present in
394 directory names.
395
396 This method uses C<commands_quote> to determine
397 the correct quote. If C<commands_quote> is
398 a space, no quoting will take place.
399
400
401 if it starts and ends with the same quote character: leave it as it is
402
403 if it contains no whitespace: leave it as it is
404
405 if it contains whitespace, then
406
407 if it contains quotes: better leave it as it is
408
409 else: quote it with the correct quote type for the box we're on
410
411 =cut
412
413 {
414     # Instead of patching the guess, set commands_quote
415     # to the right value
416     my ($quotes,$use_quote)
417         = $^O eq 'MSWin32'
418             ? ('"', '"')
419                 : (q{"'}, "'")
420                     ;
421
422     sub safe_quote {
423         my ($self, $command) = @_;
424         # Set up quote/default quote
425         my $quote = $CPAN::Config->{commands_quote} || $quotes;
426
427         if ($quote ne ' '
428             and defined($command )
429             and $command =~ /\s/
430             and $command !~ /[$quote]/) {
431             return qq<$use_quote$command$use_quote>
432         }
433         return $command;
434     }
435 }
436
437 sub init {
438     my($self,@args) = @_;
439     CPAN->debug("self[$self]args[".join(",",@args)."]");
440     $self->load(doit => 1, @args);
441     1;
442 }
443
444 # This is a piece of repeated code that is abstracted here for
445 # maintainability.  RMB
446 #
447 sub _configpmtest {
448     my($configpmdir, $configpmtest) = @_;
449     if (-w $configpmtest) {
450         return $configpmtest;
451     } elsif (-w $configpmdir) {
452         #_#_# following code dumped core on me with 5.003_11, a.k.
453         my $configpm_bak = "$configpmtest.bak";
454         unlink $configpm_bak if -f $configpm_bak;
455         if( -f $configpmtest ) {
456             if( rename $configpmtest, $configpm_bak ) {
457                 $CPAN::Frontend->mywarn(<<END);
458 Old configuration file $configpmtest
459     moved to $configpm_bak
460 END
461             }
462         }
463         my $fh = FileHandle->new;
464         if ($fh->open(">$configpmtest")) {
465             $fh->print("1;\n");
466             return $configpmtest;
467         } else {
468             # Should never happen
469             Carp::confess("Cannot open >$configpmtest");
470         }
471     } else { return }
472 }
473
474 sub require_myconfig_or_config () {
475     return if $INC{"CPAN/MyConfig.pm"};
476     local @INC = @INC;
477     my $home = home();
478     unshift @INC, File::Spec->catdir($home,'.cpan');
479     eval { require CPAN::MyConfig };
480     my $err_myconfig = $@;
481     if ($err_myconfig and $err_myconfig !~ m#locate CPAN/MyConfig\.pm#) {
482         die "Error while requiring CPAN::MyConfig:\n$err_myconfig";
483     }
484     unless ($INC{"CPAN/MyConfig.pm"}) { # this guy has settled his needs already
485       eval {require CPAN::Config;}; # not everybody has one
486       my $err_config = $@;
487       if ($err_config and $err_config !~ m#locate CPAN/Config\.pm#) {
488           die "Error while requiring CPAN::Config:\n$err_config";
489       }
490     }
491 }
492
493 sub home () {
494     my $home;
495     # Suppress load messages until we load the config and know whether
496     # load messages are desired.  Otherwise, it's unexpected and odd 
497     # why one load message pops up even when verbosity is turned off.
498     # This means File::HomeDir load messages are never seen, but I
499     # think that's probably OK -- DAGOLDEN
500     
501     # 5.6.2 seemed to segfault localizing a value in a hashref 
502     # so do it manually instead
503     my $old_v = $CPAN::Config->{load_module_verbosity};
504     $CPAN::Config->{load_module_verbosity} = q[none];
505     if ($CPAN::META->has_usable("File::HomeDir")) {
506         $home = File::HomeDir->can('my_dot_config')
507             ? File::HomeDir->my_dot_config
508                 : File::HomeDir->my_data;
509         unless (defined $home) {
510             $home = File::HomeDir->my_home
511         }
512     }
513     unless (defined $home) {
514         $home = $ENV{HOME};
515     }
516     $CPAN::Config->{load_module_verbosity} = $old_v;
517     $home;
518 }
519
520 sub load {
521     my($self, %args) = @_;
522     $CPAN::Be_Silent++ if $args{be_silent};
523     my $doit;
524     $doit = delete $args{doit};
525
526     use Carp;
527     require_myconfig_or_config;
528     my @miss = $self->missing_config_data;
529     CPAN->debug("doit[$doit]loading[$loading]miss[@miss]") if $CPAN::DEBUG;
530     return unless $doit || @miss;
531     return if $loading;
532     $loading++;
533
534     require CPAN::FirstTime;
535     my($configpm,$fh,$redo);
536     $redo ||= "";
537     if (defined $INC{"CPAN/Config.pm"} && -w $INC{"CPAN/Config.pm"}) {
538         $configpm = $INC{"CPAN/Config.pm"};
539         $redo++;
540     } elsif (defined $INC{"CPAN/MyConfig.pm"} && -w $INC{"CPAN/MyConfig.pm"}) {
541         $configpm = $INC{"CPAN/MyConfig.pm"};
542         $redo++;
543     } else {
544         my($path_to_cpan) = File::Basename::dirname($INC{"CPAN.pm"});
545         my($configpmdir) = File::Spec->catdir($path_to_cpan,"CPAN");
546         my($configpmtest) = File::Spec->catfile($configpmdir,"Config.pm");
547         my $inc_key;
548         if (-d $configpmdir or File::Path::mkpath($configpmdir)) {
549             $configpm = _configpmtest($configpmdir,$configpmtest);
550             $inc_key = "CPAN/Config.pm";
551         }
552         unless ($configpm) {
553             $configpmdir = File::Spec->catdir(home,".cpan","CPAN");
554             File::Path::mkpath($configpmdir);
555             $configpmtest = File::Spec->catfile($configpmdir,"MyConfig.pm");
556             $configpm = _configpmtest($configpmdir,$configpmtest);
557             $inc_key = "CPAN/MyConfig.pm";
558         }
559         if ($configpm) {
560           $INC{$inc_key} = $configpm;
561         } else {
562           my $text = qq{WARNING: CPAN.pm is unable to } .
563               qq{create a configuration file.};
564           output($text, 'confess');
565         }
566
567     }
568     local($") = ", ";
569     if ($redo && !$doit) {
570         $CPAN::Frontend->myprint(<<END);
571 Sorry, we have to rerun the configuration dialog for CPAN.pm due to
572 some missing parameters...
573
574 END
575         $args{args} = \@miss;
576     }
577     my $initialized = CPAN::FirstTime::init($configpm, %args);
578     $loading--;
579     return $initialized;
580 }
581
582
583 # returns mandatory but missing entries in the Config
584 sub missing_config_data {
585     my(@miss);
586     for (
587          "auto_commit",
588          "build_cache",
589          "build_dir",
590          "cache_metadata",
591          "cpan_home",
592          "ftp_proxy",
593          #"gzip",
594          "http_proxy",
595          "index_expire",
596          #"inhibit_startup_message",
597          "keep_source_where",
598          #"make",
599          "make_arg",
600          "make_install_arg",
601          "makepl_arg",
602          "mbuild_arg",
603          "mbuild_install_arg",
604          ($^O eq "MSWin32" ? "" : "mbuild_install_build_command"),
605          "mbuildpl_arg",
606          "no_proxy",
607          #"pager",
608          "prerequisites_policy",
609          "scan_cache",
610          #"tar",
611          #"unzip",
612          "urllist",
613         ) {
614         next unless exists $keys{$_};
615         push @miss, $_ unless defined $CPAN::Config->{$_};
616     }
617     return @miss;
618 }
619
620 sub help {
621     $CPAN::Frontend->myprint(q[
622 Known options:
623   commit    commit session changes to disk
624   defaults  reload default config values from disk
625   help      this help
626   init      enter a dialog to set all or a set of parameters
627
628 Edit key values as in the following (the "o" is a literal letter o):
629   o conf build_cache 15
630   o conf build_dir "/foo/bar"
631   o conf urllist shift
632   o conf urllist unshift ftp://ftp.foo.bar/
633   o conf inhibit_startup_message 1
634
635 ]);
636     undef; #don't reprint CPAN::Config
637 }
638
639 sub cpl {
640     my($word,$line,$pos) = @_;
641     $word ||= "";
642     CPAN->debug("word[$word] line[$line] pos[$pos]") if $CPAN::DEBUG;
643     my(@words) = split " ", substr($line,0,$pos+1);
644     if (
645         defined($words[2])
646         and
647         $words[2] =~ /list$/
648         and
649         (
650         @words == 3
651         ||
652         @words == 4 && length($word)
653         )
654        ) {
655         return grep /^\Q$word\E/, qw(splice shift unshift pop push);
656     } elsif (defined($words[2])
657              and
658              $words[2] eq "init"
659              and
660             (
661              @words == 3
662              ||
663              @words >= 4 && length($word)
664             )) {
665         return sort grep /^\Q$word\E/, keys %keys;
666     } elsif (@words >= 4) {
667         return ();
668     }
669     my %seen;
670     my(@o_conf) =  sort grep { !$seen{$_}++ }
671         keys %can,
672             keys %$CPAN::Config,
673                 keys %keys;
674     return grep /^\Q$word\E/, @o_conf;
675 }
676
677 sub prefs_lookup {
678     my($self,$distro,$what) = @_;
679
680     if ($prefssupport{$what}) {
681         return $CPAN::Config->{$what} unless
682             $distro
683                 and $distro->prefs
684                     and $distro->prefs->{cpanconfig}
685                         and defined $distro->prefs->{cpanconfig}{$what};
686         return $distro->prefs->{cpanconfig}{$what};
687     } else {
688         $CPAN::Frontend->mywarn("Warning: $what not yet officially ".
689                                 "supported for distroprefs, doing a normal lookup");
690         return $CPAN::Config->{$what};
691     }
692 }
693
694
695 {
696     package
697         CPAN::Config; ####::###### #hide from indexer
698     # note: J. Nick Koston wrote me that they are using
699     # CPAN::Config->commit although undocumented. I suggested
700     # CPAN::Shell->o("conf","commit") even when ugly it is at least
701     # documented
702
703     # that's why I added the CPAN::Config class with autoload and
704     # deprecated warning
705
706     use strict;
707     use vars qw($AUTOLOAD $VERSION);
708     $VERSION = "5.5";
709
710     # formerly CPAN::HandleConfig was known as CPAN::Config
711     sub AUTOLOAD { ## no critic
712         my $class = shift; # e.g. in dh-make-perl: CPAN::Config
713         my($l) = $AUTOLOAD;
714         $CPAN::Frontend->mywarn("Dispatching deprecated method '$l' to CPAN::HandleConfig\n");
715         $l =~ s/.*:://;
716         CPAN::HandleConfig->$l(@_);
717     }
718 }
719
720 1;
721
722 __END__
723
724 =head1 LICENSE
725
726 This program is free software; you can redistribute it and/or
727 modify it under the same terms as Perl itself.
728
729 =cut
730
731 # Local Variables:
732 # mode: cperl
733 # cperl-indent-level: 4
734 # End: