@Filename_Completers = qw(
'statusFace' => 'status',
'historyFile' => '.commandterm_history',
'historySaveLength' => -1,
sub Filename_Completers
{
return @Filename_Completers;
my ($self, $name, $cfgfile, $app_config, $derived_config) = @_;
$self->{'Name'} = $name || 'commandterm';
$self->{'Help'} = CommandTerm
::Help
->New;
$self->{'EvalBuffer'} = '';
$self->{'InputMode'} = 1;
$self->{'GetPromptCallback'} = sub { return $self->DefaultGetPrompt; };
$self->{'TogglePromptCallback'} = sub { return $self->DefaultTogglePrompt; };
$self->{'ExecCommandCallback'} = sub { $self->DefaultCommandExec(@_); };
$self->{'UpdatePrompt'} = 1;
$self->{'HandleInterruptCallback'} = sub { $self->DefaultInterruptCallback; };
$self->{'QuitCallback'} = undef;
$self->{'ReadLine'} = undef;
$self->{'Attribs'} = undef;
$self->{'ReDirFH'} = undef;
$self->{'DefaultPager'} = undef;
$self->{'PagerCommands'} = [];
$self->{'LogFH'} = undef;
$self->{'LogFileName'} = '';
$self->{'PerlIndex'} = 0;
$self->{'PerlMatches'} = [];
$self->{'Config'} = $self->GetConfig($cfgfile, $app_config, $derived_config);
$self->{'StatusFace'} = $self->{'Config'}{'statusFace'};
$self->{'DataFace'} = $self->{'Config'}{'dataFace'};
$self->{'ErrorFace'} = $self->{'Config'}{'errorFace'};
$self->{'HistoryFile'} = $self->{'Config'}{'historyFile'};
$self->{'HistorySaveLength'} = $self->{'Config'}{'historySaveLength'};
return undef if $method eq 'DESTROY';
if( exists($self->{$method}) ) {
my $old_val = $self->{$method};
print STDERR
"Bad method '$method' for class ", ref($self), "\n";
my ($self, $cfgfile, $app_config, $derived_config) = @_;
foreach my $key (keys %BaseConfig) {
$config->{$key} = $BaseConfig{$key};
if( $derived_config and ref($derived_config) eq 'HASH' ) {
foreach my $key (keys %$derived_config) {
$config->{$key} = $derived_config->{$key};
if( $app_config and ref($app_config) eq 'HASH' ) {
foreach my $key (keys %$app_config) {
$config->{$key} = $app_config->{$key};
my $fh = FileHandle
->new("< $cfgfile");
my ($name, $value) = /(\w+)\s*:\s*([^\s].*[^\s])\s*$/;
$config->{$name} = $value;
$SIG{'INT'} = sub { $self->quit; };
$SIG{'TERM'} = sub { $self->quit; };
$SIG{'QUIT'} = sub { $self->quit; };
sub DefaultInterruptCallback
{}
$self->Print($self->DataFace, @what);
$self->Print($self->StatusFace, @what);
$self->Print($self->ErrorFace, @what);
my ($self, $how, @what) = @_;
my $what = join '', @what;
my $LOGFILE = $self->LogFH;
print $LOGFILE $what if defined($LOGFILE);
my ($self, $text, $line, $start, $end) = @_;
my ($first) = (substr($line, 0, $start) =~ /;?([^;\[]*)$/);
my $command = ($first =~ /^\s*(\S+)\s+/)[0] || '';
my $ReadLine = $self->ReadLine;
my $Attribs = $self->Attribs;
return $self->PerlCustomCompleter($text, $line, $start, $end)
if ($self->InputMode == $PERL_MODE) or
grep(/1/, map($command =~ /^$_$/, $self->GetPerlSubs));
$Attribs->{'completion_append_character'} = ' ';
$Attribs->{'ignore_some_completions_function'} = undef;
if( $first =~ /^\s*$/ ) {
$Attribs->{'completion_word'} = [ ($self->GetPerlSubs, @Commands) ];
$completer = 'list_completion_function';
} elsif( $first =~ /\|\s*$/ ) {
} elsif( $command eq 'setenv' or $command eq 'printenv' ) {
$Attribs->{'completion_word'} = [ map { s/\$//; $_ } keys %ENV ];
$completer = 'list_completion_function';
} elsif( $command eq 'help' ) {
$Attribs->{'completion_word'} = [ @Commands ];
$completer = 'list_completion_function';
} elsif( $text =~ /\// ) {
$completer = 'filename_completion_function';
$Attribs->{'ignore_some_completions_function'} =
sub { $self->IgnoreSomeCompletions($text, @_); };
} elsif( $text =~ /^\$/ ) {
$Attribs->{'completion_append_character'} = '';
$Attribs->{'completion_word'} = [ @
{ $self->EnvVars } ];
$completer = 'list_completion_function';
} elsif( $text =~ /^~/ ) {
$completer = 'username_completion_function';
$completer = 'filename_completion_function';
$Attribs->{'ignore_some_completions_function'} =
sub { $self->IgnoreSomeCompletions($text, @_); };
return $completer ?
$ReadLine->completion_matches($text, $Attribs->{$completer}) : ();
$arg = $self->TildeExpand($arg);
return undef unless defined($arg);
return $self->EnvVarExpand($arg);
while( $arg =~ /\$([^\$\/]+)/ ) {
if( defined($ENV{$1}) ) {
$arg =~ s/\$([^\$\/]+)/$ENV{$1}/e
;
$self->PrintError("Undefined variable: '$1'\n");
if( $arg =~ /^~([^\/]*)(.*)/ ) {
my @pwnam = getpwnam($username);
$arg = $pwnam[7] . $rest;
$self->PrintError("No user '$username' exists.\n");
$arg = ($ENV{'HOME'} || $ENV{'LOGDIR'}) . $rest;
return undef unless defined $arg;
$arg =~ s/\\c(.)/pack('C*', ord(uc($1))^64)/eg;
$arg =~ s/\\x([\da-fA-F]{1,2})/pack 'C*', hex($1)/eg;
$arg =~ s/\\([01][0-7]{2})/pack 'C*', oct($1)/eg;
my ($self, $command) = @_;
foreach my $arg ($self->ReadLine->history_tokenize($command)) {
$arg = $self->TildeExpand($arg);
return () unless defined($arg);
$arg = $self->UnEscape($arg) if $arg =~ /\\/ and $arg !~ /^["']/;
my ($self, $command) = @_;
if( defined($command) ) {
if( $self->InputMode == $PERL_MODE and $command !~ /^\s*$/ ) {
$self->EvalPerl($command);
my @command = $self->Tokenize($command);
my @redir_command = @command;
if( $self->InitRedirection(\
@redir_command) ) {
my ($cmd, @args) = @redir_command;
my $qm = quotemeta($cmd);
if( grep(/^$qm$/, $self->GetPerlSubs) ) {
$self->RunPerlSub($cmd, @args);
} elsif( grep(/^$qm$/, $self->Commands) ) {
$self->ShellExec(@command);
# Setup output redirection, if any. If the command has a '|', '>', or
# '>>' token in it, use that token and the rest of the args as the redirection .
my ($self, $command) = @_;
for(my $i=0; $i<=$#$command; $i++) {
if( $self->NoPipes and $command->[$i] =~ /^\|$/ ) {
$self->PrintError("pipe redirection not supported in this commandterm :- (.\n");
} elsif( $command->[$i] =~ /^(\|)|(>>?)$/ ) {
@redir_cmd = splice @
$command, $i;
$self->PrintError("syntax error: redirection to nowhere.\n");
} elsif( $redir_cmd[0] =~ /^>/ and @redir_cmd > 2 ) {
$self->PrintError("syntax error: too many arguments after redirection .\ n");
} elsif( grep(/1/, map($command->[0] =~ /^$_$/, @
{ $self->PagerCommands })) ) {
@redir_cmd = ('|', $self->DefaultPager) if $self->DefaultPager;
if( my $fh = FileHandle
->new("@redir_cmd") ) {
$self->ReDirFH->autoflush(1);
$self->PrintError("Error opening '$redir_cmd[1]': $!\n");
sub SetDefaultPagerCommands
{
my ($self, @pager_commands) = @_;
$self->{'PagerCommands'} = [ @pager_commands ];
my $default_pager = undef;
$default_pager = $self->Which($ENV{'PAGER'}) if exists($ENV{'PAGER'});
$default_pager = $self->Which('less') unless $default_pager;
$default_pager = $self->Which('more') unless $default_pager;
unless( $default_pager ) {
$self->PrintError("Cannot find a default pager.\n");
if( exists($ENV{'PAGER'}) ) {
$self->PrintError("You have the \$PAGER environment variable set to '$ENV{'PAGER'}', ",
"but it couldn't be found.\n");
$self->PrintError("The \$PAGER environment variable isn't defined.\n");
$self->PrintError("The programs 'less' and 'more' couldn't be found either.\n");
$self->PrintError("Use the '|' command to send command output to a pager.\n");
$self->DefaultPager($default_pager);
if( -f
$file and -x _
) {
} elsif( $file =~ m
(/) ) {
@dirs = (split ':', $ENV{'PATH'});
foreach my $dir (@dirs) {
return $path if( -f
$path and -x _
);
return &{ $self->GetPromptCallback } . ($self->MLC ?
'? ' : '> ');
return ($self->InputMode == $COMMAND_MODE ?
'command' : 'perl mode' );
&{ $self->TogglePromptCallback };
sub DefaultTogglePrompt
{
my $input_mode = $self->InputMode;
if( $input_mode == $COMMAND_MODE ) {
$input_mode = $PERL_MODE;
} elsif( $input_mode == $PERL_MODE ) {
$input_mode = $COMMAND_MODE;
$input_mode = $COMMAND_MODE;
$self->InputMode($input_mode);
if( $line =~ /^\s*\\$/ ) {
} elsif( $self->EMLC and $line !~ /^\s*\.\s*$/ ) {
$self->{'EvalBuffer'} .= "$line ";
} elsif( $line =~ s/\\$/ / ) {
$self->{'EvalBuffer'} .= $line;
$line = $self->EvalBuffer . $line;
$line = $self->DoHistory($line) unless $line =~ /^(quit|exit)\s?/;
&{ $self->ExecCommandCallback }($line) if defined($line);
my $ReadLine = $self->ReadLine;
# The history_expand() function from Readline can return 4 values:
# -1 - There was an error in expansion. Command not added to history.
# This is the same behavior as tcsh.
# 0 - No expansion possible. Just a normal command. Add command to history,
# 1 - Expansion took place. Assign $command to expansion and proceed.
# 2 - Expansion took place (eg. :p modifier was used). Display exspansion
# but don't execute. Expanded command implicitly added to history by
my ($res, $str) = $ReadLine->history_expand($line);
$self->PrintError("$str\n");
$ReadLine->add_history($line);
$ReadLine->append_history(1, $self->HistoryFile);
$ReadLine->add_history($line);
$ReadLine->append_history(1, $self->HistoryFile);
$self->PrintStatus("$line\n");
$self->PrintStatus("$str\n");
$ReadLine->append_history(1, $self->HistoryFile);
my $ReadLine = new Term
::ReadLine
$self->Name;
my $Attribs = $ReadLine->Attribs;
$self->ReadLine($ReadLine);
$self->Attribs($Attribs);
# so perl variables can be <TAB> completed
$Attribs->{'special_prefixes'} = '$@&%';
$Attribs->{'attempted_completion_function'} = sub { $self->DefaultCompleter(@_); };
$Attribs->{'completion_entry_function'} = sub {};
$Attribs->{'directory_completion_hook'} = sub { $self->ExpandDirectory(@_); };
$Attribs->{'completion_display_matches_hook'} = sub { $self->MungeCompletions(@_); };
# Control-T will toggle the prompt
$ReadLine->add_defun('toggle-prompt', sub { $self->TogglePrompt; }, ord "\ct");
# inhibit the implicit call to add_history() since we do our own.
$ReadLine->stifle_history(undef);
my $history_file = $self->GlobExpand($self->HistoryFile);
unless( $history_file ) {
$history_file = '/dev/null';
$self->PrintError( "Bad history file: '", $self->HistoryFile, "'\n" );
$history_file = getcwd
. '/' . $history_file unless $history_file =~ /^\
//;
$self->HistoryFile($history_file);
$ReadLine->ReadHistory($self->HistoryFile);
$ReadLine->WriteHistory($self->HistoryFile);
$ReadLine->history_truncate_file($self->HistoryFile, $self->HistorySaveLength) if
$self->HistorySaveLength >= 0;
$ReadLine->history_set_pos( scalar($ReadLine->GetHistory) );
$ReadLine->parse_and_bind('set visible-stats on');
# read the user's init file (~/.inputrc) to override default bindings
$ReadLine->read_init_file;
foreach my $env (keys %ENV) {
push @
{ $self->{'EnvVars'} }, '$' . $env . (-d
$value ?
'/' : ' ');
if( $_[1] =~ /\$([^\$\/]+)/ ) {
$_[1] =~ s/\$([^\$\/]+)/$ENV{$1}/e
;
sub IgnoreSomeCompletions
{
my ($self, $text, $longest_match, @matchlist) = @_;
# remove the dot files only when the user hasn't typed a dot.
my $longest_matchlen = length($longest_match);
my @matches = ($longest_match);
foreach my $match (@matchlist) {
if( substr($match, $longest_matchlen-1, 2) ne '/.' or $text =~ /\
.[^\
/]*$/ ) {
my ($self, $matchlist_ref, $num_matches, $longest) = @_;
# Strip leading tildes, dollar signs
for(my $i=1; $i<=$num_matches; $i++) {
$match = $matchlist_ref->[$i];
$longest = length($match) if length($match) > $longest;
$matchlist_ref->[$i] = $match;
if( $matchlist_ref->[0] =~ /^(.*\/)/ ) {
$self->PrintCompletions($matchlist_ref, $num_matches, $longest, $striplen);
my($self, $matches, $num_matches, $longest, $striplen) = @_;
if( $num_matches >= $self->Attribs->{'completion_query_items'} ) {
$self->PrintStatus("\nDisplay all $num_matches possibilities? (y or n)");
my $char = chr($self->ReadLine->read_key);
if( $char eq 'Y' or $char eq 'y' or $char eq ' ' ) {
} elsif( $char eq 'N' or $char eq 'n' ) {
$self->ReadLine->display_match_list($matches, $num_matches, $longest);
$self->ReadLine->forced_update_display;
my $Attribs = $self->Attribs;
if( defined($func) and ref($func) eq 'CODE' ) {
$Attribs->{'attempted_completion_function'} = sub { &{ $func }(@_); };
$Attribs->{'completion_entry_function'} = sub {};
$Attribs->{'attempted_completion_function'} = undef;
$Attribs->{'completion_entry_function'} = undef;
my ($self, $cmd, @args) = @_;
$dir = $self->GlobExpand($dir);
return unless defined($dir);
$self->PrintData( getcwd
. "\n" );
$self->PrintError("Couldn't cd to '$dir': $!\n");
if( $self->Which($cmd) ) {
$self->PrintError("$cmd: Command not found.\n");
foreach my $sym (keys %sandbox::) {
local *s
= $sandbox::{$sym};
push @retval, "$sym" if defined &s
;
my ($self, $cmd, @args) = @_;
foreach my $arg (@args) {
$arg =~ s/(.*)/'$1'/ if $arg =~ /^\w+$/;
$self->EvalPerl("$cmd(@args);");
my ($self, $code, $verbose) = @_;
$verbose = 1 unless defined($verbose);
$self->PrintError( $@
) if $@
;
$eval = 'undef' unless defined($eval);
$eval = '(null)' if $eval eq '';
print "$eval\n" if $verbose;
sub PerlCustomCompleter
{
my ($self, $text, $line, $start, $end) = @_;
my $ReadLine = $self->ReadLine;
my $Attribs = $self->Attribs;
my $first = substr($line, 0, $start);
if( $first =~ /\$([\w:]+)\s*(->)?\s*{\s*['"]?$/ ) {
$Attribs->{'completion_append_character'} = '}';
return $ReadLine->completion_matches($text, sub { $self->PerlHashCompletion(@_); });
# $foo[index $foo->[index
} elsif( $first =~ /\$([\w:]+)\s*(->)?\s*\[\s*['"]?$/ ) {
$Attribs->{'completion_append_character'} = ']';
return $ReadLine->completion_matches($text, sub { $self->PerlListCompletion(@_); });
$Attribs->{'completion_append_character'} = '';
return $ReadLine->completion_matches($text, sub { $self->PerlSymbolCompletion(@_); });
my($self, $text, $state) = @_;
my $Attribs = $self->Attribs;
my ($var,$arrow) = (substr($Attribs->{line_buffer
},
0, $Attribs->{point
} - length($text))
=~ /\$([\w:]+)\s*(->)?\s*\[\s*['"]?$/);
$var = "sandbox::$var" unless ($var =~ /::/);
my $listref = eval "\$$var";
@matches = UNIVERSAL
::isa
($listref, 'ARRAY') ?
(0 .. $#$listref) : ();
@matches = (0 .. $#$var);
$self->PerlIndex($index);
@
{ $self->{'PerlMatches'} } = @matches;
$index = $self->PerlIndex + 1;
@matches = @
{ $self->PerlMatches };
$self->PerlIndex($index);
for (; $index <= $#matches; $index++) {
$self->PerlIndex($index);
return $matches[$index] if ($matches[$index] =~ /^\Q$text/);
my ($self, $text, $state) = @_;
my $Attribs = $self->Attribs;
my ($var,$arrow) = (substr($Attribs->{'line_buffer'},
0, $Attribs->{'point'} - length($text))
=~ /\$([\w:]+)\s*(->)?\s*{\s*['"]?$/);
$var = "sandbox::$var" unless ($var =~ /::/);
my $hashref = eval "\$$var";
@matches = UNIVERSAL
::isa
($hashref, 'HASH') ?
keys %$hashref : ();
$self->PerlIndex($index);
@
{ $self->{'PerlMatches'} } = @matches;
$index = $self->PerlIndex + 1;
@matches = @
{ $self->PerlMatches };
$self->PerlIndex($index);
for (; $index <= $#matches; $index++) {
$self->PerlIndex($index);
return $matches[$index] if ($matches[$index] =~ /^\Q$text/);
sub PerlSymbolCompletion
{
my ($self, $text, $state) = @_;
my ($prefix) = ($text =~ /^(\$#|[\@\$%&])/);
$prefix = '' unless defined $prefix;
foreach my $var (keys %sandbox::) {
push @matches, ($prefix eq '&') ?
"\&$var" : "$var";
push @matches, UNIVERSAL
::isa
($s, 'HASH') ?
"\$$var->\{" :
UNIVERSAL
::isa
($s, 'ARRAY') ?
"\$$var->\[" : "\$$var";
push @matches, ($prefix eq '$') ?
"\$$var\[" :
($prefix eq '@') ?
"\@$var" :
($prefix eq '$#') ?
"\$#$var" : "\@$var";
push @matches, ($prefix eq '%' or $prefix eq '') ?
"\%$var" : "\$$var\{";
$self->PerlIndex($index);
@
{ $self->{'PerlMatches'} } = @matches;
$index = $self->PerlIndex + 1;
@matches = @
{ $self->PerlMatches };
$self->PerlIndex($index);
for (; $index <= $#matches; $index++) {
$self->PerlIndex($index);
return $matches[$index] if ($matches[$index] =~ /^\Q$text/);
my ($self, $argv_ref, $config_ref, @opts) = @_;
local (@ARGV) = @
$argv_ref;
Getopt
::Long
::config
(@
$config_ref) if @
$config_ref > 0;
my $ret = GetOptions
(@opts);
Getopt
::Long
::config
('default');
# Builtin Commands below here
my ($self, $onoff, $logfile) = @_;
if( not defined $onoff ) {
if( defined $self->LogFH ) {
$self->PrintStatus( "Currently logging output to '", $self->LogFileName, "'.\n" );
$self->PrintStatus( "Logging output is off.\n" );
} elsif( $onoff eq "off" ) {
if( defined $self->LogFH ) {
$self->PrintStatus( "Closing logfile '", $self->LogFileName, "'.\n" );
$self->LogFileName(undef);
$self->PrintStatus( "Logging output is already off.\n" );
} elsif( $onoff eq "on" and defined $logfile ) {
if( defined $self->LogFH ) {
$self->PrintStatus( "Closing logfile '", $self->LogFileName, "'.\n" );
$self->LogFileName(undef);
$self->LogFH(new FileHandle
"> $logfile");
if( not defined $self->LogFH ) {
$self->PrintError( "Couldn't open '$logfile': $!\n" );
$self->LogFileName($logfile);
$self->LogFH->autoflush(1);
$self->PrintStatus( "Logging output to '", $self->LogFileName, "'.\n" );
$self->PrintError( "Bad args to logfile '$onoff'.\n" );
$self->PrintStatus( $self->Help->logfile );
my ($opt_h, $opt_r, $opt_n);
$self->get_options(\
@args, ['no_pass_through'], 'h' => \
$opt_h, 'r' => \
$opt_r) or return;
if( $opt_n and $opt_n !~ /^\d+$/ ) {
$self->PrintError("Argument to history '$opt_n' isn't numeric.\n");
my @history = $self->ReadLine->GetHistory;
for(my $i=$#history; $i>=0; $i--) {
$self->PrintData( $opt_h ?
'' : "\t" . $i+1 . "\t", "$history[$i]\n" );
last if $opt_n and ($#history - $i + 1) >= $opt_n;
$opt_n = $#history unless defined($opt_n);
for(my $i=$#history-$opt_n; $i<=$#history; $i++) {
$self->PrintData( $opt_h ?
'' : "\t" . $i+1 . "\t", "$history[$i]\n" );
my ($self, $command) = @_;
$self->PrintStatus( $self->Help->$command() );
$self->PrintStatus( $self->Help->help );
if( defined($self->QuitCallback) ) {
&{ $self->QuitCallback };
$self->PrintError("Derived class '", ref($self), "' defined no 'clear' command.\n");
$self->PrintStatus( $self->Help->do );
$file = $self->GlobExpand($file);
$self->EvalPerl("do '$file';");
my ($self, $env, $val) = @_;
if( defined($env) and defined($val) ) {
} elsif( defined($env) ) {
$self->PrintData("$_=$ENV{$_}\n");
package CommandTerm
::Help
;
use vars
qw( $AUTOLOAD );
return if $method eq 'DESTROY';
return "No help available for \'$method\'\n";
Read the <file> into the perl sandbox package.
clear -- clear the screen
logfile [on|off [<logfile>]]
This command enables or disables logging of all output to <logfile>.
With no arguments, 'logfile' will report the current logging status.
With the argument 'off', the command will close the current <logfile>,
if one exists. With the 'on' argument, the <logfile> will be opened
to receive all text window output. The <logfile> will contain ALL
output. This includes user commands.
history [-h] [-r] [<num>]
Print out the history list. If -h is supplied, don't print out the
leading numbers. If -r is supplied print the history list in
reverse order. If a <num> is supplied, only output <num> commands
setenv [<env_var> [<value>]]
Set the environment variable <env_var> to <value>. If <value> is
not specified <env_var> is set to the empty string. If neither
<env_var> nor <value> is specified, the values of all environment
variables are printed, similar to the shell ``printenv'' command.
Prints out the help message for a given command.
Type <TAB><TAB> at a blank line for a list of avaiable commands.
Up/Down arrow cycle through previous/next commands.
Username and environment variable expansion is supported.