Category : C Source Code
Archive   : QUOTE25B.ZIP
Filename : QUOTE.C

 
Output of file : QUOTE.C contained in archive : QUOTE25B.ZIP

/*-------------------------------------------------------------------------*/
/* Program: Quote .C */
/* Purpose: Displays quotes randomly selected from a quote datafile. */
/* Notes: Compiles under Borland C++, v3.1. Should work on any */
/* machine running DOS, v2.xx or higher. */
/* Must be compiled as a .COM file for config() to work. */
/* Status: Source released into the public domain. If you find this */
/* program useful, I'd appreciate a postcard from you. */
/* Updates: 21-Mar-87, GAT */
/* - initial version (in assembler). */
/* 24-Apr-87, GAT */
/* - fixed bug in FindQuote when 1st $ at end of file. */
/* 27-Jan-88, GAT */
/* - increased size of quote buffer from 256 bytes. */
/* 29-Jan-88, GAT */
/* - prevented problem when pointing to very beginning of */
/* quote datafile. */
/* 11-Jun-89, GAT */
/* - ported program to C. */
/* - provided brief, on-line help message. */
/* - added repeat, clear-screen, and reconfigure options. */
/* 25-Jun-89, GAT */
/* - changed long ints in Quote() to unsigned long ints. */
/* This avoids attempts to index negatively into file. */
/* 08-Jan-90, GAT */
/* - Replaced #defines with enumerations. */
/* - Used AT&T's getopt() for program args. */
/* - Separated usage message into its own function. */
/* - Tweaked variable storage. */
/* - Fixed bug in quote display if quote datafile began */
/* with a '$'. */
/* - Fixed bug in quote display if SpotInFile overflowed. */
/* - Changed unsigned long ints in display_Quote() back to */
/* long ints but avoided negative indices with labs(). */
/* 02-Aug-90, GAT */
/* - Rewrote display_Quote() to avoid bug when using small */
/* quote datafiles. */
/* - Added lrand() to return a long int. */
/* - Quote datafile is now opened in binary mode to */
/* minimize hassle of working with CRs. */
/* - Quotes are displayed a character at a time so that */
/* CRs occuring in buffer with LFs can be dropped. */
/* - Terminate main() by return rather than exit(). */
/* 04-Apr-91, GAT */
/* - Added copyright message. */
/* - Made use of new TifaWARE library functions. */
/* - Removed explicit support for DEC Rainbows. */
/* - Added calls to assert() for debugging. */
/* - Made casts in update_config() less convoluted. */
/* 27-Feb-93, GAT, v2.5a */
/* - Cleaned up code and comments. */
/* - Switched stdout to binary mode so I could print */
/* quotes as strings rather than series of characters. *
/* 05-Jul-93, GAT, v2.5b */
/* - compiled with BCC 3.1. */
/*-------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------*/
/* Author: George A. Theall */
/* SnailMail: TifaWARE */
/* 610 South 48th St */
/* Philadelphia, PA. 19143 */
/* U.S.A. */
/* E-Mail: [email protected] */
/* [email protected] */
/* [email protected] */
/* [email protected] */
/*-------------------------------------------------------------------------*/


#ifndef __TINY__
#error *** Turbo C's TINY model required for compilation ***
#endif

/* Useful type definitions. */
typedef int BOOLEAN;

typedef enum { /* error classes */
err_nrep, /* bad repetition count */
err_quote, /* bad quote found */
err_open, /* can't open a file */
err_read, /* can't read from file */
err_write /* can't write to file */
} ERR_TYPE;

typedef enum { /* return codes */
rc_ok = 0,
rc_help = 1,
rc_open = 10,
rc_read = 15,
rc_write = 20
} RC_TYPE;

#define FALSE 0
#define TRUE 1
#define MAX_QUOTE 512 /* maximum size of a quote */

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "tifa.h" /* TifaWARE library routines */

char ProgName[MAXFILE]; /* space for filename */
static char FullProgName[MAXPATH]; /* path, name, extension */
static char QDat[MAXPATH] = "quote.dat"; /* includes space for path */
static BOOLEAN /* option flags */
cFlag = FALSE, /* reconfigure program? */
hFlag = FALSE, /* needs help? */
sFlag = FALSE, /* clear screen first? */
vFlag = FALSE; /* verbose reports? */
static int nReps = 1; /* number of repetitions */

/* Define the program's error messages. */
/*
* NB: getopt() itself is responsible for generating the following
* error messages, which do not appear in the structure below:
* ": illegal option -- %c"
* ": option requires an argument -- %c"
*/
const static struct {
ERR_TYPE Type;
char *Msg;
} Error[] = {
{err_nrep, ": invalid rep count; %d used instead"},
{err_quote, ": invalid quote found around ==>%s<=="},
{err_open, ": can't open %s"},
{err_read, ": error reading from %s"},
{err_write, ": can't write to %s"}
};

void _setenvp(void) {}; /* drop some start-up code */


/*--- main --------------------------------------------------------------+
| Purpose: Main body of program. |
| Notes: none |
| Entry: argc = argument count, |
| argv = array of argument variables. |
| Exit: Return code as enumerated by RC_TYPE. |
+-------------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
char ch;
void write_usage(void);
void update_config(void);
void display_quote(void);


/*
* Store complete program name in case we're reconfiguring.
* Also, isolate program name for reporting errors.
*/
strcpy(FullProgName, (_osmajor < 3) ? "quote.com" : strlwr(*argv));
fnsplit(FullProgName, NULL, NULL, ProgName, NULL);

/*
* Process commandline arguments. NB: all options must appear before
* any filename. Also, specifying more than one filename is invalid.
*/
while (EOF != (ch = getopt(argc, argv, "cr:s:v?")))
switch (ch) {
case 'c': cFlag = TRUE; break; /* change config */
case 'r': /* multiple quotes */
if (strspn(optarg, "0123456789") != strlen(optarg))
write_errmsg(Error[err_nrep].Msg, nReps);
else
nReps = atoi(optarg);
break;
case 's': sFlag = ('+' == *optarg); break; /* clear screen */
case 'v': vFlag = TRUE; break; /* verbose reporting */
case '?': hFlag = TRUE; break; /* help needed */
default: break; /*** UNREACHED ***/
}
argc -= optind;
if (TRUE == hFlag || argc > 1) {
write_usage();
return rc_help;
}
if (argc > 0) { /* alternate quote datafile? */
argv += optind;
strcpy(QDat, *argv);
}

/* All that's left is do whatever the user requested! */
if (TRUE == cFlag)
update_config();
else
display_quote();
return rc_ok;
}


/*--- write_usage -------------------------------------------------------+
| Purpose: Provides a brief message about program usage. |
| Notes: none |
| Entry: n/a |
| Exit: n/a |
+-------------------------------------------------------------------------*/
void write_usage(void)
{
fprintf(stderr,
"TifaWARE QUOTE, v" VERS_STR ", displays randomly selected quotes.\n"
"Copyright (c) 1991-1993 by TifaWARE/George A. Theall.\n"
"\n"
"Usage: %s [options] [qdat]\n"
"\n"
"Options:\n"
" -c = update program configuration\n"
" -rn = set repetition count to n\n"
" -s[+,-] = clear/don't clear screen first\n"
" -v = verbose reporting\n"
" -? = provide this help message\n"
"\n"
"Qdat refers to the quote datafile; it defaults to %s.\n",
ProgName, QDat);
}


/*--- update_config ------------------------------------------------------+
| Purpose: Updates program configuration based on option settings. |
| Notes: This is highly DOS-specific and hence non-portable! |
| Requires Turbo C's TINY model and COM file format. |
| Data structures being configured must be global. |
| Program aborts if an error occurs updating configuration. |
| Entry: n/a |
| Exit: n/a |
+-------------------------------------------------------------------------*/
void update_config(void)
{
FILE *fp;


if (NULL == (fp = fopen(FullProgName, "rb+"))) {
write_errmsg(Error[err_open].Msg, FullProgName);
exit(rc_open);
}

/*
* Advance file pointer to each of options and write out their
* current values. NB: recognize that COM files are loaded
* into memory at offset 0x100 in DS.
*/
fseek(fp, ((unsigned int) &QDat-0x100), SEEK_SET);
fwrite(&QDat, 1, sizeof(QDat), fp); /* Revise name of datafile */
fseek(fp, ((unsigned int) &sFlag-0x100), SEEK_SET);
fwrite(&sFlag, 1, sizeof(sFlag), fp); /* ... then clear-screen flag */
fseek(fp, ((unsigned int) &nReps-0x100), SEEK_SET);
fwrite(&nReps, 1, sizeof(nReps), fp); /* ... and repetition count */

fclose(fp);
if (ferror(fp)) {
write_errmsg(Error[err_write].Msg, FullProgName);
exit(rc_write);
}
printf("new configuration of %s accepted.\n", FullProgName);
}


/*--- display_quote ------------------------------------------------------+
| Purpose: Displays one or more quotes chosen at random. |
| A buffer big enough for 2 quotes is used since this way |
| we're guarenteed to find at least one full quote. |
| Quote datafile is opened in binary mode so input buffer |
| will contain CRs, which must then be skipped when the |
| quote is printed. |
| Notes: Program aborts if an error occurs reading from quote file. |
| Entry: n/a |
| Exit: n/a |
+-------------------------------------------------------------------------*/
void display_quote(void)
{
char Buffer[MAX_QUOTE+2], /* buffer for 1 quote + 2 "$"s */
*qp;
int ncbuf,
nc2adjust;
long fpos,
fsize;
FILE *fp;

long lrand(void);


/* Open quote file and determine its size. */
if (NULL == (fp = fopen(QDat, "rb"))) {
write_errmsg(Error[err_open].Msg, QDat);
exit(err_open);
}
fseek(fp, 0L, SEEK_END);
fsize = ftell(fp);

/*
* Since quotes are read in binary mode, it's necessary either
* to strip out CRs or open stdout in binary mode. This prevents
* CRs from being duplicated when output is redirected.
*/
setmode(fileno(stdout), O_BINARY);

if (TRUE == sFlag)
clrscr();

/* See random number generator based on current time. */
randomize();

while (nReps--) {
fpos = lrand() % fsize; /* pick random spot in file */
if (TRUE == vFlag)
write_errmsg(": reading from %s at offset %li..", QDat, fpos);
fseek(fp, fpos, SEEK_SET);
if (0 == (ncbuf = fread(Buffer, sizeof(char), sizeof(Buffer), fp))) {
write_errmsg(Error[err_read].Msg, QDat);
exit(rc_read);
}

/*
* Find first "$"; this will mark the end of the quote. NB: memchr()
* is used instead of strchr() because Buffer is *not* guaranteed to
* be null-terminated. Also, there must be at least 1 "$" in buffer.
*/
if (NULL == (qp = memchr(Buffer, '$', ncbuf))) {
*(Buffer+ncbuf) = '\0'; /* omit junk in buffer */
write_errmsg(Error[err_quote].Msg, Buffer);
break;
}

/*
* Calculate how far over characters must be shifted so
* the terminal "$" found above lies at end of buffer.
* Adjust file pointer accordingly. NB: If this adjustment
* would cause an error then don't fill up buffer; instead,
* read from bof the number of chars between Buffer and qp.
*/
nc2adjust = Buffer + sizeof(Buffer) - qp - 1;
if (fpos < (long) nc2adjust) { /* can we read in this far? */
ncbuf = ((int) fpos) + qp - Buffer + 1;
fpos = 0L;
fseek(fp, 0L, SEEK_SET);
}
else {
fpos -= nc2adjust;
fseek(fp, (-1 * (ncbuf + nc2adjust)), SEEK_CUR);
ncbuf = sizeof(Buffer);
}

/*
* Read again from file into buffer. Nullify the final "$"
* so quote can be treated like a string. Then scan back-
* wards to find the start of the quote.
*/
ncbuf = fread(Buffer, sizeof(char), ncbuf, fp);
*(Buffer + ncbuf - 1) = '\0';
qp = strrchr(Buffer, '$');

/*
* If starting "$" was found, then advance pointer to start of
* quote; otherwise, bomb with an error message unless we're
* at the start of the file (no "$" at start of first quote).
*/
if (NULL != qp)
++qp;
else
if (0 == fpos)
qp = Buffer;
else {
write_errmsg(Error[err_quote].Msg, Buffer);
break;
}
puts(qp);
} /* end, nReps */
fclose(fp);
}


/*--- lrand --------------------------------------------------------------+
| Purpose: Returns a long int random deviate. |
| Notes: This is inherently non-portable. It *may* work if negative |
| numbers are represented using two's complement notation |
| and sizeof(long) == 2 * sizeof(int). |
| Entry: n/a |
| Exit: random long int in range [0, LONG_MAX]. |
+-------------------------------------------------------------------------*/
long lrand(void)
{
return (((long) rand() << (sizeof(int) * CHAR_BIT))
+ (rand() + (rand() % 2 << (sizeof(int) * CHAR_BIT - 1))));
}