rsalz@uunet.uu.net (Rich Salz) (10/28/89)
Submitted-by: Henry Spencer <attcan!utzoo!henry> Posting-number: Volume 20, Issue 79 Archive-name: inserts I alluded to this in comp.text a while ago, and got a number of requests for it. It's a program for things like form letters, where one wants to insert chunks of text into repetitions of a master document. It does much the same things as troff's .rd request, but it is much faster, does better error checking, and cuts the troff input down to a single stream (which is much easier to use in an environment where troff jobs get batched up and fed to a server). echo 'README': sed 's/^X//' >'README' <<'!' XI alluded to this in comp.text a while ago, and got a number of requests Xfor it. It's a program for things like form letters, where one wants to Xinsert chunks of text into repetitions of a master document. It does much Xthe same things as troff's .rd request, but it is much faster, does better Xerror checking, and cuts the troff input down to a single stream (which is Xmuch easier to use in an environment where troff jobs get batched up and Xfed to a server). X X Henry Spencer at U of Toronto Zoology X uunet!attcan!utzoo!henry henry@zoo.toronto.edu ! echo 'inserts.1': sed 's/^X//' >'inserts.1' <<'!' X.TH INSERTS 1 local X.DA 14 April 1988 X.SH NAME Xinserts \- do insertions into document X.SH SYNOPSIS X.B inserts X[ X.B \-p Xinspt X] [ X.B \-e Xend X] [ X.B \-s Xoutsep X] [ X.B \-w X] [ X.B \-i Xinsfile X] [ file ] ... X.SH DESCRIPTION X.I Inserts Xaccepts input from the named \fIfile\fR(s) (standard input default; X`\-' as a name means standard input also) Xconsisting of a document containing \fIinsertion points\fR, Xfollowed by a sequence of \fIinserts\fR, and produces X(on standard output) copies of the document with each insertion point Xreplaced by an insert. XInserts are used in succession until there are no more; Xthe number of copies of the document produced Xis the number needed to use up all the inserts. XIf the inserts end midway through a copy of the document, Xempty insertions are supplied until that copy is complete. X.PP XAn insertion point in the input Xis a line consisting entirely of the \fIinspt\fR string, X(default \fB.RD\fR). XIn the input, the document is separated from the inserts by Xa line Xconsisting entirely Xof the \fIend\fR string (default \fB.INSERTS\fR). XSuccessive inserts are separated from each other in the input by a Xsingle empty line. X.I Inserts Xseparates Xsuccessive copies of the document in the output by a line Xconsisting entirely of the \fIoutsep\fR string (default \fB.END\fR). X.PP XIf the \fB\-i\fR option is given, the effect is as if Xthe contents of \fIinsfile\fR were appended to the input, with an X\fIend\fR line in between. X.SH SEE ALSO Xtroff(1) X.SH DIAGNOSTICS XIt is an error for there to be no insertion points in the document. XIn the absence of the \fB\-w\fR option, warnings are produced if there Xare no inserts or if the supply of inserts is exhausted midway through Xa copy of the document. X.SH HISTORY XWritten at U of Toronto by Henry Spencer. X.SH BUGS XA finite in-core buffer is used to hold the document, so a very large Xdocument will fail. ! echo 'inserts.c': sed 's/^X//' >'inserts.c' <<'!' X/* X * inserts - process form-letter-ish insertions X * X * $Log$ X */ X X#include <stdio.h> X#include <sys/types.h> X#include <sys/stat.h> X#include <string.h> X X#define MAXSTR 500 /* For sizing strings -- DON'T use BUFSIZ! */ X#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0) X X#ifndef lint Xstatic char RCSid[] = "$Header$"; X#endif X X/* storage for document body */ X#define MAXBODY 30000 X#define MAXSEGS 100 Xchar body[MAXBODY]; /* relies on initialization to 0 */ Xint nbody = 0; /* chars in body */ Xchar *segs[MAXSEGS] = { body }; /* ptrs to NUL-terminated body segments */ Xint nsegs = 1; X X/* markers in text */ Xchar *inspoint = ".RD"; /* insertion point in body */ Xchar *endbody = ".INSERTS"; /* end of body */ Xchar *separator = ".END"; /* separator between copies in output */ Xint dowarn = 1; /* supply warnings? */ X Xint debug = 0; Xchar *progname; X Xchar **argvp; /* scan pointer for nextfile() */ Xchar *nullargv[] = { "-", NULL }; /* dummy argv for case of no args */ Xchar *inname; /* filename for messages etc. */ Xchar *filename = NULL; /* -f overrides inname for messages */ Xlong lineno; /* line number for messages etc. */ XFILE *in = NULL; /* current input file */ X Xchar *ifile = NULL; /* -i file, if any */ Xint istart = 0; /* just starting ifile? */ X Xextern void error(), exit(); X#ifdef UTZOOERR Xextern char *mkprogname(); X#else X#define mkprogname(a) (a) X#endif X Xchar *nextfile(); Xvoid fail(); Xvoid getfirst(); Xvoid getins(); X X/* X - main - parse arguments and handle options X */ Xmain(argc, argv) Xint argc; Xchar *argv[]; X{ X int c; X int errflg = 0; X extern int optind; X extern char *optarg; X void process(); X X progname = mkprogname(argv[0]); X X while ((c = getopt(argc, argv, "p:e:s:wi:d")) != EOF) X switch (c) { X case 'p': /* insertion-point marker */ X inspoint = optarg; X break; X case 'e': /* end-of-body marker */ X endbody = optarg; X break; X case 's': /* output separator */ X separator = optarg; X break; X case 'w': /* suppress warnings */ X dowarn = 0; X break; X case 'i': /* this is file of inserts */ X ifile = optarg; X break; X case 'd': /* Debugging. */ X debug++; X break; X case '?': X default: X errflg++; X break; X } X if (errflg) { X fprintf(stderr, "usage: %s ", progname); X fprintf(stderr, "[-i inspt] [-e end] [-s outsep] [file] ...\n"); X exit(2); X } X X if (optind >= argc) X argvp = nullargv; X else X argvp = &argv[optind]; X inname = nextfile(); X if (inname != NULL) X process(); X exit(0); X} X X/* X - getline - get next line (internal version of fgets) X */ Xchar * Xgetline(ptr, size) Xchar *ptr; Xint size; X{ X register char *namep; X X while (fgets(ptr, size, in) == NULL) { /* while no more */ X namep = nextfile(); /* try next source */ X if (namep == NULL) X return(NULL); /* really no more */ X inname = namep; X if (istart) { /* transition to ifile */ X sprintf(ptr, "%s\n", endbody); X return(ptr); X } X } X lineno++; X return(ptr); X} X X/* X - nextfile - switch files X */ Xchar * /* filename */ Xnextfile() X{ X register char *namep; X struct stat statbuf; X extern FILE *efopen(); X X namep = *argvp; X if (namep == NULL) { /* no more files */ X if (ifile == NULL) X return(NULL); X else if (istart) { /* already done the end line */ X istart = 0; X namep = ifile; X ifile = NULL; X } else { /* supply end line before ifile */ X istart = 1; X return(inname); X } X } else X argvp++; X X if (in != NULL) X (void) fclose(in); X X if (STREQ(namep, "-")) { X in = stdin; X namep = "stdin"; X } else { X in = efopen(namep, "r"); X if (fstat(fileno(in), &statbuf) < 0) X error("can't fstat `%s'", namep); X if ((statbuf.st_mode & S_IFMT) == S_IFDIR) X error("`%s' is directory!", namep); X } X X lineno = 0; X return(namep); X} X X/* X - fail - complain and die X */ Xvoid Xfail(s1, s2) Xchar *s1; Xchar *s2; X{ X fprintf(stderr, "%s: (file `%s', line %ld) ", progname, X (filename != NULL) ? filename : inname, lineno); X fprintf(stderr, s1, s2); X fprintf(stderr, "\n"); X exit(1); X} X X/* X - warn - just complain X */ Xvoid Xwarn(s1, s2) Xchar *s1; Xchar *s2; X{ X if (!dowarn) X return; X X fprintf(stderr, "%s: (file `%s', line %ld) (warning) ", progname, X (filename != NULL) ? filename : inname, lineno); X fprintf(stderr, s1, s2); X fprintf(stderr, "\n"); X} X X/* X - process - process input data X * X * The MAXBODY check is over-conservative in that it will blow up a few X * characters too early on an insertion-point or body-end line; big deal. X */ Xvoid Xprocess() X{ X char line[MAXSTR]; X# define LINSIZ ((int)sizeof(line)) X register int len; X register int seg; X int firstbody; /* first time body has been output? */ X int firstins; /* first line of first insertion in body? */ X int firstline; /* first line of an insertion? */ X X /* pick up the body */ X while ((len = getbody(line, LINSIZ)) > 0) { X if (nbody + len >= MAXBODY-1) /* -1 for final NUL */ X fail("document too large", ""); X line[len-1] = '\0'; /* get rid of newline */ X if (STREQ(line, inspoint)) { X nbody++; /* NUL ends that segment */ X body[nbody] = '\0'; /* start new segment */ X segs[nsegs++] = body + nbody; X } else { X line[len-1] = '\n'; /* put newline back */ X (void) strcpy(body+nbody, line); X nbody += len; X } X } X if (nsegs <= 1) X fail("no insertion points (%s's) found!", inspoint); X X /* insertions */ X firstbody = 1; X while (getline(line, LINSIZ) != NULL) { X if (!firstbody) { /* first body doesn't need separator */ X (void) fputs(separator, stdout); X (void) fputs("\n", stdout); X } X firstbody = 0; X firstins = 1; X (void) fputs(segs[0], stdout); X for (seg = 1; seg < nsegs; seg++) { X firstline = 1; X for (;;) { X if (!firstins) { /* first line read */ X if (firstline) X getfirst(line, LINSIZ); X else X getins(line, LINSIZ); X } X firstins = 0; X firstline = 0; X if (STREQ(line, "\n")) X break; X (void) fputs(line, stdout); X } X (void) fputs(segs[seg], stdout); X } X } X if (firstbody) X warn("no inserts supplied!", ""); X} X X/* X - getbody - get a body line X */ Xint /* length of line; 0 means end of body */ Xgetbody(buf, nbuf) Xchar *buf; Xint nbuf; X{ X register int len; X X if (getline(buf, nbuf) == NULL) X return(0); X len = strlen(buf); X if (buf[len-1] != '\n') X fail("line too long", ""); X buf[len-1] = '\0'; X if (STREQ(buf, endbody)) X return(0); X buf[len-1] = '\n'; X return(len); X} X X/* X - getfirst - get first line of insert, EOF turning into empty line X */ Xvoid Xgetfirst(buf, nbuf) Xchar *buf; Xint nbuf; X{ X static int grumped = 0; X X if (getline(buf, nbuf) == NULL) { X if (!grumped) X warn("ran out of inserts in mid-document!", ""); X grumped = 1; X (void) strcpy(buf, "\n"); X } X} X X/* X - getins - get an insert line, EOF turning into empty line X * X * like getfirst except that EOF just ends the insert X */ Xvoid Xgetins(buf, nbuf) Xchar *buf; Xint nbuf; X{ X if (getline(buf, nbuf) == NULL) X (void) strcpy(buf, "\n"); X} ! echo done -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net. Use a domain-based address or give alternate paths, or you may lose out.