rs@uunet.UU.NET (Rich Salz) (07/31/87)
Submitted-by: tektronix!tessi!bobl (Bob Lewis) Posting-number: Volume 10, Issue 70 Archive-name: nrchbar [ This program works with diff on ostensible Unix formatter input to output formatting codes to do changebars. Very nice. --r$ ] #!/bin/sh # to extract, remove the header and type "sh filename" if `test ! -s ./README` then echo "writing ./README" cat > ./README << '\Rogue\Monster\' The "nrchbar" program effectively inserts n/troff-style change bar commands into files which are (presumably) n/troff source. For more information, consult the man page. To install "nrchbar": 1) Inspect "Makefile" for proper destinations (currently, "/usr/local/bin" for the binary and "/usr/man/manl" for the man page). Correct these if necessary. If "diff" on your system isn't "/bin/diff", you'll also need to modify the "DIFFPROG" #define in "nrchbar.c". "nrchbar" assumes "getopt" is present, so you'll need to contact your friendly neighborhood comp.sources.unix archive if it isn't. 2) In the source directory: % make install 3) If everything goes well, still in the source directory: % make clean I claim that this program was written entirely by me, entitling me (with the approval of my management) to release it into the public domain, which I now do. However, I make no warrantees, either express or implied, on "nrchbar" or its associated files and I do not assume responsiblity for any damages resulting from its installation or use. The software has only been compiled and tested on a Sun 3/50, but I don't expect any major problems on other UNIX machines, including System V. In spite of the disclaimer above, I'd like to hear about bugs and/or feature requests. Acknowledgements and thanks to Robert Reed of Tektronix, who wrote the original shell script; and to Jeff Hahs, Ron Lunde, Charlie Mills, and Bill den Beste of TSSI for encouragement, ideas, and testing. - Bob Lewis Test Systems Strategies, Inc. 8205 SW Creekside Pl. Beaverton, OR 97005 (503) 643-9281 ...!tektronix!tessi!bobl or bobl@tessi.UUCP \Rogue\Monster\ else echo "will not over write ./README" fi if `test ! -s ./Makefile` then echo "writing ./Makefile" cat > ./Makefile << '\Rogue\Monster\' SRC = nrchbar.c BIN = nrchbar BINDEST = /usr/local/bin MAN = nrchbar.1 MANDEST = /usr/man/manl SHARNAME = nrchbar $(BIN): $(SRC) cc -O -o $(BIN) $(SRC) clean: rm -f $(BIN) *.o core a.out install: $(BIN) $(MAN) install -s $(BIN) $(BINDEST) cp $(MAN) $(MANDEST) lint: $(SRC) lint -bchx $(SRC) shar: README Makefile $(SRC) $(MAN) shar -f $(SHARNAME) README Makefile $(SRC) $(MAN) \Rogue\Monster\ else echo "will not over write ./Makefile" fi if `test ! -s ./nrchbar.c` then echo "writing ./nrchbar.c" cat > ./nrchbar.c << '\Rogue\Monster\' /* nrchbar -- insert nroff change bar commands into a file */ #include <stdio.h> #define DIFFPROG "/bin/diff" typedef int bool; #define TRUE 1 #define FALSE 0 #define ADD 'a' #define CHANGE 'c' #define DELETE 'd' typedef struct { int lnOldFrom; int lnOldTo; int cmd; int lnNewFrom; int lnNewTo; } diffdescr; char *progname; bool flagAll = FALSE; bool flagBreakAfter = TRUE; bool flagDelete = FALSE; /* forward declarations */ FILE *fopenOrElse(); void nrchbar(); FILE *pipeDiffs(); void skipLinesOrElse(); void userErr(); #ifndef lint char *rcsid = "$Header: nrchbar.c,v 1.3 87/03/31 16:33:16 bobl Exp $"; #endif main(argc, argv) int argc; char *argv[]; { int c; extern int optind; FILE *fpDiff, *fpNew; char *fnOld, *fnNew; progname = argv[0]; while ((c = getopt(argc, argv, "abd")) != EOF) switch (c) { case 'a': flagAll = TRUE; break; case 'b': flagBreakAfter = FALSE; break; case 'd': flagDelete = TRUE; break; case '?': (void) fprintf(stderr, "%s: unknown flag \"-%c\"\n", c); exit(1); break; } /* * Depending on the number of files on the command line. */ switch (argc - optind) { case 1: fpDiff = stdin; fnNew = argv[optind++]; fpNew = fopenOrElse(fnNew, "r"); break; case 2: fnOld = argv[optind++]; fnNew = argv[optind++]; fpDiff = pipeDiffs(fnOld, fnNew); fpNew = fopenOrElse(fnNew, "r"); break; default: (void) fprintf(stderr, "Usage: %s [ -a ] [ -b ] [ -d ] [ <old> ] new\n", progname); exit(1); } /* * Now the real work begins. */ nrchbar(fpDiff, fpNew); exit(0); } /* beginChbar -- put out a *roff command to begin a change bar */ void beginChbar(chMark) int chMark; /* in: use this character */ { (void) printf(".mc %c\n", chMark); return; } /* copyLines -- copy a given number of lines from one file to standard output */ bool copyLines(nLn, nSkip, fpFrom) int nLn; /* in: copy this many lines ... */ int nSkip; /* in: ... skipping this many characters at the start of each one ... */ FILE *fpFrom; /* in: ... from this file to stdout */ { int ch; int iSkip; while (nLn-- > 0) { for (iSkip = 0; iSkip < nSkip; iSkip++) if (getc(fpFrom) == EOF) return FALSE; do { ch = getc(fpFrom); if (ch == EOF) return FALSE; putchar(ch); } while (ch != '\n'); } return TRUE; } /* copyLinesOrElse -- copy a given number of lines from one file to standard output; exit if unable */ void copyLinesOrElse(nLn, nSkip, fpFrom) int nLn; /* in: copy this many lines ... */ int nSkip; /* in: ... skipping this many characters at the start of each one ... */ FILE *fpFrom; /* in: ... from this file to stdout */ { if (!copyLines(nLn, nSkip, fpFrom)) userErr("unexpected EOF"); return; } /* endChbar -- put out a *roff command to end a change bar section */ void endChbar() { if (flagBreakAfter) (void) printf(".br\n"); /* make sure the previous change bar is visible */ (void) printf(".mc\n"); return; } /* fopenOrElse -- open a file or else exit with an error message */ FILE *fopenOrElse(fname, type) char *fname; /* in: file name to open */ char *type; /* in: way in which to open fname */ { FILE *fp = fopen(fname, type); if (fp == NULL) { (void) fprintf(stderr, "%s: can't open \"%s\" for mode \"%s\"\n", progname, fname, type); exit(1); } return fp; } /* getDiff -- get a "diff" description line */ int getDiff(fp, ddscr) FILE *fp; /* in: file containing "diff" output */ diffdescr *ddscr; /* in: a diff section descriptor */ { #define EOA 0 #define START_ACCUM 1 #define ACCUM 2 #define SET_OLD_FROM 3 #define SET_OLD_TO 4 #define SET_CMD 5 #define SET_NEW_FROM 6 #define SET_NEW_TO 7 #define MXN_ACTION 8 static struct { int state; char *trig; int stateNext; int action[MXN_ACTION]; } fsm[] = { { 0, "0123456789", 1, { START_ACCUM, EOA } }, { 1, "0123456789", 1, { ACCUM, EOA } }, { 1, ",", 2, { SET_OLD_FROM, EOA } }, { 1, "acd", 4, { SET_OLD_FROM, SET_OLD_TO, SET_CMD, EOA } }, { 2, "123456789", 3, { START_ACCUM, EOA } }, { 3, "0123456789", 3, { ACCUM, EOA } }, { 3, "acd", 4, { SET_OLD_TO, SET_CMD, EOA } }, { 4, "0123456789", 5, { START_ACCUM, EOA } }, { 5, "0123456789", 5, { ACCUM, EOA } }, { 5, ",", 6, { SET_NEW_FROM, EOA } }, { 5, "\n", -1, { SET_NEW_FROM, SET_NEW_TO, EOA } }, { 6, "123456789", 7, { START_ACCUM, EOA } }, { 7, "0123456789", 7, { ACCUM, EOA } }, { 7, "\n", -1, { SET_NEW_TO, EOA } }, { -1, (char *) NULL, -1, { EOA } } /* sentinal */ }, *fsmCur; int stateCur = 0; int ch, accum = 0; int *action; int len = 0; extern char *index(); do { ch = getc(fp); if (ch == EOF) return EOF; len++; fsmCur = &fsm[0]; while (fsmCur->state != -1 && (fsmCur->state != stateCur || index(fsmCur->trig, ch) == NULL)) fsmCur++; if (fsmCur->state == -1) userErr("illegal syntax in 'diff' output"); for (action = fsmCur->action; *action != EOA; action++) switch (*action) { case START_ACCUM: accum = ch - '0'; /* assume ASCII */ break; case ACCUM: accum = 10*accum + (ch - '0'); /* assume ASCII */ break; case SET_CMD: ddscr->cmd = ch; break; case SET_NEW_FROM: ddscr->lnNewFrom = accum; break; case SET_NEW_TO: ddscr->lnNewTo = accum; break; case SET_OLD_FROM: ddscr->lnOldFrom = accum; break; case SET_OLD_TO: ddscr->lnOldTo = accum; break; } stateCur = fsmCur->stateNext; } while (stateCur != -1); return len; } /* isRoffCmd -- return TRUE iff the next line begins with a *roff command */ bool isRoffCmd(fp) FILE *fp; /* in: file to examine for *roff command */ { int ch; if ((ch = getc(fp)) == EOF) userErr("unexpected EOF"); (void) ungetc(ch, fp); return (ch == '.'); /* don't know about ".cc" command */ } /* markDiff -- note a changed section of a file; return # of lines copied from fpNew */ void markDiff(ddscr, fpDiff, fpNew) diffdescr *ddscr; /* in: descriptor of the differing sectinos */ FILE *fpDiff; /* in: the diff file */ FILE *fpNew; /* in: the new file */ { int nLnNew = ddscr->lnNewTo - ddscr->lnNewFrom + 1; int nLnOld = ddscr->lnOldTo - ddscr->lnOldFrom + 1; int nLnNewCopy = nLnNew; switch (ddscr->cmd) { case ADD: if (!flagAll) { while (nLnNewCopy > 0 && isRoffCmd(fpNew)) { copyLinesOrElse(1, 0, fpNew); nLnNewCopy--; } } if (nLnNewCopy > 0) { beginChbar('+'); copyLinesOrElse(nLnNewCopy, 0, fpNew); endChbar(); } skipLinesOrElse(nLnNew, fpDiff); return; case CHANGE: if (!flagAll) { while (nLnNewCopy > 0 && isRoffCmd(fpNew)) { copyLinesOrElse(1, 0, fpNew); nLnNewCopy--; } } if (nLnNewCopy > 0) { beginChbar('|'); copyLinesOrElse(nLnNewCopy, 0, fpNew); endChbar(); } skipLinesOrElse(nLnNew + 1 + nLnOld, fpDiff); /* allow for "---" in diff */ return; case DELETE: beginChbar('-'); if (flagDelete) { putchar('['); putchar('['); putchar('\n'); copyLinesOrElse(nLnOld, 2, fpDiff); /* strip off the "< " */ putchar(']'); putchar(']'); putchar('\n'); } else skipLinesOrElse(nLnOld, fpDiff); endChbar(); return; } } /* nrchbar -- produce *roff change bar file */ void nrchbar(fpDiff, fpNew) FILE *fpDiff; /* in: diff file */ FILE *fpNew; /* in: original file */ { int lnNew = 1; int nLines; diffdescr ddscr; while (getDiff(fpDiff, &ddscr) != EOF) { nLines = ddscr.lnNewFrom - lnNew; if (ddscr.cmd == DELETE) nLines++; copyLinesOrElse(nLines, 0, fpNew); markDiff(&ddscr, fpDiff, fpNew); lnNew = ddscr.lnNewTo + 1; } while (copyLines(1, 0, fpNew)) ; return; } /* pipeDiffs -- return a pipe with a diff run on the other end */ FILE *pipeDiffs(fnOld, fnNew) char *fnOld; /* in: old file name */ char *fnNew; /* in: new file name */ { char *cmd; FILE *fpPipe; cmd = (char *) malloc((unsigned int) (strlen(DIFFPROG) + 1 + strlen(fnOld) + 1 + strlen(fnNew) + 1)); (void) sprintf(cmd, "%s %s %s", DIFFPROG, fnOld, fnNew); fpPipe = popen(cmd, "r"); if (fpPipe == NULL) { (void) fprintf(stderr, "%s: can't open pipe for \"%s\"\n", progname, cmd); exit(1); } free(cmd); return fpPipe; } /* skipLinesOrElse -- skip a given number of lines in a file; exit if errors */ void skipLinesOrElse(nLn, fp) int nLn; /* in: skip this many lines ... */ FILE *fp; /* in: ... in this file */ { int ch; while (nLn-- > 0) { do { if ((ch = getc(fp)) == EOF) userErr("unexpected EOF"); } while (ch != '\n'); } return; } /* userErr -- note an error and politely exude */ void userErr(msg) char *msg; { (void) fprintf(stderr, "%s: %s\n", progname, msg); exit(1); } \Rogue\Monster\ else echo "will not over write ./nrchbar.c" fi if `test ! -s ./nrchbar.1` then echo "writing ./nrchbar.1" cat > ./nrchbar.1 << '\Rogue\Monster\' .\" $Header: nrchbar.1,v 1.2 87/03/31 16:33:02 bobl Exp $ .TH NRCHBAR 1 "March 19, 1987" .SH NAME nrchbar \- insert n/troff-style change bars in a file .SH SYNOPSIS \fBnrchbar\fP [ \fB\-a\fP ] [ \fB\-b\fP ] [ \fB\-d\fP ] [ \fIoldfile\fP ] \fInewfile\fP .SH DESCRIPTION \fINrchbar\fP inserts commands suitable for .IR nroff (1) or .IR troff (1) (hereafter, "\fI*roff\fP") to produce "change bars" denoting differences between \fIoldfile\fP and \fInewfile\fP. It sends the result to standard output. .LP If \fIoldfile\fP is defaulted, \fInrchbar\fP assumes that its standard input will contain differences between \fIoldfile\fP and \fInewfile\fP in .IR diff (1) format. .LP By default, \fInrchbar\fP ignores changes to sections containing nothing but \fI*roff\fP commands, which it takes to be any lines beginning with ".". You can override this with the \fB\-a\fP option. .LP \fINrchbar\fP inserts \fI*roff\fP ".mc" commands to provide the change bars. This will place a character to the far right of each changed section. This character is a "+" for added sections, a "|" for modified sections, and a "-" for deleted sections. .LP By default, the text of deleted sections does not appear, but the \fB\-d\fP option will cause it to be inserted, surrounded by "[[" and "]]". .LP Also by default, \fInrchbar\fP puts a break (".br") command after each changed section. This is the only way to guarantee that deletions and small changes get flagged. The \fB\-b\fP option directs the program not to insert these breaks. It makes the text look more like \fI*roff\fPed \fInewfile\fP, but means that some change bars won't appear where they should. .SH PRONUNCIATION NERCH-bar .SH ETYMOLOGY NRoff CHange BAR .SH OPTIONS .IP \fB\-a\fP Put change bars around all changed sections, even if they consist of nothing but \fI*roff\fP commands. .IP \fB\-b\fP Do not insert breaks after changed sections. .IP \fB\-d\fP Show deleted text by preceding it with "[[" and following it with "]]". .SH SEE\ ALSO nroff(1), troff(1) .SH BUGS "." is always assumed to denote \fI*roff\fP commands. \fINrchbar\fP knows nothing about the ".cc" command. .LP The need for the \fB\-b\fP hack would disappear if there were a way to tell \fI*roff\fP "Turn off the change bar character at immediately after you put out this one.". A horizontal ".wh", perhaps? .LP The ".mc" command doesn't always work, especially with diversions. This is actually a \fI*roff\fP bug, but you ought to know about it. .LP \fINrchbar\fP may mess up tables, lists, or displays. For a really polished change bar document, you may want to edit its output before sending it to \fI*roff\fP. \Rogue\Monster\ else echo "will not over write ./nrchbar.1" fi echo "Finished archive 1 of 1" exit -- Rich $alz "Anger is an energy" Cronus Project, BBN Labs rsalz@bbn.com Moderator, comp.sources.unix sources@uunet.uu.qual