CommandTool.mesa
The Cedar 5 CommandTool is a stream-oriented "glass teletype" command-line processor. This interface exports a propgrammers interface to the CommandTool and provides some facilities for managing command lines.
Last Edited by: Stewart.pa, December 2, 1983 4:17 pm
Last Edited by: Rovner.pa, November 30, 1983 5:14 pm
DIRECTORY
Commander USING [CommandProc, CommandProcHandle, Handle],
IO USING [STREAM],
List USING [AList],
ReadEvalPrint USING [ClientProc],
Rope USING [ROPE];
Procedures for processing command lines
The following procedures are usefully called by CommandProcs right after they get control.
StarExpansion:
PROCEDURE [cmd: Commander.Handle];
! may raise Failed for things like mismatched "double-quotes";
Until such time as star-expansion is better integrated with the command processor, here it is, you have to call it yourself! It rewrites cmd.commandLine with stars expanded.
ParseToList:
PROCEDURE [cmd: Commander.Handle, switchChar:
CHAR ← '-]
RETURNS [list:
LIST
OF Rope.
ROPE, length:
NAT];
! Failed;
ParseToList breaks up cmd.commandLine into a list of tokens. Tokens are generally separated by whitespace, but a "double-quoted string" is a single token. If a switchChar is given, it is always considered to be the first character of a new token (except when inside a double quoted string). For example if '+ were the switchChar, then the string foo+bar would be returned as the two tokens foo and +bar. If the switchChar is '-, then it must be preceded by whitespace. This is to preserve the validity of '- as a character in filenames. If switchChar=SP, the switch character facility is disabled.
Parse:
PROCEDURE [cmd: Commander.Handle, switchChar:
CHAR ← '-]
RETURNS [argv: ArgumentVector];
! Failed;
Parse does the same job as ParseToList, but returns an array of tokens. In accordance with historical precedent, cmd.command is returned as argv[0] and the "real" arguments start at argv[1].
DoCommand:
PROCEDURE [commandLine: Rope.
ROPE, parent: Commander.Handle]
RETURNS [result:
REF
ANY];
Execute the given commandLine. Parent will be used to provide streams and the property list. cmd.command and cmd.commandLine will be overwritten. This is particularly useful for commands which wish to execute their own command lines as commands: DoCommand[cmd.commandLine, cmd]; DoCommand is a simple interface to EachCommand (see below). Result is the result returned by the command.
DoCommandRope:
PROCEDURE [commandLine, in: Rope.
ROPE ←
NIL, parent: Commander.Handle]
RETURNS [out: Rope.
ROPE, result:
REF
ANY];
Execute the given commandLine. (The command name must be the first token on the commandLine). The in, out, and err streams connected to the corresponding ropes. (Calls EachCommand) The property list and error streams come from parent. If parent is defaulted NIL, then a standard property list will be created and error output will be directed to out along with standard output. Result is the result returned by the command.
Run:
PROCEDURE [bcdName: Rope.
ROPE, runEvenIfAlreadyRun:
BOOL ←
FALSE, runEvenIfUnbound:
BOOL ←
FALSE]
RETURNS [errMsg: Rope.
ROPE ←
NIL, error:
BOOL ←
FALSE];
Load and start a BCD. If there is no .bcd on bcdName one will be appended. errMsg will always either return "Loaded and ran: XXX" or an error message, depending on the value of error. If there is no .bcd on bcdName one will be appended. If runEvenIfAlreadyRun is FALSE, then a BCD will only be run if it has not already been run by someone else. If runEvenIfAlreadyRun is TRUE, then the bcd will be run regardless. Unless runEvenIfUnbound is TRUE, the BCD will only be STARTed if there were no unbound imports.
Useful procedures for using the file system.
Here are a collection of useful file-system related procedures.
FileWithSearchRules:
PROCEDURE [root: Rope.
ROPE, defaultExtension: Rope.
ROPE, cmd: Commander.Handle]
RETURNS [fullPath: Rope.
ROPE];
FileWithSearchRules uses the working directory and the search rules to try to translate the short name of a file into a full path name. Calls ResolveRelativePath first. NIL is returned if the file cannot be found.
FileWithSearchRules uses the following rules:
IF root is a full path name then {
tries root
tries Concat[root, defaultExtension]
tries for a unique match for Cat[root, *, defaultExtension]
}
ELSE {
tries root (automatically used $WorkingDirectory)
FOR wdir ← each element of search rules {
tries Concat[wdir, root]
}
tries Concat[root, defaultExtension] (automatically used $WorkingDirectory)
FOR wdir ← each element of search rules {
tries Cat[wdir, root, defaultExtension]
tries for a unique match for Cat[wdir, root, *, defaultExtension]
}
}
ResolveRelativePath:
PROCEDURE [path: Rope.
ROPE]
RETURNS [Rope.
ROPE];
If path starts with ./ or ../, ResolveRelativePath converts it into the equivalent full path name using the $WorkingDirectory property on the process properties list.
ConvertToSlashFormat:
PROCEDURE [path: Rope.
ROPE]
RETURNS [Rope.
ROPE];
Converts FS name strings to slash format, but makes no consistancy checks.
AddSearchRule:
PROCEDURE [cmd: Commander.Handle, dir: Rope.
ROPE, append:
BOOL ←
TRUE];
If rule = NIL THEN deletes all search rules.
CurrentWorkingDirectory:
PROCEDURE
RETURNS [Rope.
ROPE];
Returns the currentworking directory, if there is one, otherwise returns the FS default working directory. Converts to slash format. (If any conversion was necessary, stores back the new version as the working directory.)
Procedures used by the CommandTool itself
The following procedures are used by the command interpreter in the course of its preparations. They have some functionality that a client might find interesting.
AmpersandSubstitution:
PROCEDURE [cmd: Commander.Handle];
! Failed;
AmpersandSubstitution rewrites cmd.commandLine with tokens of the form &name replaced by the value of the property $name on cmd.propertyList. If there is no such property, or its value is not a rope, then the &name token is deleted.
DollarSubstitution:
PROCEDURE [cmd: Commander.Handle];
! Failed;
DollarSubstitution rewrites cmd.commandLine with tokens of the form $number replaced by the number-th argument to the currently running command file. If there is no command file running, or it didn't have a number-th argument, then the $number token is deleted. DollarSubstitution may raise Failed.
IORedirection:
PROCEDURE [cmd: Commander.Handle]
RETURNS [inRedirected:
BOOL ←
FALSE, outRedirected:
BOOL ←
FALSE];
! Failed;
IORedirection rewrites cmd.commandLine with IO redirection directives removed and cmd.in or cmd.out filled in as appropriate.
Insulate:
PROCEDURE [stream:
IO.
STREAM]
RETURNS [safeStream:
IO.
STREAM];
Insulate creates streams for which close appears to work, but which do not close the backing stream. It creates a stream layered upon the given stream. The streamProcs are the IO package defaults except for close, which is IOUtils.closedStreamProcs.close.
PutLocalProperty:
PROCEDURE [key, val:
REF
ANY, aList: List.AList, origList: List.AList ←
NIL]
RETURNS [List.AList];
PutLocalProperty is used to set a "local" property on a list. OrigList is intended to point into the middle of aList. If key is found on the part of aList in front of origList, then the old binding is changed and aList is returned. If key is not found on aList before origList is encountered, then a new property is added to the head of aList and the new list is returned. If a CommandProc wants to use the property list for internal communications, here is a polite way to do it. The standard call would be cmd.propertyList ← PutLocalProperty[key: $MyPropertyKey, val: myValue, aList: cmd.propertyList, origList: cmd.propertyList];
CopyAList:
PROCEDURE [old: List.AList]
RETURNS [new: List.AList];
CopyAList copies the CONS cells of the list itself and also copies the DotCons cells which are the elements of the list. Because the DotCons cells are copied, one can change the key-value mappings in the new list without affecting the mappings in the old list. Because the CONS cells are copied, one can alter the list without affecting the old list.
CopyListOfRefAny:
PROCEDURE [key:
REF
ANY, aList: List.AList]
RETURNS [List.AList];
CopyListOfRefAny searches for the binding of the given key. If it is a LIST OF REF ANY, then it is List.CopyTopList-ed and put back.
Several functions in the command tool use properties which are bound to lists of command procs. The following functions help to manage this arrangement.
CallList:
PROCEDURE [property:
REF
ANY, cmd: Commander.Handle, proc:
PROC [result:
REF, msg: Rope.
ROPE]
RETURNS [stop:
BOOL]];
CallList uses the given property name (typically an ATOM) to search cmd.propertyList. The value of the property should be LIST OF REF ANY. Each of the elements of the list should resolve to a Commander.CommandProcHandle. Each of the command procs found will be called with cmd as its argument. If proc is not NIL, it is called with the return values from each of the CommandProcs called. proc can stop CallList from proceeding by returning stop = TRUE;
CallList is used by the command interpreter to manage the $Before, $After, and $Lookup facilities.
AddProcToList:
PROCEDURE [aList: List.AList, listKey:
REF
ANY, proc: Commander.CommandProcHandle, append:
BOOL ←
TRUE]
RETURNS [List.AList];
AddProcToList is used to construct or alter a LIST OF REF ANY whose elements are actually Commander.CommandProcHandles.
RemoveProcFromList:
PROCEDURE [aList: List.AList, listKey:
REF
ANY, proc: Commander.CommandProcHandle]
RETURNS [List.AList];
RemoveProcToList is used to remove a particular Commander.CommandProcHandle from a LIST OF REF ANY whose elements are actually Commander.CommandProcHandles.
The following three procedures are the default command lookup procedures which are placed on the $Lookup list.
LookupWithSearchRules: Commander.CommandProc;
LookupWithSearchRules, look in the Commander registry:
try root
If root is not full path name
try Concat[$WorkingDirectory, root]
For each search rule
try Concat[search rule, root]
try to find a unique match for Concat[root, "*"];
If root is not full path name
try to find a unique match for Cat[$WorkingDirectory, root, "*"];
For each search rule
try to find a unique match for Cat[search rule, root, "*"];
LoadAndRunWithSearchRules: Commander.CommandProc;
LoadAndRunWithSearchRules: look in the FS directory for a file with extension ".load" If found, run it then attempt a normal commander lookup (using LookupWithSearchRules). Uses FileWithSearchRules[root: cmd.command, defaultExtension: ".load", cmd: cmd].
CommandFileWithSearchRules: Commander.CommandProc;
CommandFileWithSearchRules: look in the FS directory for a command file (default extension ".cm")
Uses FileWithSearchRules[root: cmd.command, defaultExtension: ".cm", cmd: cmd].
CommandFile: Commander.CommandProc;
This CommandProc is the way to start new command interpreters. It implements command files. If there are no arguments, then a new top level viewer is created. The name of the command file ahould be the first token on the command line. Other tokens will be arguments to the command file. If clientData is $Source, the command file will be run in the callers property list context.
ExecuteCommand:
PROCEDURE [cmd: Commander.Handle, background:
BOOL];
Execute the given command. The Commander.Handle should be fully set up. If background is TRUE then the call to ExecuteCommand will return right away.
EachCommand: ReadEvalPrint.ClientProc;
A command tool works by passing EachCommand to a ReadEvalPrint evaluator. The clientData of the ReadEvalPrint.Handle must be a Commander.Handle. EachCommand will use its propertyList and streams. EachCommand calls ExecuteCommand.
LookupCommand:
PROCEDURE [cmd: Commander.Handle];
LookupCommand is the procedure that EachCommand uses to look for the CommandProc corresponding to a given name. It calls each of the $Lookup procedures on cmd.propertyList. It expects cmd to have the err, command, and propertyList fields set up. It may print on err. If it returns with cmd.procData filled in, that is it. Some $Lookup procedures run command files, so you may get more than you bargained for! If cmd.commandLine is filled in on return, then the procData corresponds to some generic commandProc which takes the original command name as its first argument. Generally, cmd.command will have been replaced by the correctly spelled version.
Pass1:
PROCEDURE [initial: Rope.
ROPE, nameOnly:
BOOL]
RETURNS [first: Rope.
ROPE ←
NIL, rest: Rope.
ROPE ←
NIL, terminator:
CHAR ← '\n, someExpansion:
BOOL ←
FALSE];
Pass1 is used to isolate the part of a commandLine that is the command name (when nameOnly = TRUE) or to find that part of a command line containing arguments (when nameOnly = FALSE).
END.