package Psh; use vars qw($VERSION); $VERSION='1.8.1'; BEGIN { require Psh::OS; } require Psh::Util; require Psh::Locale; require Psh::Strategy; require Psh::Joblist; require Psh::Parser; require Psh::PerlEval; require Psh::Options; use strict; ############################################################################## ############################################################################## ## ## Variables ## ############################################################################## ############################################################################## # # Private, Lexical Variables: # my ($input,$readline_saves_history); ############################################################################## ############################################################################## ## ## SUBROUTINES: Command-line processing ## ############################################################################## ############################################################################## # # void handle_message (string MESSAGE, string FROM = 'eval') # # handles any message that an eval might have returned. Distinguishes # internal messages from Psh's signal handlers from all other # messages. It displays internal messages with print_out or does # nothing with them if FROM = 'main_loop'. It displays other messages with # print_error, and if FROM = 'main_loop', psh dies in addition. # sub handle_message { my ($message, $from) = @_; if (!defined($from)) { $from = 'eval'; } chomp $message; if ($message) { return if ($from eq 'hide'); if ($message =~ m/^SECRET $Psh::bin:(.*)$/s) { if ($from ne 'main_loop') { Psh::Util::print_out("$1\n"); } } else { Psh::Util::print_error("$from error ($message)!\n"); if ($from eq 'main_loop') { if( Psh::Options::get_option('ignoredie')) { Psh::Util::print_error_i18n('internal_error'); } else { die("Internal psh error."); } } } } } sub evl { my ($line, @use_strats) = @_; local @Psh::temp_use_strats; push @Psh::temp_use_strats, @use_strats if @use_strats; process_variable($line); return ($Psh::last_success_code, @Psh::last_result); } sub _evl { my @elements= @_; my @result=(); my $trace= Psh::Options::get_option('trace'); while( my $element= shift @elements) { my @tmp= @$element; my $type= shift @tmp; if ($type == Psh::Parser::T_EXECUTE()) { if ($trace) { for (my $i=1; $i<@tmp; $i++) { print STDERR "+ $tmp[$i][4]\n"; } } eval { @result= Psh::OS::execute_complex_command(\@tmp); }; handle_message($@); } elsif ($type == Psh::Parser::T_OR()) { return @result if @result and $result[0]; # we already had success } elsif ($type == Psh::Parser::T_AND()) { return (0) unless @result; next if ($result[0]); # we last had success return (0); } else { Psh::Util::print_error("evl: Don't know type $type\n"); } } return @result; } # # string read_until(PROMPT_TEMPL, string TERMINATOR, subr GET) # # Get successive lines via calls to GET until one of those # entire lines matches the patterm TERMINATOR. Used to implement # the `<=$control_d_max); next; } $control_d_counter=0; next unless $input; next if $input=~ m/^\s*$/; if ($input =~ m/(.*)<<([a-zA-Z_0-9\-]*)(.*)/) { my $pre= $1; my $terminator = $2; my $post= $3; my $continuation = $q_prompt ? Psh::Prompt::continue_prompt() : ''; $input = join('',$pre,'"', read_until($continuation, $terminator, $get), $terminator,'"',$post,"\n"); } elsif (Psh::Parser::incomplete_expr($input) > 0) { my $continuation = $q_prompt ? Psh::Prompt::continue_prompt() : ''; $input = read_until_complete($continuation, $input, $get); } chomp $input; my ($success,@result); my @elements= eval { Psh::Parser::parse_line($input) }; Psh::Util::print_debug_class('e',"(evl) Error: $@") if $@; if (@elements) { my $result; ($success,$result)= _evl(@elements); Psh::Util::print_debug_class('s',"Success: $success\n"); $Psh::last_success_code= $success; if ($result) { @Psh::last_result= @result= @$result; } else { undef @Psh::last_result; undef @result; } } else { undef $Psh::last_success_code; undef @Psh::last_result; } next unless $Psh::interactive; my $qEcho = 0; my $echo= Psh::Options::get_option('echo'); if (ref($echo) eq 'CODE') { $qEcho = &$echo(@result); } elsif (ref($echo)) { Psh::Util::print_warning_i18n('psh_echo_wrong',$Psh::bin); } else { if ($echo) { $qEcho = defined_and_nonempty(@result); } } if ($qEcho) { # Figure out where we'll save the result: if ($last_result_array ne $Psh::result_array) { $last_result_array = $Psh::result_array; my $what = ref($last_result_array); if ($what eq 'ARRAY') { $result_array_ref = $last_result_array; $result_array_name = find_array_name($result_array_ref); if (!defined($result_array_name)) { $result_array_name = 'anonymous'; } } elsif ($what) { Psh::Util::print_warning_i18n('psh_result_array_wrong',$Psh::bin); $result_array_ref = \@Psh::val; $result_array_name = 'Psh::val'; } else { # Ordinary string $result_array_name = $last_result_array; $result_array_name =~ s/^\@//; $result_array_ref = (Psh::PerlEval::protected_eval("\\\@$result_array_name"))[0]; } } if (scalar(@result) > 1) { my $n = scalar(@{$result_array_ref}); push @{$result_array_ref}, \@result; if ($Psh::interactive) { my @printresult=(); foreach my $val (@result) { if (defined $val) { push @printresult,qq['$val']; } else { push @printresult,qq[undef]; } } Psh::Util::print_out("\$$result_array_name\[$n] = [", join(',',@printresult), "]\n"); } } else { my $n = scalar(@{$result_array_ref}); my $res = $result[0]; push @{$result_array_ref}, $res; Psh::Util::print_out("\$$result_array_name\[$n] = \"$res\"\n"); } if (@{$result_array_ref}>100) { shift @{$result_array_ref}; } } } } # string find_array_name ( arrayref REF, string PACKAGE ) # # If REF is a reference to an array variable in the given PACKAGE or # any of its subpackages, find the name of that variable and return # it. PACKAGE defaults to main. sub find_array_name { my ($arref, $pack) = @_; if (!defined($pack)) { $pack = "::"; } my @otherpacks = (); for my $symb ( keys %{$pack} ) { if ($symb =~ m/::$/) { push @otherpacks, $symb unless ($pack eq 'main::' and $symb eq 'main::'); } elsif (\@{"$pack$symb"} eq $arref) { return "$pack$symb"; } } for my $subpack (@otherpacks) { my $ans = find_array_name($arref,"$pack$subpack"); if (defined($ans)) { return $ans; } } return undef; } # # bool defined_and_nonempty(args) # # returns true if it has any defined, nonempty args # sub defined_and_nonempty { if (!defined(@_)) { return 0; } if (scalar(@_) == 0) { return 0; } if (scalar(@_) == 1) { if (!defined($_[0])) { return 0; } if ($_[0] eq '') { return 0; } return 1; } return 1; # multiple args always true } # # void process_file(string FILENAME) # # process() the lines of FILENAME # sub process_file { my $path= shift; Psh::Util::print_debug("[PROCESSING FILE $path]\n"); local $Psh::interactive=0; if (!-r $path) { Psh::Util::print_error_i18n('cannot_read_script',$path,$Psh::bin); return; } local(*FILE); unless (open(FILE, "< $path")) { Psh::Util::print_error_i18n('cannot_open_script',$path,$Psh::bin); return; } Psh::OS::lock(*FILE); if ($Psh::debugging=~ /f/ or $Psh::debugging eq '1') { process(0, sub { my $txt=; Psh::Util::print_debug_class('f',$txt); return $txt; }); # don't prompt } else { process(0, sub { my $txt=;$txt }); } Psh::OS::unlock(*FILE); close(FILE); Psh::Util::print_debug("[FINISHED PROCESSING FILE $path]\n"); } sub process_variable { my $var= shift; local $Psh::interactive=0; my @lines; if (ref $var eq 'ARRAY') { @lines=@$var; } else { @lines= split /\n/, $var; @lines= map { $_."\n" } @lines; } process(0, sub { shift @lines }); } # # string iget(string PROMPT [, boolean returnflag [, code prompt_hook]]) # # Interactive line getting routine. If we have a # Term::ReadLine instance, use it and record the # input into the history buffer. Otherwise, just # grab an input line from STDIN. # # If returnflag is true, iget will return after # the user pressed ^C # # readline() returns a line WITHOUT a "\n" at the # end, and returns one WITH a "\n", UNLESS # the end of the input stream occurs after a non- # newline character. So, first we chomp() the # output of (if we aren't using readline()), # and then we tack the newline back on in both # cases. Other code later strips it off if necessary. # # iget() uses PROMPT as the prompt; this may be the empty string if no # prompting is necessary. # sub iget { my $prompt = shift; my $returnflag= shift; my $prompt_hook= shift; my $prompt_pre= ''; my $line; my $sigint = 0; $Psh::interactive=1; # Additional newline handling for prompts as Term::ReadLine::Perl # cannot use them properly if( $Psh::term->ReadLine eq 'Term::ReadLine::Perl' && $prompt=~ /^(.*\n)([^\n]+)$/) { $prompt_pre=$1; $prompt=$2; } Psh::OS::setup_readline_handler(); LINE: do { $sigint= 0 if ($sigint); # Trap ^C in an eval. The sighandler will die which will be # trapped. Then we reprompt if ($Psh::term) { &$prompt_hook if $prompt_hook; print $prompt_pre if $prompt_pre; eval { $line = $Psh::term->readline($prompt); }; } else { eval { &$prompt_hook if $prompt_hook; print $prompt_pre if $prompt_pre; print $prompt if $prompt; $line = ; }; } if( $@) { if( $@ =~ /Signal INT/) { $sigint= 1; Psh::Util::print_out_i18n('readline_interrupted'); if( $returnflag) { Psh::OS::remove_readline_handler(); return undef; } } else { handle_message( $@, 'iget'); } } } while ($sigint); Psh::OS::remove_readline_handler(); Psh::OS::reinstall_resize_handler(); return undef unless defined $line; chomp $line; add_history($line); return $line . "\n"; # This is expected by other code. } sub add_history { my $line=shift; return if !$line or $line =~ /^\s*$/; if (!@Psh::history || $Psh::history[$#Psh::history] ne $line) { my $len= Psh::Options::get_option('histsize'); $Psh::term->addhistory($line) if $Psh::term; push(@Psh::history, $line); if( @Psh::history>$len) { splice(@Psh::history,0,-$len); } } } sub save_history { return unless $Psh::term; Psh::Util::print_debug_class('o',"[Saving history]\n"); if( Psh::Options::get_option('save_history')) { my $file= Psh::Options::get_option('history_file'); return unless $file; if ($readline_saves_history) { $Psh::term->StifleHistory(Psh::Options::get_option('histsize')); $Psh::term->WriteHistory($file); } else { local(*F_HISTORY); if (open(F_HISTORY,">> $file")) { Psh::OS::lock(*F_HISTORY, Psh::OS::LOCK_EX()); foreach (@Psh::history) { print F_HISTORY $_; print F_HISTORY "\n"; } Psh::OS::unlock(*F_HISTORY); close(F_HISTORY); } } } } # # void minimal_initialize() # # Initialize just enough to be able to read the .pshrc file; leave # uncritical user-accessible variables until later in case the user # sets them in .pshrc. sub minimal_initialize { $| = 1; # Set output autoflush on # # Set up accessible psh:: package variables: # $Psh::eval_preamble = ''; $Psh::currently_active = 0; $Psh::result_array = ''; $Psh::which_regexp = '^[-a-zA-Z0-9_.~+]+$'; #' if ($]>=5.005) { eval { $Psh::which_regexp= qr($Psh::which_regexp); # compile for speed reasons }; Psh::Util::print_debug_class('e',"(minimal_init) Error: $@") if $@; } $Psh::cmd = 1; my @tmp= Psh::OS::splitdir($0); $Psh::bin= pop @tmp; Psh::Options::set_option('history_file', Psh::OS::catfile(Psh::OS::get_home_dir(), '.'.$Psh::bin.'_history')); $Psh::old_shell = $ENV{SHELL} if $ENV{SHELL}; $ENV{SHELL} = $0; $ENV{OLDPWD}= $ENV{PWD} = Psh::OS::getcwd_psh(); Psh::OS::inc_shlvl(); Psh::OS::setup_signal_handlers(); # The following accessible variables are undef during the # .pshrc file: undef $Psh::longhost; undef $Psh::host; @Psh::val = (); @Psh::history= (); Psh::Strategy::setup_defaults(); } # # void finish_initialize() # # Set the remaining psh:: package variables if they haven't been set # in the .pshrc file, and do other "late" initialization steps that # depend on these variable values. sub finish_initialize { Psh::OS::setup_sigsegv_handler() if Psh::Options::get_option('ignoresegfault'); if (!defined($Psh::longhost)) { $Psh::longhost = $ENV{HOSTNAME}||Psh::OS::get_hostname(); chomp $Psh::longhost; } if (!defined($Psh::host)) { $Psh::host= $Psh::longhost; $Psh::host= $1 if( $Psh::longhost=~ /([^\.]+)\..*/); } $ENV{HOSTNAME}= $Psh::host; } sub initialize_interactive_mode { if (-t STDIN) { # # Set up Term::ReadLine: # eval { require Term::ReadLine; }; if ($@) { $Psh::term = undef; Psh::Util::print_error_i18n('no_readline'); } else { eval { $Psh::term= Term::ReadLine->new('psh'); }; if( $@) { # Try one more time after a second, maybe the tty is # not setup sleep 1; eval { $Psh::term= Term::ReadLine->new('psh'); }; if( $@) { Psh::Util::print_error_i18n('readline_error',$@); $Psh::term= undef; } } if( $Psh::term) { $Psh::term->MinLine(10000); # We will handle history adding # ourselves (undef causes trouble). $Psh::term->ornaments(0); Psh::Util::print_debug_class('i','[Using ReadLine: ', $Psh::term->ReadLine(), "]\n"); if ($Psh::term->ReadLine() eq 'Term::ReadLine::Gnu') { $readline_saves_history = 1; } my $attribs= $Psh::term->Attribs; $attribs->{completion_function} = \&completion_dummy; my $word_break=" \\\n\t\"&{}('`\$\%\@~<>=;|/"; $attribs->{special_prefixes}= "\$\%\@\~\&"; $attribs->{word_break_characters}= $word_break; $attribs->{completer_word_break_characters}= $word_break ; } } Psh::OS::install_resize_handler(); Psh::OS::reinstall_resize_handler(); # ReadLine objects often mess with the SIGWINCH handler setup_term_misc(); } if (defined($Psh::term) and Psh::Options::get_option('save_history')) { my $file= Psh::Options::get_option('history_file'); return unless $file; if ($readline_saves_history) { $Psh::term->StifleHistory(Psh::Options::get_option('histsize')); $Psh::term->ReadHistory($file); } else { local(*F_HISTORY); if (open(F_HISTORY,"< $file")) { Psh::OS::lock(*F_HISTORY); while () { chomp; $Psh::term->addhistory($_); } Psh::OS::unlock(*F_HISTORY); close(F_HISTORY); } } } } # # We're used for the first TAB completion - load # the real completion module and call it # sub completion_dummy { my @args= @_; require Psh::Completion; Psh::Completion::init(); $Psh::term->Attribs->{completion_function} = \&Psh::Completion::completion; return Psh::Completion::completion(@_); } sub setup_term_misc { return unless $Psh::term; if ($Psh::term->can('add_defun')) { # Term::ReadLine::Gnu $Psh::term->add_defun('run-help', \&run_help); $Psh::term->parse_and_bind("\"\eh\":run-help"); # bind to ESC-h } } sub run_help { require Psh::Builtins::Help; my $line= substr($Psh::term->Attribs->{line_buffer},0, $Psh::term->Attribs->{end}); Psh::Builtins::Help::any_help($line); } # # void process_rc() # # Search for and process .pshrc files. # sub process_rc { my $opt_f= shift; my @rc; if ($opt_f) { push @rc, $opt_f; } else { push @rc, Psh::OS::get_rc_files(); } foreach my $rc (@rc) { if (-r $rc) { Psh::Util::print_debug_class('i',"[PROCESSING $rc]\n"); process_file($rc); } } } # # void process_args() # # Process files listed on command-line. # sub process_args { Psh::Util::print_debug_class('i',"[PROCESSING @ARGV FILES]\n"); foreach my $arg (@ARGV) { if (-r $arg) { Psh::Util::print_debug('i',"[PROCESSING $arg]\n"); process_file($arg); } } } # # void main_loop() # # Determine whether or not we are operating interactively, # set up the input routine accordingly, and process the # input. # sub main_loop { my $interactive = (-t STDIN) and (-t STDOUT); my $get; Psh::Util::print_debug_class('i',"[STARTING MAIN LOOP]\n"); if ($interactive) { $get = \&iget; } else { $get = sub { return ; }; } process($interactive, $get); } # bool is_number(ARG) # # Return true if ARG is a number # sub is_number { my $test = shift; return defined($test) && !ref($test) && $test=~/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/o; } # # End of file. # 1;