#!/usr/bin/env perl # $Id: install-menu-text.pl 57970 2021-02-27 14:17:34Z siepo $ # install-menu-txt.pl # # Copyright 2007-2021 Norbert Preining, Karl Berry # Copyright 2007-2008 Reinhard Kotucha # This file is licensed under the GNU General Public License version 2 # or any later version. # # This file implements the text based menu system for the TeX Live installer. use vars qw(@::end_install_hook $::opt_no_cls); our %vars; our $opt_in_place; our $tlpdb; our @media_available; our $media; our $previoustlpdb; our @collections_std; our $texlive_release; my $MENU_CONTINUE = -1; our $MENU_INSTALL = 0; our $MENU_ABORT = 1; our $MENU_QUIT = 2; my $RETURN = $MENU_CONTINUE; my $portable_toggled = 0; my $ptoggle_alert = "\n". " !! Portable option changed;\n" . " !! Directories have been reinitialized!\n"; # some strings to describe the different meanings of tlpdbopt_file_assoc $::fileassocdesc[0] = __("None"); $::fileassocdesc[1] = __("Only new"); $::fileassocdesc[2] = __("All"); $::deskintdesc[0] = __("None"); $::deskintdesc[1] = __("Menu shortcuts"); if (win32()) { $::deskintdesc[2] = __("Launcher"); } sub clear_screen { return 0 if ($::opt_no_cls); system (unix() ? 'clear' : 'cls'); } sub string_to_list { my $string=shift; return split(//, $string); } sub button { # for main menu: 1 char my $val=shift; my $vals=shift; # array ref to descriptions of possible values if (defined $vals) { return "[$val]"; } else { # just yes/no return ($val)? '[X]':'[ ]'; } } sub obutton { # for options menu: more chars allowed my $val=shift; my $vals=shift; # array ref to descriptions of possible values $vals = [' ', 'X'] unless defined $vals; return "[$$vals[$val]]"; } sub hbar { return '=' x79, "\n"; } sub toggle { my $var=shift; my $vals=shift; # array ref to descriptions of possible values $vals = [' ', 'X'] unless defined $vals; my $n_vals = @$vals; $vars{$var} += 1; $vars{$var} = 0 unless $vars{$var} < $n_vals; } sub menu_head { my $text = shift; clear_screen; print hbar(), "$text\n\n"; } sub other_options { my @options=@_; my %opts=( '-' => 'deselect all', '+' => 'select all', 'R' => 'return to main menu', 'P' => 'save installation profile to \'texlive.profile\' and exit', 'Q' => 'quit' ); $opts{'I'}=$vars{'instopt_portable'} ? 'start portable installation' : 'start installation to hard disk'; print "\nActions:"; if ($options[$#options] eq 'diskspace') { pop @options; calc_depends (); print " (disk space required: $vars{'total_size'} MB)"; } print "\n"; for my $option (@options) { if (defined $opts{"$option"}) { printf " <%s> %s\n", $option, $opts{$option}; } else { die "other_options: $opts{$option} undefined.\n"; } } } sub prompt { my $prompt = shift; print "\n$prompt: "; my $answer = ; $answer = "q" if !defined($answer); chomp($answer); return "$answer"; } # The menu loop. A menu is a function. Its return value is a # reference to another menu or to itself. sub run_menu_text { my (@args) = @_; my $default = 0; # select mirror if -select-mirror is given if ($::opt_select_repository) { # network is always available my @mirror_list = TeXLive::TLUtils::create_mirror_list(); print "Please select a repository:\n"; print "Network repositories:" if ($#media_available >= 0); my @sel_to_index; my $selind = 0; # this is for 0 mirror.ctan.org, but it is printed last! push @sel_to_index, 0; $selind++; for my $i (0..$#mirror_list) { if ($mirror_list[$i] !~ m/^ /) { print "\n$mirror_list[$i]\n"; } else { print " " if 1 <= $selind && $selind <= 9; # align print "[$selind] $mirror_list[$i]\n"; push @sel_to_index, $i; $selind++; } } print "----\n"; print "[0] default mirror http://mirror.ctan.org\n"; my $local_ind = "a"; if ($#media_available >= 0) { print "Local repositories:\n"; # we have some local media present, propose to use it for my $l (@media_available) { my ($a, $b) = split ('#', $l); if ($a eq 'local_compressed') { print "[$local_ind] compressed archive at $b\n"; $default = $local_ind; #$local_ind = chr(ord($local_ind)+1); $local_ind++; } elsif ($a eq 'local_uncompressed') { print "[$local_ind] uncompressed archive at $b\n"; $default = $local_ind; $local_ind++; } elsif ($a eq 'NET') { print "[$local_ind] cmd line repository: $b\n"; $default = $local_ind; $local_ind++; } else { warn "$0: Unknown media $l"; } } } print "[q] quit\n"; $selind--; my $selstr = "Your selection "; if ($local_ind ne "a") { # we got at least some local repository if ($local_ind eq "b") { $selstr .= "(a,0-$selind,q)"; $local_ind = chr(ord($local_ind)-1); } else { # that does not work!!! #$local_ind--; $local_ind = chr(ord($local_ind)-1); $selstr .= "(a-$local_ind,0-$selind,q)"; } } $selstr .= " [$default]: "; my $got_answer = 0; my $ans = undef; while (!defined($ans)) { print $selstr; $ans = readline(*STDIN); if (!defined($ans)) { print "Please select `q' to quit the program!\n"; } else { chomp($ans); $ans = $default if ($ans eq ""); if ($ans =~ m/^[0-9]+$/) { if (0 <= $ans && $ans <= $selind) { my $mfull; if ($ans == 0) { $::init_remote_needed = 'ctan'; } else { # only if something else but the predefined mirror is selected # we something here $mfull = TeXLive::TLUtils::extract_mirror_entry($mirror_list[$sel_to_index[$ans]]); print "selected mirror: ", $mfull, "\n"; $::init_remote_needed = $mfull; } } } elsif ($ans =~ m/^[a-$local_ind]$/) { my $i = ord($ans) - ord('a'); my $t = $media_available[$i]; $t =~ s/^[^#]*#//; $::init_remote_needed = $t; } elsif ($ans eq 'q' || $ans eq 'Q') { print "Goodbye.\n"; exit 0; } else { print "Not a valid answer: $ans.\n"; $ans = undef; } } } } # run remote init if (!do_remote_init($::init_remote_needed)) { warn "\n"; warn "Please select a different mirror! See info above.\n"; print STDERR "Press Enter to exit... "; $ans = readline (*STDIN); exit (1); } # the text mode installer does not take look at any argument # except -old-installation-found. while (@args) { my $f = shift @args; if ($f =~ m/^-old-installation-found=(.*)$/) { my $dn = $1; print "\nAn existing installation of TeX Live has been found in $dn\n"; print " If you want its selection of schemes/collections and various options to be used, press `y', otherwise anything else. Import settings from previous TeX Live installation: (y/n): "; chomp(my $yn = ); if (defined $yn && $yn =~ m/^y$/i) { import_settings_from_old_tlpdb($dn); } } } my $menu=\&main_menu; while ($RETURN == $MENU_CONTINUE) { $menu=$menu->(); } return($RETURN); } $::run_menu = \&run_menu_text; sub binary_menu { my %command=( 'self' => \&binary_menu, 'R' => \&main_menu, 'Q' => \&quit ); my @binaries; my @keys=string_to_list "abcdefghijklmopstuvwxyz"; my $index=0; my %keyval; my $selected_platform; menu_head "Available platforms:"; foreach my $key (keys %vars) { if ($key =~ /binary_(.*)/) { push @binaries, $1; } } @binaries=sort TeXLive::TLUtils::sort_archs @binaries; foreach my $binary (@binaries) { printf " %s %s %s (%s)\n", $keys[$index], button($vars{"binary_$binary"}), platform_desc($binary), "$binary"; $keyval{"$keys[$index]"} = "binary_$binary"; ++$index; } other_options qw(- + R Q diskspace); my $answer = prompt 'Enter letter(s) to (de)select platforms'; my @keystrokes=string_to_list $answer; foreach my $keystroke (@keystrokes) { if ($keystroke eq '-') { for my $binary (@binaries) { $vars{"binary_$binary"}=0 if defined $vars{"binary_$binary"}; } } if ($keystroke eq '+') { for my $binary (@binaries) { $vars{"binary_$binary"}=1 if defined $vars{"binary_$binary"}; } } if (defined $keyval{$keystroke}) { toggle "$keyval{$keystroke}"; } elsif (!defined $command{"\u$answer"}) { print "Unknown command: $answer\n\n"; } } if ($vars{"binary_win32"}) { $vars{"collection-wintools"} = 1; } else { $vars{"collection-wintools"} = 0; } if (defined $command{"\u$answer"}) { return $command{"\u$answer"}->(); } else { return $command{'self'}->(); } } sub scheme_menu { my %command=( 'self' => \&scheme_menu, 'R' => \&main_menu, 'Q' => \&quit ); my @schemes; my @keys=string_to_list "abcdefghijklmnopstuvwxyz"; my %keyval; my $index=0; menu_head 'Select scheme:'; @schemes = schemes_ordered_for_presentation(); foreach my $pkg (@schemes) { $vars{"$pkg"}=($vars{'selected_scheme'} eq $pkg)? 1:0; } push @schemes, "scheme-custom"; foreach my $scheme (@schemes) { $keyval{$keys[$index]}="$scheme"; if ($scheme ne "scheme-custom") { my $tlpobj = $tlpdb->get_package("$scheme"); printf " %s %s %s\n", $keys[$index], button($vars{"$scheme"}), $tlpobj->shortdesc; } else { printf " %s %s custom selection of collections\n", $keys[$index], button($vars{'selected_scheme'} eq "scheme-custom"); } ++$index; } select_scheme($vars{'selected_scheme'}); if ($vars{"binary_win32"}) { $vars{"collection-wintools"} = 1; } else { $vars{"collection-wintools"} = 0; } other_options qw(R Q diskspace); my $answer = prompt 'Enter letter to select scheme'; if (defined $keyval{"$answer"}) { $vars{'selected_scheme'}=$keyval{"$answer"}; select_scheme($vars{'selected_scheme'}); return $command{'self'}->(); } if (defined $command{"\u$answer"}) { return $command{"\u$answer"}->(); } else { print "Unknown command: $answer\n\n"; return $command{'self'}->(); } } sub collection_menu { my %command=( 'self' => \&collection_menu, 'R' => \&main_menu, 'Q' => \&quit ); my @collections; my @keys=string_to_list "abcdefghijklmnopstuvwxyzABCDEFGHIJKLMNOPSTUVWXYZ"; my %keyval; my $index=0; my @coll_short_desc; my @coll_long_desc; menu_head 'Select collections:'; @collections=sort @collections_std; foreach my $collection (@collections) { next if ($collection eq 'collection-perl'); my $tlpobj = $tlpdb->get_package("$collection"); if (length $tlpobj->shortdesc>30) { push @coll_long_desc, $collection; } else { push @coll_short_desc, $collection; } } my $singlecolumn_index=@coll_short_desc-1; ## my $lines=@coll_short_desc/2; ++$lines if (@coll_short_desc%2); for (0..$lines-1) { $index=$_; my $collection=$coll_short_desc[$index]; my $tlpobj = $tlpdb->get_package("$collection"); $keyval{$keys[$index]}="$collection"; printf " %s %s %-33s", $keys[$index], button($vars{"$collection"}), substr($tlpobj->shortdesc,0,33); if (defined $coll_short_desc[$index+$lines]) { my $collection=$coll_short_desc[$index+$lines]; my $tlpobj=$tlpdb->get_package("$collection"); $keyval{$keys[$index+$lines]}="$collection"; printf " %s %s %-32s\n", $keys[$index+$lines], button($vars{"$collection"}), substr($tlpobj->shortdesc,0,32); } else { print "\n"; } } ## $index=$singlecolumn_index; # print "\n$index\n\n"; foreach my $collection (@coll_long_desc) { my $tlpobj=$tlpdb->get_package("$collection"); $keyval{$keys[$index+1]}="$collection"; printf " %s %s %s\n", $keys[$index+1], button($vars{"$collection"}), $tlpobj->shortdesc; ++$index; } ## other_options qw(- + R Q diskspace); my $answer = prompt 'Enter letter(s) to (de)select collection(s)'; my @keystrokes=string_to_list $answer; foreach my $keystroke (@keystrokes) { if ($keystroke eq '-') { for my $collection (@collections) { $vars{"$collection"}=0 if defined $vars{"$collection"}; } } if ($keystroke eq '+') { for my $collection (@collections) { $vars{"$collection"}=1 if defined $vars{"$collection"}; } } if (defined $keyval{$keystroke}) { toggle "$keyval{$keystroke}"; } elsif (!defined $command{"\u$answer"}) { print "Unknown command: $answer\n\n"; } } if (defined $command{"\u$answer"}) { # if we play around with collections we also select custom-scheme # but we do not switch back to originally afterwards, too complicated # to be done select_scheme("scheme-custom"); return $command{"\u$answer"}->(); } else { return $command{'self'}->(); } } sub directories_menu { my %command=( 'self' => \&directories_menu, 'R' => \&main_menu, 'Q' => \&quit ); menu_head "Directories customization:"; if (!TeXLive::TLUtils::texdir_check($vars{'TEXDIR'}, 1)) { print "!! The default location as given below is forbidden or !! can't be written to. !! Either change the destination directory using <1> or create it !! outside this script. "; } my $texmfdir = $vars{'TEXDIR'} . ($vars{'TEXDIR'} =~ /\/$/ ? 'texmf-dist' : '/texmf-dist'); if (!$opt_in_place) { print <<"EOF"; <1> TEXDIR: $vars{'TEXDIR'} main tree: $texmfdir EOF } else { print <<"EOF"; TEXDIR: $vars{'TEXDIR'} main tree: $texmfdir EOF } if (!$vars{'instopt_portable'}) { print <<"EOF"; <2> TEXMFLOCAL: $vars{'TEXMFLOCAL'} <3> TEXMFSYSVAR: $vars{'TEXMFSYSVAR'} <4> TEXMFSYSCONFIG: $vars{'TEXMFSYSCONFIG'} <5> TEXMFVAR: $vars{'TEXMFVAR'} <6> TEXMFCONFIG: $vars{'TEXMFCONFIG'} <7> TEXMFHOME: $vars{'TEXMFHOME'} EOF if (win32) { print " Note: ~ will expand to %USERPROFILE%\n"; } else { print " Note: ~ will expand to \$HOME (or to %USERPROFILE% on Windows)\n"; } } other_options qw(R Q); my $answer = prompt 'Enter command'; if ("\u$answer" eq '1' and !$opt_in_place) { print "New value for TEXDIR [$vars{'TEXDIR'}]: "; $answer = &input_dirname (); $vars{'TEXDIR'} = $answer if $answer ne ""; my $texdirnoslash; if ($vars{'TEXDIR'}=~/^(.*)\/$texlive_release$/) { $texdirnoslash = $1; $vars{'TEXMFLOCAL'}="$texdirnoslash/texmf-local"; $vars{'TEXMFSYSVAR'}="$texdirnoslash/$texlive_release/texmf-var"; $vars{'TEXMFSYSCONFIG'}="$texdirnoslash/$texlive_release/texmf-config"; } elsif ($vars{'TEXDIR'}=~/^(.*)$/) { $texdirnoslash = $1; $texdirnoslash =~ s!/$!!; $vars{'TEXMFLOCAL'}="$texdirnoslash/texmf-local"; $vars{'TEXMFSYSVAR'}="$texdirnoslash/texmf-var"; $vars{'TEXMFSYSCONFIG'}="$texdirnoslash/texmf-config"; } return $command{'self'}; } elsif ("\u$answer" eq '2' and !$vars{'instopt_portable'}) { print "New value for TEXMFLOCAL [$vars{'TEXMFLOCAL'}]: "; $answer = &input_dirname (); $vars{'TEXMFLOCAL'} = $answer if $answer ne ""; return $command{'self'}; } elsif ("\u$answer" eq '3' and !$vars{'instopt_portable'}) { print "New value for TEXMFSYSVAR [$vars{'TEXMFSYSVAR'}]: "; $answer = &input_dirname (); $vars{'TEXMFSYSVAR'} = $answer if $answer ne ""; return $command{'self'}; } elsif ("\u$answer" eq '4' and !$vars{'instopt_portable'}) { print "New value for TEXMFSYSCONFIG [$vars{'TEXMFSYSCONFIG'}]: "; $answer = &input_dirname (); $vars{'TEXMFSYSCONFIG'} = $answer if $answer ne ""; return $command{'self'}; } elsif ("\u$answer" eq '5' and !$vars{'instopt_portable'}) { print "New value for TEXMFVAR [$vars{'TEXMFVAR'}]: "; $answer = &input_dirname ("noexpansion"); $vars{'TEXMFVAR'} = $answer if $answer ne ""; return $command{'self'}; } elsif ("\u$answer" eq '6' and !$vars{'instopt_portable'}) { print "New value for TEXMFCONFIG [$vars{'TEXMFCONFIG'}]: "; $answer = &input_dirname ("noexpansion"); $vars{'TEXMFCONFIG'} = $answer if $answer ne ""; return $command{'self'}; } elsif ("\u$answer" eq '7' and !$vars{'instopt_portable'}) { print "New value for TEXMFHOME [$vars{'TEXMFHOME'}]: "; $answer = &input_dirname ("noexpansion"); $vars{'TEXMFHOME'} = $answer if $answer ne ""; return $command{'self'}; } if (defined $command{"\u$answer"}) { return $command{"\u$answer"}->(); } else { print "Unknown command: $answer\n\n"; return $command{'self'}->(); } } # Helper function to read a directory name and clean it up. # Unless NO_EXPANSION is true, convert to absolute path. # sub input_dirname { my $noexpansion = shift; chomp (my $answer = ); return "" if $answer eq ""; $answer =~ s!\\!/!g if win32(); # switch to forward slashes if (!$noexpansion) { my $home = getenv('HOME'); $home = getenv('USERPROFILE') if win32(); $home ||= '~'; $answer =~ s/^~/$home/; # $home expansion } if ($answer !~ m/^~/) { # relative paths are unlikely to work in texmf.cnf, et al., # and don't have any apparent practical use. Convert to absolute. if (! File::Spec->file_name_is_absolute($answer)) { $answer = TeXLive::TLUtils::tl_abs_path($answer); $answer = "" unless defined $answer; } } return $answer; } $vars{'page'}=0; sub html2text { my $filename=shift; my @text; open IN, "$filename"; @all_lines=; close IN; chomp @all_lines; my $itemcnt; my $ordered_list=0; my $h1_indent=25; my $h2_indent=3; my $h3_indent=6; for (@all_lines) { next if /DOCTYPE/; next if /