home *** CD-ROM | disk | FTP | other *** search
- {$P128}
- {$V-}
- {$C-}
-
- PROGRAM txtfind(Output);
-
- {*************************************************************************}
- {* Copyright (c) Kim Kokkonen, TurboPower Software, 1985 *}
- {* Released to the public domain for personal, non-commercial use only *}
- {*************************************************************************}
-
- {.F-}
- {
- searches a group of files for a text string.
- type TXTFIND alone to get help screens.
-
- requires Turbo Pascal version 3.0 to compile.
- heap/stack size sets limit on depth of subdirectory searching allowed.
- compile with max heap= A000.
- program does not check for heap-stack collision, but
- Turbo run-time library will complain if that happens.
- there is a tradeoff between number of files in a given directory
- vs. the maximum depth of subdirectory checking.
- currently each level of subdirectory eats about 16K of RAM, on the heap.
- written 8/29/85, Kim Kokkonen, 408-378-3672.
-
- FEATURES:
- fast Knuth-Morris-Pratt search algorithm.
- match pattern up to 255 characters may include any ASCII character
- except #13, #10 and #26 (which are filtered out during read-in).
- 1024 character input lines with segmentation (not truncation)
- for longer lines.
- automatic but selectable exclusion of binary files.
- quiet and verbose modes.
- built-in WordStar filter and end of line sensing.
- general purpose recursive file finder (up to 256 files per directory).
- optionally specify a list of files to search.
- redirectable output.
- }
- {.F+}
-
- CONST
- maxfiles = 256; {max number of files searched in a given directory}
- maxavoids = 32; {max number of file extensions to avoid}
- optionchar = '-'; {character which prefixes options on command line}
- endstr = #0;
- esc = '\';
- lspace = 's';
- ltab = 't';
- lbackspace = 'b';
- ascii = '#';
- BufLen = 2048; {buffer size for input reads - keep buflen>=2*linlen}
- linlen = 1024; {maximum unbroken line length}
- ncrlinlen = 1022; {linlen-2}
- maxlink = 255; {maximum number of failure links in an flink array}
-
- TYPE
- drivename = STRING[2];
- filename = STRING[12];
- pathname = STRING[64];
- longstring = STRING[255];
- farray = RECORD
- num : Integer;
- arr : ARRAY[1..maxfiles] OF pathname;
- END;
- register = RECORD
- CASE Integer OF
- 1: (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
- 2: (al, ah, bl, bh, cl, ch, dl, dh : Byte);
- END;
- dtarec = RECORD
- dosnext : ARRAY[1..21] OF Byte;
- attr : Byte;
- ftime, fdate, flsize, fhsize : Integer;
- fullname : ARRAY[1..13] OF Char;
- END;
- avoidset = ARRAY[1..maxavoids] OF STRING[3];
- buffer = ARRAY[1..buflen] OF Byte;
- line = ARRAY[-1..linlen] OF Char;
- pathstring = STRING[64];
- flinkarray = ARRAY[1..maxlink] OF Byte;
-
- VAR
- reg : register;
- listfilename, startpath : pathname;
- matchpattern : longstring;
- matchlen : Byte ABSOLUTE matchpattern;
- avoidextension : avoidset;
- avoidnum : Integer;
- wordmask : Byte;
- readexclude, recursive, verbose, lineoutput, uselistfile,
- wordstar, casesens, printheader : Boolean;
- dta : dtarec;
- tstart, tstop, grandbytes, grandtotal, grandmatch : Real;
- buf : buffer;
- flink : flinkarray;
-
- PROCEDURE dohalt(exitcode : Integer);
- {-halt}
- BEGIN
- Halt(exitcode);
- END; {dohalt}
-
- FUNCTION fileexists(s : pathname; attr : Integer) : Boolean;
- {-determine whether a file exists with the specified attribute}
- BEGIN
- reg.ah := $4E;
- s[Length(s)+1] := #0;
- reg.ds := Seg(s);
- reg.dx := Ofs(s[1]);
- reg.cx := attr;
- MsDos(reg);
- fileexists := ((reg.flags AND 1) = 0) AND ((dta.attr AND 31) = attr);
- END; {fileexists}
-
- PROCEDURE parsepath(VAR start : pathname;
- VAR dname : drivename;
- VAR pname : pathname;
- VAR fname : filename);
- {-parse a full (perhaps incomplete) pathname into component parts}
- VAR
- i : Integer;
- BEGIN
- {get drive name}
- i := Pos(':', start);
- IF i = 0 THEN BEGIN
- dname := '';
- pname := start;
- END ELSE BEGIN
- dname := Copy(start, 1, i);
- pname := Copy(start, i+1, 64);
- END;
-
- {see if wildcard specified}
- i := Pos('*', start)+Pos('?', start);
-
- {separate out filename and pathname}
- IF (i = 0) AND (fileexists(start, 16) OR (pname = '\')) THEN BEGIN
- {start specifies a subdirectory}
- IF pname <> '\' THEN pname := pname+'\';
- fname := '*.*';
- END ELSE BEGIN
- {parse out filename on end}
- i := Length(pname);
- WHILE (i > 0) AND NOT(pname[i] IN [':', '\', '/']) DO i := i-1;
- fname := Copy(pname, i+1, 63);
- pname := Copy(pname, 1, i);
- END;
- END; {parsepath}
-
- FUNCTION stupcase(s : longstring) : longstring;
- {-return the uppercase of a string}
- VAR
- i : Byte;
- BEGIN
- FOR i := 1 TO Length(s) DO s[i] := UpCase(s[i]);
- stupcase := s;
- END; {stupcase}
-
- FUNCTION breakpressed : Boolean;
- {-true if Break key has been pressed}
- {-note that keypressed function executes int 23 if ^C has been pressed}
- VAR
- c : Char;
- breakdown : Boolean;
- BEGIN
- {check current state}
- breakdown := False;
- WHILE KeyPressed AND NOT(breakdown) DO BEGIN
- Read(Kbd, c);
- IF c = ^C THEN breakdown := True;
- END;
- breakpressed := breakdown;
- END; {breakpressed}
-
- PROCEDURE breakhalt;
- {-executed when break is detected}
- {-exit with return code 1}
- BEGIN
- INLINE(
- {exit with a return code of 1}
- $B8/$01/$4C/ {mov ax,4c01}
- $CD/$21 {int 21}
- );
- END; {breakhalt}
-
- PROCEDURE setbreak;
- {-set the ctrl-break address to a process exit handler}
- BEGIN
- reg.ax := $2523;
- reg.ds := CSeg;
- reg.dx := Ofs(breakhalt);
- MsDos(reg);
- END; {setbreak}
-
- PROCEDURE setoptions;
- {-read command line and set up options and defaults}
- VAR
- i : Integer;
- c : Char;
- haltsoon, gotmatch : Boolean;
- param : longstring;
-
- PROCEDURE writehelp;
- VAR
- ch : Char;
- BEGIN
- WriteLn(' IMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM;');
- WriteLn(' : TXTFIND - Finds text strings in a group of files :');
- WriteLn(' : Copyright (c) 1985, TurboPower Software :');
- WriteLn(' : written by Kim Kokkonen, (408)378-3672 :');
- WriteLn(' : Released to the public domain for personal, non-commercial use :');
- WriteLn(' HMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM<');
- WriteLn;
- WriteLn('Usage: TXTFIND [options] MatchString [output redirection]');
- WriteLn;
- WriteLn('TXTFIND is used to search a set of files for a specified text string. It is');
- WriteLn('designed specifically for use with text files but will work half-heartedly');
- WriteLn('with binary files. Each carriage-return delimited text line is limited to 1024');
- WriteLn('characters. Lines longer than this will be broken into multiple segments, but');
- WriteLn('no text will be lost. Any given match must occur within a single line.');
- WriteLn;
- WriteLn('The MatchString contains the text which TXTFIND tries to find. It must always');
- WriteLn('be specified on the command line. It may not contain blank space or carriage');
- WriteLn('returns. Use the special symbols below to insert unprintable characters into');
- WriteLn('MatchString. The search string is limited to 255 characters, although in');
- WriteLn('practice it is limited by the DOS command line. By default, TXTFIND does all');
- WriteLn('matching in uppercase (it is not case-sensitive), and it applies a high-bit');
- WriteLn('filter so that it works with WordStar files. These may be changed via options');
- WriteLn('below.');
- WriteLn;
- Write('press any key to continue (Q to quit)....'); Read(Kbd, ch);
- IF UpCase(ch) = 'Q' THEN dohalt(2);
- WriteLn;
- WriteLn;
- WriteLn;
- WriteLn('If no options are specified, the search will cover all files in the current');
- WriteLn('directory and drive. Options below allow you to specify selected files, start');
- WriteLn('in any directory or drive, or search all subdirectories of the start');
- WriteLn('directory. TXTFIND is limited to 256 files per directory.');
- WriteLn;
- WriteLn('Because TXTFIND is not designed for binary files, it can automatically exclude');
- WriteLn('certain file types from its search. You can exclude extensions by using an');
- WriteLn('optional "exclude" file named TXTFIND.EXC. TXTFIND automatically looks in the');
- WriteLn('current directory and the root directory of the default drive for this file.');
- WriteLn('If it finds one of these, it will read it. Each line of TXTFIND.EXC should');
- WriteLn('hold one extension to be excluded from searching. The TXTFIND.EXC file');
- WriteLn('provided with this software automatically excludes files with extensions of');
- WriteLn('COM, EXE, OBJ, WKS, and BIN.');
- WriteLn;
- WriteLn('For each string that TXTFIND matches, it will write out the filename and line');
- WriteLn('number of the match, followed by the matching line. TXTFIND writes to the');
- WriteLn('standard output, so these results can be redirected to a file, to the printer');
- WriteLn('or through a MORE filter as desired.');
- WriteLn;
- WriteLn;
- WriteLn;
- WriteLn;
- Write('press any key to continue (Q to quit)....'); Read(Kbd, ch);
- IF UpCase(ch) = 'Q' THEN dohalt(2);
- WriteLn;
- WriteLn;
- WriteLn('Options:');
- WriteLn;
- WriteLn(' -S Pathname Start search on specified drive and path, or with specified');
- WriteLn(' files matched. Full DOS wildcards and shorthand supported.');
- WriteLn(' -R Recursive search. Search down all subdirectories found.');
- WriteLn(' -F ListFile Search only those files named in ListFile. Files should');
- WriteLn(' be listed one per line, and may include pathname.');
- WriteLn(' -G Search for Graphics characters (turn off WordStar Filter).');
- WriteLn(' -C Make Case significant in searching.');
- WriteLn(' -H Do not print Header lines (line # and file name) with match.');
- WriteLn(' -T Do not print matched Text lines.');
- WriteLn(' -Q Quiet output mode. Normally TXTFIND keeps a status line');
- WriteLn(' running during its search. This status line shows the current');
- WriteLn(' file and line number as well as the number of matches found.');
- WriteLn(' This status always goes to the screen and is never');
- WriteLn(' redirected. -Q mode turns this status line off.');
- WriteLn(' -N No exclusions are made, that is, the file TXTFIND.EXC is not');
- WriteLn(' read.');
- WriteLn;
- WriteLn('Special Characters:');
- WriteLn(' \s space \t tab \\ backslash \b backspace');
- WriteLn(' #nnn any ASCII character nnn (3 digits or terminated with non-numeral)');
- WriteLn;
- Write('press any key to continue (Q to quit)....'); Read(Kbd, ch);
- IF UpCase(ch) = 'Q' THEN dohalt(2);
- WriteLn;
- WriteLn;
- WriteLn;
- WriteLn('Examples:');
- WriteLn;
- WriteLn(' TXTFIND stupcase');
- WriteLn(' will search all non-excluded files in the current directory for the');
- WriteLn(' string "stupcase". Case will not be significant.');
- WriteLn;
- WriteLn(' TXTFIND -R stupcase >stupcase.dat');
- WriteLn(' will search all non-excluded files in the current directory and all of');
- WriteLn(' its subdirectories for "stupcase". Results are written to STUPCASE.DAT.');
- WriteLn;
- WriteLn(' TXTFIND -S c:\pascal\*.pas -R stupcase');
- WriteLn(' looks at all files with extension .PAS starting in the \pascal directory');
- WriteLn(' and working down.');
- WriteLn;
- WriteLn(' TXTFIND -S a:graph.p -C GraphBackground');
- WriteLn(' looks only at one file, graph.p, for the word "GraphBackground". Case is');
- WriteLn(' significant in making the match.');
- WriteLn;
- WriteLn(' TXTFIND -S \*.DOC -R Now\sis\sthe\stime\sfor\sall >lpt1');
- WriteLn(' looks at all DOC files on the current drive for the string "Now is the');
- WriteLn(' time for all", and sends its results to LPT1.');
- dohalt(2);
- END; {writehelp}
-
- PROCEDURE doerror(message : longstring);
- {-display an error message}
- BEGIN
- WriteLn(Con, message);
- haltsoon := True;
- END; {doerror}
-
- FUNCTION getccl(arg : longstring; i : Integer; VAR s : longstring) : Boolean;
- {-expand a character class starting at position i of arg into a string s}
- {return true if successful}
-
- PROCEDURE dodash(delim : Char; VAR arg : longstring; VAR i : Integer; VAR s : longstring);
- {-expand the innards of the character class, including dashes}
- {stop when endc is found}
- {return a string s with the expansion}
- VAR
- c : Char;
- j : Integer;
-
- PROCEDURE addstr(c : Char; VAR j : Integer; VAR s : longstring);
- {-append a character c onto string s and increment position}
- BEGIN
- j := j+1;
- s[j] := c;
- END; {addstr}
-
- PROCEDURE addcode(VAR arg : longstring; VAR i, j : Integer; VAR s : longstring);
- {-interpret an ASCII character code}
- VAR
- npos, code, cval : Integer;
- numstring : STRING[3];
- BEGIN
- npos := 0; numstring := '';
- WHILE (arg[i+npos] IN ['0'..'9']) AND (npos <= 2) DO BEGIN
- numstring := numstring+arg[i+npos];
- npos := npos+1;
- END;
- Val(numstring, cval, code);
- IF (code = 0) AND (cval >= 0) AND (cval < 254) THEN BEGIN
- {add the char to the set string}
- addstr(Chr(cval), j, s);
- i := i+npos-1;
- END ELSE BEGIN
- {illegal character, just interpret literally}
- WriteLn('WARNING: ASCII character ', Copy(arg, i-1, npos+1), ' not interpreted');
- addstr(ascii, j, s);
- i := i-1;
- END;
- END; {addcode}
-
- BEGIN {dodash}
- j := 0;
- WHILE (arg[i] <> delim) AND (arg[i] <> endstr) DO BEGIN
- c := arg[i];
- IF (c = esc) THEN BEGIN
- IF (arg[i+1] <> endstr) THEN BEGIN
- i := i+1;
- c := arg[i];
- {replace the special characters}
- IF (c = lspace) THEN addstr(#32, j, s)
- ELSE IF (c = ltab) THEN addstr(#9, j, s)
- ELSE IF (c = lbackspace) THEN addstr(#8, j, s)
- ELSE
- {add escaped character}
- addstr(c, j, s);
- END ELSE
- {escape must be the character}
- addstr(esc, j, s);
- END ELSE IF c = ascii THEN BEGIN
- IF (arg[i+1] <> endstr) AND (arg[i+1] IN ['0'..'9']) THEN BEGIN
- i := i+1;
- addcode(arg, i, j, s);
- END ELSE
- {ascii must be the character}
- addstr(c, j, s);
- END ELSE
- {literal character}
- addstr(c, j, s);
- i := i+1;
- END;
- s[0] := Chr(j);
- END; {dodash}
-
- BEGIN {getccl}
- {expand the character class}
- dodash(endstr, arg, i, s);
- getccl := (arg[i] = endstr);
- END; {getccl}
-
- PROCEDURE buildpattern(inpat : longstring; VAR flink : flinkarray);
- {-build failure link array for KMP pattern matching}
- VAR
- len : Byte ABSOLUTE inpat;
- i, j : Byte;
- BEGIN
- {pass through outpat to build the failure link automaton}
- flink[1] := 0; i := 2;
- WHILE i <= len DO BEGIN
- j := flink[i-1];
- WHILE (j <> 0) AND (inpat[j] <> inpat[i-1]) DO j := flink[j];
- flink[i] := j+1;
- i := i+1;
- END;
- END; {buildpattern}
-
- PROCEDURE getexcludes;
- {-find and read the TXTFIND.EXC file}
- CONST
- {files searched for excludes, add more if you like}
- name1 : pathname = 'TXTFIND.EXC';
- name2 : pathname = '\TXTFIND.EXC';
-
- PROCEDURE readexclude(name : pathname);
- {-open and read the exclude file}
- VAR
- f : Text;
- l : STRING[4];
- BEGIN
- IF verbose THEN
- Write(Con, 'reading exclude file ', name);
- Assign(f, name);
- Reset(f);
- WHILE NOT(EoF(f)) DO BEGIN
- ReadLn(f, l);
- IF l <> '' THEN BEGIN
- IF l[1] = '.' THEN
- l := stupcase(Copy(l, 2, 3))
- ELSE
- l := stupcase(Copy(l, 1, 3));
- avoidnum := avoidnum+1;
- avoidextension[avoidnum] := l;
- END;
- END;
- Close(f);
- END; {readexclude}
-
- BEGIN
- IF fileexists(name1, 0) THEN readexclude(name1)
- ELSE IF fileexists(name2, 0) THEN readexclude(name2)
- ELSE IF verbose THEN BEGIN
- WriteLn(Con, 'WARNING: exclude file not found....');
- WriteLn(Con);
- END;
- END; {getexcludes}
-
- BEGIN
- {get options}
- IF ParamCount = 0 THEN
- writehelp
- ELSE BEGIN
- WriteLn(Con);
- {get default directory and disk}
- GetDir(0, startpath);
- {set default flags}
- verbose := True;
- wordstar := True;
- casesens := False;
- readexclude := True;
- uselistfile := False;
- avoidnum := 0;
- haltsoon := False;
- gotmatch := False;
- recursive := False;
- printheader := True;
- lineoutput := True;
- grandtotal := 0.0;
- grandmatch := 0.0;
- grandbytes := 0.0;
- i := 1;
- WHILE i <= ParamCount DO BEGIN
- {analyze options}
- param := ParamStr(i);
- IF param[1] = optionchar THEN BEGIN
- {an option}
- IF Length(param) = 2 THEN BEGIN
- c := UpCase(param[2]);
- CASE c OF
- 'R' : recursive := True;
- 'Q' : verbose := False;
- 'G' : wordstar := False;
- 'C' : casesens := True;
- 'N' : readexclude := False;
- 'H' : printheader := False;
- 'T' : lineoutput := False;
- 'S' : BEGIN {new start path follows}
- i := i+1;
- IF i <= ParamCount THEN
- startpath := stupcase(ParamStr(i))
- ELSE
- doerror('New start path not found....');
- END;
- 'F' : BEGIN
- uselistfile := True;
- i := i+1;
- IF i <= ParamCount THEN
- listfilename := stupcase(ParamStr(i))
- ELSE
- doerror('ListFile name not found....');
- END;
- END;
- END ELSE
- doerror('Unrecognized command option....'+ParamStr(i));
- END ELSE BEGIN
- {the match pattern}
- IF NOT(gotmatch) THEN BEGIN
- param := ParamStr(i)+endstr;
- IF NOT(getccl(param, 1, matchpattern)) THEN
- doerror('Error during expansion of match string....')
- ELSE
- {build the automaton}
- buildpattern(matchpattern, flink);
- gotmatch := True;
- END ELSE
- doerror('More than one match pattern specified....'+ParamStr(i));
- END;
- i := i+1;
- END;
- IF NOT(gotmatch) THEN
- doerror('No match pattern found....')
- ELSE IF NOT(casesens) THEN
- matchpattern := stupcase(matchpattern);
- IF wordstar THEN wordmask := 127 ELSE wordmask := 255;
- IF haltsoon THEN BEGIN
- WriteLn(Con, 'Type TXTFIND for help....');
- dohalt(2);
- END;
- IF uselistfile THEN BEGIN
- recursive := False;
- readexclude := False;
- END;
- IF readexclude THEN getexcludes;
- END;
- END; {setoptions}
-
- PROCEDURE setdta(VAR dta : dtarec);
- {-set new DTA address}
- BEGIN
- reg.ah := $1A;
- reg.ds := Seg(dta);
- reg.dx := Ofs(dta);
- MsDos(reg);
- END; {setdta}
-
- PROCEDURE scantext(startpath : pathname);
- {-get all files in pathname, scan them and output matches}
- {-called recursively in recursive mode}
- VAR
- files : farray;
- dname : drivename;
- pname, usepath : pathname;
- fname : filename;
- filnum : Integer;
- bufpos, bufcount : Integer;
- endoffile : Boolean;
-
- PROCEDURE getfiles(attr : Integer; takeall : Boolean;
- VAR files : farray;
- VAR startpath : pathname);
- {-return the files in the files array}
- VAR
- tempname, tempext : filename;
-
- PROCEDURE parsedta(VAR name, ext : filename);
- {-return a name and extension from a DTA}
- VAR
- i : Byte;
- tempname : filename;
- BEGIN
- i := 1;
- WHILE dta.fullname[i] <> #0 DO i := i+1;
- Move(dta.fullname, tempname[1], i-1);
- tempname[0] := Chr(i-1);
- i := Pos('.', tempname);
- IF i = 0 THEN BEGIN
- name := tempname;
- ext := '';
- END ELSE BEGIN
- name := Copy(tempname, 1, i);
- ext := Copy(tempname, i+1, 3);
- END;
- END; {parsedta}
-
- FUNCTION getfirst(attr : Integer; VAR startpath : pathname; VAR name, ext : filename) : Boolean;
- {-return true and a name if first file is found}
- VAR
- foundone : Boolean;
- BEGIN
- reg.ah := $4E;
- reg.ds := Seg(startpath);
- reg.dx := Ofs(startpath[1]);
- reg.cx := attr;
- MsDos(reg);
- foundone := ((reg.flags AND 1) = 0) AND ((dta.attr AND 16) = (attr AND 16));
- IF foundone THEN
- {scan the DTA for the file name and extension}
- parsedta(name, ext);
- getfirst := foundone;
- END; {getfirst}
-
- FUNCTION getnext(VAR name, ext : filename) : Boolean;
- {-return true and a name if another file is found}
- VAR
- foundone : Boolean;
- BEGIN
- reg.ah := $4F;
- reg.ds := Seg(dta);
- reg.dx := Ofs(dta);
- MsDos(reg);
- foundone := ((reg.flags AND 1) = 0) AND ((dta.attr AND 16) = (attr AND 16));
- IF foundone THEN
- {scan the DTA for the file name and extension}
- parsedta(name, ext);
- getnext := foundone;
- END; {getnext}
-
- FUNCTION goodext(VAR ext : filename) : Boolean;
- {-return true if ext is not one to avoid}
- LABEL 1;
- VAR
- i : Integer;
- BEGIN
- goodext := True;
- FOR i := 1 TO avoidnum DO
- IF ext = avoidextension[i] THEN BEGIN
- goodext := False;
- GOTO 1;
- END;
- 1:
- END; {goodext}
-
- PROCEDURE getlistoffiles;
- {-open and read the listfile, check for file existence}
- VAR
- lfile : Text;
- l : pathname;
- BEGIN
- IF fileexists(listfilename, 0) THEN BEGIN
- Assign(lfile, listfilename);
- Reset(lfile);
- WITH files DO BEGIN
- num := 0;
- WHILE NOT(EoF(lfile)) DO BEGIN
- ReadLn(lfile, l);
- IF fileexists(l, 0) THEN BEGIN
- IF num < maxfiles THEN BEGIN
- num := num+1;
- arr[num] := l;
- END ELSE
- WriteLn(Con, 'Warning: exceeded file capacity. ignoring ', l);
- END ELSE
- WriteLn(Con, 'Warning: file ', l, ' not found. proceeding....');
- END;
- END;
- Close(lfile);
- END ELSE BEGIN
- WriteLn(Con, 'ListFile ', listfilename, ' not found....');
- Halt(2);
- END;
- END; {getlistoffiles}
-
-
- BEGIN
- IF uselistfile THEN
- getlistoffiles
- ELSE BEGIN
- WITH files DO BEGIN
- startpath[Length(startpath)+1] := #0;
- num := 0;
- IF getfirst(attr, startpath, tempname, tempext) THEN
- REPEAT
- IF (takeall OR goodext(tempext)) AND
- (tempname <> '.') AND (tempname <> '..') THEN BEGIN
- num := num+1;
- arr[num] := tempname+tempext;
- END;
- UNTIL (num = maxfiles) OR NOT(getnext(tempname, tempext));
- END;
- END;
- END; {getfiles}
-
- PROCEDURE openfile(fname : pathstring; access : Integer; VAR handle, errcode : Integer);
- {-open a file for reading and return the handle}
- BEGIN
- fname := fname+#0;
- reg.ds := Seg(fname[1]); reg.dx := Ofs(fname[1]);
- reg.ax := $3D00 OR access;
- MsDos(reg);
- IF (reg.flags AND 1) = 1 THEN
- errcode := reg.ax
- ELSE BEGIN
- handle := reg.ax;
- errcode := 0;
- END;
- END; {openfile}
-
- PROCEDURE closefile(handle : Integer);
- {-close a file opened by openfile}
- BEGIN
- reg.bx := handle;
- reg.ax := $3E00;
- MsDos(reg);
- IF (reg.flags AND 1) = 1 THEN BEGIN
- WriteLn('problem closing file....');
- dohalt(2);
- END;
- END; {closefile}
-
- PROCEDURE writeline(handle : Integer; VAR l : line);
- {-send a long line to the specified file or device with a CR/LF at end}
- VAR
- len : Integer ABSOLUTE l;
- BEGIN
- l[len+1] := #13;
- l[len+2] := #10;
- reg.bx := handle;
- reg.cx := len+2;
- reg.ds := Seg(l[1]);
- reg.dx := Ofs(l[1]);
- reg.ax := $4000;
- MsDos(reg);
- IF (reg.flags AND 1) = 1 THEN BEGIN
- WriteLn(Con);
- WriteLn(Con, 'ERROR during write....');
- dohalt(2);
- END;
- END; {writeline}
-
- PROCEDURE readline(inhandle : Integer; VAR l : line);
- {-read a long line}
- VAR
- c : Byte;
- gotline : Boolean;
- len : Integer ABSOLUTE l;
- bufstart, lstart, tmp : Integer;
-
- PROCEDURE BlockRead(inhandle : Integer; VAR b : buffer; VAR count : Integer);
- {-read a chunk of characters from the specified handle}
- BEGIN
- reg.bx := inhandle;
- reg.cx := BufLen;
- reg.ds := Seg(b[1]);
- reg.dx := Ofs(b[1]);
- reg.ax := $3F00;
- MsDos(reg);
- count := reg.ax;
- END; {blockread}
-
- BEGIN
- len := 0; gotline := False; bufstart := bufpos; lstart := 1;
- REPEAT
- IF (bufpos > bufcount) THEN BEGIN
- {get a new buffer full}
- BlockRead(inhandle, buf, bufcount);
- endoffile := (bufcount = 0);
- gotline := endoffile;
- IF NOT(endoffile) THEN BEGIN
- IF buf[1] = 10 THEN BEGIN
- {skip over the line feed}
- bufpos := 2;
- bufstart := 2;
- END ELSE BEGIN
- bufpos := 1;
- bufstart := 1;
- END;
- grandbytes := grandbytes+bufcount;
- END;
- END;
- WHILE (bufpos <= bufcount) AND NOT(gotline) DO BEGIN
- IF wordstar THEN BEGIN
- {apply wordstar .DOC filter}
- c := buf[bufpos] AND wordmask;
- IF c = 30 THEN c := 32
- ELSE IF c = 31 THEN c := 45;
- buf[bufpos] := c;
- END ELSE
- c := buf[bufpos];
- IF (c = 13) OR (c = 26) THEN BEGIN
- {end of line and perhaps end of file}
- endoffile := (c = 26); gotline := True;
- tmp := bufpos-bufstart; len := len+tmp;
- Move(buf[bufstart], l[lstart], tmp);
- bufpos := bufpos+1;
- {skip over the linefeed following #13}
- IF (bufpos <= bufcount) THEN IF buf[bufpos] = 10 THEN bufpos := bufpos+1;
- END ELSE BEGIN
- IF len+bufpos-bufstart < ncrlinlen THEN BEGIN
- {almost all cycles pass through here}
- bufpos := bufpos+1;
- END ELSE BEGIN
- {line too long, break it, don't increment bufpos}
- gotline := True;
- tmp := bufpos-bufstart; len := len+tmp;
- Move(buf[bufstart], l[lstart], tmp);
- END;
- END;
- END;
- IF NOT(gotline) THEN BEGIN
- {end of buffer without finishing line}
- tmp := bufpos-bufstart; len := len+tmp;
- Move(buf[bufstart], l[lstart], tmp);
- lstart := bufpos-bufstart+1;
- {exit when file does not end with ^Z}
- IF endoffile THEN gotline := True;
- END;
- UNTIL gotline;
- END; {readline}
-
- PROCEDURE matchup(VAR fname : pathname);
- {-scan the file fname looking for the matchpattern}
- VAR
- usepath : pathname;
- handle : Integer;
- lcount, mcount : Integer;
- l : line;
- errcode : Integer;
-
- FUNCTION linematch(VAR l : line) : Boolean;
- {-return true if line matches matchpattern}
- VAR
- start, stop : Integer;
- len : Integer ABSOLUTE l;
- matched : Boolean;
- l1 : line;
-
- PROCEDURE longupcase(VAR s, s1 : line);
- {-return uppercase value of string, special version for long lines}
- VAR
- i : Integer;
- len : Integer ABSOLUTE s;
- len1 : Integer ABSOLUTE s1;
- BEGIN
- FOR i := 1 TO len DO s1[i] := UpCase(s[i]);
- len1 := len;
- END; {longupcase}
-
- PROCEDURE copyof(VAR s, s1 : line);
- {-return copy of string, special version for long lines}
- VAR
- i : Integer;
- len : Integer ABSOLUTE s;
- BEGIN
- FOR i := -1 TO len DO s1[i] := s[i];
- END; {copyof}
-
- FUNCTION kmpscan(VAR l : line; start : Integer;
- VAR stop : Integer) : Boolean;
- {-scan the line for a match, return true if found}
- {-start at position start in l}
- {-return stop as the next unmatched position after the last matched position}
- VAR
- len : Integer ABSOLUTE l;
- i, j : Integer;
- c : Char;
- unmatched : Boolean;
- BEGIN
- i := start; j := 1; unmatched := True;
- WHILE (i <= len) AND unmatched DO BEGIN
- c := l[i];
- WHILE (j <> 0) AND (matchpattern[j] <> c) DO j := flink[j];
- IF j = matchlen THEN unmatched := False ELSE j := j+1;
- i := i+1;
- END;
- stop := i;
- kmpscan := NOT(unmatched);
- END; {kmpscan}
-
- BEGIN
- {apply filter}
- IF NOT(casesens) THEN
- longupcase(l, l1)
- ELSE
- copyof(l, l1);
-
- {scan the line for matches}
- start := 1;
- matched := False;
- WHILE start <= len DO BEGIN
- matched := kmpscan(l1, start, stop);
- IF matched THEN
- {force exit}
- stop := len+1;
- start := stop;
- END;
- linematch := matched;
- END; {linematch}
-
- PROCEDURE writeoutput(VAR l : line; lcount : Integer; VAR usepath : pathname);
- {-write output for selected lines}
- VAR
- outline : longstring;
- lstring : filename;
-
- PROCEDURE putl(VAR l : longstring);
- {-send a short line to the standard output}
- VAR
- len : Byte ABSOLUTE l;
- tlen : Byte;
- BEGIN
- tlen := len+2;
- l[len+1] := #13;
- l[tlen] := #10;
- reg.bx := 1;
- reg.cx := tlen;
- reg.ds := Seg(l[1]);
- reg.dx := Ofs(l[1]);
- reg.ah := $40;
- MsDos(reg);
- IF (reg.flags AND 1) = 1 THEN BEGIN
- WriteLn('ERROR: cannot write to output device....');
- dohalt(2);
- END;
- IF reg.ax <> tlen THEN BEGIN
- WriteLn('ERROR: disk full....');
- dohalt(2);
- END;
- END; {putl}
-
- BEGIN
- IF verbose THEN BEGIN
- GoToXY(1, WhereY); ClrEol;
- END;
- IF printheader THEN BEGIN
- Str(lcount:7, lstring);
- outline := '['+lstring+'] '+usepath;
- putl(outline);
- END;
- IF lineoutput THEN BEGIN
- writeline(1, l);
- outline := '';
- putl(outline);
- END;
- mcount := mcount+1;
- IF verbose THEN Write(Con, lcount:8, mcount:8, ' ', usepath);
- END; {writeoutput}
-
- BEGIN
- IF uselistfile THEN
- usepath := fname+#0
- ELSE
- usepath := dname+pname+fname+#0;
- openfile(usepath, 0, handle, errcode);
-
- IF errcode = 0 THEN BEGIN
- {ready to read and match file}
- lcount := 0; mcount := 0;
- bufpos := 1; bufcount := 0; endoffile := False;
- IF verbose THEN
- Write(Con, lcount:8, mcount:8, ' ', usepath);
- WHILE NOT(endoffile) DO BEGIN
- readline(handle, l);
- IF breakpressed THEN BEGIN
- closefile(handle);
- dohalt(1);
- END;
- lcount := lcount+1;
- IF verbose AND ((lcount MOD 16) = 0) THEN BEGIN
- GoToXY(1, WhereY);
- Write(Con, lcount:8);
- END;
- IF linematch(l) THEN writeoutput(l, lcount, usepath);
- END;
- grandtotal := grandtotal+lcount;
- grandmatch := grandmatch+mcount;
- closefile(handle);
- IF verbose THEN BEGIN
- GoToXY(1, WhereY); ClrEol;
- END;
- END ELSE BEGIN
- WriteLn(Con);
- WriteLn(Con, 'PROGRAM ERROR in Matchup: file ', fname, ' not found');
- END;
- END; {matchup}
-
- BEGIN
- {get a list of all normal, readonly, hidden matching files in startpath}
- parsepath(startpath, dname, pname, fname);
- usepath := dname+pname+fname;
- IF verbose AND NOT(uselistfile) THEN BEGIN
- GoToXY(1, WhereY); ClrEol;
- Write(Con, 'Reading directory of ', usepath);
- END;
- getfiles(3, False, files, usepath);
- IF verbose THEN BEGIN
- GoToXY(1, WhereY); ClrEol;
- END;
-
- {check each file for match pattern}
- FOR filnum := 1 TO files.num DO matchup(files.arr[filnum]);
-
- {look at subdirectories}
- IF recursive THEN BEGIN
- {get all subdirectories}
- usepath := dname+pname+'*.*';
- getfiles(19, True, files, usepath);
- {look in the subdirectories}
- FOR filnum := 1 TO files.num DO BEGIN
- {build a pathname to the subdirectory}
- usepath := dname+pname+files.arr[filnum]+'\'+fname;
- {call recursively}
- scantext(usepath);
- END;
- END;
- END; {scantext}
-
- PROCEDURE time(VAR sec : Real);
- {-return time of day in seconds since midnight}
- BEGIN
- reg.ah := $2C;
- MsDos(reg);
- sec := 1.0*(reg.dh+60.0*(reg.cl+60.0*reg.ch)+reg.dl/100.0);
- END; {time}
-
- PROCEDURE writeresults;
- BEGIN
- WriteLn(Con, 'lines: ', grandtotal:7:0);
- WriteLn(Con, 'matches: ', grandmatch:7:0);
- WriteLn(Con, 'bytes: ', grandbytes:7:0);
- IF tstop-tstart > 0 THEN BEGIN
- WriteLn(Con, 'line rate: ', (grandtotal/(tstop-tstart)):5:1, ' lines/sec');
- WriteLn(Con, 'byte rate: ', (grandbytes/(tstop-tstart)):5:0, ' bytes/sec');
- END;
- END; {writeresults}
-
- BEGIN
- setdta(dta);
- setoptions;
- setbreak;
- time(tstart);
- scantext(startpath);
- time(tstop);
- writeresults;
- END.