<> <> <> 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: REF _ NIL, msg: ROPE _ NIL]>> <> EachFile: FS.NameProc = { <<[fullFName: ROPE] RETURNS [continue: BOOL]>> 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] = { <<... 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 { <> 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 = { <<[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. <<>>