Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Extended Directory Searches Using C++


MAR89: EXTENDED DIRECTORY SEARCHES USING C++

John is a programmer with Conductor Software and can be reached at 9208 W. Royal Lane, Irving, TX 75063 or on CompuServe at 74066, 3717.


Any decent program that deals with files must have some way to parse and construct file names. Consider, for example, a simple non-interactive program that accepts input and output file names on the command line. A simple program would accept a pair of names and blindly use them. A more robust program would check for validity, check for the extension and supply a default if needed, and generate the output name from the input name if the second parameter is missing.

In this article I'm presenting a program called MATCH.CPP that lets you use C++ to extract drive, path, basename, and extension components from an MS-DOS filename, and create a filename from component parts. This program makes use of a C++ class called dir_scanner that is much more convenient than similar methods traditionally used in C. MATCH.CPP, when used with the other files included in this article (DIRSCAN.HPP, DIRSCAN.CPP, and SCANHELP.ASM) and with your standard library, form a complete program. The latter half of main( ) shows the use of a dir_scanner, as well as a suggestion of how you can send a dir_data to a text file.

Dirpath

The first class, called "dirpath," is defined in Listing One (DIRPATH.CPP), page 99, as a structure. In C++ a structure (struct) is a class. The only difference between the two is that class members are private unless stated otherwise, while struct has all public members. Notice that the members of the structure are character arrays to hold the various pieces of a file name. You can read their contents or change them, just like any ordinary structure. The magic is in the member functions: The << operator parses a string into the structure, and >> concatenates all the pieces together into a full name.

In addition to the two operators, a pair of constructors is provided as well. If a dirpath is initialized with a single string, it is parsed into the structure (it simply calls operator<< inline). Alternatively, you can initialize a dirpath with four strings and they are copied into the four fields. Listing Two, page 99, illustrates the usage of the dirpath.

Now let's take a close look at the guts of dirpath. Both constructors are inline functions, and are stated in the header. Listing Three (DIRPATH.CPP), page 99, shows the implementation of << and >>. Defining the << operator is just like defining any other function whose name is operator<<. The operator is used between two items, but only one parameter is given. The left argument is this, which is automatically present in any member function. The this parameter is always a pointer to the class of which the function is a member. You rarely need to refer to the name this, because the members may be referred to directly--that is, you could say this->drive, but drive alone will do.

Dirpath is straightforward. It checks to see if a drive letter is present, and places it into drive if found. Next, the string is scanned for the last slash (subdirectory separator) and dot. The pieces found go their separate ways, via the copy( ) function. Dirpath copies a string but will not overflow the destination.

The operator >> reconstructs a name from the components. It is rather simple but is loaded with failsafes. Even though the parse will have put a colon after the drive name, a backslash after the path, and begun the extension with a dot, you might have replaced some of the fields and not been as neat. The operator >> will generate a valid filename.

Dirscan

Programs that deal with filenames will probably want to deal with generic filenames. In DOS, the wildcard characters * (asterisk) and ? (question mark) are used to abbreviate a name or to select a group of similar names. The use of * is very weak, however; for example, the * must be the last character in the base or extension. It would be much nicer if you could have as many *s as you like and put them anywhere in the name.

This leads us to a class called dir_scanner. In C you would probably use functions like dirfirst( ) and dirnext( ) or perhaps opendir( ) and readdir( ). These functions are difficult and clumsy to use and have subtle differences among compilers. In C++ an iterator class makes things much easier. Take a look at Listing Four (DIRSCAN.HPP), page 99.

The structure for DOS's directory entries is described by the class dir_data. Notice that this is a class and not a struct. The reserved area is private. Other than that, dir_data behaves like an ordinary struct in that the members may be freely used, like in C. Because the class has no member functions and no friends, the reserved area cannot be accessed anywhere in the program. In C you could say that this area is hands-off; in C++ you can mean it.

When you want a directory listing, you declare a variable of type dir_scanner. The constructor takes the wild file specification and the mode. If the second parameter is missing, zero is taken as a default.

The dir_scanner class is an iterator. Iterators come in different forms, reflecting the way they are intended to be used. The simplest case is a single function that returns the next value each time it is called, and some sentinel value or error code when no elements remain. (There might be an infinite number of elements, in which case you do not have to worry about the end of the series.) A two-step iterator, on the other hand, has two steps. One function advances to the next element and reports on the success or failure of the operation. Another function fetches the current element. A two-step iterator is popular for two reasons: You might refer to the current element several times before advancing, and it may be easier to structure the loop if you know in advance that an element is present. ( Listing Eight [MATCH.CCP], page 100, demonstrates the use of class dir_scanner.)

Both parts of the iterator are not ordinary functions. The fetch function is operator( ). This is a popular choice for iterators or other classes where one function is dominant. Overloading the function call operator may look strange at first but is quite handy. This function may be defined with any number of arguments (zero, in this case). If arguments are present, they are placed between the ( ) just like in a function call. Defining the function, the name operator( ) is given followed by an argument list. This function returns an item of type dir_data by reference. Other languages (like Pascal) let you pass by reference, but C++ lets you return by reference as well. Here, the function is inline, and return by reference is used for efficiency.

The function to advance and test is not an ordinary function either. This is an overloading of the int operator. Casting a dir_scanner to an int causes this function to be called. This result is handy because you can use a dir_scanner in a while loop. If d is a dir-scanner, while(d) {... causes d to be implicitly casted to an int. The construct (int)d is familiar to C programmers, and int(d) is equivalent and preferred in C++. Both would cause operator int to be called.

If the use of the operators is confusing, you might want to imagine them as functions bool next( ) and void fetch( ).

Implementation

The dir_scanner must somehow find out which files are on the disk. The class dir_scanner is portable and easy to use. It is in the implementation of the class that you can use all the tricks and environment-specific knowledge. The implementation in Listing Five (DIRSCAN.CPP), page 99, is for MS-DOS.

Most C libraries include functions to read the directory, they are, however, all different. Most also have a way to call DOS functions, but, again, differences in implementations make calling DOS functions more trouble than it's worth. The functions are quite simple, so it is easier to call them in assembly language than it is to figure out how to do it in C++ for any given implementation.

In DOS, the "find first" and "find next" call uses a data area that must be preserved. Because it is allowed to have more then one dir_scanner going at the same time, each instance must have its own data -- another reason not to use the functions that came with the library. In the case of Zortech, for example, the supplied functions use an internal static buffer. Listing Six (SCANHELP.ASM), page 100, contains the two assembly language functions.

The constructor for dir_scanner calls dirscan_findfirst( ), and the advance and test function calls dirscan_findnext( ). In Listing Five, this accounts for about ten lines. There is much more here then necessary for simply providing a shell for DOS's directory searching functions.

Wildcard Names

Under DOS's filename search (int 21 functions 4e and 4f), the file name may contain wildcards. A ? matches any character, and an * finishes out the name. The * must be the last character in the base or extension. I decided to implement a much more robust system. First, asterisks should be allowed anywhere in the name, and multiple *s should be allowed. So names like A*ED.*, *188.*F*, and *I*.*I* are legal. (The last example will find any name that contains an I.)

The key is to ask DOS for all files, and sort out the matches within the dir_scanner. A function that matches enhanced wildcard specifications is the cornerstone. The way to allow multiple *s is to use recursion. The algorithm (Listing Seven, version a, page 100) is "the strings match if the first characters match and the rest of the strings match." There is no need to worry about breaking the name into base and extension because the pattern will contain a dot, it must match the dot in the filename, and the filename will always contain a dot.

The function matchname( ) looks at the first character of the pattern. If the first character is an *, it tries to match the rest of the pattern with the name. If it does not match, it skips a character in the name and tries again. It continues this way until a match is found (the * can stand for any number of characters) or until the end of the name is reached. This recursive handling of * is fundamental to the enhanced pattern matching. If the first character of the pattern is a ?, the first character of the name can be anything except a nul.

The technique in Listing Seven(a) would be great in Prolog, but it has something to be desired in C. The overhead of recursion is high. On the other hand, the name is only 13 characters long, so maybe it is OK. But some obvious improvements can be made with regards to consecutive ordinary characters. With the exception of *, all the recursion is at the end of execution. Whenever the recursive call is being returned, you can replace the call with a jump (rather than pushing parameters, assign the new values to them, then jump back to the beginning of the function).

Listing Seven(b) shows the result of applying tail-end recursion elimination to Listing Seven(a). The meaning is the same, but it has much less recursion. It is a rather ugly sight in C++, though. The last step (Listing Seven[c]) is to get rid of all the GOTOs. Sometimes the literal translation of the algorithm can be transformed in good code. This was much simpler than trying to come up with a good function from scratch.

Now the dir_scanner could use matchname( ) to match filenames, but it does more than this. The matchname( ) function is instead called from multi_match( ). The dir_scanner accepts search strings with multiple wild names. Separating the names by + finds matches to the first or the second. Separating with a semicolon will find all the names that match the first and do not match the second. Furthermore, the multiple name specification can be preceded by drive and path information. The MATCH.CPP program (Listing Eight, page 100) lets you try out these searches.

The constructor dir_scanner.:scanner( ) separates the path from the search string by breaking it at the last slash. The name is replaced by *.* and sent to the DOS primitive. It returns all files in the directory and multi_match( ) decides which ones to keep. The search specification is then sent to buildlists( ).

The multiple names are separated by buildlists( ) into two lists. The goodlist includes all the regular names, and the badlist contains the names that were preceded with semicolons. It scans the string to count the names, and allocates storage for the two lists. The lists point into the single buffer, and nulls placed into the buffer (overwriting the + or ;) break it into individual strings. This works better than allocating a bunch of small strings from the heap.

The function multi_match( ) decides if a name matches the search request. The name is accepted if it matches any of the names in the goodlist and does not match any of the names in the badlist. Each of the tests uses the matchname( ) function with its enhanced wildcards.

But matchname only works if the names are normalized. Specifically, both name and pattern must contain a dot. If the filename does not contain a dot, it is appended on the end. The search string is processed more so it accepts more flexible input.

Now you have several more tools for your C++ programs. These should let you write more powerful programs and write programs easier. After all, that was the idea when you moved up to C++.

More Details.

Table 1: Sample uses of the Match program.

  Command                   Use
  -------------------------------------------------------------------------

  *.EXE[+]*.COM             Examines all EXE and all COM files

  T*T.*; TEST.BAT           Searches for all files that begin and end in T
                            except for TEST.BAT

  *.*;COMMAND.COM           Searches for all files except COMMAND.COM

  D:\FOO\*.EXE+*.COM;*K*.*  Finds all EXE and COM files in D:\FOO\ that do
                            not contain the letter K in their names

_EXTENDED DIRECTORY SEARCHES USING C++_ by John M. Dlugosz

[LISTING ONE]

<a name="0093_000a">

// DIRPATH.HPP  Copyright 1988 by John M. Dlugosz

struct dirpath {
   char drive[3];
   char path[65];
   char base[9];
   char ext[5];
   void operator<< (char const*name);  //throw a name into the structure
   void operator>> (char *name);  //extract name from structure
   dirpath(char const* name) {*this << name;}
   dirpath(char const* d, char const* p, char const* b, char const* e)
      {strcpy(drive,d);strcpy(path,p);strcpy(base,b);strcpy(ext,e);}
   dirpath() {}
   };




<a name="0093_000b"><a name="0093_000b">
<a name="0093_000c">
[LISTING TWO]
<a name="0093_000c">

// example using dirpath
// by John M. Dlugosz

#include <stream.hpp>
#include <string.h>
#include <dirpath.hpp>

char infile[67], outfile[67];

void checkfiles (char const* name)
{     /* find input and output filenames */
dirpath d (name);
if (*d.ext == '\0') //supply default extension for input file
   strcpy(d.exe, ".C");  //notice dot in included
d >> infile;
strcpy (d.ext, ".OBJ");
d >> outfile;
}






<a name="0093_000d"><a name="0093_000d">
<a name="0093_000e">
[LISTING THREE]
<a name="0093_000e">

/*****************************************************
File: DIRPATH.CPP
Copyright 1988 by John M. Dlugosz, all rights reserved
   parse and combine file names
*****************************************************/

// you must define NULL for your compiler.  Most have a way of checking the
// model being compiled under.
#ifdef LPTR
#define NULL 0L
#else
#define NULL 0
#endif

#include <string.h>
#include "dirpath.hpp"

static void copy (char *dest, char const* source, char const* const limit, int count)
{
while (count-- && source <= limit) *dest++ = *source++;
*dest= '\0';
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

void dirpath::operator<< (char const* name)
{
//takes a pathname and splits it up into its components.
//any or all of the components may be missing.

//first, locate DRIVE letter
if (strlen(name) >= 2 && name[1]==':') {
   drive[0]= name[0];
   drive[1]= ':';
   drive[2]= '\0';
   name += 2;  }
else drive[0]= '\0';   //no drive found

//locate last slash and dot
char *p;
char *last_slash= NULL, *dot= NULL;
for (p= name; *p; p++)
   if (*p == '\\' || *p == '/') last_slash= p;
   else if (*p == '.') dot= p;
// p now points to the \0 at the end of the string
if (last_slash) { //path exists.  copy up to and including the last slash
   copy (path, name, last_slash, 64);
   name= last_slash+1;
   }
else *path= '\0';
if (dot) {  //break int base and ext
   copy (base, name, dot-1, 8);
   copy (ext, dot, p-1, 4);
   }
else {  //rest of string is base name, no ext.
   copy (base, name, p-1, 8);
   *ext= '\0';
   }
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

void dirpath::operator>> (char *name)
{
//concatenate the parts together into a full name.
//the name parameter better be long enough.
if (*drive) {
   *name++ = *drive;
   *name++ = ':';  }
if (*path) {
   strcpy (name, path);
   name += strlen (name);
   if (name[-1] != '/' && name[-1] != '\\')
      *name++ = '\\';
   }
strcpy (name, base);
if (*ext) {
   if (*ext != '.') strcat (name,".");
   strcat (name, ext);
   }
}





<a name="0093_000f"><a name="0093_000f">
<a name="0093_0010">
[LISTING FOUR]
<a name="0093_0010">


// DIRSCAN.HPP  Copyright 1988 by John M. Dlugosz

/* these values for attribute parameter in constructor
   are passed through to DOS's function 4e */
#define fa_READ_ONLY 0x01
#define fa_HIDDEN    0x02
#define fa_SYSTEM    0x04
#define fa_LABEL     0x08
#define fa_DIR       0x10
#define fa_ARCHIVE   0x20

class dir_data {
   char reserved[21];
public:
   unsigned char attribute;
   unsigned time;
   unsigned date;
   unsigned long size;
   char name[13];
   };

class dir_scanner {
   char **goodlist, **badlist;
   int goodcount, badcount;
   dir_data current_entry;
   bool done;
   bool firsttime;
   void near buildlists (char *filespec);
   bool near multi_match();
public:
   dir_scanner (char *filespec, unsigned attribute=0);
   ~dir_scanner();
   operator int();  //look up next entry, return 1.  If no next, return 0.
   dir_data& operator() () {return current_entry;}    //fetch current entry
   };





<a name="0093_0011"><a name="0093_0011">
<a name="0093_0012">
[LISTING FIVE]
<a name="0093_0012">


/*****************************************************
File: DIRSCAN.CPP
Copyright 1988 by John M. Dlugosz, all rights reserved
   implementation of class dir_scanner, which does
   enhanced file matching in a directory list.
*****************************************************/

enum bool {FALSE,TRUE};
#define NULL 0L  //define NULL properly for your compiler and model
#include <string.h>
#include "dirscan.hpp"
// these two function in assembly language (file SCANHELP.ASM)
extern int dirscan_findfirst (char const far *name, unsigned attribute, dir_data const far *dta);
extern int dirscan_findnext (dir_data const far *dta);

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

static void near dir_scanner::buildlists (char *pattern)
{
goodcount= badcount= 0;
for (char *p= pattern;;) {  //  pass 1 -- count them
   while (*p == ' ' || *p == '\t') p++;
   if (*p == '\0') break;
   if (*p == ';') badcount++;
   else goodcount++;
   do p++; while (*p != ' ' && *p != '\t' && *p != ';' && *p != '+' && *p != '\0');
   }
typedef char* POINTER;  //I need a type name so I can use NEW
goodlist= new POINTER [goodcount];  // allocate arrays
badlist= new POINTER [badcount];
int gcount=0, bcount= 0;
/* I remember the location of the end of the substring in lastbreak.  After
   parsing the next string, I stick a '\0' here.  I do this one string
   behind because it will overwrite the '+' or ';'  */
char *lastbreak= NULL;  //NULL means none found yet
for (;;) {  //  pass 2 -- chop it up
   while (*pattern == ' ' || *pattern == '\t') pattern++;
   /* Embedded whitespace tolerated.  You can even omit the '+' and seperate
      names with spaces.  But if you do have a '+' or ';', it must
      immediately preceed the name */
   if (*pattern == '\0') break;  //end of the string
   if (*pattern == ';')  //add to BAD list
      badlist[bcount++]= ++pattern;
   else {  //add to GOOD list
      if (*pattern == '+') pattern++;
      goodlist[gcount++]= pattern;  }
   if (lastbreak) *lastbreak= '\0';
   while (! (*pattern==' ' || *pattern=='\t' || *pattern=='\0' ||
             *pattern==';' || *pattern=='+')) pattern++;
   lastbreak= pattern;
   }
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

dir_scanner::dir_scanner (char *filespec, unsigned attribute)
{
char dir[67], *lastslash= NULL, *p;
int max= 63;

firsttime= TRUE;
// seperate the path information from the search specification
for (p= filespec; *p; p++)
   if (*p == ':' || *p == '/' || *p == '\\') lastslash= p;
   // notice that both \ and / are acceptable.
p= dir;
if (lastslash)   //copy directory information
   // if it is too long, it is truncated.  You might want to check and
   // return an error if this happens (max will be -1 after the loop)
   while (max-- && filespec <= lastslash) *p++= *filespec++;
   // if it is too long, it is truncated.  You might want to check and
   // return an error if this happens (max will be -1 after the loop)
strcpy (p, "*.*");
buildlists (lastslash ? lastslash+1 : filespec);
/* the first name is read differently then the others.  So read the first
   here, and set firsttime so the advance and test function will not
   advance. */
if (0 != dirscan_findfirst(dir, attribute, ¤t_entry)) done= TRUE;
else {
   done= FALSE;
   return;  }
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

dir_scanner::~dir_scanner()
{
delete goodlist;
delete badlist;
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

static bool near matchname (char const* pattern, char const* name)
{
while (*pattern) {
   if ((*pattern == *name) || (*pattern == '?' && *name)) {
      pattern++;
      name++;
      }
   else if (*pattern == '*') {
      for (;;) {
         if (matchname (pattern+1, name)) return TRUE;
         if (*name) name++;
         else return FALSE;
         }
      }
   else return FALSE;
   }
return *name == '\0';
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

static char * near normalize (char const* pattern)
{
static char buf[16];
if (*pattern == '.') {
   // if no basename, insert '*'.  i.e.  ".EXE" => "*.EXE"
   buf[0]= '*';
   strcpy (buf+1, pattern);
   return buf;  }
else if (!strchr(pattern,'.')) {
   // if no dot, extension of '*'.  i.e.  "FOO" => "FOO.*"
   strcpy (buf, pattern);
   strcat (buf, ".*");
   return buf;  }
return pattern;
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

static void near check_for_dot (char* name)
{     /* if name does not contain a dot, add one to the end */
while (*name)
   if (*name == '.') return;
   else name++;
*name++ = '.';
*name= '\0';
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

static bool near dir_scanner::multi_match()
{
/* a match occurs if the name does not match anything on the badlist,
   and does match something on the goodlist */
check_for_dot (current_entry.name);
int count;
for (count= 0;  count < badcount;  count++)
   if (matchname (normalize(badlist[count]), current_entry.name)) return FALSE;
for (count= 0;  count < goodcount;  count++)
   if (matchname (normalize(goodlist[count]), current_entry.name)) return TRUE;
return FALSE;
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

dir_scanner::operator int()
{     /* this is the advance and test function */
while (!done) {
   if (!firsttime) {
      if (0 != dirscan_findnext (¤t_entry)) {
         done= TRUE;
         return FALSE; }
      }
   else firsttime= FALSE;
   if (multi_match()) return TRUE;
   }
return FALSE;
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */






<a name="0093_0013"><a name="0093_0013">
<a name="0093_0014">
[LISTING SIX]
<a name="0093_0014">

;SCANHELP.ASM  Copyright 1988 by John M. DLugosz, all rights reserved

;extern int dirscan_findfirst (
;        char const far *name, unsigned attribute, dir_data const far *dta);
;extern int dirscan_findnext (dir_data const far *dta);

     .MODEL LARGE,C
; to adapt to any memory model, make sure the RET is the right kind, and
; that the parameters are accessed properly on the stack frame.  In
; MASM 5.1, just change the preceeding line.  LARGE and SMALL versions
; are enogth to service any model, because the data size does not matter
; thanks to the prototypes.


     .CODE

dirscan_findfirst proc uses DS, fname:DWORD, attribute:WORD, dta:DWORD
   lds DX,dta
   mov AH,1ah
   int 21h    ;set disk transfer area
   lds DX,fname
   mov CX,attribute
   mov AH,4eh
   int 21h    ;find it.
   jc error
   xor AX,AX  ;return 0 for ok.
error:  ;error code is already in AX
   ret
dirscan_findfirst endp


dirscan_findnext proc uses DS, dta:DWORD
   lds DX,dta
   mov AH,1ah
   int 21h    ;set disk transfer area
   mov AH,4fh
   int 21h    ;find it.
   jc error
   xor AX,AX
error:  ;error code already in AX
   ret
dirscan_findnext endp

     END





<a name="0093_0015"><a name="0093_0015">
<a name="0093_0016">
[LISTING SEVEN]
<a name="0093_0016">


// Version A

static bool near matchname (char const* pattern, char const* name)
{
if (*pattern == '?')
   return (*name != '\0' && matchname (pattern+1, name+1);
else if (*pattern == '*') {
   while (*++name)
      if (matchname (pattern+1, name)) return TRUE;
   return FALSE
   }
else if (*pattern == *name) {
   if (*pattern == '\0') return TRUE;
   else matchname (pattern+1, name+1);
   }
else return FALSE;
}



// Version B - apply tail-end recursion elimination

static bool near matchname (char const* pattern, char const* name)
{
restart:
if (*pattern == '?') {
   if (!*name) return FALSE;
   pattern++;
   name++;
   goto restart;
   }
else if (*pattern == '*') {
   while (*++name)
      if (matchname (pattern+1, name)) return TRUE;
   return FALSE
   }
else if (*pattern != *name) return FALSE
else if (*pattern == '\0') return TRUE;
else {
   pattern++;
   name++;
   goto restart;
   }
}


// Version C - get rid of goto's

static bool near matchname (char const* pattern, char const* name)
{
while (*pattern) {
   if (*pattern == *name) || (*pattern == '?' && *name) {
      pattern++;
      name++;
      }
   else if (*pattern == '*') {
      for (;;) {
         if (matchname (pattern+1, name) return TRUE;
         if (*name) name++;
         else return FALSE;
         }
      }
   else return FALSE;
   }
return *name == '\0';
}






<a name="0093_0017"><a name="0093_0017">
<a name="0093_0018">
[LISTING EIGHT]
<a name="0093_0018">

/*****************************************************
File:  MATCH.CPP
Copyright 1988 by John M. Dlugosz, all rights reserved
   demonstration of dir_scanner
*****************************************************/

enum bool {FALSE, TRUE};
#include <stream.hpp>
#include "dirscan.hpp"
#include <ctype.h>  //toupper() needed

/* The nl macro might already be in stream.hpp.  If it is not,
   you might want to add it.  Otherwise, define it here. */
#define nl << "\n"

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

ostream& operator<< (ostream& o, dir_data& d)
{
/* let output streams handle directory entries.  This just prints the
   name, but you might want to enhance it to prinmt the date, size, etc. */
o << d.name;
return o;
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */

void upcase (char* s)
{     /* convert string to all caps */
while (*s) {
   *s= toupper (*s);
   s++;
   }
}

/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */


main (int argc, char* argv[])
{
char s[80];
dir_data f;
if (argc < 2) {   //prompt user
   cout << "enter search request: ";
   cin >> s;
   cout nl;
   }
else strcpy (s, argv[1]);  //use command line if present
// the dir_scanner is assumes parameter is ALL CAPS
upcase (s);
dir_scanner d (s);  //notice second parameter is missing, so default is used.
int number= 0;  //count how many found
while (d) {     //advance and test function called here
   f= d();      //fetch function called
   number++;      //count it
   cout << f nl;  //display it
   }
cout << number << " files found." nl;
}












Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.