ListCommandImpl.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Russ Atkinson, March 5, 1985 6:02:56 pm PST
DIRECTORY
BasicTime USING [GMT, nullGMT, Period],
Commander USING [CommandProc, Register],
CommandTool USING [ArgumentVector, Failed, Parse],
DFUtilities USING [DateToStream],
FileNames USING [ResolveRelativePath],
FS USING [EnumerateForNames, ExpandName, Error, FileInfo, NameProc],
IO USING [PutChar, PutF, PutRope, STREAM],
PriorityQueue USING [Create, Insert, Ref, Remove, Size, SortPred],
Process USING [CheckForAbort],
Rope USING [Compare, Equal, Fetch, Flatten, Length, ROPE, Run, SkipTo, Substr],
UserProfile USING [Token];
ListCommandImpl: CEDAR PROGRAM
IMPORTS BasicTime, Commander, CommandTool, DFUtilities, FileNames, FS, IO, PriorityQueue, Process, Rope, UserProfile
= BEGIN
GMT: TYPE = BasicTime.GMT;
LORA: TYPE = LIST OF REF ANY;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
ListCommandProc: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...]
EachFile: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
attachedTo: ROPENIL;
created: GMT ← BasicTime.nullGMT;
bytes: INT ← 0;
keep: CARDINAL ← 0;
item: FileItem ← NIL;
needInfo: BOOL ← unattachedOnly;
continue ← TRUE;
Process.CheckForAbort[];
IF exactLevelMatch AND anglesRequired # CountAngles[fullFName] THEN RETURN;
SELECT TRUE FROM
directoriesOnly => {};
briefPrint AND NOT complexSorting AND NOT attachedPrint => {};
ENDCASE => needInfo ← TRUE;
IF needInfo THEN {
pos: INT ← Rope.SkipTo[fullFName, 1, "]"]+1;
len: INT ← Rope.Length[fullFName];
[fullFName, attachedTo, keep, bytes, created]
FS.FileInfo[name: fullFName, remoteCheck: remoteCheck];
};
IF unattachedOnly AND attachedTo # NIL THEN RETURN;
IF bytes < 0 AND needInfo AND NOT remoteCheck THEN
We do not know the # of bytes in the file, so we have to check on the true length, which is on the server. This seems to happen more often than I would like. I wonder why?
[fullFName, attachedTo, keep, bytes, created]
FS.FileInfo[name: fullFName, remoteCheck: TRUE];
item ← NEW[FileItemRep ← [fullFName, attachedTo, created, bytes, keep]];
filesSeen ← filesSeen + 1;
IF bytes > 0 THEN bytesTotal ← bytesTotal + bytes;
SELECT TRUE FROM
directoriesOnly => {
oldLag: ROPE ← lagPrefix;
SetLagPrefix[fullFName];
IF oldLag # lagPrefix THEN {
item.fullFName ← lagPrefix;
PriorityQueue.Insert[pq, item];
};
};
complexSorting => PriorityQueue.Insert[pq, item];
ENDCASE => PrintOneFile[item];
};
PrintOneFile: PROC [item: FileItem] = {
item: REF [fullFName, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL]
oldLag: ROPE ← lagPrefix;
printName: ROPE ← item.fullFName;
Process.CheckForAbort[];
IF NOT fullPrint AND NOT directoriesOnly THEN {
Factor out the directories
SetLagPrefix[printName];
IF oldLag # lagPrefix THEN {
IO.PutRope[out, lagPrefix];
IO.PutChar[out, IF oneLine THEN ' ELSE '\n];
};
printName ← Rope.Substr[printName, lagPrefixLen];
IF NOT oneLine THEN IO.PutRope[out, " "];
};
SELECT TRUE FROM
directoriesOnly => IO.PutRope[out, printName];
briefPrint => {
IO.PutRope[out, printName];
IF attachedPrint AND item.attachedTo # NIL THEN {
IF NOT oneLine THEN IO.PutRope[out, "\n "];
IO.PutF[out, " => %g", [rope[item.attachedTo]] ];
};
};
ENDCASE => {
form: ROPE = IF narrowPrint THEN "%g\n%12g " ELSE "%-24g %6g ";
IO.PutF[out, form, [rope[printName]], [integer[item.bytes]] ];
IF item.created = BasicTime.nullGMT
THEN IO.PutRope[out, "??"]
ELSE DFUtilities.DateToStream[out, [explicit, item.created] ];
IF attachedPrint AND item.attachedTo # NIL THEN {
IF NOT oneLine THEN IO.PutRope[out, "\n "];
IO.PutF[out, " => %g", [rope[item.attachedTo]] ];
};
IF printKeep THEN IO.PutF[out, ", keep: %g", [cardinal[item.keep]] ];
};
IO.PutChar[out, IF oneLine THEN ' ELSE '\n];
};
TryPattern: PROC [pattern: ROPE] = {
ENABLE FS.Error => IF error.group # $bug THEN {
IO.PutRope[out, " -- "];
IO.PutRope[out, error.explanation];
GO TO err};
patternsTried ← patternsTried + 1;
pattern ← FileNames.ResolveRelativePath[pattern];
pattern ← FS.ExpandName[pattern].fullFName;
IF exactLevelMatch THEN
anglesRequired ← CountAngles[pattern];
complexSorting ← sortData # NIL;
SELECT TRUE FROM
directoriesOnly => pq ← PriorityQueue.Create[SortPred, NIL];
complexSorting => pq ← PriorityQueue.Create[SortPred, sortData];
ENDCASE => pq ← NIL;
SetLagPrefix[NIL];
FS.EnumerateForNames[pattern, EachFile];
SetLagPrefix[NIL];
IF pq # NIL THEN {
lagName: ROPENIL;
THROUGH [0..PriorityQueue.Size[pq]) DO
item: FileItem = NARROW[PriorityQueue.Remove[pq]];
IF directoriesOnly THEN {
IF Rope.Equal[item.fullFName, lagName] THEN LOOP;
lagName ← item.fullFName;
};
PrintOneFile[item];
ENDLOOP;
};
EXITS
err => {IO.PutRope[out, "\n"]; RETURN};
};
SetLagPrefix: PROC [fileName: ROPE] = {
... sets the lagging prefix from the given file name, which is presumed to be syntactically correct, although it need not be complete. A file name without a prefix will set the lagPrefix to NIL. We also enforce lagPrefixLen = Rope.Length[lagPrefix] at exit, assuming that no other routine sets lagPrefix.
IF lagPrefix # NIL THEN {
do we have a new prefix?
IF Rope.Run[lagPrefix, 0, fileName, 0, FALSE] = lagPrefixLen THEN {
So far we have a match with the lagging prefix. How far does it go?
pos: INT = Rope.SkipTo[fileName, lagPrefixLen, ">/]"];
IF pos = Rope.Length[fileName] THEN RETURN;
};
};
We have a new lagging prefix, so scan backwards for the LAST directory
FOR i: INT DECREASING IN [0..Rope.Length[fileName]) DO
SELECT Rope.Fetch[fileName, i] FROM
'>, '/, '] => {lagPrefix ← Rope.Flatten[fileName, 0, lagPrefixLen ← i+1]; RETURN};
ENDCASE;
ENDLOOP;
The file name has no prefix, so clear out the lagPrefix
lagPrefix ← NIL;
lagPrefixLen ← 0;
};
AddSortOption: PROC [option: ATOM] = {
new: LORALIST[option];
IF sortDataTail = NIL THEN sortData ← new ELSE sortDataTail.rest ← new;
sortDataTail ← new;
};
RemSortOption: PROC [option: ATOM] = {
lag: LORA ← sortData;
IF lag = NIL THEN RETURN;
IF lag.first = option THEN {
sortData ← sortData.rest;
RETURN};
FOR each: LORA ← lag.rest, each.rest WHILE each # NIL DO
IF each.first = option THEN {lag.rest ← each.rest; EXIT};
lag ← each;
ENDLOOP;
};
out: STREAM = cmd.out;
lagPrefix: ROPENIL;
lagPrefixLen: INT ← 0;
patternsTried: INT ← 0;
filesSeen: INT ← 0;
bytesTotal: INT ← 0;
directoriesOnly: BOOLFALSE;
complexSorting: BOOLFALSE;
printKeep: BOOLFALSE;
narrowPrint: BOOLFALSE;
attachedPrint: BOOLFALSE;
briefPrint: BOOLFALSE;
remoteCheck: BOOLFALSE;
fullPrint: BOOLFALSE;
oneLine: BOOLFALSE;
unattachedOnly: BOOLFALSE;
exactLevelMatch: BOOLFALSE;
anglesRequired: INT ← 0;
sortData: LORANIL;
sortDataTail: LORANIL;
pq: PriorityQueue.Ref ← NIL;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
ProcessSwitches: PROC [arg: ROPE] = {
sense: BOOLTRUE;
direction: {up, down} ← down;
FOR index: INT IN [0..Rope.Length[arg]) DO
SELECT Rope.Fetch[arg, index] FROM
'~ => {sense ← NOT sense; LOOP};
'> => direction ← down;
'< => direction ← up;
'a, 'A => attachedPrint ← sense;
'b, 'B => briefPrint ← sense;
'd, 'D => {
RemSortOption[$MoreRecent];
RemSortOption[$LessRecent];
IF sense THEN
AddSortOption[IF direction = up THEN $LessRecent ELSE $MoreRecent];
};
'f, 'F => fullPrint ← sense;
'k, 'K => printKeep ← sense;
'n, 'N => narrowPrint ← sense;
'o, 'O => oneLine ← sense;
'p, 'P => directoriesOnly ← sense;
'r, 'R => remoteCheck ← sense;
's, 'S => {
RemSortOption[$Larger];
RemSortOption[$Smaller];
IF sense THEN
AddSortOption[IF direction = up THEN $Smaller ELSE $Larger];
};
'u, 'U => unattachedOnly ← sense;
'x, 'X => exactLevelMatch ← sense;
ENDCASE;
sense ← TRUE;
ENDLOOP;
};
ProcessSwitches[UserProfile.Token["ListCommand.DefaultSwitches"]];
FOR i: NAT IN [1..argv.argc) DO
arg: ROPE = argv[i];
IF Rope.Length[arg] = 0 THEN LOOP;
IF Rope.Fetch[arg, 0] = '- THEN {
This argument sets switches for the remaining patterns
ProcessSwitches[arg];
LOOP;
};
Now the argument is assumed to be a file pattern.
TryPattern[arg];
ENDLOOP;
IF patternsTried = 0 THEN TryPattern["*"];
IF oneLine THEN IO.PutChar[out, '\n];
IF filesSeen > 0 THEN {
IO.PutF[out, "-- %g files", [integer[filesSeen]] ];
IF bytesTotal > 0 THEN IO.PutF[out, ", %g total bytes", [integer[bytesTotal]] ];
IO.PutChar[out, '\n];
};
EXITS
failed => {result ← $Failure};
};
CountAngles: PROC [pattern: ROPE] RETURNS [count: INT ← 0] = {
len: INT = Rope.Length[pattern];
pos: INT ← Rope.SkipTo[pattern, 0, ">"];
WHILE pos < len DO
pos ← Rope.SkipTo[pattern, pos+1, ">"];
count ← count + 1;
ENDLOOP;
};
FileItem: TYPE = REF FileItemRep;
FileItemRep: TYPE = RECORD [
fullFName, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL];
SortPred: PriorityQueue.SortPred = {
[x: Item, y: Item, data: REF] RETURNS [BOOL]
xx: FileItem = NARROW[x];
yy: FileItem = NARROW[y];
options: LORA = NARROW[data];
FOR each: LORA ← options, each.rest WHILE each # NIL DO
SELECT each.first FROM
$MoreRecent => {
IF xx.created = yy.created THEN LOOP;
RETURN [BasicTime.Period[xx.created, yy.created] < 0];
};
$LessRecent => {
IF xx.created = yy.created THEN LOOP;
RETURN [BasicTime.Period[xx.created, yy.created] > 0];
};
$Larger => {
IF xx.bytes = yy.bytes THEN LOOP;
RETURN [xx.bytes > yy.bytes];
};
$Smaller => {
IF xx.bytes = yy.bytes THEN LOOP;
RETURN [xx.bytes < yy.bytes];
};
ENDCASE;
ENDLOOP;
RETURN [Rope.Compare[xx.fullFName, yy.fullFName, FALSE] = less];
};
doc: ROPE = "(List | LS) {switch | pattern}*\nswitch = -a: attached print, -b: brief format, -d: date sort, -f: full name print, -k: keep print, -n: narrow print, -o: one line, -p: prefixes only, -r: remote check, -s: size sort, -u: un backed up, -x: exact level match";
Commander.Register[key: "///Commands/LS", proc: ListCommandProc, doc: doc];
Commander.Register[key: "///Commands/List", proc: ListCommandProc, doc: doc];
END.