_STRUCTURED PROGRAMMING COLUMN_ by Jeff Duntemann [LISTING ONE] UNIT DirList; { By Jeff Duntemann; for DDJ 4/92 } INTERFACE USES Crt,DOS,Objects, { Standard Borland units } When2; { From DDJ 1/91 } TYPE String40 = STRING[40]; PDirEntry = ^TDirEntry; TDirEntry = OBJECT(TObject) Path : PathStr; { Predefined in Dos unit as STRING[79] } Entry : SearchRec; { Also predefined in the Dos unit } DirLine : String40; { Preformatted directory info string } CONSTRUCTOR Init(APath : PathStr; ASearchRec : SearchRec); PROCEDURE FormatDirLine; END; PDirEntryCollection = ^TDirEntryCollection; TDirEntryCollection = OBJECT(TSortedCollection) CONSTRUCTOR InitDir(ALimit,ADelta : Integer; Path, Mask : STRING; Attr : Word); CONSTRUCTOR InitCommandLine(ALimit,ADelta : Integer; StartParam : Integer); CONSTRUCTOR InitTree(Alimit,ADelta : Integer; Path,Mask : STRING; Attr : Word); FUNCTION Compare(Key1,Key2 : Pointer) : Integer; VIRTUAL; PROCEDURE AddDir(Path,Mask : STRING; Attr : Word); PROCEDURE TreeScan(Path,Mask : STRING; Attr : Word); END; IMPLEMENTATION VAR Stamp : When; { Global When stamp for time/date string processing } {----------------------------------------} { Methods: TDirEntry } {----------------------------------------} CONSTRUCTOR TDirEntry.Init(APath : PathStr; ASearchRec : SearchRec); BEGIN TObject.Init; Path := APath; Entry := ASearchRec; FormatDirLine; END; PROCEDURE TDirEntry.FormatDirLine; VAR DotPos : Integer; WorkString,Blanker : String40; BEGIN Stamp.PutWhenStamp(Entry.Time); DirLine := ' '; Blanker := DirLine; {If the entry has the directory attribute, format differently: } IF (Entry.Attr AND $10) <> 0 THEN { Bit 4 is the directory attribute } BEGIN Insert(Entry.Name,DirLine,1); { No extensions on subdirectory names } Insert('',DirLine,14) { Tell the world it's a subdirectory } END ELSE {This compound statement separates the file from its extension } { and converts the file size to a string. Note that we did not } { insert a file size figure into DirLine for subdirectory entries. } BEGIN DotPos := Pos('.',Entry.Name); IF DotPos > 0 THEN { File name has an extension } WorkString := Copy(Entry.Name,1,DotPos-1) + Copy(Blanker,1,9-DotPos) + '.' + Copy(Entry.Name,DotPos+1,Length(Entry.Name)-DotPos) ELSE { File name has no extension } WorkString := Entry.Name + Copy(Blanker,1,8-Length(Entry.Name)) + '.'; Insert(WorkString,DirLine,1); Str(Entry.Size:7,WorkString); Insert(WorkString,DirLine,15); END; Insert(Stamp.GetDateString,DirLine,24); Insert(Stamp.GetTimeString,DirLine,34); { Finally, insert the time } Delete(DirLine,42,Length(DirLine)-42); END; {----------------------------------------} { Methods: TDirEntryCollection } {----------------------------------------} CONSTRUCTOR TDirEntryCollection.InitDir(ALimit,ADelta : Integer; Path,Mask : STRING; Attr : Word); BEGIN TSortedCollection.Init(Alimit,ADelta); AddDir(Path,Mask,Attr); END; CONSTRUCTOR TDirEntryCollection.InitCommandLine(ALimit,ADelta : Integer; StartParam : Integer); VAR I : Integer; SR : SearchRec; { Defined in the Dos unit } DEP : PDirEntry; Path : PathStr; { Defined in the Dos unit as STRING[79]; } Dir : DirStr; { Defined in the Dos unit as STRING[67]; } Name : NameStr; { Defined in the Dos unit as STRING[8]; } Ext : ExtStr; { Defined in the Dos unit as STRING[4]; } BEGIN TSortedCollection.Init(ALimit,ADelta); FOR I := StartParam TO ParamCount DO BEGIN FSplit(ParamStr(I),Dir,Name,Ext); AddDir(Dir,Name+Ext,AnyFile); END; END; CONSTRUCTOR TDirEntryCollection.InitTree(Alimit,ADelta : Integer; Path,Mask : STRING; Attr : Word); BEGIN TSortedCollection.Init(Alimit,ADelta); TreeScan(Path,Mask,Attr); END; FUNCTION TDirEntryCollection.Compare(Key1,Key2 : Pointer) : Integer; BEGIN IF (PDirEntry(Key1)^.Path + PDirEntry(Key1)^.Entry.Name) = (PDirEntry(Key2)^.Path + PDirEntry(Key2)^.Entry.Name) THEN Compare := 0 ELSE IF (PDirEntry(Key1)^.Path + PDirEntry(Key1)^.Entry.Name) < (PDirEntry(Key2)^.Path + PDirEntry(Key2)^.Entry.Name) THEN Compare := -1 ELSE Compare := 1; END; PROCEDURE TDirEntryCollection.AddDir(Path,Mask : STRING; Attr : Word); VAR I : Integer; SR : SearchRec; DEP : PDirEntry; BEGIN FindFirst(Path+Mask,Attr,SR); IF DosError = 0 THEN BEGIN DEP := New(PDirEntry,Init(Path,SR)); Insert(DEP); REPEAT FindNext(SR); IF DosError = 0 THEN BEGIN DEP := New(PDirEntry,Init(Path,SR)); Insert(DEP); END; UNTIL DosError <> 0; END; END; PROCEDURE TDirEntryCollection.TreeScan(Path,Mask : STRING; Attr : Word); VAR I : Integer; SR : SearchRec; DEP : PDirEntry; NextDirectory : STRING; BEGIN fillchar(SR,sizeof(SR),0); { We look for and search any subdirectories first: } IF Path <> '\' THEN Path := Path + '\'; FindFirst(Path+'*.*',Directory,SR); WHILE (DOSError <> 2) AND (DOSError <> 18) DO BEGIN IF ((SR.Attr AND Directory) = Directory ) AND (SR.Name[1] <> '.') THEN { We have a subdirectory } BEGIN NextDirectory := Path + SR.Name; TreeScan(NextDirectory,Mask,Attr); END; FindNext(SR); END; { At this point, we're in a directory that has no unsearched } { subdirectories, so we can search for files matching Mask: } AddDir(Path,Mask,Attr); END; { No initialization section } END. [LISTING TWO] PROGRAM JFind; { By Jeff Duntemann; from DDJ 4/92 } USES DOS,CRT,Printer, { Standard Borland units } DirList; { From DDJ for 4/92 } VAR Console : TEXT; FileSpecs : STRING; I : Integer; FilesFound : PDirEntryCollection; PROCEDURE DisplayFoundFiles(FilesFound : PDirEntryCollection); { This is the FAR local routine passed to the iterator method. } { It's called once for each item in the collection: } PROCEDURE DisplayOneFile(Target : PDirEntry); FAR; BEGIN Write(Console,Copy(Target^.DirLine,13,Length(Target^.DirLine)),' '); Writeln(Console,Target^.Path,Target^.Entry.Name); END; BEGIN { This is how you iterate a procedure over a collection: } FilesFound^.ForEach(@DisplayOneFile); END; BEGIN { JFIND Main } Assign(Console,''); { This allows us to use Standard Output } Rewrite(Console); { for Write/Writeln statements } IF ParamCount = 0 THEN BEGIN Writeln(Console,'>>>JFIND<<< by Jeff Duntemann'); Writeln(Console); Writeln(Console,'Invocation syntax:'); Writeln(Console); Writeln(Console,' JFIND ,[..] CR'); Writeln(Console); END ELSE BEGIN FilesFound := New(PDirEntryCollection,Init(256,16)); FOR I := 1 TO ParamCount DO FilesFound^.TreeScan('\',ParamStr(I),AnyFile); IF FilesFound^.Count > 0 THEN BEGIN DisplayFoundFiles(FilesFound); Writeln(Console); END ELSE Writeln(Console,'No files match that file spec.'); END; END.