ColumnLsImpl.mesa
Copyright Ó 1985, 1988, 1992 by Xerox Corporation. All rights reserved.
Last Edited by: Bloomenthal, December 29, 1992 11:58 am PST
DIRECTORY Ascii, ColumnLs, Commander, CommanderOps, DFUtilities, FileNames, FS, IO, PFS, PFSNames, Process, Real, RefText, Rope, VFonts, ViewerClasses, ViewerIO;
ColumnLsImpl: CEDAR PROGRAM
IMPORTS Commander, CommanderOps, DFUtilities, FileNames, FS, IO, PFS, PFSNames, Process, Real, RefText, Rope, VFonts, ViewerIO
EXPORTS ColumnLs
~ BEGIN
Type Declarations
ROPE:     TYPE ~ Rope.ROPE;
Font:     TYPE ~ VFonts.Font;
Viewer:    TYPE ~ ViewerClasses.Viewer;
CmdMode:   TYPE ~ RECORD [
derived:
separateDirs:    BOOL ¬ TRUE, -- there are multiple directories, so list separately
userSpecified:
columnOrder:   BOOL ¬ FALSE, -- -c:  column order (default is row order)
fixed:      BOOL ¬ FALSE, -- -f:  use fixed pitch font
markDirectory:   BOOL ¬ FALSE, -- -F:  place slash after subdirectory
directoriesOnly:   BOOL ¬ FALSE, -- -d:  sub-directories only, no files
exactLevel:    BOOL ¬ FALSE, -- -x:  if true, don't list names recursively
separateByExtension: BOOL ¬ FALSE, -- -se: list files according to extension
extensionsOnly:   BOOL ¬ FALSE, -- -e:  file extensions (suffixes following '.')
noExtensions:   BOOL ¬ FALSE, -- -ne: list those files that have no extension
period:     BOOL ¬ FALSE, -- -a:  files with name beginning with period
noBackup:    BOOL ¬ FALSE, -- -no~: omit files ending with ~
basesOnly:    BOOL ¬ FALSE, -- -b:  names only, no extensions
interfacesOnly:   BOOL ¬ FALSE, -- -i:  mesa files without 'impl.mesa' in name
implsOnly:    BOOL ¬ FALSE, -- -ni: impls without corresponding interfaces
aisAbbrev:    BOOL ¬ FALSE, -- -ais: abbreviate color ais files
totalOnly:    BOOL ¬ FALSE-- -tot: list total number of files only
ragged:     BOOLFALSE, -- -q:  list with space, not pixel, resolution
tabbed:     BOOLFALSE, -- -t:  list with tab, not pixel, resolution
minus1:     BOOLFALSE, -- -m: list those files with size of -1
];
File Listing Command
GetColumnateMode: PROC [mode: CmdMode] RETURNS [ColumnateMode] ~ {
RETURN[IF mode.fixed THEN fixed ELSE spaced];
};
CommandProc: Commander.CommandProc ~ {
mode: CmdMode;
n, nArgs: NAT ¬ 0;
once: BOOL ¬ FALSE;
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
FOR n: INT IN [1..argv.argc) DO
a: ROPE ¬ argv[n];
IF FirstChar[a, '-] THEN SELECT TRUE FROM
Rope.Equal[a, "-c"]  => mode.columnOrder ¬ TRUE;
Rope.Equal[a, "-f"]  => mode.fixed ¬ TRUE;
Rope.Equal[a, "-F"]  => mode.markDirectory ¬ TRUE;
Rope.Equal[a, "-d"]  => mode.directoriesOnly ¬ TRUE;
Rope.Equal[a, "-x"]  => mode.exactLevel ¬ TRUE;
Rope.Equal[a, "-se"]  => mode.separateByExtension ¬ TRUE;
Rope.Equal[a, "-e"]  => mode.extensionsOnly ¬ TRUE;
Rope.Equal[a, "-ne"] => mode.noExtensions ¬ TRUE;
Rope.Equal[a, "-a"]  => mode.period ¬ TRUE;
Rope.Equal[a, "-no~"] => mode.noBackup ¬ TRUE;
Rope.Equal[a, "-b"]  => mode.basesOnly ¬ TRUE;
Rope.Equal[a, "-i"]  => mode.interfacesOnly ¬ TRUE;
Rope.Equal[a, "-ni"]  => mode.implsOnly ¬ TRUE;
Rope.Equal[a, "-ais"] => mode.aisAbbrev ¬ TRUE;
Rope.Equal[a, "-tot"] => mode.totalOnly ¬ TRUE;
Rope.Equal[a, "-q"]  => mode.ragged ← TRUE;
Rope.Equal[a, "-t"]  => mode.tabbed ← TRUE;
Rope.Equal[a, "-m"]  => mode.minus1 ← TRUE;
ENDCASE
ELSE nArgs ¬ nArgs+1;
ENDLOOP;
IF NOT mode.directoriesOnly OR NOT EnumerateSubdirs[cmd, mode] THEN {
mode.separateDirs ¬ mode.separateDirs AND nArgs > 1;
WHILE n < argv.argc-1 DO
a: ROPE ¬ argv[n ¬ n+1];
IF NOT FirstChar[a, '-]
THEN once ¬ EnumerateArgument[cmd, a, mode] OR once
ELSE {
IF (n ¬ n+1) < argv.argc THEN SELECT TRUE FROM
Equal[a, "-dfi"] => once ¬ EnumerateDf[cmd, FALSE, argv[n], mode];
Equal[a, "-dfm"] => once ¬ EnumerateDf[cmd, TRUE, argv[n], mode];
ENDCASE   => n ¬ n-1;
};
ENDLOOP;
IF NOT once THEN [] ¬ EnumerateArgument[cmd, "", mode];
};
};
EnumerateDf: PROC [cmd: Commander.Handle, listImpl: BOOL, dfName: ROPE, mode: CmdMode]
RETURNS [once: BOOL ¬ TRUE]
~ {
EachDf: PFS.NameProc ~ {
ProcessItem: DFUtilities.ProcessItemProc ~ {
Bad: PROC [name, ext: ROPE, bool: BOOL] RETURNS [b: BOOL] ~ {
b ¬ NOT Equal[Extension[name], ext] OR (Rope.Find[name,"impl",0,FALSE]#-1) = bool;
};
WITH item SELECT FROM
f: REF DFUtilities.FileItem => {
name: ROPE ¬ FileNames.StripVersionNumber[f.name];
IF NOT Equal[Extension[name], "mesa"] THEN RETURN;
IF (Rope.Find[name, "impl", 0, FALSE] # -1) # listImpl THEN RETURN;
IF mode.basesOnly THEN name ¬ Base[name];
names ¬ CONS[name, names];
};
ENDCASE;
};
stream: IO.STREAM;
names: LIST OF ROPE ¬ NIL;
stream ¬ PFS.StreamOpen[name ! PFS.Error => {
IF error.group # bug THEN IO.PutF1[cmd.out, "** %g\n", IO.rope[error.explanation]];
GOTO Bad;
}];
DFUtilities.ParseFromStream[stream, ProcessItem, [FALSE, source, all, defining]];
IO.Close[stream];
Output[cmd, names, TRUE, mode];
EXITS Bad => NULL;
};
PFS.EnumerateForNames[PFS.PathFromRope[FileNames.ResolveRelativePath[dfName]], EachDf
! PFS.Error => GOTO Bad];
EXITS Bad => NULL;
};
Output: PROC [
cmd: Commander.Handle,
names: LIST OF ROPE,
rev: BOOL ¬ FALSE,
mode: CmdMode,
indent: ROPE ¬ NIL]
~ {
length: NAT ¬ 0;
FOR l: LIST OF ROPE ¬ names, l.rest WHILE l # NIL DO length ¬ length+1; ENDLOOP;
IF NOT mode.totalOnly THEN
ColumnateNames[cmd, names, rev, GetColumnateMode[mode], mode.columnOrder,, indent];
IF mode.totalOnly OR length > 19 THEN IO.PutF1[cmd.out, "%g files\n", IO.int[length]]
};
EnumerateSubdirs: PROC [cmd: Commander.Handle, mode: CmdMode]
RETURNS [success: BOOL ¬ TRUE]
~ {
EachInfo: PFS.InfoProc ~ {
Process.CheckForAbort[];
IF fileType = 1 THEN {
name: ROPE ¬ PFSNames.ShortName[fullFName].name.base;
IF NOT FirstChar[name, '.] THEN names ¬ CONS[name, names];
};
};
names: LIST OF ROPE ¬ NIL;
PFS.EnumerateForInfo[PFS.PathFromRope["*!H"], EachInfo ! PFS.Error => GOTO Bad];
Output[cmd, names, TRUE, mode];
EXITS Bad => RETURN[FALSE];
};
EnumerateArgument: PROC [
cmd: Commander.Handle,
cmdArg: ROPE,
mode: CmdMode]
RETURNS [once: BOOL ¬ FALSE]
~ {
GetNames: PROC [arg: ROPE] RETURNS [names: LIST OF ROPE ¬ NIL] ~ {
EachName: PFS.NameProc ~ {
item: ROPE;
long: BOOL ¬ PFSNames.ComponentCount[name] > nComponents;
Process.CheckForAbort[];
IF mode.minus1 AND FS.FileInfo[fullFName].bytes # -1 THEN RETURN;
IF mode.noExtensions AND Middle[PFSNames.ShortName[name].name.base, "."]
THEN RETURN;
SELECT TRUE FROM
long AND directory AND NOT mode.extensionsOnly => { -- get subdirectory
item ¬ PFS.RopeFromPath[PFSNames.Directory[name]];
IF Equal[item, subDir] THEN RETURN;
subDir ¬ item;
};
long AND mode.exactLevel => RETURN;
ENDCASE => {             -- item is a file
item ¬ PFSNames.ShortName[name].name.base;
IF NOT mode.period AND Rope.Fetch[item, 0] = '. THEN RETURN;
IF mode.noBackup AND LastChar[item, '~] THEN RETURN;
IF mode.markDirectory AND FS.FileInfo[PFS.RopeFromPath[name]].fileType = 1
THEN item ¬ Rope.Concat[item, "/"];
SELECT TRUE FROM
mode.extensionsOnly => {
IF (item ¬ Extension[item]) = NIL THEN
{IF PFS.FileInfo[name].fileType # 1 THEN item ¬ "<none>" ELSE RETURN};
IF names # NIL THEN FOR l: LIST OF ROPE ¬ names, l.rest WHILE l # NIL DO
IF Equal[item, l.first] THEN RETURN;
ENDLOOP;
};
mode.interfacesOnly => {
IF NOT Equal[Extension[item], "mesa"] THEN RETURN;
IF Rope.Find[item, "impl.mesa", 0, FALSE] # -1 THEN RETURN;
};
mode.implsOnly => {
n: INTEGER ¬ Rope.Find[item, "impl", 0, FALSE];
IF (n = -1 OR NOT Equal[Extension[item], "mesa"]) OR
FileExists[Rope.Concat[Rope.Substr[item,, n], Rope.Substr[item, n+4]]]
THEN RETURN;
};
mode.aisAbbrev => IF Equal[Extension[item], "ais"] THEN {
Suffix: PROC [color: ROPE] RETURNS [ROPE] ~ {
RETURN[Rope.Cat[base, "-", color, ".ais"]];
};
Ok: PROC [a, b: ROPE ¬ NIL] RETURNS [ok: BOOL] ~ {
ok ¬ FileExists[Suffix[a]];
IF NOT ok AND b # NIL THEN ok ¬ FileExists[Suffix[b]];
};
Find: PROC [f: ROPE] RETURNS [b: BOOL] ~ {b ¬ Rope.Find[item, f, n] # -1};
n: INT ¬ RevCharFind[item, '-]+1;
base: ROPE ¬ IF n > 1 THEN RopePiece[item, 0, n-2] ELSE NIL;
IF base # NIL THEN SELECT TRUE FROM
Find["red"] => IF Ok["grn", "green"] AND Ok["blu", "blue"]
THEN item ¬ Rope.Concat[base, " (color)"];
Find["grn"] OR Find["green"] =>
IF Ok["red"] AND Ok["blu", "blue"] THEN RETURN;
Find["blu"] OR Find["blue"] =>
IF Ok["red"] AND Ok["grn", "green"] THEN RETURN;
ENDCASE;
};
ENDCASE;
IF mode.basesOnly THEN item ¬ Base[item];
};
names ¬ CONS[item, names];
};
PFS.EnumerateForNames[PFS.PathFromRope[arg], EachName ! PFS.Error => GOTO Bad];
EXITS Bad => NULL;
};
columnateMode: ColumnateMode ¬ GetColumnateMode[mode];
star, directory, allName: BOOL ¬ TRUE;
nComponents: NAT ¬ 0;
arg, subDir: ROPE ¬ NIL;
arg ¬ cmdArg;
star ¬ LastChar[arg, '*];
WHILE LastChar[arg, '*] DO arg ¬ RopeShorten[arg, 1]; ENDLOOP;
directory ¬ Rope.IsEmpty[arg] OR LastChar[arg, '/] OR LastChar[arg, '>];
arg ¬ FileNames.ResolveRelativePath[arg];         -- handle . or ..
arg ¬ Rope.Concat[arg, "*"];             -- ensure FName
arg ¬ FS.ExpandName[arg ! FS.Error => GOTO Bad].fullFName;
allName ¬ Middle[cmdArg, "*"] AND (Middle[cmdArg, ">"] OR Middle[cmdArg, "/"]);
IF NOT directory AND NOT star THEN arg ¬ RopeShorten[arg, 1];   -- remove '*
nComponents ¬ PFSNames.ComponentCount[PFS.PathFromRope[arg]];
IF mode.separateDirs AND directory THEN {
IF once THEN IO.PutRope[cmd.out, "\n"];
IO.PutF[cmd.out, "%l%g%l\n", IO.rope["b"], IO.rope[cmdArg], IO.rope["B"]];
};
once ¬ TRUE;
IF mode.separateByExtension
THEN {
oldMode: CmdMode ¬ mode;
extensions, names: LIST OF ROPE;
mode.extensionsOnly ¬ TRUE;
extensions ¬ Alphabetize[GetNames[arg]];
mode.extensionsOnly ¬ oldMode.extensionsOnly;
mode.basesOnly ¬ TRUE;
FOR l: LIST OF ROPE ¬ extensions, l.rest WHILE l # NIL DO
none: BOOL ¬ Rope.Equal[l.first, "<none>"];
IO.PutF[cmd.out, "%l.%g:%l\n", IO.rope["b"], IO.rope[l.first], IO.rope["B"]];
IF none
THEN {mode.noExtensions ¬ TRUE; names ¬ GetNames[Rope.Concat[arg, "!H"]]}
ELSE names ¬ GetNames[Rope.Cat[arg, ".", l.first, "!H"]];
Output[cmd, names, TRUE, mode, " "];
mode.noExtensions ¬ oldMode.noExtensions;
ENDLOOP;
mode.basesOnly ¬ oldMode.basesOnly;
}
ELSE {
l: LIST OF ROPE ¬ GetNames[Rope.Concat[arg, "!H"]];
IF mode.extensionsOnly
THEN Output[cmd, Alphabetize[l],, mode]
ELSE Output[cmd, l, TRUE, mode];
};
EXITS
Bad => NULL;
};
File Listing Procedures
fixedCharWidth: NAT ~ 7;
nSpacesInTab: NAT ~ 4;
nPixelsInCR:  NAT ~ 4;
Name:    TYPE ~ RECORD [rope: ROPE, width: INTEGER];
Names:   TYPE ~ RECORD [length: NAT ¬ 0, seq: SEQUENCE maxLength: NAT OF Name];
ColumnateMode: TYPE ~ ColumnLs.ColumnateMode;
RopeWidth: PROC [name: ROPE, mode: ColumnateMode, font: Font] RETURNS [w: INTEGER] ~ {
w ¬ SELECT mode FROM
fixed => fixedCharWidth*Rope.Length[name],
tabbed => nPixelsInCR+VFonts.StringWidth[name, font], -- screwy, but needed for tabs
ENDCASE => VFonts.StringWidth[name, font];
};
PadColumn: PROC [width: NAT, font: Font, mode: ColumnateMode] RETURNS [w: NAT] ~ {
w ¬ width+2*(IF mode = fixed THEN fixedCharWidth ELSE VFonts.StringWidth[" ", font]);
IF mode = tabbed THEN {
tabWidth: NAT ~ nSpacesInTab*VFonts.StringWidth[" ", font];
nTabs: NAT ¬ width/tabWidth;
IF width MOD tabWidth # 0 THEN nTabs ¬ nTabs+1;
w ¬ nTabs*tabWidth;
};
};
ColumnateNames: PUBLIC PROC [
cmd: Commander.Handle,
names: LIST OF ROPE,
reverseOrder: BOOL ¬ FALSE,
mode: ColumnateMode ¬ tabbed,
columnOrder: BOOL ¬ FALSE,
font: Font ¬ NIL,
rowIndentation: ROPE ¬ NIL]
~ {
IF names # NIL THEN {
seq: REF Names;
columnWidth, nNames: NAT ¬ 0;
IF font = NIL THEN font ¬ defaultFont;
FOR l: LIST OF ROPE ¬ names, l.rest WHILE l # NIL DO nNames ¬ nNames+1; ENDLOOP;
seq ¬ NEW[Names[nNames]];
FOR l: LIST OF ROPE ¬ names, l.rest WHILE l # NIL DO
width: INTEGER ¬ RopeWidth[l.first, mode, font];
IF width > columnWidth THEN columnWidth ¬ width;
seq[IF reverseOrder THEN nNames-seq.length-1 ELSE seq.length] ¬ [l.first, width];
seq.length ¬ seq.length+1;
ENDLOOP;
columnWidth ¬ PadColumn[columnWidth, font, mode];
DoColumnate[cmd, seq, mode, font, columnWidth, columnOrder, rowIndentation];
};
};
GetViewer: PROC [cmd: Commander.Handle] RETURNS [Viewer] ~ {
RETURN[ViewerIO.GetViewerFromStream[cmd.out]];
};
ColumnWidth: PUBLIC PROC [
cmd: Commander.Handle,
names: LIST OF ROPE,
mode: ColumnateMode ¬ tabbed,
font: Font ¬ NIL]
RETURNS [columnWidth: NAT ¬ 0]
~ {
IF names # NIL THEN {
viewer: Viewer ¬ GetViewer[cmd];
viewerWidth: NAT ¬ viewer.cw;
f: Font ¬ IF font # NIL THEN font ELSE defaultFont;
FOR l: LIST OF ROPE ¬ names, l.rest WHILE l # NIL DO
width: INTEGER ¬ RopeWidth[l.first, mode, f];
IF width > columnWidth THEN columnWidth ¬ width;
ENDLOOP;
columnWidth ¬ PadColumn[columnWidth, font, mode];
};
};
ColumnateGivenColumnWidth: PUBLIC PROC [
cmd: Commander.Handle,
names: LIST OF ROPE,
columnWidth: NAT,
reverseOrder: BOOL ¬ FALSE,
mode: ColumnateMode ¬ tabbed,
columnOrder: BOOL ¬ FALSE,
font: Font ¬ NIL,
rowIndentation: ROPE ¬ NIL]
~ {
IF names # NIL THEN {
seq: REF Names;
nNames: NAT ¬ 0;
IF font = NIL THEN font ¬ defaultFont;
FOR l: LIST OF ROPE ¬ names, l.rest WHILE l # NIL DO nNames ¬ nNames+1; ENDLOOP;
seq ¬ NEW[Names[nNames]];
FOR l: LIST OF ROPE ¬ names, l.rest WHILE l # NIL DO
width: INTEGER ¬ RopeWidth[l.first, mode, font];
seq[IF reverseOrder THEN nNames-seq.length-1 ELSE seq.length] ¬ [l.first, width];
seq.length ¬ seq.length+1;
ENDLOOP;
DoColumnate[cmd, seq, mode, font, columnWidth, columnOrder, rowIndentation];
};
};
DoColumnate: PROC [
cmd: Commander.Handle,
names: REF Names,
mode: ColumnateMode ¬ tabbed,
font: Font,
columnWidth: NAT,
columnOrder: BOOL,
rowIndentation: ROPE]
~ {
text1: REF TEXT ¬ RefText.ObtainScratch[150];
SELECT mode FROM
tabbed => {
tabWidth: NAT ~ nSpacesInTab*VFonts.StringWidth[" ", font];
rowIndentationWidth: NAT ~ VFonts.StringWidth[rowIndentation, font];
row, index, nDone: NAT ← 0;
viewerWidth: NAT ← GetViewer[cmd].cw;
nColumns: NATMAX[1, (viewerWidth-20-rowIndentationWidth)/columnWidth];
nRows: NAT ← names.length/nColumns;
IF names.length MOD nColumns # 0 THEN nRows ← nRows+1;
WHILE nDone < names.length DO
IO.PutRope[cmd.out, rowIndentation];
FOR i: NAT IN [1..nColumns] DO
name: Name ← names[index];
nPixels: NAT ← columnWidth-name.width;
nTabs: NAT ← nPixels/tabWidth;
IF (nPixels MOD tabWidth) # 0 THEN nTabs ← nTabs+1;
CharText[text1, nTabs, Ascii.TAB];
IF i = nColumns
THEN IO.PutRope[cmd.out, name.rope]
ELSE IO.PutF[cmd.out, "%g%g", IO.rope[name.rope], IO.text[text1]];
IF (nDone ← nDone+1) >= names.length THEN EXIT;
index ← index+(IF columnOrder THEN nRows ELSE 1);
IF index >= names.length THEN EXIT;
ENDLOOP;
IO.PutF[cmd.out, "\n"];
row ← row+1;
IF columnOrder THEN index ← row;
ENDLOOP;
};
ragged, spaced => {
text2: REF TEXT ¬ RefText.ObtainScratch[150];
normSpaceWidth: NAT ~ VFonts.StringWidth[" ", font];
smallSpaceWidth: NAT ~ VFonts.StringWidth[" ", VFonts.EstablishFont["Timesroman", 8]];
rowIndentationWidth: NAT ~ VFonts.StringWidth[rowIndentation, font];
row, index, nDone: NAT ¬ 0;
viewerWidth: NAT ¬ GetViewer[cmd].cw;
nPixels, totWidth, nextTotal, nText1, nText2: INTEGER;
nColumns: NAT ¬ MAX[1, (viewerWidth-20-rowIndentationWidth)/columnWidth];
nRows: NAT ¬ names.length/nColumns;
IF names.length MOD nColumns # 0 THEN nRows ¬ nRows+1;
WHILE nDone < names.length DO
IO.PutRope[cmd.out, rowIndentation];
nextTotal ¬ totWidth ¬ 0;
FOR i: NAT IN [1..nColumns] DO
name: Name ¬ names[index];
nextTotal ¬ nextTotal+columnWidth;
totWidth ¬ totWidth+name.width;
nPixels ¬ nextTotal-totWidth;
IF i = nColumns
THEN IO.PutRope[cmd.out, name.rope]
ELSE {
IF mode = ragged
THEN {
nText1 ¬ Real.Round[REAL[nextTotal-totWidth]/REAL[normSpaceWidth]];
nText2 ¬ 0;
}
ELSE {
nText1 ¬ nPixels MOD smallSpaceWidth;
nText2 ¬ nPixels/smallSpaceWidth-nText1;
IF nText2 < 0 THEN {nText2 ¬ 0; nText1 ¬ nText1-1}
};
totWidth ¬ totWidth+nText1*normSpaceWidth+nText2*smallSpaceWidth;
CharText[text1, nText1, ' ];
CharText[text2, nText2, ' ];
IF mode = ragged OR nText2 = 0
THEN IO.PutF[cmd.out, "%g%g", IO.rope[name.rope], IO.text[text1]]
ELSE IO.PutFL[cmd.out, "%g%g%l%g%l", LIST[IO.rope[name.rope], IO.text[text1], IO.rope["s"], IO.text[text2], IO.rope["S"]]];
};
IF (nDone ¬ nDone+1) >= names.length THEN EXIT;
index ¬ index+(IF columnOrder THEN nRows ELSE 1);
IF index >= names.length THEN EXIT;
ENDLOOP;
IO.PutRope[cmd.out, "\n"];
row ¬ row+1;
IF columnOrder THEN index ¬ row;
ENDLOOP;
RefText.ReleaseScratch[text2];
};
tabbed, fixed => { -- really just for fixed, but tabbed seems busted
rowIndentationWidth: NAT ~ fixedCharWidth*Rope.Length[rowIndentation];
row, index, nDone: NAT ¬ 0;
viewerWidth: NAT ¬ GetViewer[cmd].cw;
nColumns: NAT ¬ MAX[1, (viewerWidth-20-rowIndentationWidth)/columnWidth];
nRows: NAT ¬ names.length/nColumns;
IF names.length MOD nColumns # 0 THEN nRows ¬ nRows+1;
IO.PutF1[cmd.out, "%l", IO.rope["f"]];
WHILE nDone < names.length DO
IO.PutRope[cmd.out, rowIndentation];
FOR i: NAT IN [1..nColumns] DO
name: Name ¬ names[index];
CharText[text1, (columnWidth-name.width)/fixedCharWidth, ' ];
IF i = nColumns
THEN IO.PutRope[cmd.out, name.rope]
ELSE IO.PutF[cmd.out, "%g%g", IO.rope[name.rope], IO.text[text1]];
IF (nDone ¬ nDone+1) >= names.length THEN EXIT;
index ¬ index+(IF columnOrder THEN nRows ELSE 1);
IF index >= names.length THEN EXIT;
ENDLOOP;
IO.PutRope[cmd.out, "\n"];
row ¬ row+1;
IF columnOrder THEN index ¬ row;
ENDLOOP;
IO.PutF1[cmd.out, "%l", IO.rope["F"]];
};
ENDCASE;
RefText.ReleaseScratch[text1];
};
Support Code
Alphabetize: PROC [ropes: LIST OF ROPE] RETURNS [ret: LIST OF ROPE] ~ {
IF ropes # NIL THEN {
l: LIST OF ROPE ¬ ropes.rest;
ret ¬ LIST[ropes.first];
WHILE l # NIL DO
name: ROPE ¬ l.first;
rest: LIST OF ROPE ¬ l.rest;
IF Rope.Compare[ret.first, name, FALSE] = greater
THEN ret ¬ CONS[name, ret]
ELSE FOR r: LIST OF ROPE ¬ ret, r.rest DO
IF r.rest = NIL OR Rope.Compare[r.rest.first, name, FALSE] = greater THEN {
l.rest ¬ r.rest;
r.rest ¬ l;
EXIT;
};
ENDLOOP;
l ¬ rest;
ENDLOOP;
};
};
Equal: PROC [r1, r2: ROPE] RETURNS [BOOL] ~ {
RETURN[Rope.Equal[r1, r2, FALSE]];
};
Capitalize: PROC [r: ROPE] RETURNS [ROPE] ~ {
c: CHAR ¬ Rope.Fetch[r];
RETURN[IF c IN ['a..'z] THEN Rope.Replace[r, 0, 1, Rope.FromChar[c-'a+'A]] ELSE r];
};
Middle: PROC [searchee, target: ROPE] RETURNS [BOOL] ~ {
location: INT ¬ Rope.Find[searchee, target];
RETURN[location # -1 AND location # Rope.Length[searchee]-1];
};
Base: PROC [name: ROPE] RETURNS [ROPE] ~ {
n: INT ¬ RevCharFind[name, '.];
RETURN[IF n > 0 THEN RopePiece[name, 0, n-1] ELSE name];
};
Extension: PROC [name: ROPE] RETURNS [r: ROPE ¬ NIL] ~ {
n: INT ¬ RevCharFind[name, '.];
IF n >= 0 THEN r ¬ RopePiece[name, n+1, Rope.Length[name]-1];
};
CharText: PROC [text: REF TEXT, nSpaces: INT, char: CHAR] ~ {
text.length ¬ 0;
THROUGH [1..nSpaces] DO
text ¬ RefText.AppendChar[text, char];
ENDLOOP;
};
NChars: PROC [rope: ROPE, char: CHAR] RETURNS [retval: INT ¬ 0] ~ {
length: INT ¬ Rope.Length[rope];
FOR n: INT ¬ 0, n+1 WHILE n < length DO
IF rope.Fetch[n] = char THEN retval ¬ retval+1;
ENDLOOP;
RETURN[retval];
};
RopePiece: PROC [rope: ROPE, n1: INT, n2: INT] RETURNS [ROPE] ~ {
IF n1 < 0 OR n2 < 0 THEN RETURN[NIL];
IF n2 > n1 THEN RETURN[Rope.Substr[rope, n1, n2-n1+1]];
RETURN[Rope.Substr[rope, n2, n1-n2+1]];
};
RevCharFind: PROC [rope: ROPE, char: CHAR] RETURNS [INT] ~ {
n: INT ¬ Rope.Length[rope]-1;
WHILE n >= 0 AND Rope.Fetch[rope, n] # char DO n ¬ n-1; ENDLOOP;
RETURN[n];
};
FirstChar: PROC [rope: ROPE, char: CHAR] RETURNS [BOOL] ~ {
RETURN[Rope.Length[rope] > 0 AND Rope.Fetch[rope, 0] = char];
};
LastChar: PROC [rope: ROPE, char: CHAR] RETURNS [BOOL] ~ {
ropeLength: INT ¬ Rope.Length[rope];
RETURN[ropeLength > 0 AND Rope.Fetch[rope, ropeLength-1] = char];
};
RopeShorten: PROC [rope: ROPE, n: INT] RETURNS [ROPE] ~ {
ropeLength: INT ¬ Rope.Length[rope];
IF ropeLength >= n THEN rope ¬ Rope.Substr[rope, 0, ropeLength-n];
RETURN [rope];
};
MultiSkip: PROC [rope: ROPE, char: CHAR, nSkip: INT] RETURNS [ROPE] ~ {
n: INT;
limit: INT ¬ Rope.Length[rope]-1;
FOR n ¬ 0, n+1 WHILE n < limit DO
IF Rope.Fetch[rope, n] = char THEN nSkip ¬ nSkip-1;
IF nSkip = 0 THEN EXIT;
ENDLOOP;
RETURN[Rope.Substr[rope, n+1]];
};
FileExists: PROC [fileName: ROPE] RETURNS [exists: BOOL ¬ TRUE] ~ {
[] ¬ FS.FileInfo[fileName ! FS.Error => {exists ¬ FALSE; CONTINUE}];
};
Start Code
usage: ROPE ~ "
columnLs <pattern> [option]
a concise listing program, options are:
-c\t\t\t\tcolumn (rather than row) ordered listing
-f\t\t\t\tfixed width font
-F\t\t\t\taffix trailing slash to a subdirectory
-d\t\t\t\tdirectories only
-x\t\t\t\texact level: list only files in current directory
-se\t\t\t\tlist according to extensions
-e\t\t\t\tfile extensions only
-ne\t\t\t\tlist only files with no extension
-a\t\t\t\tlist files with names beginning with a period
-no~\t\t\tdon't list backup files (~)
-b\t\t\t\tfile base name only
-i\t\t\t\tlist interfaces only (.mesa)
-ni\t\t\t\tlist impls without corresponding interfaces
-ais\t\t\tabbreviate listing color ais files
-tot\t\t\t\tlist total number of files only
-dfi <dfFile>\tlist source interfaces from df file
-dfm <dfFile>\tlist source implementations from df file.";
defaultFont: Font ~ VFonts.EstablishFont["Tioga", 10];
Commander.Register["ColumnLs", CommandProc, usage];
END.
..
EnumerateArgument: PROC [
cmd: Commander.Handle,
cmdArg: ROPE,
mode: CmdMode]
RETURNS [once: BOOLFALSE]
~ {
GetNames: PROC [arg: ROPE] RETURNS [names: LIST OF ROPENIL] ~ {
EachName: PFS.NameProc ~ {
GetName: PROC [fullFName: ROPE, directory, long, allName: BOOLFALSE]
RETURNS [name: ROPE]
~ INLINE {
r: ROPE ← name ← FileNames.ConvertToSlashFormat[fullFName];
pos: INTIF NOT directory AND long THEN 0 ELSE RevCharFind[name, '/]+1;
IF NOT allName THEN name ← RopePiece[name, pos, Rope.SkipTo[name, pos, "!"]-1];
};
name: ROPE;
long: BOOL ← NChars[fullFName, '>] > nArgArrows;
Process.CheckForAbort[];
IF mode.minus1 AND FS.FileInfo[fullFName].bytes # -1 THEN RETURN;
IF mode.noExtensions AND Middle[FileNames.GetShortName[fullFName],"."] THEN RETURN;
SELECT TRUE FROM
long AND directory AND NOT mode.extensionsOnly => { -- get subdirectory
name ← MultiSkip[fullFName, '>, nArgArrows];
name ← Rope.Concat[RopePiece[name, 0, Rope.SkipTo[name, 0, ">"]-1], "/"];
IF Equal[name, subDir] THEN RETURN;
subDir ← name;
};
long AND mode.exactLevel => RETURN;
ENDCASE => {           -- name is a file
name ← GetName[fullFName, directory, long, allName];
IF NOT mode.period AND Rope.Fetch[name, 0] = '. THEN RETURN;
IF mode.noBackup AND LastChar[name, '~] THEN RETURN;
IF mode.markDirectory AND FS.FileInfo[fullFName].fileType = 1
THEN name ← Rope.Concat[name, "/"];
SELECT TRUE FROM
mode.extensionsOnly => {
name ← Extension[name];
IF names # NIL THEN FOR l: LIST OF ROPE ← names, l.rest WHILE l # NIL DO
IF Equal[name, l.first] THEN RETURN;
ENDLOOP;
};
mode.interfacesOnly => {
IF NOT Equal[Extension[name], "mesa"] THEN RETURN;
IF Rope.Find[name, "impl.mesa", 0, FALSE] # -1 THEN RETURN;
};
mode.implsOnly => {
n: INTEGER ← Rope.Find[name, "impl", 0, FALSE];
IF (n = -1 OR NOT Equal[Extension[name], "mesa"]) OR
FileExists[Rope.Cat[Rope.Substr[name,, n], Rope.Substr[name, n+4]]]
THEN RETURN;
};
mode.aisAbbrev => IF Equal[Extension[name], "ais"] THEN {
Suffix: PROC [color: ROPE] RETURNS [ROPE] ~ {
RETURN[Rope.Cat[base, "-", color, ".ais"]];
};
Ok: PROC [a, b: ROPENIL] RETURNS [ok: BOOL] ~ {
ok ← FileExists[Suffix[a]];
IF NOT ok AND b # NIL THEN ok ← FileExists[Suffix[b]];
};
Find: PROC [f: ROPE] RETURNS [b: BOOL] ~ {bRope.Find[name, f, n] # -1};
n: INT ← RevCharFind[name, '-]+1;
base: ROPEIF n > 1 THEN RopePiece[name, 0, n-2] ELSE NIL;
IF base # NIL THEN SELECT TRUE FROM
Find["red"] => IF Ok["grn", "green"] AND Ok["blu", "blue"]
THEN name ← Rope.Cat[base, " (color)"];
Find["grn"] OR Find["green"] =>
IF Ok["red"] AND Ok["blu", "blue"] THEN RETURN;
Find["blu"] OR Find["blue"] =>
IF Ok["red"] AND Ok["grn", "green"] THEN RETURN;
ENDCASE;
};
ENDCASE;
IF mode.basesOnly THEN name ← Base[name];
};
names ← CONS[name, names];
};
PFS.EnumerateForNames[PFS.PathFromRope[arg], EachName ! PFS.Error => GOTO Bad];
EXITS Bad => NULL;
};
columnateMode: ColumnateMode ← GetColumnateMode[mode];
star, directory, allName: BOOLTRUE;
nArgArrows: NAT ← 0;
arg, subDir: ROPENIL;
arg ← cmdArg;
star ← LastChar[arg, '*];
WHILE LastChar[arg, '*] DO arg ← RopeShorten[arg, 1]; ENDLOOP;
directory ← Rope.IsEmpty[arg] OR LastChar[arg, '/] OR LastChar[arg, '>];
arg ← FileNames.ResolveRelativePath[arg];         -- handle . or ..
arg ← Rope.Concat[arg, "*"];             -- ensure FName
arg ← FS.ExpandName[arg ! FS.Error => GOTO Bad].fullFName;
allName ← Middle[cmdArg, "*"] AND (Middle[cmdArg, ">"] OR Middle[cmdArg, "/"]);
IF NOT directory AND NOT star THEN arg ← RopeShorten[arg, 1];   -- remove '*
nArgArrows ← NChars[arg, '>];
IF mode.separateDirs AND directory THEN {
IF once THEN IO.PutRope[cmd.out, "\n"];
IO.PutF[cmd.out, "%l%g%l\n", IO.rope["b"], IO.rope[cmdArg], IO.rope["B"]];
};
once ← TRUE;
IF mode.separateByExtension
THEN {
oldMode: CmdMode ← mode;
extensions, names: LIST OF ROPE;
mode.extensionsOnly ← TRUE;
extensions ← Alphabetize[GetNames[arg]];
mode.extensionsOnly ← oldMode.extensionsOnly;
mode.basesOnly ← TRUE;
FOR l: LIST OF ROPE ← extensions, l.rest WHILE l # NIL DO
IO.PutF[cmd.out, "%l.%g:%l\n", IO.rope["b"], IO.rope[l.first], IO.rope["B"]];
names ← GetNames[Rope.Cat[arg, ".", l.first, "!H"]];
ColumnateNames[cmd, names, TRUE, columnateMode, mode.columnOrder,, " "];
ENDLOOP;
mode.basesOnly ← oldMode.basesOnly;
}
ELSE {
l: LIST OF ROPE ← GetNames[Rope.Concat[arg, "!H"]];
IF mode.extensionsOnly
THEN ColumnateNames[cmd, Alphabetize[l],, columnateMode, mode.columnOrder]
ELSE ColumnateNames[cmd, l, TRUE, columnateMode, mode.columnOrder];
};
EXITS
Bad => NULL;
};
Stolen from DirectoryListImpl.mesa:
names: LIST OF ROPENIL;
longDir, shortDir: ROPENIL;
workingDir: ROPEFS.ExpandName["*"].fullFName;
nLevels: INTEGER ← NChars[workingDir, '>];
length: INTEGER ← Rope.Length[workingDir]-1;
longDir ← workingDir ← Rope.Substr[workingDir, 0, length];
DO
longDir ← DirectoryList.GetNext[longDir, nLevels
! FS.Error => {
IO.PutF[cmd.out, Rope.Cat[error.explanation, " -- this will take longer\n"]];
GOTO NotLocal}];
IF Rope.Find[longDir, workingDir, 0, FALSE] = -1 THEN EXIT;
shortDir ← Capitalize[Rope.Substr[longDir, length, Rope.Length[longDir]-length-1]];
names ← CONS[shortDir, names];
ENDLOOP;
ColumnateNames[cmd, names, TRUE, GetColumnateMode[mode], mode.columnOrder];
EXITS NotLocal => RETURN[FALSE];