home *** CD-ROM | disk | FTP | other *** search
- /*-------------------------------------------------------------------------*/
- /* Program: Quote .C */
- /* Purpose: Displays quotes randomly selected from a quote database. */
- /* Notes: Compiles under TURBO C++, v1.0. Should work on any machine */
- /* running MS-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, please send me a postcard. */
- /* 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 database. */
- /* 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 database 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 database 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. */
- /*-------------------------------------------------------------------------*/
-
- /*-------------------------------------------------------------------------*/
- /* Author: George A. Theall */
- /* Phone: +1 215 662 0558 */
- /* SnailMail: TifaWARE */
- /* 506 South 41st St., #3M */
- /* Philadelphia, PA. 19104 USA */
- /* E-Mail: GTHEALL@PENNDRLS.UPENN.EDU (Internet) */
- /*-------------------------------------------------------------------------*/
-
-
- #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_cfg, /* program reconfigured */
- 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_cfg = 2,
- rc_open = 10,
- rc_read = 15,
- rc_write = 20
- } RC_TYPE;
-
- #define FALSE 0
- #define TRUE 1
- #ifndef VERS
- # define VERS "???" /* someone goofed in compiling */
- #endif
- #define MAX_QUOTE 512 /* maximum size of a quote */
-
- #include <assert.h> /* for assert() */
- #include <conio.h> /* clrscr() - via BIOS call */
- #include <dir.h> /* for fnsplit(), MAXFILE, etc */
- #include <limits.h> /* for CHAR_BIT */
- #include <stdarg.h> /* for va_arg, etc.. */
- #include <stdio.h>
- #include <stdlib.h> /* for exit(), getenv(), etc */
- #include <string.h> /* for strlwr() */
- #include <time.h> /* for randomize() */
- #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_cfg, ": new configuration of %s accepted",
- err_quote, ": invalid quote found around ==>%s<==",
- err_open, ": can't open %s",
- err_read, ": error while reading from %s",
- err_write, ": unable to write new configuration 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 for later in case we're reconfiguring. */
- if (_osmajor < 3) /* TURBO C extension! */
- strcpy(FullProgName, "QUOTE.COM"); /* since argv[0] is NULL */
- else
- strcpy(FullProgName, *argv);
-
- /* Isolate program name to keep error messages neat. */
- fnsplit(FullProgName, NULL, NULL, ProgName, NULL); /* TURBO C extension! */
- *argv = strlwr(ProgName); /* TURBO C extension! */
-
- /* All options must appear before any filenames. */
- while ((ch = getopt(argc, argv, "cr:s:v?")) != EOF)
- switch (ch)
- {
- case 'c': cFlag = TRUE; break; /* reconfigure program */
- case 'r': /* multiple quotes */
- if (strspn(optarg, "0123456789") != strlen(optarg))
- write_errmsg(Error[err_nrep].Msg, nReps); /* bad nreps */
- else
- nReps = atoi(optarg);
- break;
- case 's': /* clear screen */
- sFlag = (*optarg == '+');
- break;
- case 'v': vFlag = TRUE; break; /* verbose reporting */
- case '?': hFlag = TRUE; break; /* help needed or requested */
- default: ; /* EMPTY */ /* Unreached */
- }
- do
- {
- --argc;
- ++argv;
- } while (--optind); /* nb: optind >= 1 in getopt() */
-
- if (hFlag == TRUE || argc > 1) /* no more than 1 file name */
- {
- write_usage();
- return rc_help;
- }
-
- /*
- * At this point, argc should be 0 or 1, depending on whether
- * default quote database is being used.
- */
- assert(argc == 0 || argc == 1);
- if (argc == 1)
- strcpy(QDat, *argv);
- if (cFlag == TRUE) /* update configuration */
- {
- update_config();
- return rc_cfg;
- }
- else /* or display quote */
- {
- 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 ", displays randomly selected quotes.\n"
- "Copyright (c) 1991 by TifaWARE/George A. Theall.\n"
- "\n"
- "Usage: %s [options] [qdat]\n"
- "\n"
- "Options:\n"
- " -c = configure program using current arguments\n"
- " -rn = set the repetition count to n quotes\n"
- " -s[+,-] = clear/don't clear screen first\n"
- " -v = verbose reporting\n"
- " -? = provide this help message\n"
- "\n"
- "Qdat refers to the quote database. If it's not specified, then file\n"
- "%s will be used.\n", ProgName, QDat);
- }
-
-
- /*--- update_config ------------------------------------------------------+
- | Purpose: Updates program configuration based on option settings. |
- | Notes: Requires Turbo C's TINY model and a .COM file. |
- | This works only with MS-DOS! |
- | Data structures being configured must be global. |
- | If DOS v2.xx or earlier is being used, then the program |
- | must be named .\QUOTE.COM. |
- | Program aborts if an error occurs updating configuration. |
- | Entry: n/a |
- | Exit: n/a |
- +-------------------------------------------------------------------------*/
- void update_config(void)
- {
- FILE *fp;
-
- if ((fp = fopen(FullProgName, "rb+")) == NULL)
- {
- write_errmsg(Error[err_open].Msg, FullProgName); /* couldn't open */
- exit(rc_open);
- }
-
- /*
- * Advance file pointer to each of options and write out their
- * current values. NB: it must be recognized that .COM files
- * are loaded into memory at offset 0x100 in DS.
- */
- fseek(fp, (char *) &QDat - (char *) 0x100, SEEK_SET);
- fwrite(&QDat, 1, sizeof(QDat), fp); /* Revise name of database */
- fseek(fp, (char *) &sFlag - (char *) 0x100, SEEK_SET);
- fwrite(&sFlag, 1, sizeof(sFlag), fp); /* ... then clear-screen flag */
- fseek(fp, (char *) &nReps - (char *) 0x100, SEEK_SET);
- fwrite(&nReps, 1, sizeof(nReps), fp); /* ... and repetition count */
-
- fclose(fp);
- if (ferror(fp) != FALSE)
- {
- write_errmsg(Error[err_write].Msg, FullProgName); /* couldn't write */
- exit(rc_write);
- }
- write_errmsg(Error[err_cfg].Msg, FullProgName); /* new configuration */
- }
-
-
- /*--- 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. |
- | 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; /* pointer to a quote */
- int ncbuf, /* num of chars in buffer */
- nc2adjust; /* num of chars to adjust */
- long fpos, /* some spot in file */
- fsize; /* file size */
- FILE *fp;
- long lrand(void);
-
- /*
- * Open file and figure out its size. NB: fp is opened in binary
- * mode so input buffer will contain CRs. Later they must be
- * avoided when it comes time to print out the quote.
- */
- if ((fp = fopen(QDat, "rb")) == NULL)
- {
- write_errmsg(Error[err_open].Msg, QDat); /* couldn't open */
- exit(err_open);
- }
- fseek(fp, 0L, SEEK_END);
- fsize = ftell(fp);
-
- randomize(); /* based on clock ticks */ /* TURBO C extension! */
- if (sFlag != FALSE)
- clrscr(); /* TURBO C extension! */
-
- while (nReps-- > 0)
- {
-
- /*
- * Generate a random number between 0 and file size. Then
- * move the file pointer there and fill up the buffer.
- */
- fpos = lrand() % fsize;
- if (vFlag == TRUE)
- write_errmsg(": reading from %s at offset %li..", QDat, fpos);
- fseek(fp, fpos, SEEK_SET);
- if ((ncbuf = fread(Buffer, sizeof(char), sizeof(Buffer), fp)) == 0)
- {
- write_errmsg(Error[err_read].Msg, QDat); /* couldn't read */
- 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 ((qp = memchr(Buffer, '$', ncbuf)) == NULL)
- {
- *(Buffer + ncbuf) = '\0'; /* omit junk in buffer */
- write_errmsg(Error[err_quote].Msg, Buffer); /* bad quote */
- 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 (qp != NULL)
- ++qp;
- else
- if (fpos == 0)
- qp = Buffer;
- else
- {
- write_errmsg(Error[err_quote].Msg, Buffer); /* bad quote */
- break;
- }
-
- /*
- * If stdout were opened as a binary stream CRs would be
- * duplicated should output be redirected to, say, LIST.
- * Character-at-a-time output may be slow, but it works.
- */
- for (; *qp != '\0'; ++qp)
- if (!(*qp == '\r' && *(qp+1) == '\n'))
- fputc(*qp, stdout);
- fputc('\n', stdout);
- } /* 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))));
- }
-