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 = { EachFile: FS.NameProc = { attachedTo: ROPE _ NIL; 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 [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] = { oldLag: ROPE _ lagPrefix; printName: ROPE _ item.fullFName; Process.CheckForAbort[]; IF NOT fullPrint AND NOT directoriesOnly THEN { 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: ROPE _ NIL; 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] = { IF lagPrefix # NIL THEN { IF Rope.Run[lagPrefix, 0, fileName, 0, FALSE] = lagPrefixLen THEN { pos: INT = Rope.SkipTo[fileName, lagPrefixLen, ">/]"]; IF pos = Rope.Length[fileName] THEN RETURN; }; }; 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; lagPrefix _ NIL; lagPrefixLen _ 0; }; AddSortOption: PROC [option: ATOM] = { new: LORA _ LIST[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: ROPE _ NIL; lagPrefixLen: INT _ 0; patternsTried: INT _ 0; filesSeen: INT _ 0; bytesTotal: INT _ 0; directoriesOnly: BOOL _ FALSE; complexSorting: BOOL _ FALSE; printKeep: BOOL _ FALSE; narrowPrint: BOOL _ FALSE; attachedPrint: BOOL _ FALSE; briefPrint: BOOL _ FALSE; remoteCheck: BOOL _ FALSE; fullPrint: BOOL _ FALSE; oneLine: BOOL _ FALSE; unattachedOnly: BOOL _ FALSE; exactLevelMatch: BOOL _ FALSE; anglesRequired: INT _ 0; sortData: LORA _ NIL; sortDataTail: LORA _ NIL; pq: PriorityQueue.Ref _ NIL; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; ProcessSwitches: PROC [arg: ROPE] = { sense: BOOL _ TRUE; 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 { ProcessSwitches[arg]; LOOP; }; 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 = { 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. ϊListCommandImpl.mesa Copyright c 1984, 1985 by Xerox Corporation. All rights reserved. Russ Atkinson, March 5, 1985 6:02:56 pm PST [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] [fullFName: ROPE] RETURNS [continue: BOOL] 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? item: REF [fullFName, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL] Factor out the directories ... 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. do we have a new prefix? So far we have a match with the lagging prefix. How far does it go? We have a new lagging prefix, so scan backwards for the LAST directory The file name has no prefix, so clear out the lagPrefix This argument sets switches for the remaining patterns Now the argument is assumed to be a file pattern. [x: Item, y: Item, data: REF] RETURNS [BOOL] Κ 0˜šœ™Jšœ Οmœ7™BJšœ+™+—J˜šΟk ˜ Jšœ žœžœ˜'Jšœ žœ˜(Jšœ žœ!˜2Jšœ žœ˜!Icodešœ žœ˜&Jšžœžœ<˜DJšžœžœžœ˜*Jšœžœ/˜BJšœžœ˜Jšœžœ*žœ˜OJšœ žœ ˜J˜—šœžœž˜Kšžœ:žœžœ+˜tJšœž˜—˜Jšžœžœ žœ˜Jš žœžœžœžœžœžœ˜Jšžœžœžœ˜Jšžœžœžœžœ˜—J˜šœ*˜*š œžœ žœžœžœžœ™:Jšœžœžœ™G—šœ žœ ˜Jšœ žœžœ žœ™*Jšœ žœžœ˜Jšœ žœ˜!Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœ žœ˜ Jšœ žœ˜Jšœ˜Jšžœžœ)žœžœ˜Kšžœžœž˜Jšœ˜Jš œ žœžœžœžœ˜>Jšžœžœ˜—šžœ žœ˜Jšœžœ$˜,Jšœžœ˜"šœ-˜-Jšœžœ5˜9—J˜—Jš žœžœžœžœžœ˜3š žœ žœ žœžœ ž˜2Jšœ­™­šœ-˜-Jšœžœ(žœ˜2——Jšœžœ>˜HJ˜Jšžœ žœ!˜2šžœžœž˜šœ˜Jšœžœ ˜Jšœ˜šžœžœ˜Jšœ˜Jšœ˜J˜—J˜—Jšœ1˜1Jšžœ˜—J˜—šΟn œžœ˜'Jš œžœžœ žœ žœžœ™QJšœžœ ˜Jšœ žœ˜!Jšœ˜š žœžœ žœžœžœ˜/Jšœ™Jšœ˜šžœžœ˜Jšžœ˜Jšžœžœ žœžœ˜-J˜—Jšœ1˜1Jšžœžœ žœžœ˜*J˜—šžœžœž˜Jšœžœ˜.šœ˜Jšžœ˜šžœžœžœžœ˜1Jšžœžœ žœžœ˜+Jšžœ/˜1J˜—J˜—šžœ˜ Jš œžœžœ žœžœ˜@Jšžœ<˜>šžœ!˜#Jšžœžœ˜Jšžœ:˜>—šžœžœžœžœ˜1Jšžœžœ žœžœ˜+Jšžœ/˜1J˜—Jšžœ žœžœ1˜EJ˜——Jšžœžœ žœžœ˜-J˜—šŸ œžœ žœ˜$šžœžœ žœžœ˜/Jšžœ˜Jšžœ!˜#Jšžœžœ˜ —J˜"Jšœ1˜1Jšœ žœ˜+šžœž˜Jšœ&˜&—Jšœžœ˜ šžœžœž˜Jšœ7žœ˜Jšœžœ˜ Jšœžœ ˜(šžœ ž˜Jšœ'˜'Jšœ˜Jšžœ˜—J˜J˜—Jšœ žœžœ ˜!šœ žœžœ˜Jš œžœ žœ žœžœ˜GJ˜—šœ$˜$Jšœžœžœžœ™,Jšœžœ˜Jšœžœ˜Jšœ žœžœ˜š žœžœžœžœž˜7šžœ ž˜˜Jšžœžœžœ˜%Jšžœ0˜6J˜—˜Jšžœžœžœ˜%Jšžœ0˜6J˜—˜ Jšžœžœžœ˜!Jšžœ˜J˜—˜ Jšžœžœžœ˜!Jšžœ˜J˜—Jšž˜—Jšžœ˜—Jšžœ+žœ ˜@J˜—J˜Jšœžœ…˜ŽJ˜JšœK˜KJ˜JšœM˜MJ˜Jšžœ˜J˜šœ˜J™——…—"48