Contributor: EDDY THILLEMAN

{$A+,B-,D-,E-,F-,G-,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X-,Y-}
{$M 16384,0,655360}
{$DEFINE Kort}
Program Extract;
  { extract filenames and accompanying descriptions from bbs files listings }
  { Author: Eddy Thilleman, 19 mei 1994 }
  { written in Borland Pascal version 7.01 }
  {  modified: augustus 1994 - choose between long vs. short directory name }
  {  modified: januari  1995 - keep only filenames with entries found on screen
                             - total number of found entries
                             - delete destination directory if no entries found }

Uses
  Dos;

Type
  TypeNotAllowed = set of char;  { filter out (some) header lines }
Const
  NotAllowed : TypeNotAllowed = [''..' ','*',':'..'?','|','°'..'ß'];
  NoFAttr : word =   $1C;  { dir-, volume-, system attributen }
  FAttr   : word =   $23;  { readonly-, hidden-, archive attributes }
  BufSizeBig     = 49152;  { 48 KB }
  BufSizeSmall   =  8192;  {  8 KB }
  Cannot         = 'Cannot create destination ';
  MaxNrLines     =    20;  { max # of lines for one entry }
  MaxNrSearch    =    18;  { max # of words to search for }

Type
  BufTypeSource  = array [1..BufSizeBig  ] of char;
  BufTypeDest    = array [1..BufSizeSmall] of char;
  string3        = string[03];
  String12       = string[12];
  String16       = string[16];
  String25       = string[25];
  String65       = string[65];
  TypeLine       = array [1..MaxNrLines] of string;

Var
  Line             : TypeLine;         { filename and description  }
  Tmp1, Tmp2       : string;           { temporary hold lines here }
  FileName         : String12;         { filename in files listing }
  SearchText       : array [1..MaxNrSearch] of String65;
  Count, TotalCount: word;             { # of found entries        }
  SourceFile, DestFile : text;         { sourcefile and dest. file }
  SourceBuf        : BufTypeSource;    { source text buffer        }
  DestBuf          : BufTypeDest;      { destination text buffer   }
{$IFDEF Kort}
  DestListing      : string16;         { name of destination file  }
  DestDir          : string3 ;         { name of destination directory }
{$ELSE}
  DestListing      : string25;         { name of destination file  }
  DestDir          : string12;         { name of destination directory }
{$ENDIF}
  FR               : SearchRec;        { FileRecord }
  FMask, DirName   : String12;
  Exists           : boolean;
  nr,                                  { nr: points to element# where
                                             to put the next read-in line   }
  NrLines          : byte;             { NrLines: number of lines belonging
                                             to this entry }
  found, Header    : boolean;
  T                : byte;             { points to char in line: allowed? }
  NrSearch,                            { current word to search for       }
  TotalNrSearch    : byte;             { total # of words to search for   }


procedure LowerFast( var Str: String );
  { 52 Bytes by Bob Swart, 11-6-1993, '80XXX' FASTEST! }
InLine(
  $8C/$DA/               {       mov   DX,DS                 }
  $BB/Ord('A')/
      Ord('Z')-Ord('A')/ {       mov   BX,'Z'-'A'/'A'        }
  $5E/                   {       pop   SI                    }
  $1F/                   {       pop   DS                    }
  $FC/                   {       cld                         }
  $AC/                   {       lodsb                       }
  $88/$C1/               {       mov   CL,AL                 }
  $30/$ED/               {       xor   CH,CH                 }
  $D1/$E9/               {       shr   CX,1                  }
  $73/$0B/               {       jnc   @Part1                }
  $AC/                   {       lodsb                       }
  $28/$D8/               {       sub   AL,BL                 }
  $38/$F8/               {       cmp   AL,BH                 }
  $77/$04/               {       ja    @Part1                }
  $80/$44/$FF/
      Ord('a')-Ord('A')/ {@Loop: ADD   Byte Ptr[SI-1],'a'-'A'}
  $E3/$14/               {@Part1:jcxz  @Exit                 }
  $AD/                   {       lodsw                       }
  $28/$D8/               {       sub   AL,BL                 }
  $38/$F8/               {       cmp   AL,BH                 }
  $77/$04/               {       ja    @Part2                }
  $80/$44/$FE/
      Ord('a')-Ord('A')/ {       ADD   Byte Ptr[SI-2],'a'-'A'}
  $49/                   {@Part2:dec   CX                    }
  $28/$DC/               {       sub   AH,BL                 }
  $38/$FC/               {       cmp   AH,BH                 }
  $77/$EC/               {       ja    @Part1                }
  $EB/$E6/               {       jmp   @Loop                 }
  $8E/$DA                {@Exit: mov   DS,DX                 }
) { LowerFast };


procedure CopySubStr( Str1: string; start, nrchars: byte; var Str2: string );
assembler;
  { copy part of Str1 (beginning at start for nrchars) to Str2
    if start > length of Str1, Str2 will contain a empty string.
    if nrchars specifies more characters than remain starting at the
    start position, Str2 will contain just that remainder of Str1. }
asm     { setup }
        lds   si, str1     { load in DS:SI pointer to str1 }
        cld                { string operations forward     }
        les   di, str2     { load in ES:DI pointer to str2 }
        mov   ah, [si]     { length str1 --> AH            }
        and   ah, ah       { length str1 = 0?              }
        je    @null        { yes, empty string in Str2     }
        mov   bl, [start]  { starting position --> BL      }
        cmp   ah, bl       { start > length str1?          }
        jb    @null        { yes, empty string in Str2     }

        { start + nrchars - 1 > length str1?               }
        mov   al, [nrchars]{ nrchars --> AL                }
        mov   dh, al       { nrchars --> DH                }
        add   dh, bl       { add start                     }
        dec   dh
        cmp   ah, dh       { nrchars > rest of str1?       }
        jb    @rest        { yes, copy rest of str1        }
        jmp   @copy
@null:  xor   ax, ax       { return a empty string         }
        jmp   @done
@rest:  sub   ah, bl       { length str1 - start           }
        inc   ah
        mov   al, ah
@copy:  mov   cl, al       { how many chars to copy        }
        xor   ch, ch       { clear CH                      }
        xor   bh, bh       { clear BH                      }
        add   si, bx       { starting position             }
        mov   dx, di       { save pointer to str2          }
        inc   di
    rep movsb              { copy part str1 to str2        }
        mov   di, dx       { restore pointer to str2       }
@done:  mov   [di], al     { overwrite length byte of str2 }
@exit:
end  { CopySubStr };


procedure StrCopy( var Str1, Str2: string ); assembler;
  { copy str1 to str2 }
asm
        lds   si, str1     { load in DS:SI pointer to str1 }
        cld                { string operations forward     }
        les   di, str2     { load in ES:DI pointer to str2 }
        xor   ch, ch       { clear CH                      }
        mov   cl, [si]     { length str1 --> CX            }
        inc   cx           { include length byte           }
    rep movsb              { copy str1 to str2             }
@exit:
end  { StrCopy };


function StrPos( var str1, str2: string ): byte; assembler;
  { returns position of the first occurrence of str1 in str2 }
  { str1 - string to search for }
  { str2 - string to search in  }
  { return value in AX }
asm
        cld                 { string operations forward                 }
        les   di, str2      { load in ES:DI pointer to str2             }
        xor   cx, cx        { clear cx                                  }
        mov   cl, [di]      { length str2 --> CL                        }
        jcxz  @not          { if length str2 = 0, nothing to search in  }
        mov   bh, cl        { length str2 --> BH                        }
        inc   di            { di point to 1st char of str2              }
        lds   si, str1      { load in DS:SI pointer to str1             }
        lodsb               { load in AL length str1                    }
        and   al, al        { length str1 = 0?                          }
        jz    @not          { length str1 = 0, nothing to search for    }
        dec   al            { 1st char need not be compared again       }
        sub   cl, al        { length str2 - length str1                 }
        jbe   @not          { length str2 < length str1                 }
        mov   ah, al        { length str1 --> AH                        }
        lodsb               { load in AL 1st character of str1          }
@start:
  repne scasb               { scan for next occurrence 1st char in str2 }
        jne   @not          { no success                                }
        mov   dx, si        { pointer to 2nd char in str1 --> DX        }
        mov   bl, cl        { number of chars in str2 to go --> BL      }
        mov   cl, ah        { length str1 --> CL                        }
   repe cmpsb               { compare until characters don't match      }
        je    @pos          { full match                                }
        sub   si, dx        { current SI - prev. SI = # of chars moved  }
        sub   di, si        { current DI - # of chars moved = prev. DI  }
        mov   si, dx        { restore pointer to 2nd char in str1       }
        mov   cl, bl        { number of chars in str2 to go --> BL      }
        jmp   @start        { scan for next occurrence 1st char in str2 }
@not:   xor   ax, ax        { str1 is not in str2, result 0             }
        jmp   @exit
@pos:   add   bl, ah        { number of chars in str2 left              }
        mov   al, bh        { length str2 --> AX                        }
        sub   al, bl        { start position of str1 in str2            }
@exit:                      { we are finished. }
end  { StrPos };


procedure Trim( var Str: string ); assembler;
  { remove leading and trailing white space from str }
  { white space = all ASCII chars 0h - 20h }
asm     { setup }
        lds   si, str        { load in DS:SI pointer to Str       }
        xor   cx, cx         { clear cx                           }
        mov   cl, [si]       { length Str --> cx                  }
        jcxz  @exit          { if length Str = 0, exit            }
        mov   bx, si         { save pointer to length byte of Str }
        add   si, cx         { last character                     }

        { look for trailing space }
@loop1: mov   al, [si]       { load character                     }
        cmp   al, ' '        { no white space                     }
        ja    @stop1         { first non-blank character found    }
        dec   si             { next character                     }
        dec   cx             { count down                         }
        jcxz  @done          { if no more characters left, done   }
        jmp   @loop1         { try again                          }
@stop1: mov   si, bx         { point to start of Str              }
        inc   si             { point to 1st character             }
        mov   di, si         { pointer to Str --> DI              }
        { look for leading white space }
@loop2: mov   al, [si]       { load character                     }
        cmp   al, ' '        { no white space                     }
        ja    @stop2         { first non-blank character found    }
        inc   si             { next character                     }
        dec   cx             { count down                         }
        jcxz  @done          { if no more characters left, done   }
        jmp   @loop2         { try again                          }

        { remove leading white space }
@stop2: cld                  { string operations forward          }
        mov   dx, cx         { save new length Str                }
    rep movsb                { move remaining part of Str         }
        mov   cx, dx         { restore new length Str             }
@done:  mov   [bx], cl       { new length of Str                  }
@exit:
end  { Trim };


function InSet25(var _Set; OrdElement: Byte): Boolean;
  { I got this function from Bob Swart }
InLine(
  $58/         {   pop   AX                   }
  $30/$E4/     {   xor   AH,AH                }
  $5F/         {   pop   DI                   }
  $07/         {   pop   ES                   }
  $89/$C3/     {   mov   BX,AX                }
  $B1/$03/     {   mov   CL,3                 }
  $D3/$EB/     {   shr   BX,CL                }
  $88/$C1/     {   mov   CL,AL                }
  $80/$E1/$07/ {   and   CL,$07               }
  $B0/$01/     {   mov   AL,1                 }
  $D2/$E0/     {   shl   AL,CL                }
  $26/         {   ES:                        }
  $22/$01/     {   and   AL,BYTE PTR [DI+BX]  }
  $D2/$E8);    {   shr   AL,CL                }
{ InSet25 }


function OpenTextFile (var InF: text; const name: string; var buffer: BufTypeSource): boolean;
begin
  Assign( InF, Name );
  SetTextBuf( InF, buffer );
  Reset( InF );
  OpenTextFile := (IOResult = 0);
end  { OpenTextFile };

function CreateTextFile (var OutF: text; const name: string; var buffer: BufTypeDest): boolean;
begin
  Assign( OutF, Name );
  SetTextBuf( OutF, buffer );
  Rewrite( OutF );
  CreateTextFile := (IOResult = 0);
end  { CreateTextFile };

function Exist( Name : string ) : Boolean;
  { Return true if directory or file with the same name is found}
var
  F    : file;
  Attr : Word;
begin
  Assign( F, Name );
  GetFAttr( F, Attr );
  Exist := (DosError = 0)
end;

{$IFDEF Kort}
procedure UniekeEntry( var Naam : string3 );
const
  max    = $39;  { '0'..'9' = $30..$39 }
var
  Nbyte  : array [0..3] of byte absolute Naam;
  Exists : boolean;

begin
  Nbyte [0] := 3;  { FileName of 3 characters }

  Exists := True;
  Nbyte [1] := $30;
  while (Nbyte [1] <= max) and Exists do begin
    Nbyte [2] := $30;
    while (Nbyte [2] <= max) and Exists do begin
      Nbyte [3] := $30;
      while (Nbyte [3] <= max) and Exists do begin
        Exists := Exist( Naam );
        if Exists then inc( Nbyte [3] );
      end;
      if Exists then inc( Nbyte [2] );
    end;
    if Exists then inc( Nbyte [1] );
  end;
end;  { end procedure UniekeEntry }

{$ELSE}
procedure UniekeEntry( var Naam : string12 );
const
  max    = $39;  { '0'..'9' = $30..$39 }
var
  Nbyte  : array [0..12] of byte absolute Naam;
  Exists : boolean;

begin
  Nbyte [0] := 12;  { FileName of 12 characters (8+3+".") }
  Nbyte [9] := $2E; { '.' as 9e character }

  Exists := True;
  Nbyte [1] := $30;
  while (Nbyte [1] <= max) and Exists do begin
    Nbyte [2] := $30;
    while (Nbyte [2] <= max) and Exists do begin
      Nbyte [3] := $30;
      while (Nbyte [3] <= max) and Exists do begin
        Nbyte [4] := $30;
        while (Nbyte [4] <= max) and Exists do begin
          Nbyte [5] := $30;
          while (Nbyte [5] <= max) and Exists do begin
            Nbyte [6] := $30;
            while (Nbyte [6] <= max) and Exists do begin
              Nbyte [7] := $30;
              while (Nbyte [7] <= max) and Exists do begin
                Nbyte [8] := $30;
                while (Nbyte [8] <= max) and Exists do begin
                  Nbyte [10] := $30;
                  while (Nbyte [10] <= max) and Exists do begin
                    Nbyte [11] := $30;
                    while (Nbyte [11] <= max) and Exists do begin
                      Nbyte [12] := $30;
                      while (Nbyte [12] <= max) and Exists do begin
                        Exists := Exist( Naam );
                        if Exists then inc( Nbyte [12] );
                      end;
                      if Exists then inc( Nbyte [11] );
                    end;
                    if Exists then inc( Nbyte [10] );
                  end;
                  if Exists then inc( Nbyte [8] );
                end;
                if Exists then inc( Nbyte [7] );
              end;
              if Exists then inc( Nbyte [6] );
            end;
            if Exists then inc( Nbyte [5] );
          end;
          if Exists then inc( Nbyte [4] );
        end;
        if Exists then inc( Nbyte [3] );
      end;
      if Exists then inc( Nbyte [2] );
    end;
    if Exists then inc( Nbyte [1] );
  end;
end;  { end procedure UniekeEntry }
{$ENDIF}


procedure Search;
begin
  found := False;
  NrSearch := 1;
  while (NrSearch <= TotalNrSearch) and not found do
  begin
    nr := 1;
    while (nr <= NrLines) and not found do
    begin                                { search wanted text    }
      StrCopy( Line[nr], Tmp1 );
      LowerFast( Tmp1 );                 { convert to lower case }
      if StrPos( SearchText[NrSearch], Tmp1 ) > 0 then found := True;
      inc( nr );
    end;
    inc( NrSearch );
  end;
  if found then                      { at least one of the wanted words found }
  begin
    for nr := 1 to NrLines do WriteLn( DestFile, Line[nr] );
    inc( Count );
  end;
end;


procedure Process( var SourceListing : string12 );
begin
  Count := 0;
  DestListing  := DestDir + '\' + SourceListing;
  if OpenTextFile( SourceFile, SourceListing, SourceBuf ) then
  begin
    if CreateTextFile( DestFile, DestListing, DestBuf ) then
    begin
      write( SourceListing:12 );
      Header   := False;
      FileName := '';
      NrLines  := 0;
      nr := 1;
      ReadLn( SourceFile, Line[nr] );
      while not Eof(SourceFile) and (IOResult = 0) do
      begin
        StrCopy( Line[nr], Tmp1 );
        Trim( Tmp1 );
        if Length( Tmp1 ) > 0 then                  { no empty lines }
        begin
          CopySubStr( Line[nr], 1, 12, FileName );
          Trim( FileName );
          T := 1;
          while (T <= Length( FileName ))
          and not InSet25( NotAllowed, Byte( FileName[T] ) ) do
            inc( T );                               { look out for headers }
          { }
          Header := (T <= Length( FileName ))
            or ((Length( FileName ) > 0) and (Line[nr][1]=' '));  { header? }
          if Header then
            FileName := ''                          { read next line }
          else                                      { no header }
          begin
            if (Length( FileName ) = 0) then        { more description }
            begin
              inc( nr );
              inc( NrLines );
            end
            else
            begin
              StrCopy( Line[nr], Tmp2 );     { save new textline    }
              Search;

              { setup for next entry }
              NrLines  := 1;                 { already got one line }
              nr       := 2;                 { so next line in #2   }
              StrCopy( Tmp2, Line[1] );      { restore new textline }
              FileName := '';                { make sure a new line is read }
            end;  { endif (Length( FileName ) = 0)) }
          end;  { if Header }
        end;  { if Length( Tmp1 ) > 0 }
        if (Length( FileName ) = 0) then
          ReadLn( SourceFile, Line[nr] );
        { }
      end;  { while not Eof(SourceFile) and (IOResult = 0) }
      inc( NrLines );   { include the last line in the search }
      Search;
      Close( DestFile );
      if (Count = 0) then
      begin
        Erase( DestFile );
        Write( #13 );
      end
      else
      begin
        writeln( Count:7, ' in ', DestListing );
        TotalCount := TotalCount + Count;
      end
    end  { if CreateTextFile }
    else
      writeln( Cannot, 'file ', DestListing );
    { }
    Close( SourceFile );
  end   { if OpenTextFile }
  else
    writeln( 'Cannot open sourcefile ', SourceListing );
  { }
end;


begin
  if ParamCount > 1 then                 { parameters: listing catchwords  }
  begin
    TotalCount := 0;
    TotalNrSearch := ParamCount - 1;
    if (TotalNrSearch > MaxNrSearch) then
      TotalNrSearch := MaxNrSearch;      { no more catchwords than maximum }
    UniekeEntry( DestDir );
    if not Exists then
    begin
      MkDir( DestDir );
      if (IOResult=0) then
      begin
        Write( 'Searching:' );
        FMask        := ParamStr( 1 );                    { filemask       }
        for NrSearch := 1 to TotalNrSearch do             { all catchwords }
        begin
          SearchText[NrSearch] := ParamStr( NrSearch+1 ); { each catchword }
          LowerFast( SearchText[NrSearch] );     { translate to lower case }
          Write(' ', SearchText[NrSearch] );
        end;
        WriteLn;
        FindFirst(FMask, FAttr, FR);
        while DosError = 0 do
        begin
          Process(FR.Name);
          FindNext(FR);
        end;
        WriteLn( 'Total found ', TotalCount, ' entries.' );
        if (TotalCount = 0) then RmDir( DestDir );
      end;  { if not IOResult }
    end   { if not Exists }
    else
      writeln( Cannot, 'directory ', DestListing );
    { }
  end   { if ParamCount > 1 }
  else
    WriteLn( 'Extract filename word(s)' );
end.