CommanderFileCommandsImpl.mesa
Copyright Ó 1989, 1990, 1991 by Xerox Corporation. All rights reserved.
Michael Plass, March 31, 1993 5:18 pm PST
Chauser, October 1, 1991 4:13 pm PDT
Willie-s, July 10, 1992 3:10 pm PDT
Swinehar, June 2, 1992 5:44 pm PDT
DIRECTORY Commander, CommanderOps, CommanderRegistry, Convert, IO, IOClasses, List, PFS, PFSNames, Process, ProcessProps, RefTab, Rope, RopeList, SymTab, SystemNames;
CommanderFileCommandsImpl: CEDAR MONITOR
IMPORTS Commander, CommanderOps, CommanderRegistry, Convert, IO, IOClasses, List, PFS, PFSNames, Process, ProcessProps, RefTab, Rope, RopeList, SymTab, SystemNames
~ BEGIN
PATH: TYPE ~ PFS.PATH;
ROPE: TYPE ~ Rope.ROPE;
searchRules: LIST OF REF ¬ LIST[Rope.Flatten["/Cedar/Commands/"]];
ChangeSearchRules: ENTRY PROC [from, to: LIST OF REF] RETURNS [ok: BOOL] ~ {
A conditional store.
IF searchRules # from THEN RETURN [FALSE];
searchRules ¬ to;
RETURN [TRUE]
};
searchRuleCache: RefTab.Ref ~ RefTab.Create[];
RemPath: PROC [a: PATH, b: LIST OF PATH] RETURNS [LIST OF PATH] ~ {
SELECT TRUE FROM
(b = NIL) => RETURN [b];
PFSNames.Equal[a, b.first] => RETURN [b.rest];
ENDCASE => {
rest: LIST OF PATH ~ RemPath[a, b.rest];
RETURN [IF b.rest = rest THEN b ELSE CONS[b.first, rest]]
};
};
MergePaths: PROC [a, b: LIST OF PATH] RETURNS [LIST OF PATH] ~ {
IF a = NIL
THEN RETURN [b]
ELSE {
rest: LIST OF PATH ~ RemPath[a.first, MergePaths[a.rest, b]];
RETURN [IF rest = a.rest THEN a ELSE CONS[a.first, rest]]
};
};
ComputeSearchRules: PROC [rules: REF] RETURNS [paths: LIST OF PATH ¬ NIL] ~ {
Prunes the searching order by removing duplicates; caches the result.
found: BOOL; val: REF;
IF rules = NIL THEN RETURN;
[found: found, val: val] ¬ RefTab.Fetch[searchRuleCache, rules];
IF found THEN RETURN [paths: NARROW[val]];
IF RefTab.GetSize[searchRuleCache] > 50 THEN RefTab.Erase[searchRuleCache];
WITH rules SELECT FROM
rope: ROPE => {
paths ¬ LIST[PFS.AbsoluteName[PFS.PathFromRope[rope]]];
};
path: PATH => {
IF PFSNames.IsADirectory[path] THEN paths ← LIST[path];
};
list: LIST OF REF => {
paths ¬ MergePaths[ComputeSearchRules[list.first], ComputeSearchRules[list.rest]];
};
ENDCASE => NULL;
};
GetSearchRules: PROC [cmd: Commander.Handle] RETURNS [LIST OF PATH] ~ {
<<RETURN [ComputeSearchRules[CommanderOps.GetProp[cmd, $SearchRules]]];>>
RETURN [ComputeSearchRules[searchRules]];
};
PrintSearchRules: Commander.CommandProc = {
Inner: PROC [rules: REF] = {
WITH rules SELECT FROM
rope: ROPE => {IO.PutRope[out, rope]};
list: LIST OF REF => {
IO.PutRope[out, "( "];
FOR each: LIST OF REF ¬ list, each.rest WHILE each # NIL DO
Inner[each.first];
IO.PutChar[out, ' ];
ENDLOOP;
IO.PutRope[out, ")"];
};
ENDCASE => IF rules = NIL THEN IO.PutRope[out, "( )"] ELSE IO.PutRope[out, "??"];
Process.CheckForAbort[];
};
out: IO.STREAM = cmd.out;
<<Inner[CommanderOps.GetProp[cmd, $SearchRules]];>>
Inner[searchRules];
IO.PutRope[out, "\n"];
};
SetSearchRules: Commander.CommandProc = {
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
data: REF ¬ cmd.procData.clientData;
DO
oldRules: LIST OF REF ¬ searchRules;
newRules: LIST OF REF ¬ NIL;
WITH CommanderOps.GetProp[cmd, $SearchRules] SELECT FROM
list: LIST OF REF => oldRules ← list;
ENDCASE;
SELECT data FROM
$Pop =>
IF oldRules # NIL THEN {
newRules ¬ oldRules.rest;
IF newRules # NIL AND newRules.rest = NIL THEN
WITH newRules.first SELECT FROM
list: LIST OF REF => newRules ¬ list;
Remove the extra LIST layer induced by Push
ENDCASE;
};
ENDCASE => {
tail: LIST OF REF ¬ NIL;
FOR i: NAT DECREASING IN [1..argv.argc) DO
dir: ROPE ← Rope.Concat[FileNames.ResolveRelativePath[argv[i]], "$"];
dir ← FileNames.ConvertToSlashFormat[UFS.ExpandName[dir].fullUName];
dir ← Rope.Substr[dir, 0, Rope.Size[dir]-1];
dir: ROPE ¬ argv[i];
IF NOT Rope.Match["/*", dir] THEN {
ERROR CommanderOps.Failed["Please use absolute directories"];
};
IF NOT Rope.Match["*/", dir] THEN dir ¬ Rope.Concat[dir, "/"];
newRules ¬ CONS[dir, newRules];
IF tail = NIL THEN tail ¬ newRules;
Process.CheckForAbort[];
ENDLOOP;
IF tail = NIL THEN newRules ← tail ← LIST[FileNames.CurrentWorkingDirectory[]];
SELECT data FROM
$Add => IF tail = NIL THEN newRules ¬ oldRules ELSE tail.rest ¬ oldRules;
$Push => newRules ¬ LIST[newRules, oldRules];
ENDCASE;
};
IF ChangeSearchRules[from: oldRules, to: newRules] THEN EXIT;
ENDLOOP;
};
StreamName: PROC [stream: IO.STREAM] RETURNS [ROPE] ~ {
RETURN [PFS.RopeFromPath[PFS.GetName[PFS.OpenFileFromStream[stream]].fullFName]]
};
PreRegisterSimple: PROC [commandName: ROPE, cmd: Commander.Handle] RETURNS [foundOne: BOOL ¬ FALSE] ~ {
commandSearchRules: LIST OF PATH ~ GetSearchRules[cmd];
shortName: PATH ~ PFS.PathFromRope[Rope.Concat[commandName, ".command"]];
Inner: PROC ~ {
in: IO.STREAM ¬ PFS.StreamOpen[shortName ! PFS.Error => GOTO NoDice];
streamName: ROPE ~ StreamName[in];
fail: BOOL ¬ FALSE;
foundOne ¬ TRUE;
BEGIN ENABLE UNWIND => {IO.Close[in]};
subCmd: Commander.Handle ~ CommanderOps.CreateFromStreams[in: in, parentCommander: cmd];
fail ¬ CommanderOps.ReadEvalPrintLoop[subCmd];
IF Commander.Lookup[commandName] = NIL THEN ERROR CommanderOps.Failed[Rope.Concat[streamName, " failed to register command"]];
IO.Close[in];
END;
IF fail THEN ERROR CommanderOps.Failed[Rope.Concat[streamName, " failed."]]
EXITS NoDice => NULL;
};
Inner[];
FOR tail: LIST OF PATH ¬ commandSearchRules, tail.rest UNTIL foundOne OR tail = NIL DO
IO.PutF[cmd.err, " ---> PFS.DoInWDir[PATH[%g], Inner]; (shortName = %g)\n", [rope[PFS.RopeFromPath[tail.first]]], [rope[PFS.RopeFromPath[shortName]]]];
PFS.DoInWDir[wDir: tail.first, inner: Inner];
ENDLOOP;
};
GetBase: PROC [shortName: PFSNames.Component] RETURNS [ROPE] ~ {
dot: INT ¬ Rope.FindBackward[s1: shortName.name.base, s2: ".", pos1: shortName.name.start+shortName.name.len];
len: INT ¬ IF dot < shortName.name.start THEN shortName.name.len ELSE dot-shortName.name.start;
RETURN[Rope.Substr[base: shortName.name.base, start: shortName.name.start, len: len]];
};
AbbreviationCommand: Commander.CommandProc ~ {
[result: result, msg: msg] ¬ CommanderOps.ExecuteCommand[cmd, Rope.Cat[NARROW[cmd.procData.clientData], " ", cmd.commandLine]];
};
RegisterAbbreviation: PROC [cmd: Commander.Handle, new, old: ROPE] ~ {
cmd parameter is not used right now.
IF Commander.Lookup[new] # NIL THEN RETURN; -- avoid self-reference if names differ only by case.
Commander.Register[key: new, proc: AbbreviationCommand, doc: Rope.Cat["(short for ", old,")"], clientData: old, interpreted: FALSE];
};
commandPattern: ROPE ~ "*.command";
cmPattern: ROPE ~ "*.cm";
PreRegister: PROC [commandName: ROPE, cmd: Commander.Handle] RETURNS [fullname: ROPE ¬ NIL] ~ {
ENABLE {
PFS.Error => { ERROR CommanderOps.Failed[error.explanation] };
};
IF NOT (Commander.Lookup[commandName] # NIL OR PreRegisterSimple[commandName, cmd]) THEN {
pathPattern: PATH ~ PFS.PathFromRope[Rope.Concat[commandName, "*!H"]];
pattern: ROPE ~ Rope.Concat[commandName, "*"];
matches: LIST OF ROPE ¬ NIL;
EachRegisteredCommand: Commander.EnumerateAction ~ {
IF procData.proc # AbbreviationCommand AND Rope.Match[pattern: pattern, object: key, case: FALSE] THEN {
matches ¬ CONS[key, matches];
};
};
[] ¬ CommanderRegistry.EnumeratePattern[pattern, EachRegisteredCommand];
IF matches # NIL
THEN { fullname ¬ matches.first }
ELSE {
cm: BOOL ¬ FALSE;
foundCM: ROPE ¬ NIL;
EachFileCommand: PFS.NameProc ~ {
shortName: PFSNames.Component ¬ PFSNames.ShortName[name];
shortNameRope: ROPE ¬ Rope.Substr[base: shortName.name.base, start: shortName.name.start, len: shortName.name.len];
key: ROPE ~ GetBase[shortName];
IF Rope.Match[pattern: commandPattern, object: shortNameRope, case: FALSE] THEN {
matches ¬ CONS[key, matches]
}
ELSE IF Rope.Match[pattern: cmPattern, object: shortNameRope, case: FALSE] THEN {
foundCM ¬ key;
matches ¬ CONS[Rope.Concat["Source ", PFS.RopeFromPath[PFSNames.StripVersionNumber[name]]], matches];
};
};
Inner: PROC ~ {
PFS.EnumerateForNames[pattern: pathPattern, proc: EachFileCommand];
IF matches # NIL AND matches.rest = NIL THEN {
Do preRegister now, while we are in the right directory anyway.
SELECT TRUE FROM
(foundCM # NIL) => {
RegisterAbbreviation[cmd, foundCM, matches.first];
fullname ¬ foundCM;
};
(PreRegisterSimple[matches.first, cmd]) => {
fullname ¬ matches.first;
};
ENDCASE => NULL;
};
};
Inner[];
FOR tail: LIST OF PATH ¬ GetSearchRules[cmd], tail.rest UNTIL matches # NIL OR tail = NIL DO
PFS.DoInWDir[wDir: tail.first, inner: Inner];
ENDLOOP;
};
SELECT TRUE FROM
(matches = NIL) => ERROR CommanderOps.Failed[Rope.Concat["No .command or .cm file found for ", commandName]];
(matches.rest # NIL) => {
matches ¬ RopeList.Sort[matches, RopeList.IgnoreCase];
IO.PutRope[cmd.err, commandName];
IO.PutRope[cmd.err, " ambiguous: "];
FOR tail: LIST OF ROPE ¬ matches, tail.rest UNTIL tail = NIL DO
IO.PutRope[cmd.err, " "];
IO.PutRope[cmd.err, IF Rope.Match["Source *", tail.first] THEN tail.first.Substr[7] ELSE tail.first];
ENDLOOP;
IO.PutRope[cmd.err, "\n"];
CommanderOps.Failed["Ambiguous command name"];
};
ENDCASE => NULL;
};
};
PreRegisterCommand: Commander.CommandProc ~ {
fullName: ROPE ¬ NIL;
n: INT ¬ 0;
DO
token: ROPE ~ CommanderOps.NextArgument[cmd];
IF token = NIL THEN EXIT;
fullName ¬ PreRegister[token, cmd];
n ¬ n + 1;
ENDLOOP;
IF n = 1 THEN result ¬ fullName;
};
SourceCommand: Commander.CommandProc ~ {
ENABLE PFS.Error => { ERROR CommanderOps.Failed[error.explanation] };
argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd];
fileName: ROPE ~ IF argv.argc > 1 THEN argv[1] ELSE NIL;
path: PATH ~ PFS.AbsoluteName[PFS.PathFromRope[fileName]];
fileStream: IO.STREAM ~ PFS.StreamOpen[path];
BEGIN ENABLE UNWIND => { IO.Close[fileStream, TRUE] };
child: Commander.Handle ~ CommanderOps.CreateFromStreams[in: fileStream, parentCommander: cmd];
child.propertyList ¬ CONS[List.DotCons[$CommandFileArgumentVector, argv], child.propertyList];
child.propertyList ¬ CONS[List.DotCons[$CommandFileDirectory, PFS.RopeFromPath[PFSNames.Directory[path]]], child.propertyList];
IF CommanderOps.ReadEvalPrintLoop[child].hadFailure THEN result ¬ $Failure;
END;
IO.Close[fileStream];
};
RedirectIOCommand: Commander.CommandProc ~ {
ENABLE PFS.Error => { ERROR CommanderOps.Failed[error.explanation] };
cmds: IO.STREAM ~ IO.RIS[cmd.commandLine];
index: INT ¬ 0;
in: IO.STREAM ¬ NIL;
out: IO.STREAM ¬ NIL;
pipe: IO.STREAM ¬ NIL;
IF cmd.procData.clientData = $ReceivePipe THEN {
in ¬ WITH CommanderOps.GetProp[cmd, $PipedOutput] SELECT FROM
rope: ROPE => IO.RIS[rope],
ENDCASE => IO.noInputStream;
CommanderOps.PutProp[cmd, $PipedOutput, NIL];
};
DO
token: CommanderOps.Token ~ CommanderOps.GetCmdToken[cmds];
SELECT TRUE FROM
Rope.Equal[token.value, "-from", FALSE] => {
fName: ROPE ~ CommanderOps.GetCmdToken[cmds].value;
in ¬ PFS.StreamOpen[PFS.PathFromRope[fName]];
};
Rope.Equal[token.value, "-to", FALSE] => {
fName: ROPE ~ CommanderOps.GetCmdToken[cmds].value;
out ¬ PFS.StreamOpen[PFS.PathFromRope[fName], create];
};
Rope.Equal[token.value, "-append", FALSE] => {
fName: ROPE ~ CommanderOps.GetCmdToken[cmds].value;
out ¬ PFS.StreamOpen[PFS.PathFromRope[fName], append];
};
Rope.Equal[token.value, "-pipe", FALSE] => {
pipe ¬ out ¬ IO.ROS[];
};
ENDCASE => { index ¬ token.start; EXIT };
ENDLOOP;
[result, msg] ¬ CommanderOps.ExecuteCommand[
cmd: CommanderOps.CreateFromStreams[in: in, out: out, parentCommander: cmd],
wholeCommandLine: Rope.Substr[cmd.commandLine, index]
];
IF pipe # NIL THEN {
CommanderOps.PutProp[cmd, $PipedOutput, IO.RopeFromROS[pipe]];
};
IF out # NIL THEN { IO.Close[out] };
IF in # NIL THEN { IO.Close[in] };
};
Working Directory Commands
NullSeparatorProc: PFSNames.SeparatorProc ~ {};
PFSFail: PROC [msg: ROPE, path: PATH] ~ {
CommanderOps.Failed[Rope.Concat[msg, PFS.RopeFromPath[path]]];
};
ForceDirectory: PROC [path: PATH, mustExist: BOOL] RETURNS [PATH] ~ {
ENABLE PFS.Error => {
-- FileInfo will raise an error if it doesn't like the path
SELECT error.code FROM
$unknownFile => PFSFail["No such directory: ", path];
$invalidNameSyntax => PFSFail["Invalid name: ", path];
$fileTypeMismatch => PFSFail["Not a directory: ", path];
ENDCASE => ERROR CommanderOps.Failed[error.explanation];
};
path ¬ PFS.AbsoluteName[path];
IF mustExist THEN {
nMatches: INT ¬ 0;
match: PATH ¬ NIL;
EachMatch: PFS.InfoProc ~ {
IF fileType = PFS.tDirectory THEN {
nMatches ¬ nMatches + 1;
match ¬ fullFName;
};
};
PFS.EnumerateForInfo[pattern: PFSNames.SubName[name: path, absolute: TRUE, directory: FALSE], proc: EachMatch];
IF nMatches = 0 THEN {
Kludge: maybe it's not yet automounted
OneMatch: PFS.InfoProc ~ {
we don't care what's in the directory; we just had to try enumerating it to get it mounted
RETURN[FALSE]
};
PFS.EnumerateForInfo[pattern: PFSNames.SubName[name: path, absolute: TRUE, directory: TRUE], proc: OneMatch];
PFS.EnumerateForInfo[pattern: PFSNames.SubName[name: path, absolute: TRUE, directory: FALSE], proc: EachMatch];
IF nMatches = 0 THEN PFSFail["Not a directory: ", path];
};
IF nMatches # 1 THEN PFSFail["Ambiguous pattern: ", path];
path ¬ match;
};
IF NOT PFSNames.IsADirectory[path] THEN {
path ¬ PFSNames.SubName[name: path, absolute: TRUE, directory: TRUE];
};
RETURN [path];
};
SetWDirRope: PROC [rope: ROPE, mustExist: BOOL] ~ { SetWDir[PFS.PathFromRope[rope], mustExist] };
SetWDir: PROC [wDir: PATH, mustExist: BOOL] ~ {
wDirRope: ROPE ~ PFS.RopeFromPath[ForceDirectory[wDir, mustExist]];
old: List.AList ~ ProcessProps.GetPropList[];
new: List.AList ~ List.PutAssoc[key: $WorkingDirectory, val: wDirRope, aList: old];
IF old # new THEN ERROR CommanderOps.Failed["Could not add $WorkingDirectory property"];
[] ¬ List.PutAssoc[key: $WorkingDirectory, val: wDirRope, aList: ProcessProps.GetPropList[]];
};
UserCedarDir: PROC RETURNS [PATH] ~ {
localDir: PATH ¬ PFS.PathFromRope[SystemNames.UserCedarDir[NIL, $releaseDir]];
RETURN [localDir]
};
HomeDir: PROC [cmd: Commander.Handle] RETURNS [ROPE] ~ {
WITH CommanderOps.GetProp[cmd, $HOME] SELECT FROM
rope: ROPE => RETURN [rope];
ENDCASE;
RETURN [NIL]
};
CDCommand: Commander.CommandProc ~ {
-- Three variants of this procedure. CDV moves relative to UserCedarDir[] and is indicated by the $V clientData. CDF moves to a directory regardless of its existence ($F). The default CD checks if the directory exists. Note that this assumes that the UserCedarDir and the user's home directory exist (it doesn't check). See ForceDirectory for checking.
ENABLE PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation];
mustExist: BOOL ~ SELECT cmd.procData.clientData FROM $N, $H => TRUE ENDCASE => FALSE;
arg: ROPE ¬ CommanderOps.NextArgument[cmd];
IF CommanderOps.NextArgument[cmd] # NIL THEN ERROR CommanderOps.Failed["Too many arguments"];
SELECT TRUE FROM
(cmd.procData.clientData = $V) => { -- CDV or PUSHV commands
SetWDir[wDir: UserCedarDir[], mustExist: FALSE];
};
(arg = NIL) => { arg ¬ HomeDir[cmd] };
(cmd.procData.clientData = $H) => {
SetWDirRope[rope: HomeDir[cmd], mustExist: FALSE];
};
ENDCASE;
SetWDirRope[rope: arg, mustExist: mustExist];
RETURN PWDCommand[cmd];
};
PushCommand: Commander.CommandProc ~ {
old: LIST OF PATH ~ WITH CommanderOps.GetProp[cmd, $WorkingDirectoryStack] SELECT FROM
list: LIST OF PATH => list,
ENDCASE => NIL;
new: LIST OF PATH ~ CONS[PFS.GetWDir[], old];
[result: result, msg: msg] ¬ CDCommand[cmd];
CommanderOps.PutProp[cmd, $WorkingDirectoryStack, new];
};
PopCommand: Commander.CommandProc ~ {
WITH CommanderOps.GetProp[cmd, $WorkingDirectoryStack] SELECT FROM
list: LIST OF PATH => {
SetWDir[list.first, FALSE];
CommanderOps.PutProp[cmd, $WorkingDirectoryStack, list.rest];
RETURN PWDCommand[cmd];
};
ENDCASE => RETURN [result: $Failure, msg: "working directory stack is empty"];
};
PWDCommand: Commander.CommandProc ~ {
IO.PutRope[cmd.out, PFS.RopeFromPath[PFS.GetWDir[]]];
IO.PutRope[cmd.out, "\n"];
};
FromCommand: Commander.CommandProc ~ {
ENABLE {
PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation];
};
ris: IO.STREAM ~ IO.RIS[cmd.commandLine];
directory: ROPE ~ CommanderOps.GetCmdToken[ris].value;
path: PATH ~ IF directory = NIL THEN NIL ELSE PFS.PathFromRope[directory];
IF path = NIL
THEN ERROR CommanderOps.Failed["Usage: From <directory> command ..."]
ELSE {
Inner: PROC = {
child: Commander.Handle ~ CommanderOps.CreateFromStreams[in: ris, parentCommander: cmd];
IF CommanderOps.ReadEvalPrintLoop[child].hadFailure THEN result ¬ $Failure;
};
PFS.DoInWDir[wDir: ForceDirectory[path, FALSE], inner: Inner];
IO.Close[ris];
};
};
Type/Files Commands
MyFileType: TYPE ~ {datafile, realdir, symdir};
QuickIsItADirectory: PROC [path: PATH] RETURNS [MyFileType] ~ {
Uses heuristics to make a guess: never calls a real file a directory, but it might call a directory a plain file.
short: PFSNames.Component ~ PFSNames.ShortName[path];
xx: INT ~ short.name.start+short.name.len;
end: INT ~ MIN[xx, Rope.Size[short.name.base]]; -- to keep compiler happier
xlen: INT ¬ 0;
GetMyFileType: PROC [fullFName, attachedTo: PFS.PATH, uniqueID: PFS.UniqueID, bytes: INT, mutability: PFS.Mutability, fileType: PFS.FileType] RETURNS [MyFileType] ~ {
RETURN[IF (fileType = PFS.tDirectory) THEN IF attachedTo=NIL THEN realdir ELSE symdir ELSE datafile];
};
IF short.version.versionKind = numeric THEN RETURN [datafile]; -- vux does not have versioned directories; this could be wrong for XNS.
FOR i: INT DECREASING IN [short.name.start..end) DO
SELECT Rope.Fetch[short.name.base, i] FROM
'. => IF xlen > 0 THEN RETURN [datafile]; -- looks like it has an extension; call it a file.
IN ['a..'z] => xlen ¬ xlen + 1;
ENDCASE => EXIT;
ENDLOOP;
RETURN [APPLY [GetMyFileType, PFS.FileInfo[path]]];
};
FetchEnd: PROC [path: PATH, begin: BOOL] RETURNS [CHAR] ~ {
short: PFSNames.Component ~ PFSNames.ShortName[path];
xx: INT ~ short.name.start+short.name.len;
end: INT ~ MIN[xx, Rope.Size[short.name.base]];
i: INT ~ IF begin THEN short.name.start ELSE end-1;
IF i >= 0 THEN RETURN [Rope.Fetch[short.name.base, i]];
RETURN ['?]
};
Hidden: PROC [path, pattern: PATH] RETURNS [BOOL] ~ {
c: CHAR ¬ FetchEnd[path, TRUE];
IF c = '. AND c # FetchEnd[pattern, TRUE] THEN RETURN [TRUE];
c ¬ FetchEnd[path, FALSE];
IF c = '~ AND c # FetchEnd[pattern, FALSE] THEN RETURN [TRUE];
RETURN [FALSE]
};
Dotty: PROC [path: PATH] RETURNS [BOOL] ~ {
short: PFSNames.Component ~ PFSNames.ShortName[path];
xx: INT ~ short.name.start+short.name.len;
end: INT ~ MIN[xx, Rope.Size[short.name.base]];
len: INT ~ end-short.name.start;
IF len NOT IN [1..2] THEN RETURN [FALSE];
FOR i: INT IN [short.name.start..end) DO
IF Rope.Fetch[short.name.base, i] # '. THEN RETURN [FALSE];
ENDLOOP;
RETURN [TRUE];
};
bangH: PATH ~ PFS.PathFromRope["*!H"];
PatternFromDirectory: PROC [path: PATH] RETURNS [PATH] ~ {
RETURN [PFSNames.Cat[PFSNames.SetVersionNumber[path, [none]], bangH]]
};
fileArgumentSwitches: ROPE ~ "
switches:
-a include files like .* and *~
-f full name always (otherwise strips working directory)
-r recursive - delve into subdirectories
-s slow - don't use heuristics to guess which might be subdirectories
-n never follow symbolic links to directories
";
FileArguments: PROC [cmd: Commander.Handle, nullCheck: BOOL, recursive: BOOL, stripPrefix: BOOL, quick: BOOL, all: BOOL, dirs: BOOL, files: BOOL, action: PROC[PATH]] RETURNS [n: INT ¬ 0] ~ {
wd: PATH ~ PFS.GetWDir[];
stripVersion: BOOL ¬ FALSE;
followDirLinks: BOOL ¬ TRUE;
MaybeStrip: PROC [name: PATH] RETURNS [PATH] ~ {
Abbrev: PROC [isa: BOOL, suffix: PATH] ~ { IF isa THEN name ¬ suffix };
IF stripPrefix THEN [] ¬ APPLY [Abbrev, PFSNames.IsAPrefix[wd, name]];
IF stripVersion THEN name ¬ PFSNames.SetVersionNumber[name, [none]];
RETURN [name]
};
Emit: PROC [name, pattern: PATH] ~ {
IF all OR NOT Hidden[name, pattern] THEN {
n ¬ n + 1;
action[MaybeStrip[name]]
};
};
argi: INT ¬ 0;
arg: ROPE;
[] ¬ CommanderOps.ArgN[cmd, argi];
WHILE Rope.Match["-*", arg ¬ CommanderOps.NextArgument[cmd, leaveQuotes]] DO
sense: BOOL ¬ TRUE;
FOR i: INT IN (0..Rope.Size[arg]) DO
c: CHAR ~ Rope.Fetch[arg, i];
SELECT c FROM
'~ => sense ¬ NOT sense;
'a, 'A => { all ¬ sense; sense ¬ TRUE };
'f, 'F => { stripPrefix ¬ NOT sense; sense ¬ TRUE };
'r, 'R => { recursive ¬ sense; sense ¬ TRUE };
's, 'S => { quick ¬ NOT sense; sense ¬ TRUE };
'n, 'N => { followDirLinks ¬ NOT sense; sense ¬ TRUE };
ENDCASE => CommanderOps.Failed[cmd.procData.doc];
ENDLOOP;
argi ¬ argi + 1;
ENDLOOP;
[] ¬ CommanderOps.ArgN[cmd, argi];
WHILE (arg ¬ CommanderOps.NextArgument[cmd]) # NIL DO
before: INT ¬ n;
pattern0: PATH ¬ PFS.PathFromRope[arg];
Enum: PROC [pattern: PATH] ~ IF quick THEN EnumNames ELSE EnumInfo;
EnumInfo: PROC [pattern: PATH] ~ {
InfoMatch: PFS.InfoProc ~ {
Match[fullFName, IF (fileType = PFS.tDirectory) THEN IF attachedTo=NIL THEN realdir ELSE symdir ELSE datafile, pattern] };
PFS.EnumerateForInfo[pattern: pattern, proc: InfoMatch];
};
EnumNames: PROC [pattern: PATH] ~ {
NameMatch: PFS.NameProc ~ {Match[name, QuickIsItADirectory[name], pattern]};
PFS.EnumerateForNames[pattern: pattern, proc: NameMatch]
};
Match: PROC [name: PATH, type: MyFileType, pattern: PATH] ~ {
IF (type=realdir) OR (type=symdir)
THEN {
IF dirs THEN Emit[name, pattern];
IF recursive AND (followDirLinks OR type=realdir) AND NOT Dotty[name] AND (all OR NOT Hidden[name, pattern]) THEN {
Enum[pattern: pattern ¬ PatternFromDirectory[name]
! PFS.Error => IF error.code = $accessDenied THEN {
cmd.err.PutRope[" -- "];
cmd.err.PutRope[PFS.RopeFromPath[name]];
cmd.err.PutRope[" - accessDenied --\n"];
CONTINUE;
};
];
};
}
ELSE { IF NOT dirs THEN Emit[name, pattern] };
};
stripVersion ¬ PFSNames.ShortName[pattern0].version.versionKind = none;
IF stripVersion THEN { pattern0 ¬ PFSNames.SetVersionNumber[pattern0, [highest]] };
Enum[pattern0];
IF nullCheck AND before = n THEN {
PFSFail["No files from enumeration of pattern ", pattern0];
};
ENDLOOP;
};
FilesCommand: Commander.CommandProc = {
ENABLE PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation];
dirs: BOOL ~ cmd.procData.clientData = $Dirs;
n: INT ~ FileArguments[cmd: cmd, nullCheck: FALSE, recursive: FALSE, stripPrefix: TRUE, quick: TRUE, all: FALSE, dirs: dirs, files: NOT dirs, action: Inner];
Inner: PROC [name: PATH] ~ {
IO.PutRope[cmd.out, PFS.RopeFromPath[name]];
IO.PutRope[cmd.out, "\n"];
};
};
TypeCommand: Commander.CommandProc = {
ENABLE PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation];
n: INT ~ FileArguments[cmd: cmd, nullCheck: TRUE, recursive: FALSE, stripPrefix: FALSE, quick: FALSE, all: FALSE, dirs: FALSE, files: TRUE, action: Inner];
Inner: PROC [name: PATH] ~ {
fileStream: IO.STREAM ~ PFS.StreamOpen[name];
IOClasses.Copy[from: fileStream, to: cmd.out, closeFrom: TRUE, closeTo: FALSE];
};
IF n = 0 THEN CommanderOps.Failed["Usage: Type pattern*"];
};
PrintFilePropertiesCommand: Commander.CommandProc ~ {
ENABLE PFS.Error => CommanderOps.Failed[error.explanation];
arg0: ROPE ← CommanderOps.NextArgument[cmd];
IF arg0 = NIL THEN CommanderOps.Failed[cmd.procData.doc];
FOR arg: ROPE ← arg0, CommanderOps.NextArgument[cmd] UNTIL arg = NIL DO
EachProp: PROC [propertyName: ROPE, propertyValue: ROPE] RETURNS [continue: BOOL ¬ TRUE] ~ {
IO.PutRope[cmd.out, " "];
IO.PutRope[cmd.out, propertyName];
IO.PutRope[cmd.out, ": "];
IO.PutRope[cmd.out, Convert.RopeFromRope[propertyValue, TRUE]];
IO.PutRope[cmd.out, "\n"];
};
file: PFS.OpenFile ~ PFS.Open[PFS.PathFromRope[arg]];
IO.PutRope[cmd.out, PFS.RopeFromPath[PFS.GetName[file].fullFName]];
IO.PutRope[cmd.out, "\n"];
PFS.EnumerateClientProperties[file, EachProp];
PFS.Close[file];
ENDLOOP;
};
SetFilePropertiesCommand: Commander.CommandProc ~ {
ENABLE PFS.Error => CommanderOps.Failed[error.explanation];
changes: SymTab.Ref ~ SymTab.Create[];
arg0: ROPE ← CommanderOps.NextArgument[cmd];
IF arg0 = NIL THEN CommanderOps.Failed[cmd.procData.doc];
FOR arg: ROPE ← arg0, CommanderOps.NextArgument[cmd] UNTIL arg = NIL DO
IF Rope.Match["-*", arg]
THEN {
[] ¬ changes.Store[key: Rope.Substr[arg, 1], val: CommanderOps.NextArgument[cmd]];
}
ELSE {
file: PFS.OpenFile ~ PFS.Open[PFS.PathFromRope[arg]];
EachChange: SymTab.EachPairAction ~ {
value: ROPE ~ NARROW[val];
PFS.SetClientProperty[file, key, value ! PFS.Error => {
cmd.err.PutRope[error.explanation];
cmd.err.PutRope["\n"];
CONTINUE;
}];
};
[] ¬ changes.Pairs[EachChange];
PFS.Close[file];
};
ENDLOOP;
};
IfFilesDifferCommand: Commander.CommandProc ~ {
ris: IO.STREAM ~ IO.RIS[cmd.commandLine];
filename1: ROPE ~ CommanderOps.GetCmdToken[ris].value;
filename2: ROPE ~ CommanderOps.GetCmdToken[ris].value;
same: BOOL ¬ TRUE;
IF filename2 = NIL THEN CommanderOps.Failed[cmd.procData.doc];
BEGIN ENABLE PFS.Error => CommanderOps.Failed[error.explanation];
stream1: IO.STREAM ~ PFS.StreamOpen[PFS.PathFromRope[filename1]];
stream2: IO.STREAM ~ PFS.StreamOpen[PFS.PathFromRope[filename2]];
UNTIL (NOT same) OR IO.EndOf[stream1] OR IO.EndOf[stream2] DO
same ¬ IO.GetChar[stream1] = IO.GetChar[stream2];
ENDLOOP;
IF same THEN same ¬ IO.EndOf[stream1] AND IO.EndOf[stream2];
IO.Close[stream1];
IO.Close[stream2];
END;
IF cmd.procData.clientData=$NOT THEN same ¬ NOT same;
IF NOT same THEN RETURN CommanderOps.ExecuteCommand[cmd, IO.GetRope[ris]];
};
YankCommand: Commander.CommandProc ~ {
bytesMoved: INT ¬ 0;
bytesWouldMove: INT ¬ 0;
verbose: BOOL ¬ FALSE;
FinalReport: PROC = {
IF verbose THEN {
IF bytesMoved + bytesWouldMove = 0 THEN cmd.out.PutRope["Moved 0 bytes\n"];
IF bytesMoved # 0 THEN cmd.out.PutF1["Moved %g bytes\n", [integer[bytesMoved]]];
IF bytesWouldMove # 0 THEN cmd.out.PutF1["Would have moved %g bytes\n", [integer[bytesWouldMove]]];
};
};
BEGIN
ENABLE {
Convert.Error => CommanderOps.Failed[cmd.procData.doc];
PFS.Error => CommanderOps.Failed[error.explanation];
UNWIND => { cmd.err.PutRope[" *** "]; FinalReport[] };
};
infoOnly: BOOL ¬ FALSE;
arg0: ROPE ← CommanderOps.NextArgument[cmd];
IF arg0 = NIL THEN CommanderOps.Failed[cmd.procData.doc];
FOR arg: ROPE ← arg0, CommanderOps.NextArgument[cmd] UNTIL arg = NIL DO
IF Rope.Match["-*", arg]
THEN {
sense: BOOL ¬ TRUE;
FOR i: INT IN (0..Rope.Size[arg]) DO
SELECT Rope.Lower[Rope.Fetch[arg, i]] FROM
'v => { verbose ¬ sense; sense ¬ TRUE };
'n => { infoOnly ¬ sense; sense ¬ TRUE };
'~ => { sense ¬ NOT sense };
ENDCASE => CommanderOps.Failed[cmd.procData.doc];
ENDLOOP;
}
ELSE {
pattern: PFS.PATH ~ PFS.PathFromRope[arg];
matches: INT ¬ 0;
EachMatch: PFS.InfoProc = {
PROC [fullFName, attachedTo: PATH, uniqueID: UniqueID, bytes: INT, mutability: Mutability, fileType: FileType] RETURNS [continue: BOOL ¬ TRUE]
Report: PROC [s: IO.STREAM, fmt: ROPE] = {
s.PutF[fmt, [rope[PFS.RopeFromPath[fullFName]]], [rope[PFS.RopeFromPath[attachedTo]]]]
};
matches ¬ matches + 1;
IF fileType # PFS.tDirectory AND attachedTo # NIL THEN {
IF bytes < 0
THEN {
result ¬ $Failure;
Report[cmd.err, "Bad attachment: %q --> %q\n"];
}
ELSE {
IF verbose THEN {
Report[cmd.out, "%q := %q"];
cmd.out.PutF1[" (%g bytes)\n", [integer[bytes]]];
};
IF infoOnly
THEN {
bytesWouldMove ¬ bytesWouldMove + bytes;
}
ELSE {
PFS.Delete[fullFName, uniqueID];
PFS.Copy[from: attachedTo, to: fullFName, wantedUniqueID: uniqueID, confirmProc: NIL !
UNWIND => {
Report[cmd.err, " *** So sorry, the link from %q to %q has been lost\n"];
}];
bytesMoved ¬ bytesMoved + bytes;
};
};
};
};
PFS.EnumerateForInfo[pattern: pattern, proc: EachMatch];
IF matches = 0 THEN {result ¬ $Failure; cmd.err.PutF1["No files matching %q\n", [rope[arg]]]};
};
ENDLOOP;
END;
FinalReport[];
};
Registration
Commander.Register[key: "AddSearchRules", proc: SetSearchRules,
doc: "Add search rules", clientData: $Add, interpreted: TRUE];
Commander.Register[key: "Cat", proc: TypeCommand,
doc: "Write the contents of the named file(s) onto stdout (switches and patterns like the files command)\n", interpreted: TRUE];
Commander.Register[key: "CD", proc: CDCommand,
doc: "Change Working Directory", interpreted: TRUE, clientData: $N];
Commander.Register[key: "CDF", proc: CDCommand,
doc: "Change Working Directory regardless of its existence", interpreted: TRUE, clientData: $F];
Commander.Register[key: "CDH", proc: CDCommand,
doc: "Change Working Directory (home relative)", interpreted: TRUE, clientData: $H];
Commander.Register[key: "CDV", proc: CDCommand,
doc: "Change Working Directory (version relative)", interpreted: TRUE, clientData: $V];
Commander.Register[key: "Dirs", proc: FilesCommand, doc: Rope.Concat["List directories matching given pattern(s)", fileArgumentSwitches], clientData: $Dirs];
Commander.Register[key: "Files", proc: FilesCommand, doc: Rope.Concat["List files matching given pattern(s)", fileArgumentSwitches]];
Commander.Register[key: "From", proc: FromCommand,
doc: "Execute a command in a remote directory", interpreted: FALSE];
Commander.Register[key: "Pop", proc: PopCommand,
doc: "Pop Working Directory", interpreted: TRUE];
Commander.Register[key: "PopSearchRules", proc: SetSearchRules,
doc: "Pop search rules (remove one rule set)", clientData: $Pop, interpreted: TRUE];
Commander.Register[key: "PreRegister", proc: PreRegisterCommand,
doc: "commandName ... - Ensure the registration of one or more commands.", interpreted: TRUE];
Commander.Register[key: "PrintSearchRules", proc: PrintSearchRules,
doc: "Print command search rules", interpreted: TRUE];
Commander.Register[key: "Push", proc: PushCommand,
doc: "Push Working Directory", interpreted: TRUE, clientData: $N];
Commander.Register[key: "PushH", proc: PushCommand,
doc: "Push Working Directory (home relative)", interpreted: TRUE, clientData: $H];
Commander.Register[key: "PushSearchRules", proc: SetSearchRules,
doc: "Push search rules (and add new rules)", clientData: $Push, interpreted: TRUE];
Commander.Register[key: "PushV", proc: PushCommand,
doc: "Push Working Directory (version relative)", interpreted: TRUE, clientData: $V];
Commander.Register[key: "PWD", proc: PWDCommand,
doc: "Print Working Directory", interpreted: TRUE];
Commander.Register[key: "RedirectIO", proc: RedirectIOCommand,
doc: "Execute a command with IO redirection", interpreted: FALSE];
Commander.Register[key: "SetSearchRules", proc: SetSearchRules,
doc: "Set command search rules: SetSearchRules directory*", clientData: $Set, interpreted: TRUE];
Commander.Register[key: "Source", proc: SourceCommand,
doc: "Execute commands from a file", interpreted: TRUE];
Commander.Register[key: "Type", proc: TypeCommand,
doc: Rope.Concat["Type file contents\n", fileArgumentSwitches], interpreted: TRUE];
Commander.Register[key: "PrintFileProperties", proc: PrintFilePropertiesCommand,
doc: "Show the PFS client properties of files", interpreted: TRUE];
Commander.Register[key: "SetFileProperties", proc: SetFilePropertiesCommand,
doc: "Set PFS client properties of files\nsyntax: -propname \"propval\" filename1 filename2 ...", interpreted: TRUE];
Commander.Register[key: "|", proc: RedirectIOCommand,
doc: "Receive piped output from previous command", clientData: $ReceivePipe, interpreted: FALSE];
Commander.Register["IfFilesDiffer", IfFilesDifferCommand, "do the commandLine if the named files differ\nargs: filename1 filename2 commandLine"];
Commander.Register["IfFilesEqual", IfFilesDifferCommand, "do the commandLine if the named files are equal in contents\nargs: filename1 filename2 commandLine", $NOT];
Commander.Register["Yank", YankCommand, "Makes files local (eliminating attachments), by copying bits if required.\nargs: pattern1 pattern2 ...\nswitches: -v (verbose) -n (don't really do it)"];
END.