[net.sources] save news articles and later search them

guido@mcvax.cwi.nl (Guido van Rossum) (11/26/86)

: This is a shell archive.
: Extract with 'sh this_file'.
echo 'Start of pack.out, part 01 out of 01:'
echo -n 'Making directories ... '
err="no"
test -d 'lib' || mkdir 'lib' || err="yes"
echo 'done'
if test "$err" = "yes"
then echo "didn't make it."
fi
echo 'x - Makefile'
sed 's/^X//' > 'Makefile' << 'EOF'
XDESTBIN=$$HOME/bin
XLIBES=	lib/lib.a
X
Xsearch: search.c $(LIBES)
X	$(CC) $(CFLAGS) -o search search.c $(LIBES)
X
Xmunge: munge.c $(LIBES)
X	$(CC) $(CFLAGS) -o munge munge.c $(LIBES)
X
Xall: search munge save.sh
X
Xinstall: all
X	cp search $(DESTBIN)/search
X	cp munge $(DESTBIN)/munge
X	cp save.sh $(DESTBIN)/save
EOF
echo 'x - README'
sed 's/^X//' > 'README' << 'EOF'
XHere are a few programs I use for saving news articles and the like.
XAll the code contained here (as far as I wrote it :-) is hereby placed
Xin the public domain -- feel free to steal it, remove all comments, add
Xyour own name as author, add a few bugs of your own, and sell it for lots
Xof $$, as long as you don't keep me responsible.
X
X	Guido van Rossum, CWI, Amsterdam <guido@mcvax.uucp>
X
XThe most important aspect of saving stuff is not storing it (that's easy
Xenough) but being able to find it back again later.  The program
X'search' is for finding back articles.  It will search a default
Xdirectory or a list of files and directories for articles matching a
Xsimple search criterion, and format the matching files in one of many
Xways.  Since there is no manual page, here is at least a list of options
Xto 'search'.  Note that all string comparisons are made case independent
X(but not [yet] with regular expressions).
X
X
Xsearch [options] ... [file|directory] ...
X
XMatching options:
X	-m key:string	Match criterion; a file matches if it has a
X			header line with a keyword starting with 'key'
X			and containing 'string' as a substring
X			furtheron.  Default is "-m :", meaning all
X			articles match.  Only the last -m parameter is
X			honoured.
X
XFormatting options:
X	-f		Print file name of matching files.
X	-h		Print the entire header.
X	-b		Print the article's body.
X	-a		Print the entire article (equivalent to -bh).
X	-k key		Print header lines with a keyword starting with
X			'key'.  If -h or -a is also specified, *don't*
X			print such lines.
X
XIf no output options are given, "-k Subject" is used (otherwise the
Xoutput would be empty).  If more than one output option is given, a
Xblank line is inserted between output for each article.
X
XProcessing options:
X	-p		Pipe the output for a single article through a
X			paging program: $PAGER, or "more" if $PAGER is
X			not set.
X	-e		After the normal output for an article is
X			produced, edit it using the editor specified in
X			$EDITOR, or $VISUAL, or "vi" if neither is set.
X
XFile specification options:
X	file|directory	All non-option arguments are treated as files
X			or directories to search for matches.  If a
X			directory is named, every file it contains is
X			searched, and all its subdirectories are
X			processed recursively.  If no files or
X			directories are given, the directory $SAVE is
X			searched, or $HOME/News/save if $SAVE is not
X			set.
X
X
XI also like to change the header a bit when I save an article; this is
Xdone by the shell script "save.sh" and the program "munge".  I believe
Xthese speak for themselves.  I an a fervent "rn" user, so I put the
Xfollowing macro in my .rnmac file:
X	S	!save %A\012
X
X
XNote that maybe the most important part of this posting is the set of
Xgenerally useable routines for processing article headers, case-
Xindependent conparisons and a few other things, contained in the
Xsubdirectory "lib".
X
XA hint on more complicated searches than the 'one-criterion' searches
Ximplemented by search: I routinely do something like
X
X	F=`search -f -m :foobar`
X
Xto find articles that have to do with "foobar", and then list them or
Xmake a subselection with
X
X	search <other options> $F
X
XSimilarly, you can 'or' matches together with something like
X
X	F=`{ search -f -m <match1>; search -f -m <match2>; } | sort -u`
X	search <format options> $F
X
X
XInstallation notes:
X	cd lib
X	make
X	cd ..
X	make all
X	# play with it
X	make install	# this moves the files to $HOME/bin
X
XYou need getopt and readdir+friends to compile this, which should not be
Xa problem if you have previously archived net.sources.  It's only been
Xtested on a VAX running 4.3 BSD but I don't see why it wouldn't work
Xelsewhere if you've got getopt and readdir c.s.
EOF
echo 'x - lib/Makefile'
sed 's/^X//' > 'lib/Makefile' << 'EOF'
XOBJS=	cimatch.o cistrcmp.o cistrncmp.o getmem.o strsave.o trim.o \
X	fatal.o scanheaders.o isdir.o safegets.o descend.o copyrest.o
X
Xlib.a:	$(OBJS)
X	ar cr @lib.a $(OBJS)
X	ranlib @lib.a
X	mv @lib.a lib.a
X
Xcimatch.o: cimatch.c
Xcimatch.o: ./defs.h
Xcimatch.o: /usr/include/stdio.h
Xcimatch.o: /usr/include/ctype.h
Xcimatch.o: /usr/include/strings.h
Xcistrcmp.o: cistrcmp.c
Xcistrcmp.o: ./defs.h
Xcistrcmp.o: /usr/include/stdio.h
Xcistrcmp.o: /usr/include/ctype.h
Xcistrcmp.o: /usr/include/strings.h
Xcistrncmp.o: cistrncmp.c
Xcistrncmp.o: ./defs.h
Xcistrncmp.o: /usr/include/stdio.h
Xcistrncmp.o: /usr/include/ctype.h
Xcistrncmp.o: /usr/include/strings.h
Xgetmem.o: getmem.c
Xgetmem.o: ./defs.h
Xgetmem.o: /usr/include/stdio.h
Xgetmem.o: /usr/include/ctype.h
Xgetmem.o: /usr/include/strings.h
Xstrsave.o: strsave.c
Xstrsave.o: ./defs.h
Xstrsave.o: /usr/include/stdio.h
Xstrsave.o: /usr/include/ctype.h
Xstrsave.o: /usr/include/strings.h
Xtrim.o: trim.c
Xtrim.o: ./defs.h
Xtrim.o: /usr/include/stdio.h
Xtrim.o: /usr/include/ctype.h
Xtrim.o: /usr/include/strings.h
Xfatal.o: fatal.c
Xfatal.o: ./defs.h
Xfatal.o: /usr/include/stdio.h
Xfatal.o: /usr/include/ctype.h
Xfatal.o: /usr/include/strings.h
Xisdir.o: isdir.c
Xisdir.o: ./defs.h
Xisdir.o: /usr/include/stdio.h
Xisdir.o: /usr/include/ctype.h
Xisdir.o: /usr/include/strings.h
Xisdir.o: /usr/include/sys/types.h
Xisdir.o: /usr/include/sys/stat.h
Xscanheaders.o: scanheaders.c
Xscanheaders.o: ./defs.h
Xscanheaders.o: /usr/include/stdio.h
Xscanheaders.o: /usr/include/ctype.h
Xscanheaders.o: /usr/include/strings.h
Xscanheaders.o: ./scanheaders.h
Xsafegets.o: safegets.c
Xsafegets.o: ./defs.h
Xsafegets.o: /usr/include/stdio.h
Xsafegets.o: /usr/include/ctype.h
Xsafegets.o: /usr/include/strings.h
Xdescend.o: descend.c
Xdescend.o: ./defs.h
Xdescend.o: /usr/include/stdio.h
Xdescend.o: /usr/include/ctype.h
Xdescend.o: /usr/include/strings.h
Xcopyrest.o: copyrest.c
Xcopyrest.o: ./defs.h
Xcopyrest.o: /usr/include/stdio.h
Xcopyrest.o: /usr/include/ctype.h
Xcopyrest.o: /usr/include/strings.h
EOF
echo 'x - lib/cimatch.c'
sed 's/^X//' > 'lib/cimatch.c' << 'EOF'
X/* See if str contains match. */
X
X#include "defs.h"
X
Xbool
Xcimatch(match, str, mlen)
X	char *match;
X	char *str;
X	int mlen;
X{
X	int slen= strlen(str);
X	
X	while (slen >= mlen) {
X		if (cistrncmp(match, str, mlen) == 0)
X			return TRUE;
X		++str;
X		--slen;
X	}
X	return FALSE;
X}
EOF
echo 'x - lib/cistrcmp.c'
sed 's/^X//' > 'lib/cistrcmp.c' << 'EOF'
X/* Case-independent strcmp. */
X
X#include "defs.h"
X
Xint
Xcistrcmp(s1, s2)
X	register char *s1, *s2;
X{
X	register c1, c2;
X	
X	for (;;) {
X		c1= *s1++;
X		c2= *s2++;
X		if (c1 == c2) {
X			if (c1 == EOS)
X				return 0;
X		}
X		else {
X			if (isupper(c1))
X				c1= _tolower(c1);
X			if (isupper(c2))
X				c2= _tolower(c2);
X			if (c1 < c2)
X				return -1;
X			else if (c1 > c2)
X				return 1;
X		}
X	}
X}
EOF
echo 'x - lib/cistrncmp.c'
sed 's/^X//' > 'lib/cistrncmp.c' << 'EOF'
X/* Case-independent strncmp. */
X
X#include "defs.h"
X
Xint
Xcistrncmp(s1, s2, len)
X	register char *s1, *s2;
X	int len;
X{
X	register c1, c2;
X	
X	while (--len >= 0) {
X		c1= *s1++;
X		c2= *s2++;
X		if (c1 == c2) {
X			if (c1 == EOS)
X				return 0;
X		}
X		else {
X			if (isupper(c1))
X				c1= _tolower(c1);
X			if (isupper(c2))
X				c2= _tolower(c2);
X			if (c1 < c2)
X				return -1;
X			else if (c1 > c2)
X				return 1;
X		}
X	}
X	return 0;
X}
EOF
echo 'x - lib/copyrest.c'
sed 's/^X//' > 'lib/copyrest.c' << 'EOF'
X/* Copy the rest of a file. */
X
X#include "defs.h"
X
Xcopyrest(ifp, ofp)
X	FILE *ifp;
X	FILE *ofp;
X{
X	int n;
X	char buf[10*BUFSIZ];
X	
X	while ((n= fread(buf, 1, sizeof(buf), ifp)) > 0)
X		(void) fwrite(buf, 1, n, ofp);
X}
EOF
echo 'x - lib/defs.h'
sed 's/^X//' > 'lib/defs.h' << 'EOF'
X#include <stdio.h>
X#include <ctype.h>
X#include <strings.h>
X
X/* Functions returning char *. */
X
Xchar *malloc();
Xchar *realloc();
X
Xchar *getenv();
X
X/* Defined in this library. */
X
Xchar *getmem();
Xchar *strsave();
Xchar *safegets();
Xchar *trim();
X
X/* Convenient shorthands. */
X
Xtypedef int bool;
X#define FALSE 0
X#define TRUE 1
X
Xtypedef int (*funcptr)();
X
X#define EOS '\0'
X#define SEP '/'
X
X/* Some System 5 compatibility. */
X
X#ifndef _tolower
X#define _tolower(c) ((c) + ('a'-'A'))
X#endif
X
X#define strchr index
X#define strrchr rindex
EOF
echo 'x - lib/descend.c'
sed 's/^X//' > 'lib/descend.c' << 'EOF'
X/* Recursively call 'process' for all files in a directory, and for all
X   files in subdirectories, etc.
X   Return code is 1 if the dir. couldn't be opened, else the OR of the
X   return codes of all process calls.
X   Don't let this loose on devices or other weird files like sockets... */
X
X#include "defs.h"
X
X#include <sys/types.h>
X#include <sys/param.h>
X#include <sys/dir.h>
X
Xint
Xdescend(name, process)
X	char *name;
X	funcptr process;
X{
X	DIR *dirp= opendir(name);
X	struct direct *dp;
X	char namebuf[MAXPATHLEN];
X	char *ebuf;
X	int status= 0;
X	
X	if (dirp == NULL) {
X		perror(name);
X		return 1;
X	}
X	strcpy(namebuf, name);
X	ebuf= namebuf + strlen(namebuf);
X	if (ebuf > namebuf && ebuf[-1] != SEP) {
X		*ebuf++ = SEP;
X		*ebuf= EOS;
X	}
X	while ((dp= readdir(dirp)) != NULL) {
X		if (dp->d_name[0] == '.' &&
X			(dp->d_name[1] == EOS ||
X			dp->d_name[1] == '.' && dp->d_name[2] == EOS))
X			continue;
X		strcpy(ebuf, dp->d_name);
X		if (isdir(namebuf))
X			status |= descend(namebuf, process);
X		else
X			status |= (*process)(namebuf);
X	}
X	closedir(dirp);
X	return status;
X}
EOF
echo 'x - lib/fatal.c'
sed 's/^X//' > 'lib/fatal.c' << 'EOF'
X/* Issue fatal error message and exit.
X   Can be replaced by a more specific routine in the calling program. */
X
X#include "defs.h"
X
Xfatal(message)
X	char *message;
X{
X	fprintf(stderr, "Fatal error: %s\n", message);
X	exit(3);
X}
EOF
echo 'x - lib/getmem.c'
sed 's/^X//' > 'lib/getmem.c' << 'EOF'
X/* Allocate memory, issue fatal error instead of returning NULL. */
X
X#include "defs.h"
X
Xchar *
Xgetmem(n)
X	int n;
X{
X	register char *p= malloc((unsigned) n);
X	
X	if (p == NULL) {
X		fatal("out of memory");
X		exit(2); /* If fatal doesn't exit */
X	}
X	return p;
X}
EOF
echo 'x - lib/isdir.c'
sed 's/^X//' > 'lib/isdir.c' << 'EOF'
X/* Is this a directory? */
X
X#include "defs.h"
X
X#include <sys/types.h>
X#include <sys/stat.h>
X
Xbool
Xisdir(name)
X	char *name;
X{
X	struct stat s;
X	
X	return stat(name, &s) >= 0 && (s.st_mode & S_IFMT) == S_IFDIR;
X}
EOF
echo 'x - lib/safegets.c'
sed 's/^X//' > 'lib/safegets.c' << 'EOF'
X/* Call fgets but also truncate long line. */
X
X#include "defs.h"
X
Xchar *
Xsafegets(buf, size, fp)
X	char *buf;
X	int size;
X	register FILE *fp;
X{
X	register char *q;
X	register char *p= fgets(buf, size, fp);
X	register int c;
X	
X	if (p != NULL) {
X		q= p+strlen(p);
X		if (q[-1] != '\n') {
X			while ((c= getc(fp)) != '\n' && c != EOF)
X				;
X		}
X	}
X	return p;
X}
EOF
echo 'x - lib/scanheaders.c'
sed 's/^X//' > 'lib/scanheaders.c' << 'EOF'
X/* Scan the headers of a mail/news article, calling a given function for
X   each header line.
X   The function has three string parameters: keyword, value, filename,
X   and a boolean which is FALSE for continuation lines.
X   When this function returns TRUE, abandon the scan and return 1.
X   Otherwise, return 0 if the headers have the correct format,
X   or a negative error code if not. */
X
X#include "defs.h"
X#include "scanheaders.h"
X
Xint
Xscanheaders(fp, name, match)
X	FILE *fp;
X	char *name;
X	funcptr match;
X{
X	char linebuf[BUFSIZ];
X	char *p;
X	int c;
X	int count= 0;
X	
X	while (safegets(linebuf, BUFSIZ, fp) != NULL) {
X		p= linebuf;
X		if (*p == '\n')
X			break; /* End of headers */
X		/* Check for header line: */
X		while (iskwchar(*p))
X			++p; /* Skip keyword */
X		if (p[0] != ':' || !isspace(p[1])) {
X			if (count == 0)
X				return BAD_HEADER;
X			else
X				return BAD_LINE;
X		}
X		*p++ = EOS; /* Turn keyword into a separate string */
X		if ((*match)(linebuf, p, name, TRUE))
X			return FOUND_MATCH;
X		for (;;) {
X			++count;
X			/* Check for continuation line. */
X			c= getc(fp);
X			(void) ungetc(c, fp);
X			if (c == '\n' || !isspace(c))
X				break;
X			(void) safegets(p, BUFSIZ-(p-linebuf), fp);
X			if ((*match)(linebuf, p, name, FALSE))
X				return FOUND_MATCH;
X		}
X		++count;
X	}
X	if (count == 0) {
X		if (ftell(fp) == 0)
X			return EMPTY_FILE;
X		else
X			return NO_HEADERS;
X	}
X	else
X		return NO_MATCH;
X}
X
X/* A header line consists of a keyword immediately followed by a colon
X   and whitespace; keywords can contain letters, digits, '-' and '.'.
X   (These rules are stricter than those for ARPANET-mail headers, where
X   keywords can contain any printable characters except colon and space,
X   and no whitespace is required after the colon; however, they conform
X   to observed practice in USENET mail.)
X   A header line can be followed by any number of continuation lines;
X   a continuation line starts with a space or tab.
X   A file is considered to have an error if there are no header lines
X   or if the last header line is not followed by a blank line.
X*/
EOF
echo 'x - lib/scanheaders.h'
sed 's/^X//' > 'lib/scanheaders.h' << 'EOF'
X/* Error codes returned by scanheaders. */
X
X#define FOUND_MATCH	1
X#define NO_MATCH	0
X#define EMPTY_FILE	-1
X#define NO_HEADERS	-2
X#define BAD_HEADER	-3
X#define BAD_LINE	-4
X
X/* What characters are valid in a keyword: */
X
X#define iskwchar(c) (isalnum(c) || (c) == '-' || (c) == '.')
EOF
echo 'x - lib/strsave.c'
sed 's/^X//' > 'lib/strsave.c' << 'EOF'
X/* Save a copy of a string on the heap. */
X
X#include "defs.h"
X
Xchar *
Xstrsave(s)
X	register char *s;
X{
X	register char *p;
X	
X	if (s == NULL)
X		return s;
X	p= getmem(strlen(s) + 1);
X	return strcpy(p, s);
X}
EOF
echo 'x - lib/trim.c'
sed 's/^X//' > 'lib/trim.c' << 'EOF'
X/* Trim leading and trailing whitespace. */
X
X#include "defs.h"
X
Xchar *
Xtrim(s)
X	register char *s;
X{
X	register char *p;
X	
X	while (isspace(*s))
X		++s;
X	p= s + strlen(s) - 1;
X	while (p >= s && isspace(*p))
X		--p;
X	p[1]= EOS;
X	return s;
X}
EOF
echo 'x - munge.c'
sed 's/^X//' > 'munge.c' << 'EOF'
X/* munge [file]
X
X   Header munger, copies a news article but puts the header in a
X   somewhat standard format, removing unnecessary headers, and
X   adding others.  This could do much, much more...
X*/
X
X#include "lib/defs.h"
X
X#include <sys/types.h>
X#include <sys/stat.h>
X
X/* Getopt globals: */
Xextern int optind;
Xextern char *optarg;
X
Xmain(argc, argv)
X	int argc;
X	char **argv;
X{
X	int c;
X	int status= 0;
X	
X	while ((c= getopt(argc, argv, "k:")) != EOF) {
X		switch (c) {
X		case 'k':
X			addkeyarg(optarg);
X			break;
X		default:
X			fprintf(stderr, "usage: %s [-k key=value] [file]\n",
X				argv[0]);
X			exit(2);
X		}
X	}
X	if (optind >= argc)
X		status= fprocess(stdin, "-");
X	else if (optind < argc-1) {
X		fprintf(stderr, "%s: only one file allowed\n", argv[0]);
X		exit(2);
X	}
X	else if (strcmp(argv[optind], "-") == 0)
X		status= fprocess(stdin, "-");
X	else {
X		FILE *fp= fopen(argv[optind], "r");
X		if (fp == NULL) {
X			perror(argv[optind]);
X			exit(1);
X		}
X		status= fprocess(fp, argv[optind]);
X		(void) fclose(fp);
X	}
X	exit(status);
X}
X
X/* Process one file, given as a FILE pointer. */
X
Xint maycopy(); /* Forward */
X
Xint
Xfprocess(fp, name)
X	FILE *fp;
X	char *name;
X{
X	if (scanheaders(fp, name, maycopy) != 0)
X		return 1;
X	addnew(fp, name);
X	printf("\n");
X	copyrest(fp, stdout);
X	return 0;
X}
X
X/* List of headers to be ignored. */
X
Xchar *ignore[]= {
X	"Relay-Version",
X	"Posting-Version",
X	"Path",
X	"Message-ID",
X	"Article-I.D.",
X	"Distribution",
X	"Lines",
X	"Apparently-To",
X	"Approved",
X	"Xref",
X	"NF-ID",
X	"NF-From",
X	/* These dates should really be processesed specially: */
X	"Date",
X	"Posted",
X	"Date-Received",
X	NULL
X};
X
X/* Copy a header line, or not.
X   It may be ignored, printed, or transformed and printed. */
X
X/*ARGSUSED*/
Xbool
Xmaycopy(key, value, file, first)
X	char *key;
X	char *value;
X	char *file;
X	bool first;
X{
X	char **pp;
X	
X	for (pp= ignore; *pp != NULL; ++pp) {
X		if (cistrcmp(*pp, key) == 0)
X			return;
X	}
X	if (first)
X		printf("%s: ", key);
X	else
X		printf("\t");
X	printf("%s\n", trim(value));
X	return FALSE;
X}
X
X/* Add key=value pairs from the command line. */
X
Xaddkeyarg(s)
X	char *s;
X{
X	char *p;
X	
X	for (p= s; *p != EOS; ++p) {
X		if (*p == ':' || *p == '=') {
X			*p++= EOS;
X			break;
X		}
X	}
X	printf("%s: %s\n", s, trim(p));
X}
X
X/* Calculate a file's size, return -1 if unknown. */
X
Xlong
Xcalcsize(fp, name)
X	FILE *fp;
X	char *name;
X{
X	struct stat s;
X	
X	if (fstat(fileno(fp), &s) < 0)
X		return -1;
X	else
X		return s.st_size;
X}
X
X/* Make up some useful headers.
X   Eventually this will also ask the user for keywords. */
X
Xaddnew(fp, name)
X	FILE *fp;
X	char *name;
X{
X	long size;
X	long now;
X	
X	time(&now);
X	printf("Date-Saved: %ld (%s)\n", now, trim(ctime(&now)));
X	
X	if ((size= calcsize(fp, name)) >= 0) {
X		size -= ftell(fp);
X		/* Don't do this from a pipe --
X		   pipes seem to be recognizable because size == 1. */
X		if (size > 1)
X			printf("Size: %ld bytes\n", size);
X	}
X}
EOF
echo 'x - save.sh'
sed 's/^X//' > 'save.sh' << 'EOF'
X: Save a news article
X
Xcase $# in
X0)	echo "usage: `basename $0` file ..." >&2; exit 2;;
Xesac
X
Xsavedir=$HOME/News/save
Xif	(cd $savedir) 2>/dev/null
Xthen	:
Xelse	mkdir $savedir || exit
Xfi
X
Xtemp=/tmp/sv$$
X
Xtrap 'rm -f $temp; exit' 0 1 2 3 13 14 15
X
Xsave=$savedir/sv$$
X
Xfor file
Xdo
X	# Generate a new random number: use the pid of a small background job
X	while test -f $save
X	do
X		sleep 1 &
X		save=$savedir/sv$!
X	done
X	
X	>$temp
X
X	echo -n "Format: (e.g. binhex, shar, uuencode, diff, ...) "
X	read format
X	case $format in
X	?*)	echo "Format: $format" >>$temp ;;
X	esac
X	
X	echo -n "Type: (e.g. program, source, howto, info, discussion, ...) "
X	read type
X	case $type in
X	?*)	echo "Type: $type" >>$temp ;;
X	esac
X	
X	echo -n "Topic: (e.g., unix, c, mac, pc, c++, languages, ...) "
X	read topic
X	case $topic in
X	?*)	echo "Topic: $topic" >>$temp ;;
X	esac
X	
X	echo -n "Keywords (comma-separated list): "
X	read keywords
X	case $keywords in
X	?*)	echo "Keywords: $keywords" >>$temp ;;
X	esac
X	
X	cat $file >>$temp; munge $temp >$save
X
Xdone
EOF
chmod +x 'save.sh'
echo 'x - search.c'
sed 's/^X//' > 'search.c' << 'EOF'
X/* search [options] ... [file|directory] ...
X
X   Search saved news articles for a match, and summarize the matching
X   articles.
X   Options are:
X	-f		show file name
X	-b		show article's body
X	-h		show article's header
X	-a		show entire article (== -b -h)
X	-k key		show header lines with this key; *suppress* matching
X			header lines if combined with -h or -a
X	-m [key]:[val]	process only matching articles
X	-p		pipe output through pager (for each article separately)
X	-e		allow editing of the file
X   Directories are scanned recursively for matching files.
X   If no files or directories are given, searches ~/News/save, or $SAVE if
X   defined.
X   If none of the parameters -f, -b, -h, -a, -k or -e are given, -k Subject
X   is assumed.
X*/
X
X#include "lib/defs.h"
X#include "lib/scanheaders.h"
X
X/* Getopt globals: */
Xextern int optind;
Xextern char *optarg;
X
Xint process(); /* Forward */
X
X/* Command line options. */
X
Xbool do_name= FALSE;
Xbool do_header= FALSE;
Xbool do_body= FALSE;
Xbool use_pager= FALSE;
Xbool edit_file= FALSE;
X
Xmain(argc, argv)
X	int argc;
X	char **argv;
X{
X	int c;
X	int status= 0;
X	
X	while ((c= getopt(argc, argv, "abefhpk:m:")) != EOF) {
X		switch (c) {
X		case 'f':
X			do_name= TRUE;
X			break;
X		case 'h':
X			do_header= TRUE;
X			break;
X		case 'k':
X			set_keyword(optarg);
X			break;
X		case 'm':
X			set_pattern(optarg);
X			break;
X		case 'a':
X			do_header= do_body= TRUE;
X			break;
X		case 'b':
X			do_body= TRUE;
X			break;
X		case 'p':
X			use_pager= TRUE;
X			break;
X		case 'e':
X			edit_file= TRUE;
X			break;
X		default:
X			usage(argv[0]);
X		}
X	}
X	add_defaults(); /* If no flags given */
X	if (argc-optind < 1) {
X		char *save= getenv("SAVE");
X		if (save == NULL || *save == EOS) {
X			char buf[BUFSIZ];
X			save= getenv("HOME");
X			if (save == NULL || *save == EOS)
X				usage(argv[0]);
X			sprintf(buf, "%s/News/save", save);
X			save= strsave(buf);
X		}
X		status |= descend(save, process);
X	}
X	else {
X		do {
X			if (isdir(argv[optind]))
X				status |= descend(argv[optind], process);
X			else
X				status |= process(argv[optind]);
X		} while (++optind < argc);
X	}
X	exit(status);
X}
X
X/* Issue 'usage' error message and exit. */
X
Xusage(progname)
X	char *progname;
X{
X	fprintf(stderr,
X	    "usage: %s [-abefhp] [-m key:match] [-k key] ... file|dir ...\n",
X	    progname);
X	exit(2);
X}
X
X/* Process one file given by name. */
X
Xint
Xprocess(name)
X	char *name;
X{
X	FILE *fp;
X	int status;
X	
X	if ((fp= fopen(name, "r")) == NULL) {
X		perror(name);
X		return 1;
X	}
X	status= fprocess(fp, name);
X	(void) fclose(fp);
X	return status;
X}
X
X/* Process one file, given as a FILE pointer. */
X
Xint match(); /* Forward */
Xint display(); /* Forward */
X
Xbool have_pattern= FALSE;
X
Xint
Xfprocess(fp, name)
X	FILE *fp;
X	char *name;
X{
X	int code= have_pattern ? scanheaders(fp, name, match) : FOUND_MATCH;
X	
X	switch (code) {
X	
X	case FOUND_MATCH:
X		startfile(name);
X		rewind(fp);
X		(void) scanheaders(fp, name, display);
X		endfile(fp, name);
X		/* Fall through */
X	case NO_MATCH:
X		return 0;
X		
X	case EMPTY_FILE:
X		fprintf(stderr, "%s: empty file\n", name);
X		break;
X	
X	case NO_HEADERS:
X		fprintf(stderr, "%s: no headers\n", name);
X		break;
X	
X	case BAD_HEADER:
X		fprintf(stderr, "%s: bad headers\n", name);
X		break;
X	
X	case BAD_LINE:
X		fprintf(stderr, "%s: bad header line\n", name);
X		break;
X	
X	default:
X		fprintf(stderr, "%s: bad header, code %d\n", name,
X		code);
X		break;
X	
X	}
X	return 1;
X}
X
X/* Set the search pattern. */
X
Xchar *the_key;
Xint key_len;
Xchar *the_val;
Xint val_len;
X
Xset_pattern(pattern)
X	char *pattern;
X{
X	char *key= pattern;
X	char *value= pattern;
X	
X	while (iskwchar(*value))
X		++value;
X	if (*value != EOS)
X		*value++ = EOS;
X	the_key= key;
X	key_len= strlen(the_key);
X	the_val= trim(value);
X	val_len= strlen(the_val);
X	have_pattern= key_len + val_len > 0;
X}
X
X/* See if key, value match with the given pattern. */
X
X/*ARGSUSED*/
Xbool
Xmatch(key, value, file, first)
X	char *key;
X	char *value;
X	char *file;
X	bool first;
X{
X	return cistrncmp(the_key, key, key_len) == 0 &&
X		cimatch(the_val, value, val_len);
X}
X
X/* Set the keywords to be used in displaying the header. */
X
X#define MAXKEYWORDS 50
Xint nkeywords= 0;
Xchar *keywords[MAXKEYWORDS];
X
Xset_keyword(key)
X	char *key;
X{
X	if (nkeywords >= MAXKEYWORDS) {
X		fprintf(stderr, "too many -k parameters\n");
X		exit(2);
X	}
X	keywords[nkeywords++]= key;
X}
X
X/* Print a header line, if it matches the specifications. */
X
XFILE *ofile= stdout;
X
Xbool
Xdisplay(key, value, file, first)
X	char *key;
X	char *value;
X	char *file;
X	bool first;
X{
X	if (okay(key)) {
X		if (first)
X			fprintf(ofile, "%s: ", key);
X		else
X			fprintf(ofile, "\t");
X		fprintf(ofile, "%s\n", trim(value));
X	}
X	return FALSE;
X}
X
X/* Do we want to print this key? */
X
Xbool
Xokay(key)
X	char *key;
X{
X	int i;
X	
X	for (i= 0; i < nkeywords; ++i) {
X		if (cistrncmp(key, keywords[i], strlen(keywords[i])) == 0)
X			return !do_header;
X	}
X	return do_header;
X}
X
Xstartfile(file)
X	char *file;
X{
X	if (use_pager) {
X		static char *pager;
X		if (pager == NULL) {
X			pager= getenv("PAGER");
X			if (pager == NULL || *pager == EOS)
X				pager= "more";
X		}
X		ofile= popen(pager, "w");
X	}
X	if (do_name)
X		fprintf(ofile, "%s\n", file);
X}
X
Xendfile(fp, file)
X	FILE *fp;
X	char *file;
X{
X	if (do_header || (nkeywords + do_name) > 1)
X		fprintf(ofile, "\n");
X	if (do_body)
X		copyrest(fp, ofile);
X	if (use_pager) {
X		int status= pclose(ofile);
X		if (status != 0)
X			fprintf("pager exit code %d\n", status);
X	}
X	if (edit_file) {
X		static char *editor;
X		char command[BUFSIZ];
X		char line[10];
X		char *reply;
X		int status;
X		
X		if (editor == NULL) {
X			editor= getenv("EDITOR");
X			if (editor == NULL || *editor == EOS)
X				editor= getenv("VISUAL");
X			if (editor == NULL || *editor == EOS)
X				editor= "vi";
X		}
X		sprintf(command, "%s %s", editor, file);
X		fprintf(stderr, "Edit? [yn] ");
X		safegets(line, sizeof line, stdin);
X		reply= trim(line);
X		if (*reply == 'n' || *reply == 'N')
X			return;
X		status= system(command);
X		if (status != 0)
X			fprintf(stderr, "editor exit code %d\n", status);
X	}
X}
X
X/* Add defaults, to ensure at least some output is generated
X   for every match. */
X
Xadd_defaults()
X{
X	if (do_header + do_body + do_name + nkeywords == 0)
X		set_keyword("Subject");
X}
EOF
echo 'Part 01 out of 01 of pack.out complete.'
exit 0