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 ROPE: TYPE ~ Rope.ROPE; Font: TYPE ~ VFonts.Font; Viewer: TYPE ~ ViewerClasses.Viewer; CmdMode: TYPE ~ RECORD [ separateDirs: BOOL ¬ TRUE, -- there are multiple directories, so list separately 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 ]; 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; 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.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 ¬ "" 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, ""]; 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; }; 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 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]; }; 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}]; }; usage: ROPE ~ " columnLs [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 \tlist source interfaces from df file -dfm \tlist source implementations from df file."; defaultFont: Font ~ VFonts.EstablishFont["Tioga", 10]; Commander.Register["ColumnLs", CommandProc, usage]; END. ..  ColumnLsImpl.mesa Copyright Σ 1985, 1988, 1992 by Xerox Corporation. All rights reserved. Last Edited by: Bloomenthal, December 29, 1992 11:58 am PST Type Declarations derived: userSpecified: ragged: BOOL _ FALSE, -- -q: list with space, not pixel, resolution tabbed: BOOL _ FALSE, -- -t: list with tab, not pixel, resolution minus1: BOOL _ FALSE, -- -m: list those files with size of -1 File Listing Command Rope.Equal[a, "-q"] => mode.ragged _ TRUE; Rope.Equal[a, "-t"] => mode.tabbed _ TRUE; Rope.Equal[a, "-m"] => mode.minus1 _ TRUE; IF mode.minus1 AND FS.FileInfo[fullFName].bytes # -1 THEN RETURN; File Listing Procedures tabbed => { tabWidth: NAT ~ nSpacesInTab*VFonts.StringWidth[" ", font]; rowIndentationWidth: NAT ~ VFonts.StringWidth[rowIndentation, font]; 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; 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; }; Support Code Start Code 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 ~ { GetName: PROC [fullFName: ROPE, directory, long, allName: BOOL _ FALSE] RETURNS [name: ROPE] ~ INLINE { r: ROPE _ name _ FileNames.ConvertToSlashFormat[fullFName]; pos: INT _ IF 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: 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[name, f, n] # -1}; n: INT _ RevCharFind[name, '-]+1; base: ROPE _ IF 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: BOOL _ TRUE; nArgArrows: 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 '* 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 ROPE _ NIL; longDir, shortDir: ROPE _ NIL; workingDir: ROPE _ FS.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]; Κ$‹–"cedarcode" style•NewlineDelimiter ™™Jšœ Οeœ=™HJ™;J˜—JšΟk œCžœžœžœJ˜‘J˜šΠln œžœž˜Jšžœ2žœžœžœ:˜~Jšžœ ˜J˜—Jšœž˜headšΟl™Jšžœžœžœ˜Jšœ žœ˜šœ žœ˜'J˜—šœ žœžœ˜™JšœžœžœΟc5˜S—™Jšœžœžœ‘+˜HJšœ žœžœ‘˜6Jšœžœžœ‘&˜EJšœžœžœ‘&˜GJšœžœžœ‘-˜JJšœžœžœ‘)˜LJšœžœžœ‘0˜PJšœžœžœ‘/˜MJšœ žœžœ‘-˜GJšœ žœžœ‘!˜Jšœžœžœ˜HJšœ2‘˜CJšœ)‘˜8Jšœžœžœ žœ˜:Jšœžœžœ˜OJš žœžœ žœžœžœ‘€ ˜LJšœ&žœ˜=šžœžœ žœ˜)Jšžœžœžœ˜'Jšžœžœ žœžœ ˜JJ˜—Jšœžœ˜ šžœ˜šžœ˜J˜Jšœžœžœžœ˜ Jšœžœ˜J˜(J˜-Jšœžœ˜š žœžœžœžœžœžœž˜9Jšœžœ!˜+Jšžœžœ žœžœ ˜Mšžœ˜Jšžœžœ+˜IJšžœ5˜9—Jšœžœ˜%J˜)Jšžœ˜—J˜#J˜—šžœ˜Jšœžœžœžœ$˜3šžœ˜Jšžœ#˜'Jšžœžœ˜ —J˜——šž˜Jšœžœ˜ —Jšœ˜——š ™Jšœžœ˜Jšœžœ˜šœžœ˜J˜—Jš œ žœžœžœ žœ˜4š œ žœžœ žœ žœ žœžœ˜OJ˜—šœžœ˜-J˜—š ’ œžœžœ#žœžœ˜Všœžœž˜Jšœ*˜*Jšœ6‘˜TJšžœ#˜*—J˜J˜—š ’ œžœ žœ#žœžœ˜RJšœ žœžœžœ ˜Ušžœžœ˜Jšœ žœ.˜;Jšœžœ˜Jšžœžœžœ˜/J˜J˜—J˜J˜—š’œžœžœ˜J˜Jšœžœžœžœ˜Jšœžœžœ˜J˜Jšœ žœžœ˜Jšœ žœ˜Jšœžœžœ˜Jšœ˜šžœ žœžœ˜Jšœžœ˜Jšœžœ˜Jšžœžœžœ˜&Jšžœžœžœžœžœžœžœžœ˜PJšœžœ˜š žœžœžœžœžœžœž˜4Jšœžœ"˜0Jšžœžœ˜0Jšœžœžœžœ ˜QJ˜Jšžœ˜—J˜1JšœL˜LJ˜—J˜J˜—š’ œžœžœ ˜Jšœžœžœ™HJšœ2‘™CJšœ)‘™8Jšœžœžœ žœ™:Jšœžœžœ™OJš žœžœ žœžœžœ‘€ ™LJšœ™šžœžœ žœ™)Jšžœžœžœ™'Jšžœžœ žœžœ ™JJ™—Jšœžœ™ šžœ™šžœ™Jšœ™Jšœžœžœžœ™ Jšœžœ™Jšœ(™(Jšœ-™-Jšœžœ™š žœžœžœžœžœžœž™9Jšžœžœ žœžœ ™MJ™4Jšœžœ*™IJšžœ™—Jšœ#™#J™—šžœ™Jšœžœžœžœ$™3šžœ™JšžœF™JJšžœžœ#™C—J™——šž™Jšœžœ™ —Jšœ™J™—™#Jš œžœžœžœžœ™J–[stream: STREAM]šœžœžœ™Jšœ žœžœ™0Jšœ žœ™*Jšœžœ™,Jšœ:™:šž™šœ0™0šœžœ ™JšžœK™MJšžœ ™——Jšžœ#žœžœžœ™;JšœS™SJšœžœ™Jšžœ™—Jšœžœ,™KJšžœ žœžœ™ ———…—Kζ‹}