[alt.sources] lq-text Full Text Retrieval Database Part 07/13

lee@sq.sq.com (Liam R. E. Quin) (03/04/91)

: cut here --- cut here --
: To unbundle, sh this file
#! /bin/sh
: part 07
echo x - lq-text/src/lqtext/lqkwik.c 1>&2
sed 's/^X//' >lq-text/src/lqtext/lqkwik.c <<'@@@End of lq-text/src/lqtext/lqkwik.c'
X/* lqkwik.c -- Copyright 1991 Liam R. Quin.  All Rights Reserved.
X * This code is NOT in the public domain.
X * See the file COPYRIGHT for full details.
X */
X
X/* lqkwik -- produce a keyword-in-context list of matches...
X * Liam R. Quin, February 1991 and later...
X *
X * $Id: lqkwik.c,v 1.1 91/03/02 20:37:47 lee Rel1-10 $
X */
X
X#define COLS 65   /* the width of kwik    word index */
X#define WORDCOL 25 /* where to put the    word in the index */
X#define GAPWIDTH 2 /* space before the    word itself */
X#define SCREENWIDTH 79
X
Xint Cols = COLS;
Xint WordCol = WORDCOL;
Xint GapWidth = GAPWIDTH;
Xint ScreenWidth = SCREENWIDTH;
X
Xstatic int LinesAbove = 5;
Xstatic int linesBelow = 5;
X
X#include "globals.h" /* defines and declarations for database filenames */
X
X#include <malloc.h>
X#include <fcntl.h>
X#include <ctype.h>
X#include <sys/types.h> /* for fileinfo.h */
X#include <sys/stat.h>
X
X#include <stdio.h>
X
X#include "fileinfo.h"
X#include "wordinfo.h"
X#include "wordrules.h"
X#include "pblock.h"
X#include "emalloc.h"
X
X/** Unix system calls that need declaring: **/
Xextern long lseek();
Xextern int open(), close();
Xextern int read();
Xextern void exit();
Xextern int stat();
X
X/** Unix/C Library Functions that need declaring: **/
X#ifndef tolower
X extern int tolower();
X#endif
Xextern int strlen();
Xextern int strcmp();
Xextern unsigned sleep();
Xextern int atoi();
Xextern long atol();
Xextern void perror();
X
X/** lqtext library functions that need declaring: **/
Xextern int MySystem();
Xextern int TooCommon();
Xextern void SetDefault();
Xextern void DefaultUsage();
X
X/** Functions within this file that are used before being defined: **/
Xint ReadMatchFile();
Xint ShowFile();
Xvoid Output();
X
X/** **/
X
X/** some useful macros: **/
X#define max(choir,boy) (((choir)>(boy))?(choir):(boy))
X#define min(choir,boy) (((choir)<(boy))?(choir):(boy))
X
X/** **/
X
Xint AsciiTrace = 0;
X
Xextern int errno;
X
Xchar *progname = "lqkwik"; /* set from argv[] in main() */
X
Xint SelectedNames = -1;
XFILE *InfoStream = 0;
X
Xstatic char *Revision = "@(#) showfile.c 2.2";
X
Xint
Xmain(argc, argv)
X    int argc;
X    char *argv[];
X{
X    extern int optind, getopt();
X    extern char *optarg; /* for getopt */
X    int ch; /* for getopt */
X    int ErrFlag = 0; /* see how getopt makes programs cleaner? */
X    int NumberOfFiles;
X    char *FileWithMatches = (char *) 0;
X    char **MatchList;
X    int MatchCount = 0;
X    int Origc;
X    int Right = Cols - (WordCol + GapWidth);
X    int Left = WordCol - GapWidth;
X
X    progname = argv[0];
X
X    SetDefaults(argc, argv);
X
X    /* All lq-text programs must call SetDefaults() before getopt, and
X     * must then be prepared to ignore options z with arg and Z without.
X     */
X    while ((ch = getopt(argc, argv, "a:b:f:l:r:g:w:o:z:ZVvx")) != EOF) {
X	switch (ch) {
X	case 'z':
X	    break; /* done by SetDefaults(); */
X	case 'V':
X	    fprintf(stderr, "%s version %s\n", progname, Revision);
X	    break;
X	case 'v':
X	    AsciiTrace = 1;
X	    break;
X	case 'f':
X	    FileWithMatches = optarg;
X	    break;
X	case 'g':
X	    GapWidth = atoi(optarg);
X	    break;
X	case 'l':
X	    Left = atoi(optarg);
X	    break;
X	case 'r':
X	    Right = atoi(optarg);
X	    break;
X	case 'w':
X	    ScreenWidth = atoi(optarg);
X	    break;
X	case 'x':
X	    ErrFlag = (-1);
X	    break;
X	case '?':
X	default:
X	    ErrFlag = 1;
X	}
X    }
X
X    if (ErrFlag < 0) { /* -x or -xv was used */
X	fprintf(stderr, "usage: %s [-xv] [options] [matches...]\n", progname);
X	fprintf(stderr,
X	"use %s -x, -xv or -xvv for more detailed explanations.\n", progname);
X
X	if (AsciiTrace) {
X	    DefaultUsage();
X	    fprintf(stderr, "\n\
X	-f file -- \"file\" contains a list of matches, one per line\n");
X	    fprintf(stderr, "\
X	-g n    -- set gap between text and matched phrase to n [%d]\n\
X	-l n    -- display n characters to the left of each phrase [%d]\n\
X	-r n    -- display r chars to the right of each phrase's start [%d]\n\
X	-w n    -- truncate the line after n characters [default: %d]\n",
X		    GapWidth,
X		    Left,
X		    Right,
X		    ScreenWidth
X	    );
X	}
X	if (AsciiTrace > 1) {
X	    fputs("\
X	Matches should be in the form of\n\
X		BlockNumber  WordInBlock  FileName\n\
X	where BlockBumber and WordInBlock are positive numbers.\n\
X	(This is the format produced by the lqword -l command.)\n\
X", stderr);
X	}
X	exit(0);
X    } else if (ErrFlag > 0) {
X	fprintf(stderr, "use %s -x for an explanation.\n", progname);
X	exit(1);
X    }
X
X    Cols = Left + Right + GapWidth;
X    WordCol = Left + GapWidth;
X
X    if (AsciiTrace) {
X	fprintf(stderr,"Left:%d  Right:%d  Cols:%d  Gap:%d  WC:%d  SW:%d\n",
X			Left, Right,	  Cols, GapWidth, WordCol,ScreenWidth);
X    }
X
X    if (ScreenWidth <= Cols + 2) {
X	fprintf(stderr,
X	    "%s: ScreenWidth %d, %d text cols -- no room for file names!\n",
X	    progname, ScreenWidth, Cols);
X	exit(1);
X    }
X
X    /* open the file for the selected output */
X    if (SelectedNames > 0) {
X	if ((InfoStream = fdopen(SelectedNames, "w")) == (FILE *) 0) {
X	    int e = errno;
X
X	    fprintf(stderr, "%s: -o %d: can't open stream ",
X	    					progname, SelectedNames);
X	    errno = e;
X	    perror("for writing");
X	    exit(1);
X	}
X    }
X
X    /* check that we can get at the file containing the matches, if one
X     * was supplied.
X     */
X    if (FileWithMatches) {
X	struct stat StatBuf;
X	char *msg = 0;
X
X	if (stat(FileWithMatches, &StatBuf) < 0) {
X	    int e = errno; /* on many systems, fprintf() changes errno! */
X	    fprintf(stderr, "%s: can't open match-list file ", FileWithMatches);
X	    errno = e;
X	    perror(progname);
X	    exit(1);
X	} else if (AsciiTrace) {
X	    switch (StatBuf.st_mode & S_IFMT) {
X	    case S_IFDIR:
X		fprintf(stderr,
X		"%s: ca't read matches from \"%s\" -- it's a directory!\n",
X						progname, FileWithMatches);
X		exit(1);
X	    case S_IFREG:
X		break;
X#ifdef S_IFIFO
X	    case S_IFIFO:
X		msg = "named pipe or fifo";
X		/* fall through */
X#endif
X	    case S_IFCHR:
X		if (!msg) msg = "raw special device";
X		/* fall through */
X	    case S_IFBLK:
X		if (!msg) msg = "block special device";
X		/* fall through */
X#ifdef S_IFNAM
X	    case S_IFNAM:
X		if (!msg) msg = "named special file"; /* wot dat? */
X		/* fall through */
X#endif
X	    default:
X		if (!msg) msg = "special file";
X
X		fprintf(stderr,
X		    "%s: warning: file \"%s\" containing matches is a %s\n",
X		    progname, FileWithMatches, msg);
X		
X		/* but continue anyway... */
X
X	    }
X	}
X	/* Now read the file, and make an array of matches... */
X	if (ReadMatchFile(FileWithMatches, StatBuf.st_size, &MatchCount, &MatchList) < 0) {
X	    fprintf(stderr, "%s: couldn't read matches from \"%s\"\n",
X						progname, FileWithMatches);
X	    exit(1);
X	}
X    }
X
X    argv += optind;
X    argc -= optind;
X
X    if (MatchCount) {
X	argc = MatchCount;
X	argv = MatchList;
X    }
X
X    if (argc < 3) {
X	fprintf(stderr,
X	"%s: matches must have at least 3 parts; use -xv for an explanation\n",
X								progname);
X	exit(1);
X    } else if (argc % 3) {
X	/* Note: I could detect lqword output here (i.e., without -l) */
X	fprintf(stderr, "%s: can't understand match format;\n", progname);
X	fprintf(stderr, "%s: use -xv for more explanation.\n", progname);
X	exit(1);
X    }
X
X    Origc = argc;
X
X    NumberOfFiles = argc / 3;
X
X    while (argc > 0) {
X	int Where;
X
X	if (ShowFile(argv[2], atol(*argv), (unsigned) atoi(argv[1])) < 0) {
X	    int i;
X
X	    /* This avoids repeated messages about the same file */
X	    for (i = argc - 3; i > 0; i -= 3) {
X		if (STREQ(argv[2], argv[2 + 3])) {
X		    argv += 3;
X		} else {
X		    break;
X		}
X	    }
X	    argc = i + 3; /* so we can subtract 3 ... */
X
X	}
X	argv += 3;
X	argc -= 3;
X    }
X
X    return 0;
X}
X
Xint
XReadMatchFile(FileWithMatches, FileSize, MatchCount, MatchList)
X    char *FileWithMatches;
X    off_t FileSize;
X    int *MatchCount;
X    char ** *MatchList;
X{
X    extern char *strtok();
X
X    int fd;
X    char **Result;
X    char *StorageArea;
X    char *p;
X    unsigned int n_matches;
X    int BytesRead;
X    int i;
X    char *NextStr;
X
X    if (!FileWithMatches || !*FileWithMatches) {
X	fprintf(stderr, "%s: match-list file (from -f) has empty name!\n",
X								progname);
X	exit(1);
X    }
X
X    if ((fd = open(FileWithMatches, O_RDONLY)) == 0) {
X	int e = errno;
X	fprintf(stderr, "%s: can't open match-list file ", progname);
X	errno = e;
X	perror(FileWithMatches);
X	exit(1);
X    }
X
X    /* We know the number of bytes, and each space or newline will get
X     * turned into a null, so here goes...
X     * The +1 below is to ensure that there is space for a \0, even if a
X     * pesky user didn't put a \n at * the end of the file...
X     * Sometimes I hate emacs...
X     */
X    if ((StorageArea = malloc((unsigned) FileSize + 1)) == (char *) 0) {
X	fprintf(stderr, "%s: not enough memory to read match-list \"%s\"\n",
X						    progname, FileWithMatches);
X	exit(1);
X    }
X
X    /* now read the list... */
X    if ((BytesRead = read(fd, StorageArea, FileSize)) != FileSize) {
X	if (BytesRead < 0) {
X	    int e = errno;
X
X	    fprintf(stderr, "%s: couldn't read %u bytes from ",
X							progname, FileSize);
X	    errno = e;
X	    perror(FileWithMatches);
X	    exit(1);
X	} else {
X	    int e = errno;
X
X	    fprintf(stderr, "%s: ", progname);
X	    if (BytesRead > 4) { /* minimum plausible for 3 items */
X		fprintf(stderr, "warning: ");
X	    }
X	    fprintf(stderr, "only read %u bytes, not %u, from file ",
X						progname, BytesRead, FileSize);
X	    errno = e;
X	    if (errno) perror(FileWithMatches);
X	    else fprintf(stderr, "\"%s\"\n", FileWithMatches);
X
X	    if (BytesRead <= 4) exit(1);
X
X	    StorageArea = realloc(StorageArea, (unsigned) (BytesRead + 1));
X	    if (StorageArea == (char *) 0) { /* unlikely, it got smaller  */
X		fprintf(stderr, "%s: can't realloc for \"%s\"\n",
X						progname, FileWithMatches);
X		exit(1);
X	    }
X	    FileSize = BytesRead;
X	}
X    }
X
X    /* null-terminate it */
X    StorageArea[FileSize] = '\0';
X
X    /* got the data, now make an array... first, count the matches */
X    for (n_matches = 1, p = StorageArea; p - StorageArea < FileSize; p++) {
X	if isspace(*p) ++n_matches;
X    }
X
X    /* If there *was* trailing new-line, we overestimated by one.
X     * This doesn't matter.  If memory is that tight, initscr() will fail.
X     * In any case, allow extra space for a trailing null entry.
X     */
X    ++n_matches;
X    if (n_matches < 3) n_matches = 3;
X
X    Result = (char **) malloc((unsigned) n_matches * sizeof(char *));
X    if (Result == (char **) 0) {
X	fprintf(stderr, "%s: out of memory reading match file \"%s\"\n",
X						progname, FileWithMatches);
X	exit(1);
X    }
X
X    /* Now step through the Storage Area filling in the pointers to the args */
X    ;
X
X    NextStr = (char *) 0;
X    i = -1;
X    for (p = StorageArea; p - StorageArea <= BytesRead; p++) {
X	if (!NextStr) NextStr = p;
X	if (isspace(*p) || p - StorageArea == BytesRead) {
X	    if (p - StorageArea != BytesRead) *p = '\0';
X	    while (isspace(*p)) { /* eat multiple blanks */
X		p++;
X	    }
X	    if (++i >= n_matches) {
X		n_matches += 20;
X		if ((Result = (char **)
X		    realloc((char *) Result, n_matches * sizeof(char *))) ==
X								(char **) 0) {
X		    fprintf(stderr,
X			"%s: out of memory [%u] in match-file \"%s\"\n",
X			progname, n_matches * sizeof(char *), FileWithMatches);
X		    /* TODO -- return with fewer matches -- NOTDONE */
X		    exit(1);
X		}
X	    }
X	    *p = '\0'; /* OK at the very end cos of the extra byte! */
X	    Result[i] = NextStr;
X	    NextStr = (char *) 0;
X	}
X    }
X
X    if (i + 2 < n_matches) {
X	Result = (char **) realloc((char *)Result,
X					(unsigned) (i+2) * sizeof(char **));
X	if (Result == (char **) 0) {
X	    fprintf(stderr, "%s: no memory for match-list from \"%s\"\n",
X						progname, FileWithMatches);
X	    exit(1);
X	}
X    }
X
X    if (close(fd) < 0) {
X	fprintf(stderr, "%s: warning: obscure problem closing %d (\"%s\")\n",
X						progname, fd, FileWithMatches);
X	sleep(5);
X    }
X
X    (*MatchList) = Result;
X    return (*MatchCount = i);
X}
X
Xint
XShowFile(FileName, BlockInFile, WordInBlock)
X    char *FileName;
X    unsigned long BlockInFile;
X    unsigned int WordInBlock;
X{
X    static char *Buffer = 0;
X    int fd;
X    static unsigned int BufLen;
X    int AmountRead;
X    register char *p;
X    register char *q;
X    int InTargetWord = 0;
X    char *StartOfMyWord;
X    int ThisWord = 0;
X    char *Start;
X    char *ThisLine = emalloc(Cols + 1); /* +1 for trailing \0 */
X    char *FirstBit = emalloc(WordCol - GapWidth + 1); /* +1 for trailing \0 */
X    char *LastBit = emalloc(Cols - WordCol + 1); /* +1 for trailing \0 */
X    char *FirstStart;
X
X    if (Buffer == (char *) 0) {
X	BufLen = Cols * 10;
X	if (BufLen < FileBlockSize * 3) BufLen = FileBlockSize * 3;
X	Buffer = emalloc(BufLen);
X    }
X
X    errno = 0;
X
X    if ((fd = open(FileName, O_RDONLY, 0)) < 0) {
X	int e = errno;
X	char *doc;
X
X	if ((doc = FindFile(FileName)) == (char *) 0) {
X	    fprintf(stderr, "%s: %s: ", progname, FileName);
X	    errno = e;
X	    perror(FileName);
X	    efree(ThisLine); efree(FirstBit); efree(LastBit);
X	    return -1;
X	} else if ((fd = open(doc, O_RDONLY, 0)) < 0) {
X	    fprintf(stderr, "%s: %s: ", progname, FileName);
X	    errno = e;
X	    perror(doc);
X	    efree(ThisLine); efree(FirstBit); efree(LastBit);
X	    return -1;
X	}
X	FileName = doc;
X    }
X
X    errno = 0;
X    if (lseek(fd, BlockInFile? (long) ((BlockInFile - 1) * FileBlockSize) : 0L,
X								    0) < 0) {
X	int e = errno;
X	fprintf(stderr, "%s: %s: ", progname, FileName);
X	errno = e;
X	perror("lseek");
X	efree(ThisLine); efree(FirstBit); efree(LastBit);
X	return;
X    }
X
X    errno = 0;
X    if ((AmountRead = read(fd, Buffer, BufLen)) < MinWordLength) {
X	int e = errno;
X	fprintf(stderr, "%s: %s: ", progname, FileName);
X	errno = e;
X	perror("read");
X	efree(ThisLine); efree(FirstBit); efree(LastBit);
X	return -1;
X    }
X
X
X    /** Find the required word */
X    if (BlockInFile) {
X	/* start 1 char before the end of the previous block */
X	StartOfMyWord = &Buffer[FileBlockSize - 1];
X	/* perhaps the last word of the previous block spans the block
X	 * boundary?
X	 */
X	while (WithinWord(*StartOfMyWord)) StartOfMyWord++;
X	if (StartOfMyWord < &Buffer[FileBlockSize]) {
X	    StartOfMyWord = &Buffer[FileBlockSize];
X	}
X    } else {
X	StartOfMyWord = Buffer;
X    }
X
X    (void) close(fd);
X
X    for (ThisWord = 0; ThisWord <= WordInBlock + 1; ThisWord++) {
Xbored:
X	/* skip to the start of a word */
X	while (!StartsWord(*StartOfMyWord)) {
X	    ++StartOfMyWord;
X	}
X
X	Start = StartOfMyWord;
X
X	/* find the end of the word */
X	while (WithinWord(*StartOfMyWord)) {
X	    if (*StartOfMyWord == '\'' && !EndsWord(StartOfMyWord[1])) break;
X	    StartOfMyWord++;
X	}
X
X	/* Assert: StartOfMyWord points 1 character beyond the end of the
X	 * word pointed to by Start
X	 */
X	/* see if it's long enough */
X	if (StartOfMyWord - Start < MinWordLength) {
X	    goto bored;
X	}
X
X	/** See if it's the right one */
X	if (ThisWord == WordInBlock) {
X	    StartOfMyWord = Start;
X	    break;
X	}
X    }
X
X
X    /* Find context before the keyword */
X
X    q = &FirstBit[WordCol - GapWidth];
X    *q-- = '\0';
X
X    for (p = StartOfMyWord - 1; p >= Buffer; --p, --q) {
X	*q = (isspace(*p)) ? ' ' : *p;
X	if (q == FirstBit) break;
X    }
X
X    FirstStart = q;
X
X    /* now build up the rest of the buffer */
X
X    q = LastBit;
X    *q = '\0';
X
X    InTargetWord = 0;
X
X    for (p = StartOfMyWord; p - Buffer < AmountRead; p++) {
X	if (q >= &LastBit[Cols - WordCol]) break;
X
X	switch (InTargetWord) {
X	case 0:
X	    if (StartsWord(*p)) {
X		InTargetWord = 1;
X	    }
X	    break;
X	case 1:
X	    if (!WithinWord(*p)) {
X		InTargetWord = 2;
X	    }
X	}
X	if (isspace(*p)) {
X	    *q = ' ';
X	} else {
X	    *q = *p;
X	}
X	*++q = '\0';
X	if (q >= &LastBit[Cols - WordCol]) break;
X    }
X
X    printf("%*.*s", WordCol - GapWidth, WordCol - GapWidth, FirstStart);
X
X    /* do the gap */
X    {
X
X	register int i;
X
X	for (i = GapWidth; i > 0; i--) {
X	    putchar(' ');
X	}
X    }
X
X    printf("%-*.*s", Cols - WordCol, Cols - WordCol, LastBit);
X	
X    printf(":");
X    {
X	int OverShoot = Cols + 2 + strlen(FileName) - ScreenWidth; /* +2 is ": " */
X
X	if (OverShoot > 0) {
X	    FileName += OverShoot + 2;
X	    printf("...");
X	} else {
X	    putchar(' ');
X	}
X
X    }
X    printf("%s\n", FileName);
X
X    efree(ThisLine); efree(FirstBit); efree(LastBit);
X    return 0;
X}
X
X
X/*
X * $Log:	lqkwik.c,v $
X * Revision 1.1  91/03/02  20:37:47  lee
X * Initial revision
X * 
X *
X */
@@@End of lq-text/src/lqtext/lqkwik.c
echo x - lq-text/src/lqtext/lqphrase.c 1>&2
sed 's/^X//' >lq-text/src/lqtext/lqphrase.c <<'@@@End of lq-text/src/lqtext/lqphrase.c'
X/* lqphrase.c -- Copyright 1989, 1990 Liam R. Quin.  All Rights Reserved.
X * This code is NOT in the public domain.
X * See the file COPYRIGHT for full details.
X *
X * $Id: lqphrase.c,v 1.4 90/10/06 00:50:56 lee Rel1-10 $
X */
X
X/* lqphrase, part of Liam Quin's text retrieval package...
X *
X * lqphrase is intended to be an example of one way to use the programming
X * interface to lq-text.
X *
X * The idea is quite simple:
X * Simply take a phrase p, held in a string (char *p), and call
X *	t_Phrase *Phrase = String2Phrase(p);
X * The result, if not null, contains only one interesting thing at this
X * point:
X *	Phrase->ModifiedString
X * is the canonical version of p -- with common and short words removed.
X * for example,
X *	p = "The boy sat down in His Boat and playd with his toes.";
X * might result in Phrase->ModifiedString containing
X *	"[*the] boy sat down [in] [*his] boat [*and] [?playd] with [*his] toe"
X * Common words are marked with a *, and unknown words with ?.
X * An attempt may have been made to reduce plurals.
X * Since this phrase contains a word not in the database (playd), it will
X * never match anything.  As a result, it is a good idea to print this string
X * (possibly massaging it first) so users can see what is going on.  If you
X * have it, the curses-based "lqtext" does this.
X *
X * If we change "playd" to "played", the above string is equivalent to
X *	"[*the] boy sat down [xx] [*the] boat [*the] played with [*the] toe"
X * In other words, all common words are equivalent.  The package remembers
X * that one or more common words were skipped, and also that one or more
X * lumps of letters too small to make up a word were skipped.
X * The following are equivalent:
X * L.R.E. Quin    L. Quin	L.R.Quin	X.X.Quin
X * in a QUIN	a QuIn
X * and the following are not the same as those:
X * Quin (no preceding garbage)
X * L.R.E. quin (first letter of `Quin' is not upper case (the rest is ignored)
X * [*the] Quin (common words are not the same as skipped letters)
X * L. Quin's (the presence of the posessive ('s) is significant)
X * L. Quins (plural (two Quins) not the same as singular)
X * L. Quinn (spelt incorrectly!)
X *
X * Now, having sorted that out, we have our canonical string (and lots of
X * other things) in Phrase, so we can now call
X *	MakeMatches(Phrase);
X * This will return the number of matches (*NOT* the number of files) for
X * the given ModifiedPhrase in the database.
X * This can take several seconds, so again, it can be worth printing out
X * the modified string as soon as it is available, so the user is looking at
X * that whilst MakeMatches is working!  I have experimented with faster
X * versions of MakeMatches involving binary search, but the extra complexity
X * slowed things down on smaller databases.  I don't have enough disk space
X * here to make a large enough database to do real timings, sorry.
X *
X * Now we have done MakeMatches, we can marck along the linked list of
X * pointers to linked lists of arrays of matches.  Clear?  No?  Well,
X * that's why there's en axample.  See Match() below.
X *
X * Now, each match currently gives us
X * t_FID FID; Files are numbered from 1 in the database
X * unsigned long BlockInFile; -- the block in the file
X * unsigned char WordInBlock; -- the word in the block
X * unsigned char StuffBefore; -- the amount of leading garbage
X * unsigned char Flags, including (see wordrules.h):
X *
X * WPF_WASPLURAL		The word...  ended in s
X * WPF_UPPERCASE		...Started with a capital letter
X * WPF_POSSESSIVE		...ended in 's
X * WPF_ENDEDINING		...ended in ing
X * WPF_LASTWASCOMMON	the previous word was common
X * WPF_LASTHADLETTERS	we skipped some letters to get here
X * WPF_LASTINBLOCK	I'm the last word in this block
X *
X */
X
X#include "globals.h" /* defines and declarations for database filenames */
X
X#include <stdio.h> /* stderr, also for fileinfo.h */
X#include <fcntl.h>
X#include <sys/types.h>
X#include <malloc.h>
X#include "emalloc.h"
X#include "fileinfo.h" /* for wordinfo.h */
X#include "wordinfo.h"
X#include "pblock.h"
X#include "phrase.h"
X
X#ifndef STREQ
X# define STREQ(boy,girl) ((*(boy) == *(girl)) && (!strcmp((boy),(girl))))
X#endif
X
Xextern int AsciiTrace;
Xextern t_PhraseCaseMatch PhraseMatchLevel;
X
X/** System calls and functions... **/
X/** Unix system calls used in this file: **/
Xextern void exit();
X
X/** Unix Library Functions used: **/
X/** lqtext library functions: **/
Xextern void SetDefaults();
Xextern void DefaultUsage();
X
X/** functions used before they're defined within this file: **/
Xvoid Match();
X/** **/
X
Xstatic char *Revision = "@(#) $Id: lqphrase.c,v 1.4 90/10/06 00:50:56 lee Rel1-10 $";
X
Xchar *progname = "tryphrase";
X
Xint SilentMode = 0; /* don't print matches if set to one */
X
Xint
Xmain(argc, argv)
X    int argc;
X    char *argv[];
X{
X    extern int optind, getopt();
X    /** extern char *optarg; (unused at present) **/
X    int ch;
X    int ErrorFlag = 0;
X
X    progname = argv[0];
X
X    SetDefaults(argc, argv);
X
X    while ((ch = getopt(argc, argv, "Zz:ahpslxVv")) != EOF) {
X	switch (ch) {
X	case 'z':
X	case 'Z':
X	    break; /* done by SetDefaults(); */
X	case 'V':
X	    fprintf(stderr, "%s version %s\n", progname, Revision);
X	    break;
X	case 'v': /* same as -t 1 */
X	    AsciiTrace = 1;
X	    break;
X	case 'l':
X	    break; /* list mode is the default */
X	case 's':
X	    SilentMode = 1;
X	    break;
X	case 'x':
X	    ErrorFlag = (-1);
X	    break;
X	case '?':
X	    ErrorFlag = 1;
X	}
X    }
X
X    /* Normally put call to lrqError here to give a helpful message,
X     * but not yet ready to ship the error handling package, sorry
X     */
X    if (ErrorFlag) {
X	fprintf(stderr, "Usage: %s [options] \"phrase\" [...]\n", progname);
X	fprintf(stderr, "%s: options are:\n", progname);
X	fputs("\
X	-l	-- list mode, suitable for lqshow (the default)\n\
X	-s	-- silent mode; exit status indicates success of matching\n\
X\n", stderr);
X
X	DefaultUsage();
X	exit( ErrorFlag > 0 ? 1 : 0); /* 0 means -x was used */
X    }
X    
X    if (AsciiTrace > 1) {
X	switch (PhraseMatchLevel) {
X	case PCM_HalfCase:
X	    fprintf(stderr, "%s: Matching phrases heuristically.\n", progname);
X	    break;
X	case PCM_SameCase:
X	    fprintf(stderr, "%s: Matching phrases precisely.\n", progname);
X	    break;
X	case PCM_AnyCase:
X	    fprintf(stderr, "%s: Matching phrases approximately.\n", progname);
X	    break;
X	default:
X	    fprintf(stderr, "%s: internall error, case matching is %d\n",
X						progname, PhraseMatchLevel);
X	    exit(2);
X	}
X    }
X
X    while (optind < argc) {
X	Match(argv[optind++]);
X    }
X
X    if (SilentMode) {
X	/* if we got to here we didn't find anything */
X	exit(1);
X    }
X    return 0;
X}
X
Xvoid
XMatch(Phrase)
X    char *Phrase;
X{
X    extern t_Phrase *String2Phrase();
X    extern t_FileInfo *GetFileInfo();
X    extern long MakeMatches();
X
X    t_Phrase *P;
X    t_MatchList *Matches;
X    t_FID LastFID = (t_FID) 0;
X    t_FileInfo *FileInfo = 0;
X
X    if (!Phrase || !*Phrase) return;
X    if ((P = String2Phrase(Phrase)) == (t_Phrase *) 0) return;
X
X    if (MakeMatches(P) <= 0L) return;
X
X    if (P) {
X	for (Matches = P->Matches; Matches != (t_MatchList *) 0;
X						Matches = Matches->Next) {
X	    if (Matches->Match != (t_Match *) 0) {
X		if (Matches->Match->Where->FID != LastFID) {
X		    t_FID FID = Matches->Match->Where->FID;
X		    /*TODO: use DestroyFileInfo instead of efree:... */
X		    if (FileInfo) efree((char *) FileInfo);
X		    if ((FileInfo = GetFileInfo(FID)) == (t_FileInfo *) 0) {
X			continue;
X		    }
X		    LastFID = FID;
X		}
X
X		/* Now that we know that we have something to print... */
X		if (SilentMode) {
X		    exit(0); /* OK, found something */
X		}
X		if (AsciiTrace) {
X		    printf("%-7lu %-7u %-3d %-3d %s\n",
X				Matches->Match->Where->BlockInFile,
X				(unsigned) Matches->Match->Where->WordInBlock,
X				(unsigned) Matches->Match->Where->StuffBefore,
X				(unsigned) Matches->Match->Where->Flags,
X				FileInfo->Name);
X		} else {
X		    printf("%-7.7lu %-7.7u %s\n",
X				Matches->Match->Where->BlockInFile,
X				Matches->Match->Where->WordInBlock,
X				FileInfo->Name);
X		}
X	    }
X	}
X    }
X}
X
X/*
X * $Log:	lqphrase.c,v $
X * Revision 1.4  90/10/06  00:50:56  lee
X * Prepared for first beta release.
X * 
X * Revision 1.3  90/08/29  21:45:29  lee
X * Alpha release
X * 
X * Revision 1.2  90/08/09  19:17:16  lee
X * *** empty log message ***
X * 
X * Revision 1.1  90/03/24  20:22:49  lee
X * Initial revision
X * 
X */
X
@@@End of lq-text/src/lqtext/lqphrase.c
echo x - lq-text/src/lqtext/lqshow.c 1>&2
sed 's/^X//' >lq-text/src/lqtext/lqshow.c <<'@@@End of lq-text/src/lqtext/lqshow.c'
X/* lqshow.c -- Copyright 1989, 1990 Liam R. Quin.  All Rights Reserved.
X * This code is NOT in the public domain.
X * See the file COPYRIGHT for full details.
X */
X
X/* lqshow -- show a file according to keywords, highlighting matches
X * Liam R. Quin, September 1989 and later...
X *
X * $Id: lqshow.c,v 1.9 91/03/03 00:18:26 lee Rel1-10 $
X */
X
X#include "globals.h" /* defines and declarations for database filenames */
X
X#ifdef SYSV
X /* for lint: */
X extern int w32addch();
X#endif
X#ifdef ultrix
X# include <cursesX.h>
X#else
X# include <curses.h>
X#endif
X#include <malloc.h>
X#include <fcntl.h>
X#include <ctype.h>
X#include <sys/types.h> /* for fileinfo.h */
X#include <sys/stat.h>
X
X/* Check for old (or BSD) curses: */
X#ifndef A_STANDOUT
X# define A_STANDOUT 10193 /* random */
X# define A_UNDERLINE 312
X# define attrset(a) ((a == 0) ? standend() : standout())
Xtypedef char chtype; /* long on sysV */
Xbeep() {
X    fprintf(stderr, "\007");
X    fflush(stderr);
X}
X#endif
X
X#include "fileinfo.h"
X#include "wordinfo.h"
X#include "wordrules.h"
X#include "pblock.h"
X#include "emalloc.h"
X
X/** Unix system calls that need declaring: **/
Xextern long lseek();
Xextern int open(), close();
Xextern int read();
Xextern void exit();
Xextern int stat();
X
X/** Unix/C Library Functions that need declaring: **/
X#ifndef tolower
X extern int tolower();
X#endif
Xextern int strlen();
Xextern int strcmp();
Xextern unsigned sleep();
Xextern int atoi();
Xextern long atol();
Xextern void perror();
X
X/** Curses library functions: **/
X#ifdef SYSV
Xextern int box32();
X#endif
X#ifndef nonl
X extern int nonl();
X#endif /**nonl*/
X#ifndef noecho
Xextern int noecho();
X#endif
X#ifndef wmove
X extern int wmove();
X#endif
X#ifndef waddstr
X extern int waddstr();
X#endif
X#ifndef wrefresh
X extern int wrefresh();
X#endif
X#ifndef beep
X extern int beep();
X#endif
X#ifndef wprintw
X extern int printw();
X#endif
Xextern int mvwprintw(), delwin(), wclear(), wclrtoeol(), endwin();
X
X/** lqtext library functions that need declaring: **/
Xextern int MySystem();
Xextern int TooCommon();
Xextern void SetDefault();
Xextern void DefaultUsage();
X
X/** Functions within this file that are used before being defined: **/
Xint ReadMatchFile();
Xvoid ShowFile();
Xvoid Output();
X
X/** **/
X
X/** some useful macros: **/
X#define max(choir,boy) (((choir)>(boy))?(choir):(boy))
X#define min(choir,boy) (((choir)<(boy))?(choir):(boy))
X
X/** **/
X
Xint AsciiTrace = 0;
X
X/* number of lines above and below each match to show by default. */
X#define DFLTABOVE 6
X#define DFLTBELOW 9
X
Xint LinesBelow = DFLTBELOW;
Xint LinesAbove = DFLTABOVE;
X
X#define DISPLAY_TOP 3
X
Xextern int errno;
X
Xchar *progname;
Xint ThisRow, ThisCol;
Xint SelectedNames = -1;
XFILE *InfoStream = 0;
X
Xstatic char *Revision = "@(#) showfile.c 2.2";
X
Xint
Xmain(argc, argv)
X    int argc;
X    char *argv[];
X{
X    extern int optind, getopt();
X    extern char *optarg; /* for getopt */
X    int ch; /* for getopt */
X    int ErrFlag = 0; /* see how getopt makes programs cleaner? */
X    int NumberOfFiles;
X    char **Origv;
X    int Origc;
X    char *FileWithMatches = (char *) 0;
X    char **MatchList;
X    int MatchCount = 0;
X
X    progname = argv[0];
X
X    SetDefaults(argc, argv);
X
X    /* All lq-text programs must call SetDefaults() before getopt, and
X     * must then be prepared to ignore options z with arg and Z without.
X     */
X    while ((ch = getopt(argc, argv, "a:b:f:o:z:ZVvx")) != EOF) {
X	switch (ch) {
X	case 'z':
X	    break; /* done by SetDefaults(); */
X	case 'V':
X	    fprintf(stderr, "%s version %s\n", progname, Revision);
X	    break;
X	case 'v':
X	    AsciiTrace = 1;
X	    break;
X	case 'a': /* lines above */
X	    LinesAbove = atoi(optarg); /* need cknum() */
X	    break;
X	case 'b':
X	    LinesBelow = atoi(optarg);
X	    break;
X	case 'f':
X	    FileWithMatches = optarg;
X	    break;
X	case 'o': /* -o fd --- write the selected files to fp */
X	    if (SelectedNames >= 0) {
X		fprintf(stderr,
X		"%s: -o %d -o %s: you must not give more than one -o option.\n",
X					progname, SelectedNames, optarg);
X		ErrFlag = (-1);
X	    } else {
X		if (!isdigit(*optarg)) {
X		    fprintf(stderr, "%s: -o must be followed by a number\n",
X								progname);
X		    exit(1);
X		}
X		SelectedNames = atoi(optarg);
X		break;
X	    }
X	    break;
X	case 'x':
X	    ErrFlag = (-1);
X	    break;
X	case '?':
X	default:
X	    ErrFlag = 1;
X	}
X    }
X
X    if (ErrFlag < 0) { /* -x or -xv was used */
X	fprintf(stderr, "usage: %s [-xv] [options] [matches...]\n", progname);
X	fprintf(stderr,
X	"use %s -x, -xv or -xvv for more detailed explanations.\n", progname);
X
X	if (AsciiTrace) {
X	    DefaultUsage();
X	    fprintf(stderr, "\n\
X	-a above - set the number of lines shown above each matching\n\
X		   match to \"above\" [default is %d]\n", DFLTABOVE);
X	    fprintf(stderr, "\
X	-b below - set the number of lines shown below each match\n\
X		   match to \"above\" [default is %d]\n", DFLTBELOW);
X	    fprintf(stderr, "\
X	-f file -- \"file\" contains a list of matches, one per line\n");
X	}
X	if (AsciiTrace > 1) {
X	    fputs("\
X	Matches should be in the form of\n\
X		BlockNumber  WordInBlock  FileName\n\
X	where BlockBumber and WordInBlock are positive numbers.\n\
X	(This is the format produced by the lqword -l command.)\n\
X", stderr);
X	}
X	exit(0);
X    } else if (ErrFlag > 0) {
X	fprintf(stderr, "use %s -x for an explanation.\n", progname);
X	exit(1);
X    }
X
X    /* open the file for the selected output */
X    if (SelectedNames > 0) {
X	if ((InfoStream = fdopen(SelectedNames, "w")) == (FILE *) 0) {
X	    int e = errno;
X
X	    fprintf(stderr, "%s: -o %d: can't open stream ",
X	    					progname, SelectedNames);
X	    errno = e;
X	    perror("for writing");
X	    exit(1);
X	}
X    }
X
X    /* check that we can get at the file containing the matches, if one
X     * was supplied.
X     */
X    if (FileWithMatches) {
X	struct stat StatBuf;
X	char *msg = 0;
X
X	if (stat(FileWithMatches, &StatBuf) < 0) {
X	    int e = errno; /* on many systems, fprintf() changes errno! */
X	    fprintf(stderr, "%s: can't open match-list file ", FileWithMatches);
X	    errno = e;
X	    perror(progname);
X	    exit(1);
X	} else if (AsciiTrace) {
X	    switch (StatBuf.st_mode & S_IFMT) {
X	    case S_IFDIR:
X		fprintf(stderr,
X		"%s: ca't read matches from \"%s\" -- it's a directory!\n",
X						progname, FileWithMatches);
X		exit(1);
X	    case S_IFREG:
X		break;
X#ifdef S_IFIFO
X	    case S_IFIFO:
X		msg = "named pipe or fifo";
X		/* fall through */
X#endif
X	    case S_IFCHR:
X		if (!msg) msg = "raw special device";
X		/* fall through */
X	    case S_IFBLK:
X		if (!msg) msg = "block special device";
X		/* fall through */
X#ifdef S_IFNAM
X	    case S_IFNAM:
X		if (!msg) msg = "named special file"; /* wot dat? */
X		/* fall through */
X#endif
X	    default:
X		if (!msg) msg = "special file";
X
X		fprintf(stderr,
X		    "%s: warning: file \"%s\" containing matches is a %s\n",
X		    progname, FileWithMatches, msg);
X		
X		/* but continue anyway... */
X
X	    }
X	}
X	/* Now read the file, and make an array of matches... */
X	if (ReadMatchFile(FileWithMatches, StatBuf.st_size, &MatchCount, &MatchList) < 0) {
X	    fprintf(stderr, "%s: couldn't read matches from \"%s\"\n",
X						progname, FileWithMatches);
X	    exit(1);
X	}
X    }
X
X    argv += optind;
X    argc -= optind;
X
X    if (MatchCount) {
X	argc = MatchCount;
X	argv = MatchList;
X    }
X
X    if (argc < 3) {
X	fprintf(stderr,
X	"%s: matches must have at least 3 parts; use -xv for an explanation\n",
X								progname);
X	exit(1);
X    } else if (argc % 3) {
X	/* Note: I could detect lqword output here (i.e., without -l) */
X	fprintf(stderr, "%s: can't understand match format;\n", progname);
X	fprintf(stderr, "%s: use -xv for more explanation.\n", progname);
X	exit(1);
X    }
X
X    Origv = argv;
X    Origc = argc;
X
X    ThisRow = DISPLAY_TOP - 1;
X    NumberOfFiles = argc / 3;
X
X    initscr();
X    nonl();
X    raw();
X    noecho();
X
X    while (argc > 0) {
X	char Buffer[120];
X	int Where;
X
X	ThisRow = DISPLAY_TOP;
X	ThisCol = (-1);
X	ShowFile(argv[2], atol(*argv), (unsigned) atoi(argv[1]), argc - Origc);
X	(void) sprintf(Buffer, "Match %d of %d", 
X			    NumberOfFiles - (argc / 3) + 1, NumberOfFiles);
X	Where = COLS - (strlen(Buffer) + 10);
X	mvwaddstr(stdscr, LINES - 1, Where, Buffer);
X	refresh();	/* Where (above) is in case mvwaddstr is a macro */
X
X	if (argc > 0) {
X	    attrset(A_STANDOUT);
X	    mvwaddstr(stdscr, LINES - 1, 0, "[Press SPACE to continue]");
X	    attrset(0);
X	    wmove(stdscr, 0, 0);
X	    (void) refresh();
X	    switch (getch()) {
X	    case '?':
X	    case 'x':
X	    case 'h':
X	    case 'i':
X#ifdef KEY_HELP
X	    case KEY_HELP:
X#endif
X		{
X		    WINDOW *HelpWin = newwin(12, 40, 5, (COLS - 40) / 2);
X
X		    if (HelpWin == (WINDOW *) 0) {
X			beep();
X		    } else {
X#ifndef ACS_HLINE
X			box(HelpWin, '#', '#');
X#else
X			box(HelpWin, 0, 0);
X			/* Versions of curses with ASC_HLINE take 0 to
X			 * mean that line-drawing should be done
X			 * "properly".
X			 */
X#endif
X			wmove(HelpWin, 1, 2);
X			mvwprintw(HelpWin, 1,2, "x, ?    -- print this explanation");
X			mvwprintw(HelpWin, 2,2, "space   -- go to next match");
X			mvwprintw(HelpWin, 3,2, "return  -- go to next match");
X			mvwprintw(HelpWin, 4,2, "0, ^, F -- go to First match");
X			mvwprintw(HelpWin, 5,2, "$, L    -- go to the Last match");
X			mvwprintw(HelpWin, 6,2, "n, +    -- go to the next file");
X			mvwprintw(HelpWin, 7,2, "p, -    -- go to previous file");
X			mvwprintw(HelpWin, 8,2, "s, g    -- save this filename");
X			mvwprintw(HelpWin, 9,2, "u, d    -- drop this filename");
X			mvwprintw(HelpWin, 10,2, "q, Q    -- quit browsing");
X			wrefresh(HelpWin);
X			(void) getch();
X			delwin(HelpWin);
X#ifndef CURSESX /* This is because 4.2 BSD a brain-dead curses... */
X			clearok(stdscr, TRUE);
X			wrefresh(stdscr);
X#endif
X		    }
X		}
X		break;
X	    case 'q':
X	    case 'Q':
X		goto AllDone;
X		    /* the goto is to surmount an AT&T compiler bug */
X	    case '0': /* reset to beginning */
X	    case '1':
X	    case 'f':  case 'F':
X	    case '^': case '6': /* (6 is often unshifted ^) */
X		argc = Origc;
X		argv = Origv;
X		break;
X	    case '$': /* to the end */
X	    case 'l': case 'L': /* Last match */
X		argv += (argc - 3);
X		argc = 3;
X		break;
X	    case 'v': /* view the file -- use PAGER */
X		{
X		    char Buffer[4096];
X		    char *doc;
X		    int e = errno;
X
X		    if ((doc = FindFile(argv[2])) == (char *) 0) {
X			errno = e;
X			perror(argv[2]);
X			sleep(2);
X			goto AllDone;
X		    }
X
X		    (void) sprintf(Buffer, "%s \"%s\"", PAGER, doc);
X		    (void) MySystem(Buffer);
X		    clearok(stdscr, TRUE);
X		    wrefresh(stdscr);
X		}
X		break;
X	    case 's': /* keep this filename for later use */
X	    case 'k': case 'g': /* keep, get */
X		fprintf(InfoStream, "%c %s\n", 's', argv[2]);
X		break; /*NOTDONE*/
X	    case 'd': /* delete this file from the list */
X		fprintf(InfoStream, "%c %s\n", 'd', argv[2]);
X		break; /* NOTDONE */
X	    case 'R': /* revert to initial state */
X		break; /* NOTDONE*/
X	    case '-':
X	    case 'p':
X		{
X		    char *p = argv[2];
X		    char **Argv = argv;
X		    int Argc = argc;
X
X		    while (Argc + 3 <= Origc && STREQ(Argv[2], p)) {
X			Argv -= 3;
X			Argc += 3;
X		    }
X
X		    if (Argc == argc) {
X			beep();
X		    } else {
X			argv = Argv;
X			argc = Argc;
X		    }
X		}
X		break;
X	    case '+':
X	    case 'n':
X		{
X		    char *p = argv[2];
X		    char **Argv = argv;
X		    int Argc = argc;
X
X		    while (Argc > 3 && STREQ(Argv[2], p)) {
X			Argv += 3;
X			Argc -= 3;
X		    }
X
X		    if (Argc == argc) {
X			beep();
X		    } else {
X			argv = Argv;
X			argc = Argc;
X		    }
X		}
X		break;
X	    case 'R' ^ 64: /* control-R */
X	    case 'L' ^ 64: /* control-L */
X		clearok(stdscr, TRUE);
X		wrefresh(stdscr);
X		break;
X	    case '=':
X		clearok(stdscr, TRUE);
X		wrefresh(stdscr);
X		{
X		    FILE *Pager = popen(PAGER, "w");
X		    char **p;
X		    int i;
X
X		    if (!p) {
X			beep();
X			break;
X		    }
X		    for (p = Origv, i = Origc; i > 0; i -= 3, p += 3) {
X			(void) fprintf(Pager, "%s\n", *p);
X		    }
X		    (void) pclose(Pager);
X		}
X		clearok(stdscr, TRUE);
X		wrefresh(stdscr);
X		break;
X	    case ' ':
X	    case '\r':
X	    case '\n':
X		argv += 3;
X		argc -= 3;
X		break;
X	    default:
X		beep();
X		break;
X	    }
X	}
X    }
X
XAllDone:
X    wmove(stdscr, LINES - 1, 0);
X    clrtoeol();
X    /* Try to revent the screen from scrolling when we exit */
X    wmove(stdscr, LINES - 2, 0);
X    refresh();
X    endwin();
X    return 0;
X}
X
Xint
XReadMatchFile(FileWithMatches, FileSize, MatchCount, MatchList)
X    char *FileWithMatches;
X    off_t FileSize;
X    int *MatchCount;
X    char ** *MatchList;
X{
X    extern char *strtok();
X
X    int fd;
X    char **Result;
X    char *StorageArea;
X    char *p;
X    unsigned int n_matches;
X    int BytesRead;
X    int i;
X    char *NextStr;
X
X    if (!FileWithMatches || !*FileWithMatches) {
X	fprintf(stderr, "%s: match-list file (from -f) has empty name!\n",
X								progname);
X	exit(1);
X    }
X
X    if ((fd = open(FileWithMatches, O_RDONLY)) == 0) {
X	int e = errno;
X	fprintf(stderr, "%s: can't open match-list file ", progname);
X	errno = e;
X	perror(FileWithMatches);
X	exit(1);
X    }
X
X    /* We know the number of bytes, and each space or newline will get
X     * turned into a null, so here goes...
X     * The +1 below is to ensure that there is space for a \0, even if a
X     * pesky user didn't put a \n at * the end of the file...
X     * Sometimes I hate emacs...
X     */
X    if ((StorageArea = malloc((unsigned) FileSize + 1)) == (char *) 0) {
X	fprintf(stderr, "%s: not enough memory to read match-list \"%s\"\n",
X						    progname, FileWithMatches);
X	exit(1);
X    }
X
X    /* now read the list... */
X    if ((BytesRead = read(fd, StorageArea, FileSize)) != FileSize) {
X	if (BytesRead < 0) {
X	    int e = errno;
X
X	    fprintf(stderr, "%s: couldn't read %u bytes from ",
X							progname, FileSize);
X	    errno = e;
X	    perror(FileWithMatches);
X	    exit(1);
X	} else {
X	    int e = errno;
X
X	    fprintf(stderr, "%s: ", progname);
X	    if (BytesRead > 4) { /* minimum plausible for 3 items */
X		fprintf(stderr, "warning: ");
X	    }
X	    fprintf(stderr, "only read %u bytes, not %u, from file ",
X						progname, BytesRead, FileSize);
X	    errno = e;
X	    if (errno) perror(FileWithMatches);
X	    else fprintf(stderr, "\"%s\"\n", FileWithMatches);
X
X	    if (BytesRead <= 4) exit(1);
X
X	    StorageArea = realloc(StorageArea, (unsigned) (BytesRead + 1));
X	    if (StorageArea == (char *) 0) { /* unlikely, it got smaller  */
X		fprintf(stderr, "%s: can't realloc for \"%s\"\n",
X						progname, FileWithMatches);
X		exit(1);
X	    }
X	    FileSize = BytesRead;
X	}
X    }
X
X    /* null-terminate it */
X    StorageArea[FileSize] = '\0';
X
X    /* got the data, now make an array... first, count the matches */
X    for (n_matches = 1, p = StorageArea; p - StorageArea < FileSize; p++) {
X	if isspace(*p) ++n_matches;
X    }
X
X    /* If there *was* trailing new-line, we overestimated by one.
X     * This doesn't matter.  If memory is that tight, initscr() will fail.
X     * In any case, allow extra space for a trailing null entry.
X     */
X    ++n_matches;
X    if (n_matches < 3) n_matches = 3;
X
X    Result = (char **) malloc((unsigned) n_matches * sizeof(char *));
X    if (Result == (char **) 0) {
X	fprintf(stderr, "%s: out of memory reading match file \"%s\"\n",
X						progname, FileWithMatches);
X	exit(1);
X    }
X
X    /* Now step through the Storage Area filling in the pointers to the args */
X    ;
X
X    NextStr = (char *) 0;
X    i = -1;
X    for (p = StorageArea; p - StorageArea <= BytesRead; p++) {
X	if (!NextStr) NextStr = p;
X	if (isspace(*p) || p - StorageArea == BytesRead) {
X	    if (p - StorageArea != BytesRead) *p = '\0';
X	    while (isspace(*p)) { /* eat multiple blanks */
X		p++;
X	    }
X	    if (++i >= n_matches) {
X		n_matches += 20;
X		if ((Result = (char **)
X		    realloc((char *) Result, n_matches * sizeof(char *))) ==
X								(char **) 0) {
X		    fprintf(stderr,
X			"%s: out of memory [%u] in match-file \"%s\"\n",
X			progname, n_matches * sizeof(char *), FileWithMatches);
X		    /* TODO -- return with fewer matches -- NOTDONE */
X		    exit(1);
X		}
X	    }
X	    *p = '\0'; /* OK at the very end cos of the extra byte! */
X	    Result[i] = NextStr;
X	    NextStr = (char *) 0;
X	}
X    }
X
X    if (i + 2 < n_matches) {
X	Result = (char **) realloc((char *)Result,
X					(unsigned) (i+2) * sizeof(char **));
X	if (Result == (char **) 0) {
X	    fprintf(stderr, "%s: no memory for match-list from \"%s\"\n",
X						progname, FileWithMatches);
X	    exit(1);
X	}
X    }
X
X    if (close(fd) < 0) {
X	fprintf(stderr, "%s: warning: obscure problem closing %d (\"%s\")\n",
X						progname, fd, FileWithMatches);
X	sleep(5);
X    }
X
X    (*MatchList) = Result;
X    return (*MatchCount = i);
X}
X
Xvoid
XShowFile(FileName, BlockInFile, WordInBlock, UniqueID)
X    char *FileName;
X    unsigned long BlockInFile;
X    unsigned int WordInBlock;
X    int UniqueID;
X{
X    static char *Buffer = 0;
X    int fd;
X    static unsigned int BufLen;
X    int AmountRead;
X    register char *p;
X    int LinesFound;
X    int InTargetWord = 0;
X    char *StartOfMyWord;
X    int ThisWord = 0;
X    unsigned long FirstLumpSize;
X    char *Start;
X    static int LastID = (-1);
X
X    if (UniqueID == LastID) {
X	return;
X    } else {
X	LastID = UniqueID;
X    }
X    wclear(stdscr);
X    ThisRow = DISPLAY_TOP;
X
X    if (Buffer == (char *) 0) {
X	BufLen = COLS * (LinesAbove + LinesBelow + 1) + 1;
X	if (BufLen < FileBlockSize * 3) BufLen = FileBlockSize * 3;
X	Buffer = emalloc(BufLen);
X    }
X
X    errno = 0;
X
X    if ((fd = open(FileName, O_RDONLY, 0)) < 0) {
X	int e = errno;
X	char *doc;
X
X	if ((doc = FindFile(FileName)) == (char *) 0) {
X	    errno = e;
X	    perror(FileName);
X	    sleep(2);
X	    return;
X	} else if ((fd = open(doc, O_RDONLY, 0)) < 0) {
X	    perror(doc);
X	    sleep(2);
X	    return;
X	}
X	FileName = doc;
X    }
X
X    /* display a helpful message: */
X    move(DISPLAY_TOP, 0);
X    clrtoeol();
X    move(DISPLAY_TOP - 1, 0);
X    clrtoeol();
X    mvwprintw(stdscr, DISPLAY_TOP - 1, 0,
X		"Block %lu/Word %u in document: ", BlockInFile, WordInBlock);
X    attrset(A_UNDERLINE);
X    wprintw(stdscr, "%s", FileName);
X    attrset(0);
X
X    errno = 0;
X    if (lseek(fd, BlockInFile? (long) ((BlockInFile - 1) * FileBlockSize) : 0L,
X								    0) < 0) {
X	perror("lseek");
X	sleep(2);
X	clearok(stdscr, TRUE);
X	close(fd);
X	return;
X    }
X
X    errno = 0;
X    if ((AmountRead = read(fd, Buffer, BufLen)) < MinWordLength) {
X	perror("read");
X	sleep(5);
X	close(fd);
X	clearok(stdscr, TRUE);
X	return;
X    }
X
X    /* clear the bottom bit of screen */
X    {
X	register int i;
X
X	for (i = ThisRow; i < LINES; i++) {
X	    move(i, 0);
X	    wclrtoeol(stdscr);
X	}
X    }
X
X    /** Find the required word */
X    if (BlockInFile) {
X	/* start 1 char before the end of the previous block */
X	StartOfMyWord = &Buffer[FileBlockSize - 1];
X	/* perhaps the last word of the previous block spans the block
X	 * boundary?
X	 */
X	while (WithinWord(*StartOfMyWord)) StartOfMyWord++;
X	if (StartOfMyWord < &Buffer[FileBlockSize]) {
X	    StartOfMyWord = &Buffer[FileBlockSize];
X	}
X    } else {
X	StartOfMyWord = Buffer;
X    }
X
X    for (ThisWord = 0; ThisWord <= WordInBlock + 1; ThisWord++) {
Xbored:
X	/* skip to the start of a word */
X	while (!StartsWord(*StartOfMyWord)) {
X	    ++StartOfMyWord;
X	}
X
X	Start = StartOfMyWord;
X
X	/* find the end of the word */
X	while (WithinWord(*StartOfMyWord)) {
X	    if (*StartOfMyWord == '\'' && !EndsWord(StartOfMyWord[1])) break;
X	    StartOfMyWord++;
X	}
X
X	/* Assert: StartOfMyWord points 1 character beyond the end of the
X	 * word pointed to by Start
X	 */
X	/* see if it's long enough */
X	if (StartOfMyWord - Start < MinWordLength) {
X	    goto bored;
X	}
X
X#if 0
X	/* see if it is too common */
X	{
X	    extern char *WordRoot();
X
X	    t_WordInfo W;
X	    register char *p, *q;
X	    char RootBuf[MaxWordLength + 1];
X
X	    for (p = RootBuf, q = Start; *q; p++, q++) {
X		if (q == StartOfMyWord) break;
X		*p = isupper(*q) ? tolower(*q) : *q;
X	    }
X	    *p = '\0';
X
X	    W.Word = RootBuf;
X	    W.Length = strlen(W.Word);
X	    W.WordPlace.Flags = 0;
X
X	    (void) WordRoot(&W);
X
X	    if (TooCommon(&W)) goto bored;
X
X	}
X#endif
X
X	/** See if it's the right one */
X	if (ThisWord == WordInBlock) {
X	    StartOfMyWord = Start;
X	    break;
X	}
X    }
X
X    FirstLumpSize = StartOfMyWord - Buffer;
X
X    /* Find N lines before it */
X    LinesFound = 0;
X    for (p = StartOfMyWord; p > Buffer; --p) {
X	if (*p == '\n') {
X	    if (++LinesFound > LinesAbove) break;
X	}
X    }
X
X    /* display them */
X    while (p < StartOfMyWord) {
X	Output(*p); /* Output might be a macro later */
X	p++;
X    }
X
X    /* find N lines after it */
X
X    LinesFound = 0;
X    while (p - Buffer < AmountRead) {
X	switch (InTargetWord) {
X	case 0:
X	    if (StartsWord(*p)) {
X		attrset(A_STANDOUT);
X		InTargetWord = 1;
X	    }
X	    break;
X	case 1:
X	    if (!WithinWord(*p)) {
X		InTargetWord = 2;
X		attrset(0);
X	    }
X	}
X	Output(*p);
X
X	if (*p == '\n') {
X	    if (++LinesFound > LinesBelow) break;
X	}
X	p++;
X    }
X
X    (void) refresh();
X
X    (void) close(fd);
X    return;
X}
X
Xvoid
XOutput(ch)
X    int ch;
X{
X    switch(ch) {
X    default:
X	if (++ThisCol > COLS) {
X	    if (++ThisRow >= LINES - 1) {
X		ThisRow = DISPLAY_TOP;
X	    }
X	    ThisCol = 0;
X	}
X	if (ThisCol <= 0) {
X	    ThisCol = 0;
X	    move(ThisRow, ThisCol);
X	    clrtoeol();
X	}
X	mvwaddch(stdscr, ThisRow, ThisCol, (chtype) ch);
X	break;
X    case '\n':
X	if (++ThisRow >= LINES - 1) {
X	    ThisRow = DISPLAY_TOP;
X	}
X	ThisCol = (-1);
X	move(ThisRow, 0);
X	clrtoeol();
X	break;
X    case '\t':
X	ThisCol |= 7;
X	break;
X    case ' ':
X	ThisCol++;
X    }
X}
X
X/*
X * $Log:	lqshow.c,v $
X * Revision 1.9  91/03/03  00:18:26  lee
X * No longer needs to check common words.
X * 
X * Revision 1.8  90/10/06  00:50:58  lee
X * Prepared for first beta release.
X * 
X * Revision 1.7  90/10/05  23:49:25  lee
X * Moved the Match %d of %d message left somewhat.
X * 
X * Revision 1.6  90/10/03  21:26:47  lee
X * Removed BSD/SYSV diffs and used CURSESX instead.
X * 
X * Revision 1.5  90/08/29  21:45:34  lee
X * Alpha release
X * 
X * Revision 1.4  90/08/09  19:17:17  lee
X * BSD lint and Saber
X * 
X * Revision 1.3  90/07/11  10:57:46  lee
X * Added limited ultrix support...  also some small optimisations and
X * changes to the help screen.
X * 
X * Revision 1.2  90/04/21  16:06:24  lee
X * Cleaned up the gode for gcc -W
X * 
X * Revision 1.1  90/02/14  18:32:39  lee
X * Initial revision
X * 
X * Revision 2.1  89/10/02  01:15:51  lee
X * New index format, with Block/WordInBlock/Flags/BytesSkipped info.
X * 
X * Revision 1.3  89/09/17  23:04:04  lee
X * Various fixes; NumberInBlock now a short...
X * 
X * Revision 1.2  89/09/16  21:18:35  lee
X * First demonstratable version.
X * 
X * Revision 1.1  89/09/16  20:02:58  lee
X * Initial revision
X * 
X */
@@@End of lq-text/src/lqtext/lqshow.c
echo end of part 07
-- 
Liam R. E. Quin,  lee@sq.com, SoftQuad Inc., Toronto, +1 (416) 963-8337