xref: /csrg-svn/usr.bin/fmt/fmt.c (revision 3197)
11232Skas #
21232Skas 
31232Skas #include <stdio.h>
41232Skas #include <ctype.h>
51232Skas 
61232Skas /*
71232Skas  * fmt -- format the concatenation of input files or standard input
81232Skas  * onto standard output.  Designed for use with Mail ~|
91232Skas  *
10*3197Skas  * Syntax: fmt [ -width ] [ name ... ]
111232Skas  * Author: Kurt Shoens (UCB) 12/7/78
121232Skas  */
131232Skas 
14*3197Skas static char *SccsId = "@(#)fmt.c	1.3 03/11/81";
151232Skas 
161232Skas #define	NOSTR	((char *) 0)	/* Null string pointer for lint */
171232Skas 
181232Skas int	pfx;			/* Current leading blank count */
191232Skas int	lineno;			/* Current input line */
201232Skas int	mark;			/* Last place we saw a head line */
21*3197Skas int	width = 72;		/* Width that we will not exceed */
221232Skas 
231232Skas char	*calloc();		/* for lint . . . */
241232Skas char	*headnames[] = {"To", "Subject", "Cc", 0};
251232Skas 
261232Skas /*
271232Skas  * Drive the whole formatter by managing input files.  Also,
281232Skas  * cause initialization of the output stuff and flush it out
291232Skas  * at the end.
301232Skas  */
311232Skas 
321232Skas main(argc, argv)
331232Skas 	char **argv;
341232Skas {
351232Skas 	register FILE *fi;
361232Skas 	register int errs = 0;
37*3197Skas 	char sobuf[BUFSIZ];
38*3197Skas 	register char *cp;
39*3197Skas 	int nofile;
401232Skas 
411232Skas 	setout();
421232Skas 	lineno = 1;
431232Skas 	mark = -10;
44*3197Skas 	setbuf(stdout, sobuf);
451232Skas 	if (argc < 2) {
46*3197Skas single:
471232Skas 		fmt(stdin);
481232Skas 		oflush();
491232Skas 		exit(0);
501232Skas 	}
51*3197Skas 	nofile = 1;
521232Skas 	while (--argc) {
53*3197Skas 		cp = *++argv;
54*3197Skas 		if (*cp == '-') {
55*3197Skas 			width = atoi(cp+1);
56*3197Skas 			if (width <= 0 || width >= BUFSIZ-2) {
57*3197Skas 				fprintf(stderr, "fmt:  bad width: %d\n", width);
58*3197Skas 				exit(1);
59*3197Skas 			}
60*3197Skas 			continue;
61*3197Skas 		}
62*3197Skas 		nofile = 0;
63*3197Skas 		if ((fi = fopen(cp, "r")) == NULL) {
64*3197Skas 			perror(cp);
651232Skas 			errs++;
661232Skas 			continue;
671232Skas 		}
681232Skas 		fmt(fi);
691232Skas 		fclose(fi);
701232Skas 	}
71*3197Skas 	if (nofile)
72*3197Skas 		goto single;
731232Skas 	oflush();
741232Skas 	exit(errs);
751232Skas }
761232Skas 
771232Skas /*
781232Skas  * Read up characters from the passed input file, forming lines,
791232Skas  * doing ^H processing, expanding tabs, stripping trailing blanks,
801232Skas  * and sending each line down for analysis.
811232Skas  */
821232Skas 
831232Skas fmt(fi)
841232Skas 	FILE *fi;
851232Skas {
861232Skas 	char linebuf[BUFSIZ], canonb[BUFSIZ];
871232Skas 	register char *cp, *cp2;
881232Skas 	register int c, col;
891232Skas 
901232Skas 	c = getc(fi);
911232Skas 	while (c != EOF) {
921232Skas 
931232Skas 		/*
941232Skas 		 * Collect a line, doing ^H processing.
951232Skas 		 * Leave tabs for now.
961232Skas 		 */
971232Skas 
981232Skas 		cp = linebuf;
991232Skas 		while (c != '\n' && c != EOF && cp-linebuf < BUFSIZ-1) {
1001232Skas 			if (c == '\b') {
1011232Skas 				if (cp > linebuf)
1021232Skas 					cp--;
1031232Skas 				c = getc(fi);
1041232Skas 				continue;
1051232Skas 			}
1061232Skas 			if ((c < ' ' || c >= 0177) && c != '\t') {
1071232Skas 				c = getc(fi);
1081232Skas 				continue;
1091232Skas 			}
1101232Skas 			*cp++ = c;
1111232Skas 			c = getc(fi);
1121232Skas 		}
1131232Skas 		*cp = '\0';
1141232Skas 
1151232Skas 		/*
1161232Skas 		 * Toss anything remaining on the input line.
1171232Skas 		 */
1181232Skas 
1191232Skas 		while (c != '\n' && c != EOF)
1201232Skas 			c = getc(fi);
1211232Skas 
1221232Skas 		/*
1231232Skas 		 * Expand tabs on the way to canonb.
1241232Skas 		 */
1251232Skas 
1261232Skas 		col = 0;
1271232Skas 		cp = linebuf;
1281232Skas 		cp2 = canonb;
1291232Skas 		while (c = *cp++) {
1301232Skas 			if (c != '\t') {
1311232Skas 				col++;
1321232Skas 				if (cp2-canonb < BUFSIZ-1)
1331232Skas 					*cp2++ = c;
1341232Skas 				continue;
1351232Skas 			}
1361232Skas 			do {
1371232Skas 				if (cp2-canonb < BUFSIZ-1)
1381232Skas 					*cp2++ = ' ';
1391232Skas 				col++;
1401232Skas 			} while ((col & 07) != 0);
1411232Skas 		}
1421232Skas 
1431232Skas 		/*
1441232Skas 		 * Swipe trailing blanks from the line.
1451232Skas 		 */
1461232Skas 
1471232Skas 		for (cp2--; cp2 >= canonb && *cp2 == ' '; cp2--)
1481232Skas 			;
1491232Skas 		*++cp2 = '\0';
1501232Skas 		prefix(canonb);
1511232Skas 		if (c != EOF)
1521232Skas 			c = getc(fi);
1531232Skas 	}
1541232Skas }
1551232Skas 
1561232Skas /*
1571232Skas  * Take a line devoid of tabs and other garbage and determine its
1581232Skas  * blank prefix.  If the indent changes, call for a linebreak.
1591232Skas  * If the input line is blank, echo the blank line on the output.
1601232Skas  * Finally, if the line minus the prefix is a mail header, try to keep
1611232Skas  * it on a line by itself.
1621232Skas  */
1631232Skas 
1641232Skas prefix(line)
1651232Skas 	char line[];
1661232Skas {
1671232Skas 	register char *cp, **hp;
1681232Skas 	register int np, h;
1691232Skas 
1701232Skas 	if (strlen(line) == 0) {
1711232Skas 		oflush();
1721232Skas 		putchar('\n');
1731232Skas 		return;
1741232Skas 	}
1751232Skas 	for (cp = line; *cp == ' '; cp++)
1761232Skas 		;
1771232Skas 	np = cp - line;
1781232Skas 
1791232Skas 	/*
1801232Skas 	 * The following horrible expression attempts to avoid linebreaks
1811232Skas 	 * when the indent changes due to a paragraph.
1821232Skas 	 */
1831232Skas 
1841232Skas 	if (np != pfx && (np > pfx || abs(pfx-np) > 8))
1851232Skas 		oflush();
1861232Skas 	if (h = ishead(cp))
1871232Skas 		oflush(), mark = lineno;
1881232Skas 	if (lineno - mark < 3 && lineno - mark > 0)
1891232Skas 		for (hp = &headnames[0]; *hp != (char *) 0; hp++)
1901232Skas 			if (ispref(*hp, cp)) {
1911232Skas 				h = 1;
1921232Skas 				oflush();
1931232Skas 				break;
1941232Skas 			}
1951232Skas 	if (!h && (h = (*cp == '.')))
1961232Skas 		oflush();
1971232Skas 	pfx = np;
1981232Skas 	split(cp);
1991232Skas 	if (h)
2001232Skas 		oflush();
2011232Skas 	lineno++;
2021232Skas }
2031232Skas 
2041232Skas /*
2051232Skas  * Split up the passed line into output "words" which are
2061232Skas  * maximal strings of non-blanks with the blank separation
2071232Skas  * attached at the end.  Pass these words along to the output
2081232Skas  * line packer.
2091232Skas  */
2101232Skas 
2111232Skas split(line)
2121232Skas 	char line[];
2131232Skas {
2141232Skas 	register char *cp, *cp2;
2151232Skas 	char word[BUFSIZ];
2161232Skas 
2171232Skas 	cp = line;
2181232Skas 	while (*cp) {
2191232Skas 		cp2 = word;
2201232Skas 
2211232Skas 		/*
2221232Skas 		 * Collect a 'word,' allowing it to contain escaped
2231232Skas 		 * white space.
2241232Skas 		 */
2251232Skas 
2261232Skas 		while (*cp && *cp != ' ') {
2271232Skas 			if (*cp == '\\' && isspace(cp[1]))
2281232Skas 				*cp2++ = *cp++;
2291232Skas 			*cp2++ = *cp++;
2301232Skas 		}
2311232Skas 
2321232Skas 		/*
2331232Skas 		 * Guarantee a space at end of line.
2341232Skas 		 * Two spaces after end of sentence punctuation.
2351232Skas 		 */
2361232Skas 
2371232Skas 		if (*cp == '\0') {
2381232Skas 			*cp2++ = ' ';
2392060Skas 			if (any(cp[-1], ".:!?"))
2401232Skas 				*cp2++ = ' ';
2411232Skas 		}
2421232Skas 		while (*cp == ' ')
2431232Skas 			*cp2++ = *cp++;
2441232Skas 		*cp2 = '\0';
2451232Skas 		pack(word);
2461232Skas 	}
2471232Skas }
2481232Skas 
2491232Skas /*
2501232Skas  * Output section.
2511232Skas  * Build up line images from the words passed in.  Prefix
2521232Skas  * each line with correct number of blanks.  The buffer "outbuf"
2531232Skas  * contains the current partial line image, including prefixed blanks.
2541232Skas  * "outp" points to the next available space therein.  When outp is NOSTR,
2551232Skas  * there ain't nothing in there yet.  At the bottom of this whole mess,
2561232Skas  * leading tabs are reinserted.
2571232Skas  */
2581232Skas 
2591232Skas char	outbuf[BUFSIZ];			/* Sandbagged output line image */
2601232Skas char	*outp;				/* Pointer in above */
2611232Skas 
2621232Skas /*
2631232Skas  * Initialize the output section.
2641232Skas  */
2651232Skas 
2661232Skas setout()
2671232Skas {
2681232Skas 	outp = NOSTR;
2691232Skas }
2701232Skas 
2711232Skas /*
2721232Skas  * Pack a word onto the output line.  If this is the beginning of
2731232Skas  * the line, push on the appropriately-sized string of blanks first.
2741232Skas  * If the word won't fit on the current line, flush and begin a new
2751232Skas  * line.  If the word is too long to fit all by itself on a line,
2761232Skas  * just give it its own and hope for the best.
2771232Skas  */
2781232Skas 
2791232Skas pack(word)
2801232Skas 	char word[];
2811232Skas {
2821232Skas 	register char *cp;
2831232Skas 	register int s, t;
2841232Skas 
2851232Skas 	if (outp == NOSTR)
2861232Skas 		leadin();
2871232Skas 	t = strlen(word);
2881232Skas 	s = outp-outbuf;
289*3197Skas 	if (t+s <= width) {
2901232Skas 
2911232Skas 		/*
2921232Skas 		 * In like flint!
2931232Skas 		 */
2941232Skas 
2951232Skas 		for (cp = word; *cp; *outp++ = *cp++)
2961232Skas 			;
2971232Skas 		return;
2981232Skas 	}
2991232Skas 	if (s > pfx) {
3001232Skas 		oflush();
3011232Skas 		leadin();
3021232Skas 	}
3031232Skas 	for (cp = word; *cp; *outp++ = *cp++)
3041232Skas 		;
3051232Skas }
3061232Skas 
3071232Skas /*
3081232Skas  * If there is anything on the current output line, send it on
3091232Skas  * its way.  Set outp to NOSTR to indicate the absence of the current
3101232Skas  * line prefix.
3111232Skas  */
3121232Skas 
3131232Skas oflush()
3141232Skas {
3151232Skas 	if (outp == NOSTR)
3161232Skas 		return;
3171232Skas 	*outp = '\0';
3181232Skas 	tabulate(outbuf);
3191232Skas 	outp = NOSTR;
3201232Skas }
3211232Skas 
3221232Skas /*
3231232Skas  * Take the passed line buffer, insert leading tabs where possible, and
3241232Skas  * output on standard output (finally).
3251232Skas  */
3261232Skas 
3271232Skas tabulate(line)
3281232Skas 	char line[];
3291232Skas {
3301232Skas 	register char *cp, *cp2;
3311232Skas 	register int b, t;
3321232Skas 
3331232Skas 	/*
3341232Skas 	 * Toss trailing blanks in the output line.
3351232Skas 	 */
3361232Skas 
3371232Skas 	cp = line + strlen(line) - 1;
3381232Skas 	while (cp >= line && *cp == ' ')
3391232Skas 		cp--;
3401232Skas 	*++cp = '\0';
3411232Skas 
3421232Skas 	/*
3431232Skas 	 * Count the leading blank space and tabulate.
3441232Skas 	 */
3451232Skas 
3461232Skas 	for (cp = line; *cp == ' '; cp++)
3471232Skas 		;
3481232Skas 	b = cp-line;
3491232Skas 	t = b >> 3;
3501232Skas 	b &= 07;
3511232Skas 	if (t > 0)
3521232Skas 		do
3531232Skas 			putc('\t', stdout);
3541232Skas 		while (--t);
3551232Skas 	if (b > 0)
3561232Skas 		do
3571232Skas 			putc(' ', stdout);
3581232Skas 		while (--b);
3591232Skas 	while (*cp)
3601232Skas 		putc(*cp++, stdout);
3611232Skas 	putc('\n', stdout);
3621232Skas }
3631232Skas 
3641232Skas /*
3651232Skas  * Initialize the output line with the appropriate number of
3661232Skas  * leading blanks.
3671232Skas  */
3681232Skas 
3691232Skas leadin()
3701232Skas {
3711232Skas 	register int b;
3721232Skas 	register char *cp;
3731232Skas 
3741232Skas 	for (b = 0, cp = outbuf; b < pfx; b++)
3751232Skas 		*cp++ = ' ';
3761232Skas 	outp = cp;
3771232Skas }
3781232Skas 
3791232Skas /*
3801232Skas  * Save a string in dynamic space.
3811232Skas  * This little goodie is needed for
3821232Skas  * a headline detector in head.c
3831232Skas  */
3841232Skas 
3851232Skas char *
3861232Skas savestr(str)
3871232Skas 	char str[];
3881232Skas {
3891232Skas 	register char *top;
3901232Skas 
3911232Skas 	top = calloc(strlen(str) + 1, 1);
3921232Skas 	if (top == NOSTR) {
3931232Skas 		fprintf(stderr, "fmt:  Ran out of memory\n");
3941232Skas 		exit(1);
3951232Skas 	}
3961232Skas 	copy(str, top);
3971232Skas 	return(top);
3981232Skas }
3991232Skas 
4001232Skas /*
4011232Skas  * Is s1 a prefix of s2??
4021232Skas  */
4031232Skas 
4041232Skas ispref(s1, s2)
4051232Skas 	register char *s1, *s2;
4061232Skas {
4071232Skas 
4081232Skas 	while (*s1++ == *s2)
4091232Skas 		;
4101232Skas 	return(*s1 == '\0');
4111232Skas }
412