3 =head1 NAME $RCSfile: CmdLine.pm,v $
5 Library to implement generic command line interface
13 Andrew DeFaria <Andrew@ClearSCM.com>
21 Fri May 13 15:23:37 PDT 2011
25 $Date: 2011/12/23 01:02:49 $
31 Provides an interface to a command line utilizing Term::ReadLine and
32 Term::ReadLine::Gnu. Note, the latter is not part of Perl core and
33 must be downloaded from CPAN. Without Term::ReadLine::Gnu a lot of
34 functionality doesn't work.
36 CmdLine uses a hash to describe what your valid commands are along
37 with help and longer help, i.e. description strings. If you do not
38 define your commands then no command name completion nor help will be
46 help => 'help [<cmd>]'
47 description => 'This is a longer description
51 help => 'execute <cmd>',
52 description => 'Longer description of the execute command',
56 # Create a new cmdline:
57 my $cmdline = CmdLine->new ($FindBin::Script, %cmds);
59 while (my $cmd = $cmdline->get) {
65 This module implements a command line stack using Term::ReadLine and
66 Term::ReadLine::Gnu. If Term::ReadLine::Gnu is not installed then many
67 of the functions do not work. Command completion if commands are
68 defined with a hash as shown above.
70 =head1 DEFAULT COMMANDS
72 The for a list of the builtin commands see %builtin_cmds below
74 Additionally !<n> will re-exeucte a comand from history and !<cmd>
75 will execute <cmd as a shell command.
79 The following routines are exported:
96 use Term::ANSIColor qw (color);
102 my (%_cmds, $_cmdline, $_attribs);
105 # See if we can load Term::ReadLine::Gnu
106 eval { require Term::ReadLine::Gnu };
109 warning "Unable to load Term::ReadLine::Gnu\nCmdLine functionality will be limited!";
121 help => 'history [[start] [end]]',
123 "Displays cmd history. You can specify where to <start> and where to <end>\n"
124 . "Default is to list only the last screen full lines of history (as denoted\n"
129 help => 'help [<cmd>]',
130 description => 'Displays help.',
135 description => 'Display all help, including builtin commands',
139 help => 'savehist [file] [[start] [end]]',
141 "Saves a section of the history to a file. You can specify where to <start>\n"
142 . "and where to <end>. Default is to save all of the history to the specified\n"
148 description => 'Gets a variable.',
152 help => 'set [var]=[expression]',
154 'Sets a variable. Note that expression can be any valid expression.',
160 'Displays all known variables.',
164 help => 'source [file]',
166 'Run commands from a file.',
170 help => 'color [(on|off)]',
172 'Turn on|off color. With no options displays status of color.',
176 help => 'trace [(on|off)]',
178 'Turn on|off tracing. With no options displays status of trace.',
182 sub _cmdCompletion($$) {
183 my ($text, $state) = @_;
185 return unless %_cmds;
187 $_pos = 0 unless $state;
189 my @cmds = keys %_cmds;
191 for (; $_pos < @cmds;) {
192 return $cmds[$_pos - 1]
193 if $cmds[$_pos++] =~ /^$text/i;
199 sub _complete($$$$) {
200 my ($text, $line, $start, $end) = @_;
202 return $_cmdline->completion_matches($text, \&CmdLine::_cmdCompletion);
208 return unless %_cmds;
210 my $line = $_cmdline->{line_buffer};
218 # Sometimes we are called by ReadLine's callback and can't pass $self
219 if (ref $self eq 'CmdLine') {
222 $CmdLine::cmdline->help($line);
225 $_cmdline->on_new_line;
230 sub _interpolate($) {
231 my ($self, $str) = @_;
233 # Skip interpolation for the perl command (Note this is raid specific)
235 if $str =~ /^\s*perl\s*/i;
237 while ($str =~ /\$/) {
238 if ($str =~ /\$(\w+)/) {
241 if (defined $self->{vars}{$varname}) {
242 if ($self->{vars}{$varname} =~ / /) {
243 $str =~ s/\$$varname/\'$self->{vars}{$varname}\'/;
245 $str =~ s/\$$varname/$self->{vars}{$varname}/;
248 $str =~ s/\$$varname//;
256 sub _builtinCmds($) {
257 my ($self, $line) = @_;
266 # Short circut "psuedo" commands of !<n> and !<shellcmd>
267 if ($line =~ /^\s*!\s*(\d+)/) {
268 $line = $self->history ('redo', $1);
269 } elsif ($line =~ /^\s*!\s*(\S+)\s*(.*)/) {
276 #$_cmdline->remove_history ($_cmdline->where_history);
281 if ($line =~ /^\s*(\S+)/) {
288 # Search for matches of partial commands
291 for (keys %builtin_cmds) {
293 # Exact match - honor it
297 # Command matched partially
299 # Found first instance of a match
302 # Found second instance of a match - $cmd is not unique
309 # If we found a command, substitute it into line
311 $line =~ s/^\s*$cmd\s*/$foundCmd /;
315 if ($builtin_cmds{$cmd}) {
316 if ($line =~ /^\s*helpall\s*$/i) {
318 } elsif ($line =~ /^\s*help\s*(.*)/i) {
324 } elsif ($line =~ /^\s*history\s*(.*)/i) {
325 if ($1 =~ /(\d+)\s+(\d+)\s*$/) {
326 $self->history('list', $1, $2);
327 } elsif ($1 =~ /^\s*$/) {
328 $self->history('list');
330 error "Invalid usage";
331 $self->help('history');
333 } elsif ($line =~ /^\s*savehist\s*(.*)/i) {
334 if ($1 =~ /(\S+)\s+(\d+)\s+(\d+)\s*$/) {
335 $self->history('save', $1, $2, $3);
337 error 'Invalid usage';
338 $self->help('savehist');
340 } elsif ($line =~ /^\s*get\s*(.*)/i) {
341 if ($1 =~ (/^\$*(\S+)\s*$/)) {
342 my $value = $self->_get($1);
345 display "$1 = $value";
347 error "$1 is not set";
350 error 'Invalid usage';
353 } elsif ($line =~ /^\s*set\s*(.*)/i) {
354 if ($1 =~ /^\$*(\S+)\s*=\s*(.*)/) {
357 error 'Invalid usage';
360 } elsif ($line =~ /^\s*source\s+(\S+)/i) {
361 $result = $self->source($1);
362 } elsif ($line =~ /^\s*vars\s*/) {
364 } elsif ($line =~ /^\s*color\s*(.*)/i) {
365 if ($1 =~ /(1|on)/i) {
367 delete $ENV{ANSI_COLORS_DISABLED}
368 if $ENV{ANSI_COLORS_DISABLED};
369 } elsif ($1 =~ /(0|off)/i) {
371 local $ENV{ANSI_COLORS_DISABLED} = 1;
372 } elsif ($1 =~ /\s*$/) {
373 if ($ENV{ANSI_COLORS_DISABLED}) {
374 display 'Color is currently off';
376 display 'Color is currently on';
379 error 'Invalid usage';
380 $self->help('color');
382 } elsif ($line =~ /^\s*trace\s*(.*)/i) {
383 if ($1 =~ /(1|on)/i) {
385 } elsif ($1 =~ /(0|off)/i) {
387 } elsif ($1 =~ /\s*$/) {
389 display 'Trace is currently on';
391 display 'Trace is currently off';
394 error 'Invalid usage';
395 $self->help('trace');
400 return ($cmd, $line, $result);
404 # Announce that we have hit an interrupt
405 print color ('yellow') . "<Control-C>\n" . color ('reset');
407 # Free up all of the line state info
408 $_cmdline->free_line_state;
410 # Allow readline to clean up
411 $_cmdline->cleanup_after_signal;
413 # Redisplay prompt on a new line
414 $_cmdline->on_new_line;
415 $_cmdline->{line_buffer} = '';
416 $_cmdline->redisplay;
421 sub _displayMatches($$$) {
422 my ($matches, $numMatches, $maxLength) = @_;
424 # Work on a copy... (Otherwise we were getting "Attempt to free unreferenced
425 # scalar" internal errors from perl)
428 push @Matches, $_ for (@$matches);
430 my $match = shift @Matches;
432 if ($match =~/^\s*(.*) /) {
434 } elsif ($match =~ /^\s*(\S+)$/) {
445 $newMatches{$1} = $1;
449 my @newMatches = sort keys %newMatches;
451 unshift @newMatches, $match;
453 $_cmdline->display_match_list (\@newMatches);
454 $_cmdline->on_new_line;
455 $_cmdline->redisplay;
461 my ($class, $histfile, $eval, %cmds) = @_;
467 Construct a new CmdLine object. Note there is already a default
468 CmdLine object created named $cmdline. You should use that unless you
469 have good reason to instantiate another CmdLine object.
473 =for html <blockquote>
479 Set to a file name where to write the history file. If not defined no
484 A hash describing the valid commands and their help/description
489 help => 'List all known commands',
490 description => 'This is a longer description
491 of the list command',
494 help => 'This is a help command',
495 description => 'help <cmd>
496 Longer description of help',
502 =for html </blockquote>
506 =for html <blockquote>
514 =for html </blockquote>
520 $histfile ||= "$ENV{HOME}/.${me}_hist";
523 histfile => $histfile,
526 error "Creating bogus .${me}_hist file!"
527 if $me eq '-' or $me eq '';
529 unless (-f $histfile) {
530 open my $hist, '>', $histfile
531 or error "Unable to open $histfile for writing - $!", 1;
536 # Instantiate a commandline
537 $_cmdline = Term::ReadLine->new ($me);
539 # Store the function pointer of what to call when sourcing a file or
540 # evaluating an expression.
542 if (ref $eval eq 'CODE') {
543 $self->{eval} = $eval;
545 error "Invalid function pointer\nUsage: CmdLine->new ($histfile, $eval, %cmds)", 1;
549 # Default prompt is "$me:"
550 $self->{prompt} = "$me:";
553 $self->set_cmds(%cmds);
555 # Set some ornamentation
556 $_cmdline->ornaments ('s,e,u,') unless $Config{cppflags} =~ /win32/i;
559 $self->set_histfile($histfile);
561 # Generator function for completion matches
562 $_attribs = $_cmdline->Attribs;
564 $_attribs->{attempted_completion_function} = \&CmdLine::_complete;
565 $_attribs->{completion_display_matches_hook} = \&CmdLine::_displayMatches;
566 $_attribs->{completer_word_break_characters} =~ s/ //
567 if $_attribs->{completer_word_break_characters};
569 # The following functionality requires Term::ReadLine::Gnu
571 # Bind a key to display completion
572 $_cmdline->add_defun('help-on-command', \&CmdLine::_gethelp, ord ("\cl"));
574 # Save a handy copy of RL_PROMPT_[START|END]_IGNORE
575 $self->{ignstart} = $_cmdline->RL_PROMPT_START_IGNORE;
576 $self->{ignstop} = $_cmdline->RL_PROMPT_END_IGNORE;
579 if ($Config{cppflags} =~ /win32/i) {
581 local $ENV{ANSI_COLORS_DISABLED} = 1;
594 Retrieves a command line
598 =for html <blockquote>
606 =for html </blockquote>
610 =for html <blockquote>
618 =for html </blockquote>
622 my ($cmd, $line, $result);
625 # Substitute cmdnbr into prompt if we find a '\#'
626 my $prompt = $self->{prompt};
628 $prompt =~ s/\\\#/$self->{cmdnbr}/g;
632 # Term::ReadLine::Gnu restarts whatever system call it is using, such that
633 # once we ctrl C, we don't get back to Perl until the user presses enter,
634 # finally whereupon we get our signal handler called. We use sigaction
635 # instead to use the old perl unsafe signal handling, but only in this read
636 # routine. Sure, sigaction poses race conditions, but you'd either be at a
637 # prompt or executing whatever command your prompt prompted for. The user
638 # has said "Abort that!" with his ctrl-C and we're attempting to honor that.
640 # Damn Windows can't do any of this
643 if ($Config{cppflags} !~ /win32/i) {
644 my $sigset = POSIX::SigSet->new;
645 my $sigaction = POSIX::SigAction->new (\&_interrupt, $sigset, 0);
647 $oldaction = POSIX::SigAction->new;
649 # Set up our unsafe signal handler
650 POSIX::sigaction (&POSIX::SIGINT, $sigaction, $oldaction);
653 $line = $_cmdline->readline ($prompt);
655 # Restore the old signal handler
656 if ($Config{cppflags} !~ /win32/i) {
657 POSIX::sigaction (&POSIX::SIGINT, $oldaction);
660 $line = $self->_interpolate($line)
663 $self->{cmdnbr}++ unless $self->{sourcing};
665 ($cmd, $line, $result) = $self->_builtinCmds($line);
667 $line = '' unless $cmd;
668 } while ($cmd and $builtin_cmds{$cmd});
670 return ($line, $result);
674 my ($self, %cmds) = @_;
684 =for html <blockquote>
694 =for html </blockquote>
698 =for html <blockquote>
706 =for html </blockquote>
713 for (keys %builtin_cmds) {
714 $_cmds{$_}{help} = $builtin_cmds{$_}{help};
715 $_cmds{$_}{description} = $builtin_cmds{$_}{description};
722 my ($self, $prompt) = @_;
732 =for html <blockquote>
742 =for html </blockquote>
746 =for html <blockquote>
754 =for html </blockquote>
758 my $return = $self->{prompt};
760 $self->{prompt} = $prompt;
765 sub set_histfile($) {
766 my ($self, $histfile) = @_;
776 =for html <blockquote>
786 =for html </blockquote>
790 =for html <blockquote>
798 =for html </blockquote>
802 if ($histfile and -f $histfile) {
803 $self->{histfile} = $histfile;
806 # Clear old history (if any);
807 $_cmdline->clear_history;
810 $_cmdline->ReadHistory ($histfile);
813 # Determine the number of lines in the history file
814 open my $hist, '<', $histfile
815 or croak "Unable to open history file $histfile";
819 $self->{cmdnbr} = $. + 1;
828 my ($self, $eval) = @_;
834 Sets the eval function pointer
838 =for html <blockquote>
844 Function to set eval to. This function will be called with the command
845 line as the only paramter and it should return a result.
849 =for html </blockquote>
853 =for html <blockquote>
861 =for html </blockquote>
865 my $returnEval = $self->{eval};
867 $self->{eval} = $eval;
873 my ($self, $cmd, $builtins) = @_;
881 Note that the user does not need to explicitly call help - CmdLine's
882 get method will already sense that the builtin help command was
883 invoked and handle it. This method is provided if the caller wishes to
884 call this internally for some reason.
888 =for html <blockquote>
894 Optional command help
898 =for html </blockquote>
902 =for html <blockquote>
910 =for html </blockquote>
922 if ($cmd =~ /^\s*(.+)/) {
923 my ($searchStr, $helpFound);
927 for (sort keys %_cmds) {
931 my $cmdcolor = $builtin_cmds{$_} ? color('cyan') : color('magenta');
933 $cmd = "$cmdcolor$_";
934 $cmd .= " $_cmds{$_}{parms}" if $_cmds{$_}{parms};
935 $cmd .= color('reset');
936 $cmd .= " - $_cmds{$_}{help}" if $_cmds{$_}{help};
940 if ($_cmds{$_}{description}) {
942 for (split /\n/, $_cmds{$_}{description});
947 unless ($helpFound) {
948 display "I don't know about $cmd";
953 for (sort keys %_cmds) {
954 next if $builtin_cmds{$_} and not $builtins;
956 my $cmdcolor = $builtin_cmds{$_} ? color ('cyan') : color ('magenta');
958 my $cmd = "$cmdcolor$_";
959 $cmd .= " $_cmds{$_}{parms}" if $_cmds{$_}{parms};
960 $cmd .= color ('reset');
961 $cmd .= " - $_cmds{$_}{help}" if $_cmds{$_}{help};
965 if ($_cmds{$_}{description}) {
967 for (split /\n/, $_cmds{$_}{description});
972 $self->handleOutput($cmd, @help);
978 my ($self, $action) = @_;
982 =head2 history <action> [<file>] [<start> <end>]
984 This method lists, saves or executes (redo) a command from the history
985 stack. <action> can be one of 'list', 'save' or 'redo'. If listing
986 history one can specify the optional <start> and <end> parameters. If
987 saving then <file> must be specified and optionally <start> and
988 <end>. If redoing a command then only <start> or the command number
991 Note that the user does not need to explicitly call history -
992 CmdLine's get method will already sense that the builtin history
993 command was invoked and handle it. This method is provided if the
994 caller wishes to call this internally for some reason.
998 =for html <blockquote>
1004 One of 'list', 'save' or 'redo'
1008 =for html </blockquote>
1012 =for html <blockquote>
1020 =for html </blockquote>
1024 if ($Config{cppflags} =~ /win32/i) {
1025 warning 'The history command does not work on Windows (sorry)';
1030 my ($file, $start, $end);
1032 if ($action eq 'list') {
1035 } elsif ($action eq 'save') {
1039 } elsif ($action eq 'redo') {
1040 $_cmdline->remove_history ($_cmdline->where_history);
1043 my $line = $_cmdline->history_get ($nbr);
1045 $_cmdline->add_history ($line);
1048 my ($cmd, $result) = $self->_builtinCmds($line);
1050 if ($builtin_cmds{$cmd}) {
1056 error "Unknown action $action in history";
1060 my $current = $_cmdline->where_history;
1062 my $lines = ($ENV{LINES} ? $ENV{LINES} : 24) - 2;
1064 $start = $current - $lines
1065 unless defined $start;
1069 unless defined $end;
1073 if ($start > $end) {
1074 error "Start ($start) is > end ($end)";
1079 if ($action eq 'save') {
1081 error "Usage: savehist <file> [<start> <end>]";
1086 display_nolf "Overwrite $file (yN)? ";
1088 my $response = <STDIN>;
1090 unless ($response =~ /(y|yes)/i) {
1091 display "Not overwritten";
1096 my $success = open $savefile, '>', $file;
1099 error "Unable to open history file $file - $!";
1104 for (my $pos = $start; $pos <= $end; $pos++) {
1105 my $histline = $_cmdline->history_get ($pos);
1107 last unless $histline;
1109 if ($action eq 'list') {
1110 display "$pos) $histline";
1112 print $savefile "$histline\n";
1117 if $action eq 'save';
1124 my ($self, $name) = @_;
1130 This method gets a variable to a value stored in the CmdLine
1135 =for html <blockquote>
1141 Name of the variable
1145 =for html </blockquote>
1149 =for html <blockquote>
1157 =for html </blockquote>
1161 return $self->{vars}{$name}
1165 my ($self, $name, $value) = @_;
1169 =head2 _set ($name, $value)
1171 This method sets a variable to a value stored in the CmdLine
1172 object. Note $value will be evaluated if eval is set.
1176 =for html <blockquote>
1182 Name of the variable
1186 Value of the variable
1190 =for html </blockquote>
1194 =for html <blockquote>
1202 =for html </blockquote>
1206 my $returnValue = $self->{vars}{$name};
1208 if (defined $value) {
1209 $value = $self->_interpolate($value);
1211 # Do not call eval if we are setting result - otherwise we recurse
1213 unless ($name eq 'result') {
1215 $value = $self->{eval}($value) if $self->{eval};
1219 $self->{vars}{$name} = $value;
1221 delete $self->{vars}{$name};
1224 return $returnValue;
1228 my ($self, $cmd) = @_;
1234 This method will print out all known variables
1238 =for html <blockquote>
1246 =for html </blockquote>
1250 =for html <blockquote>
1258 =for html </blockquote>
1264 push @output, "$_ = $self->{vars}{$_}"
1265 for (keys %{$self->{vars}});
1267 $self->handleOutput($cmd, @output);
1272 sub handleOutput($@) {
1273 my ($self, $line, @output) = @_;
1277 =head2 handleOutput ($line, @output)
1279 This method will handle outputing the array @output. It also handles redirection
1280 (currently only output redirection) and piping
1284 =for html <blockquote>
1290 The command line used to produce @output. This method parses out redirection
1291 (i.e. > and >>) and piping (|) from $cmd
1295 The output produced by the command to redirect or pipe. (Note this isn't true
1296 piping in that command must run first and produce all of @output before we are
1297 called. Need to look into how to use Perl's pipe command here).
1301 =for html </blockquote>
1305 =for html <blockquote>
1313 =for html </blockquote>
1317 my ($outToFile, $pipeToCmd);
1319 # Handle piping and redirection
1320 if ($line =~ /(.*)\>{2}\s*(.*)/) {
1323 } elsif ($line =~ /(.*)\>{1}\s*(.*)/) {
1326 } elsif ($line =~ /(.*?)\|\s*(.*)/) {
1332 $self->{output} = \@output;
1337 local $SIG{PIPE} = 'IGNORE';
1339 open $pipe, '|', $pipeToCmd
1342 # TODO: Not handling the output here. Need open2 and then recursively call
1349 or error "Unable to close pipe for $pipeToCmd - $!";
1351 error "Unable to open pipe for $pipeToCmd - $!";
1354 unless ($outToFile) {
1357 open my $output, '>', $outToFile;
1360 print $output "$_\n"
1367 error "Unable to open $outToFile for writing - $!"
1376 my ($self, $file) = @_;
1380 =head2 source <file>
1382 This method opens a file and sources it's content by executing each
1383 line. Note that the user must have set $self->{eval} to a function
1384 pointer. The function will be called with one parameter - the command
1385 line to execute. The function will return the result from the
1386 execution of the final command.
1388 Note that the user does not need to explicitly call source -
1389 CmdLine's get method will already sense that the builtin source
1390 command was invoked and handle it. This method is provided if the
1391 caller wishes to call this internally for some reason.
1395 =for html <blockquote>
1405 =for html </blockquote>
1409 =for html <blockquote>
1413 =item Returns the result of the last command executed
1417 =for html </blockquote>
1422 error "Unable to open file $file - $!";
1426 open my $source, '<', $file;
1430 $self->{sourcing} = 1;
1439 my $prompt = $self->{prompt};
1441 $prompt =~ s/\\\#/$file:$i/;
1443 display "$prompt$_" if $CmdLine::opts{trace};
1445 next if /^\s*($|\#)/;
1447 $_ = $self->_interpolate ($_);
1449 # Check to see if it's a builtin
1450 my ($cmd, $line, $result) = $self->_builtinCmds ($_);
1452 next if $builtin_cmds{$cmd};
1455 $result = $self->{eval}($line);
1458 if (defined $result) {
1459 if (ref \$result eq 'SCALAR') {
1460 PageOutput (split /\n/, $result);
1462 display "Sorry but I cannot display structured results";
1467 $self->{sourcing} = 0;
1477 $_cmdline->WriteHistory($self->{histfile})
1478 if $_cmdline and $_haveGnu;
1483 our $cmdline = CmdLine->new;
1493 =head2 ClearSCM Perl Modules
1495 =for html <p><a href="/php/scm_man.php?file=lib/Display.pm">Display</a></p>
1497 =head1 BUGS AND LIMITATIONS
1499 There are no known bugs in this module
1501 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
1503 =head1 LICENSE AND COPYRIGHT
1505 Copyright (c) 2007, ClearSCM, Inc. All rights reserved.