ColumnLsImpl.mesa: concise list program
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by: Bloomenthal, November 6, 1985 1:17:12 pm PST
DIRECTORY
Commander, CommandTool, FS, IO, List, ReadEvalPrint,
Real, RefText, Rope, VFonts, ViewerClasses, Process, FileNames;
ColumnLsImpl:
CEDAR
PROGRAM
IMPORTS
Commander, CommandTool, FS, IO, List, Real, RefText, Rope, VFonts, Process, FileNames
~
{
once: BOOL;
printStream: IO.STREAM;
ROPE: TYPE ~ Rope.ROPE;
Mode: TYPE ~ RECORD[sep, dir, rag, minus1: BOOL];
font: VFonts.Font ~ VFonts.EstablishFont["Tioga", 10];
NameRecord: TYPE = RECORD[name: ROPE, width: INTEGER, next: REF NameRecord];
normSpaceWidth: INT ~ VFonts.StringWidth[" ", font];
smallSpaceWidth: INT ~ VFonts.StringWidth[" ", VFonts.EstablishFont["Timesroman", 8]];
ListCommand:
PROC [
cmd: Commander.Handle, lsArg: ROPE, vWidth: INT, mode: Mode] ~ {
dir: BOOL;
arg: ROPE;
ok: BOOL ← TRUE;
nArgArrows: INT;
maxWidth: INT ← 0;
subDir: ROPE ← NIL;
nameRecords: REF NameRecord ← NIL;
nameRecordsTail: REF NameRecord ← NIL;
EachName:
FS.NameProc ~ {
pos: INT;
width: INT;
name: ROPE;
long
: BOOL ← NChars[fullFName, '>] > nArgArrows;
Process.CheckForAbort[];
IF mode.minus1 AND FS.FileInfo[fullFName].bytes # -1 THEN RETURN[TRUE];
IF dir
AND long
THEN
{
-- get subdirectory
name ← MultiSkip[fullFName, '>, nArgArrows];
name ← RopePiece[name, 0, name.SkipTo[0, ">"]-1];
IF ~mode.dir THEN name ← name.Concat["/"];
IF name.Equal[subDir, FALSE] THEN RETURN[TRUE];
subDir ← name;
}
ELSE {
-- name is a file
IF mode.dir THEN RETURN[TRUE];
name ← FileNames.ConvertToSlashFormat[fullFName];
IF ~dir AND long THEN pos ← 0
ELSE pos ← RevCharFind[name, '/]+1;
name ← RopePiece[name, pos, name.SkipTo[pos, "!"]-1];
};
width ← VFonts.StringWidth[name, font];
IF width > maxWidth
THEN maxWidth ← width;
IF nameRecords =
NIL
THEN
nameRecordsTail ← nameRecords ← NEW[NameRecord ← [name, width, NIL]]
ELSE
nameRecordsTail ← nameRecordsTail.next ← NEW[NameRecord ← [name, width, NIL]];
RETURN[TRUE];
};
[arg, nArgArrows, dir, ok] ← Arg[lsArg];
IF ~ok THEN RETURN;
IF mode.sep
AND dir
THEN
{
IF once THEN IO.PutRope[printStream, "\n"];
printStream.PutRope[lsArg.Concat[":\n"]];
};
once ← TRUE;
FS.EnumerateForNames[arg, EachName ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN Columnate[cmd, nameRecords, maxWidth, vWidth, mode];
};
Arg:
PROC
[arg:
ROPE]
RETURNS [
ROPE,
INT,
BOOL, BOOL] ~ {
dir: BOOL;
ok: BOOL ← TRUE;
star: BOOL ← LastChar[arg, '*];
WHILE LastChar[arg, '*] DO arg ← RopeShorten[arg, 1]; ENDLOOP;
dir ← arg.IsEmpty[] OR LastChar[arg, '/] OR LastChar[arg, '>];
arg ← FileNames.ResolveRelativePath[arg]; -- handle .> or ..>
arg ← arg.Concat["*"]; -- ensure FName
arg ← FS.ExpandName[arg ! FS.Error => {ok ← FALSE; CONTINUE}].fullFName;
IF ~ok THEN RETURN[arg, 0, dir, FALSE];
IF ~dir AND ~star THEN arg ← RopeShorten[arg, 1]; -- remove '*
arg ← arg.Concat["!H"];
RETURN[arg, NChars[arg, '>], dir, TRUE];
};
Columnate:
PROC [cmd: Commander.Handle, nameRecords:
REF NameRecord,
columnWidth, viewerWidth: INT, mode: Mode] ~ {
nPixels: INT;
totWidth: INT;
nextTotal: INT;
nColumns: INT;
nNormSpaces: INT;
nSmallSpaces: INT;
record: REF NameRecord ← nameRecords;
normSpaces: REF TEXT ← RefText.ObtainScratch[100];
smallSpaces: REF TEXT ← RefText.ObtainScratch[100];
columnWidth ← columnWidth+2*normSpaceWidth;
nColumns ← (viewerWidth-20)/columnWidth;
WHILE record #
NIL
DO
nextTotal ← totWidth ← 0;
FOR i:
INT
IN [1..nColumns]
WHILE record #
NIL
DO
nextTotal ← nextTotal+columnWidth;
totWidth ← totWidth+record.width;
nPixels ← nextTotal-totWidth;
IF i = nColumns THEN nNormSpaces ← nSmallSpaces ← 0
ELSE IF mode.rag
THEN {
nNormSpaces ← Real.RoundI[Real.Float[nextTotal-totWidth]/Real.Float[normSpaceWidth]];
nSmallSpaces ← 0;
}
ELSE {
nNormSpaces ← nPixels MOD smallSpaceWidth;
nSmallSpaces ← nPixels/smallSpaceWidth-nNormSpaces;
IF nSmallSpaces < 0 THEN { nSmallSpaces ← 0; nNormSpaces ← nNormSpaces-1; }
};
totWidth ← totWidth+nNormSpaces*normSpaceWidth+nSmallSpaces*smallSpaceWidth;
SpaceText[normSpaces, nNormSpaces];
SpaceText[smallSpaces, nSmallSpaces];
IF mode.rag OR nSmallSpaces = 0 THEN cmd.out.PutF["%g%g", IO.rope[record.name], IO.text[normSpaces]]
ELSE cmd.out.PutF["%g%g%l%g%l", IO.rope[record.name], IO.text[normSpaces], IO.rope["s"], IO.text[smallSpaces], IO.rope["S"]];
record ← record.next;
ENDLOOP;
cmd.out.PutF["\n"];
ENDLOOP;
RefText.ReleaseScratch[smallSpaces];
RefText.ReleaseScratch[normSpaces];
};
SpaceText:
PROC [text:
REF
TEXT, nSpaces:
INT] ~ {
text.length ← 0;
THROUGH[1..nSpaces]
DO
text ← RefText.AppendChar[text, ' ];
ENDLOOP;
};
NChars:
PUBLIC PROC [rope: Rope.
ROPE, char:
CHAR]
RETURNS [retval:
INT ← 0] ~ {
length: INT ← rope.Length[];
FOR n:
INT ← 0, n+1
WHILE n < length
DO
IF rope.Fetch[n] = char THEN retval ← retval+1;
ENDLOOP;
RETURN[retval];
};
RopePiece:
PUBLIC PROC [rope: Rope.
ROPE, n1:
INT, n2:
INT]
RETURNS [Rope.
ROPE] ~ {
IF n1 < 0 OR n2 < 0 THEN RETURN[NIL];
IF n2 > n1 THEN RETURN[rope.Substr[n1, n2-n1+1]];
RETURN[rope.Substr[n2, n1-n2+1]];
};
RevCharFind:
PUBLIC PROC [rope: Rope.
ROPE, char:
CHAR]
RETURNS [
INT] ~ {
n: INT ← rope.Length[]-1;
WHILE n >= 0 AND rope.Fetch[n] # char DO n ← n-1; ENDLOOP;
RETURN[n];
};
FirstChar:
PUBLIC PROC [rope: Rope.
ROPE, char:
CHAR]
RETURNS [
BOOL] ~ {
RETURN[rope.Length[] > 0 AND rope.Fetch[0] = char];
};
LastChar:
PUBLIC PROC [rope: Rope.
ROPE, char:
CHAR]
RETURNS [
BOOL] ~ {
ropeLength: INT ← rope.Length[];
RETURN[ropeLength > 0 AND rope.Fetch[ropeLength-1] = char];
};
RopeShorten:
PUBLIC PROC [rope: Rope.
ROPE, n:
INT]
RETURNS [Rope.
ROPE] ~ {
ropeLength: INT ← rope.Length[];
IF ropeLength >= n THEN rope ← rope.Substr[0, ropeLength-n];
RETURN [rope];
};
MultiSkip:
PUBLIC PROC [rope: Rope.
ROPE, char:
CHAR, nSkip:
INT]
RETURNS [Rope.
ROPE] ~ {
n: INT;
limit: INT ← rope.Length[]-1;
FOR n ← 0, n+1
WHILE n < limit
DO
IF rope.Fetch[n] = char THEN nSkip ← nSkip-1;
IF nSkip = 0 THEN EXIT;
ENDLOOP;
RETURN[rope.Substr[n+1]];
};
ListCommandProc: Commander.CommandProc ~ {
nArgs: INT ← 0;
mode: Mode ← [TRUE, FALSE, FALSE, FALSE];
v: ViewerClasses.Viewer ~ CommandTool.GetViewer[cmd];
v: ViewerClasses.Viewer ← NARROW[List.Assoc[$ReadEvalPrintHandle, cmd.propertyList], ReadEvalPrint.Handle].viewer;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd];
once ← FALSE;
printStream ← cmd.out;
FOR i:
INT
IN [1..argv.argc)
DO
IF FirstChar[argv[i], '-]
THEN SELECT
TRUE
FROM
argv[i].Equal["-s", FALSE] => mode.sep ← FALSE;
argv[i].Equal["-d", FALSE] => mode.dir ← TRUE;
argv[i].Equal["-m", FALSE] => mode.minus1 ← TRUE;
argv[i].Equal["-q", FALSE] => mode.rag ← TRUE;
ENDCASE
ELSE nArgs ← nArgs+1;
ENDLOOP;
mode.sep ← mode.sep AND nArgs>1;
FOR i:
INT
IN [1..argv.argc)
DO
IF ~FirstChar[argv[i], '-] THEN ListCommand[cmd, argv[i], v.cw, mode];
ENDLOOP;
IF ~once THEN ListCommand[cmd, "", v.cw, mode];
};
msg: ROPE ~ "\nls {switch | pattern}*\na concise listing program\n\t-d for directories only\n\t-s for separation of multiple directories\n\t-q for quicker (somewhat ragged) columnation\n\t-m for listing only files with -1 byte counts.";
Commander.Register["ls", ListCommandProc, msg];
Commander.Register["ColumnLs", ListCommandProc, msg];
}.