xref: /csrg-svn/usr.bin/fmt/fmt.c (revision 1232)
1*1232Skas #
2*1232Skas 
3*1232Skas #include <stdio.h>
4*1232Skas #include <ctype.h>
5*1232Skas 
6*1232Skas /*
7*1232Skas  * fmt -- format the concatenation of input files or standard input
8*1232Skas  * onto standard output.  Designed for use with Mail ~|
9*1232Skas  *
10*1232Skas  * Syntax: fmt [ name ... ]
11*1232Skas  * Author: Kurt Shoens (UCB) 12/7/78
12*1232Skas  */
13*1232Skas 
14*1232Skas static char *SccsId = "@(#)fmt.c	1.1 10/08/80";
15*1232Skas 
16*1232Skas #define	LENGTH	72		/* Max line length in output */
17*1232Skas #define	NOSTR	((char *) 0)	/* Null string pointer for lint */
18*1232Skas 
19*1232Skas int	pfx;			/* Current leading blank count */
20*1232Skas int	lineno;			/* Current input line */
21*1232Skas int	mark;			/* Last place we saw a head line */
22*1232Skas 
23*1232Skas char	*calloc();		/* for lint . . . */
24*1232Skas char	*headnames[] = {"To", "Subject", "Cc", 0};
25*1232Skas 
26*1232Skas /*
27*1232Skas  * Drive the whole formatter by managing input files.  Also,
28*1232Skas  * cause initialization of the output stuff and flush it out
29*1232Skas  * at the end.
30*1232Skas  */
31*1232Skas 
32*1232Skas main(argc, argv)
33*1232Skas 	char **argv;
34*1232Skas {
35*1232Skas 	register FILE *fi;
36*1232Skas 	register int errs = 0;
37*1232Skas 
38*1232Skas 	setout();
39*1232Skas 	lineno = 1;
40*1232Skas 	mark = -10;
41*1232Skas 	setbuf(stdout, calloc(1, BUFSIZ));
42*1232Skas 	if (argc < 2) {
43*1232Skas 		setbuf(stdin, calloc(1, BUFSIZ));
44*1232Skas 		fmt(stdin);
45*1232Skas 		oflush();
46*1232Skas 		exit(0);
47*1232Skas 	}
48*1232Skas 	while (--argc) {
49*1232Skas 		if ((fi = fopen(*++argv, "r")) == NULL) {
50*1232Skas 			perror(*argv);
51*1232Skas 			errs++;
52*1232Skas 			continue;
53*1232Skas 		}
54*1232Skas 		fmt(fi);
55*1232Skas 		fclose(fi);
56*1232Skas 	}
57*1232Skas 	oflush();
58*1232Skas 	exit(errs);
59*1232Skas }
60*1232Skas 
61*1232Skas /*
62*1232Skas  * Read up characters from the passed input file, forming lines,
63*1232Skas  * doing ^H processing, expanding tabs, stripping trailing blanks,
64*1232Skas  * and sending each line down for analysis.
65*1232Skas  */
66*1232Skas 
67*1232Skas fmt(fi)
68*1232Skas 	FILE *fi;
69*1232Skas {
70*1232Skas 	char linebuf[BUFSIZ], canonb[BUFSIZ];
71*1232Skas 	register char *cp, *cp2;
72*1232Skas 	register int c, col;
73*1232Skas 
74*1232Skas 	c = getc(fi);
75*1232Skas 	while (c != EOF) {
76*1232Skas 
77*1232Skas 		/*
78*1232Skas 		 * Collect a line, doing ^H processing.
79*1232Skas 		 * Leave tabs for now.
80*1232Skas 		 */
81*1232Skas 
82*1232Skas 		cp = linebuf;
83*1232Skas 		while (c != '\n' && c != EOF && cp-linebuf < BUFSIZ-1) {
84*1232Skas 			if (c == '\b') {
85*1232Skas 				if (cp > linebuf)
86*1232Skas 					cp--;
87*1232Skas 				c = getc(fi);
88*1232Skas 				continue;
89*1232Skas 			}
90*1232Skas 			if ((c < ' ' || c >= 0177) && c != '\t') {
91*1232Skas 				c = getc(fi);
92*1232Skas 				continue;
93*1232Skas 			}
94*1232Skas 			*cp++ = c;
95*1232Skas 			c = getc(fi);
96*1232Skas 		}
97*1232Skas 		*cp = '\0';
98*1232Skas 
99*1232Skas 		/*
100*1232Skas 		 * Toss anything remaining on the input line.
101*1232Skas 		 */
102*1232Skas 
103*1232Skas 		while (c != '\n' && c != EOF)
104*1232Skas 			c = getc(fi);
105*1232Skas 
106*1232Skas 		/*
107*1232Skas 		 * Expand tabs on the way to canonb.
108*1232Skas 		 */
109*1232Skas 
110*1232Skas 		col = 0;
111*1232Skas 		cp = linebuf;
112*1232Skas 		cp2 = canonb;
113*1232Skas 		while (c = *cp++) {
114*1232Skas 			if (c != '\t') {
115*1232Skas 				col++;
116*1232Skas 				if (cp2-canonb < BUFSIZ-1)
117*1232Skas 					*cp2++ = c;
118*1232Skas 				continue;
119*1232Skas 			}
120*1232Skas 			do {
121*1232Skas 				if (cp2-canonb < BUFSIZ-1)
122*1232Skas 					*cp2++ = ' ';
123*1232Skas 				col++;
124*1232Skas 			} while ((col & 07) != 0);
125*1232Skas 		}
126*1232Skas 
127*1232Skas 		/*
128*1232Skas 		 * Swipe trailing blanks from the line.
129*1232Skas 		 */
130*1232Skas 
131*1232Skas 		for (cp2--; cp2 >= canonb && *cp2 == ' '; cp2--)
132*1232Skas 			;
133*1232Skas 		*++cp2 = '\0';
134*1232Skas 		prefix(canonb);
135*1232Skas 		if (c != EOF)
136*1232Skas 			c = getc(fi);
137*1232Skas 	}
138*1232Skas }
139*1232Skas 
140*1232Skas /*
141*1232Skas  * Take a line devoid of tabs and other garbage and determine its
142*1232Skas  * blank prefix.  If the indent changes, call for a linebreak.
143*1232Skas  * If the input line is blank, echo the blank line on the output.
144*1232Skas  * Finally, if the line minus the prefix is a mail header, try to keep
145*1232Skas  * it on a line by itself.
146*1232Skas  */
147*1232Skas 
148*1232Skas prefix(line)
149*1232Skas 	char line[];
150*1232Skas {
151*1232Skas 	register char *cp, **hp;
152*1232Skas 	register int np, h;
153*1232Skas 
154*1232Skas 	if (strlen(line) == 0) {
155*1232Skas 		oflush();
156*1232Skas 		putchar('\n');
157*1232Skas 		return;
158*1232Skas 	}
159*1232Skas 	for (cp = line; *cp == ' '; cp++)
160*1232Skas 		;
161*1232Skas 	np = cp - line;
162*1232Skas 
163*1232Skas 	/*
164*1232Skas 	 * The following horrible expression attempts to avoid linebreaks
165*1232Skas 	 * when the indent changes due to a paragraph.
166*1232Skas 	 */
167*1232Skas 
168*1232Skas 	if (np != pfx && (np > pfx || abs(pfx-np) > 8))
169*1232Skas 		oflush();
170*1232Skas 	if (h = ishead(cp))
171*1232Skas 		oflush(), mark = lineno;
172*1232Skas 	if (lineno - mark < 3 && lineno - mark > 0)
173*1232Skas 		for (hp = &headnames[0]; *hp != (char *) 0; hp++)
174*1232Skas 			if (ispref(*hp, cp)) {
175*1232Skas 				h = 1;
176*1232Skas 				oflush();
177*1232Skas 				break;
178*1232Skas 			}
179*1232Skas 	if (!h && (h = (*cp == '.')))
180*1232Skas 		oflush();
181*1232Skas 	pfx = np;
182*1232Skas 	split(cp);
183*1232Skas 	if (h)
184*1232Skas 		oflush();
185*1232Skas 	lineno++;
186*1232Skas }
187*1232Skas 
188*1232Skas /*
189*1232Skas  * Split up the passed line into output "words" which are
190*1232Skas  * maximal strings of non-blanks with the blank separation
191*1232Skas  * attached at the end.  Pass these words along to the output
192*1232Skas  * line packer.
193*1232Skas  */
194*1232Skas 
195*1232Skas split(line)
196*1232Skas 	char line[];
197*1232Skas {
198*1232Skas 	register char *cp, *cp2;
199*1232Skas 	char word[BUFSIZ];
200*1232Skas 
201*1232Skas 	cp = line;
202*1232Skas 	while (*cp) {
203*1232Skas 		cp2 = word;
204*1232Skas 
205*1232Skas 		/*
206*1232Skas 		 * Collect a 'word,' allowing it to contain escaped
207*1232Skas 		 * white space.
208*1232Skas 		 */
209*1232Skas 
210*1232Skas 		while (*cp && *cp != ' ') {
211*1232Skas 			if (*cp == '\\' && isspace(cp[1]))
212*1232Skas 				*cp2++ = *cp++;
213*1232Skas 			*cp2++ = *cp++;
214*1232Skas 		}
215*1232Skas 
216*1232Skas 		/*
217*1232Skas 		 * Guarantee a space at end of line.
218*1232Skas 		 * Two spaces after end of sentence punctuation.
219*1232Skas 		 */
220*1232Skas 
221*1232Skas 		if (*cp == '\0') {
222*1232Skas 			*cp2++ = ' ';
223*1232Skas 			if (any(cp[-1], ".:!"))
224*1232Skas 				*cp2++ = ' ';
225*1232Skas 		}
226*1232Skas 		while (*cp == ' ')
227*1232Skas 			*cp2++ = *cp++;
228*1232Skas 		*cp2 = '\0';
229*1232Skas 		pack(word);
230*1232Skas 	}
231*1232Skas }
232*1232Skas 
233*1232Skas /*
234*1232Skas  * Output section.
235*1232Skas  * Build up line images from the words passed in.  Prefix
236*1232Skas  * each line with correct number of blanks.  The buffer "outbuf"
237*1232Skas  * contains the current partial line image, including prefixed blanks.
238*1232Skas  * "outp" points to the next available space therein.  When outp is NOSTR,
239*1232Skas  * there ain't nothing in there yet.  At the bottom of this whole mess,
240*1232Skas  * leading tabs are reinserted.
241*1232Skas  */
242*1232Skas 
243*1232Skas char	outbuf[BUFSIZ];			/* Sandbagged output line image */
244*1232Skas char	*outp;				/* Pointer in above */
245*1232Skas 
246*1232Skas /*
247*1232Skas  * Initialize the output section.
248*1232Skas  */
249*1232Skas 
250*1232Skas setout()
251*1232Skas {
252*1232Skas 	outp = NOSTR;
253*1232Skas }
254*1232Skas 
255*1232Skas /*
256*1232Skas  * Pack a word onto the output line.  If this is the beginning of
257*1232Skas  * the line, push on the appropriately-sized string of blanks first.
258*1232Skas  * If the word won't fit on the current line, flush and begin a new
259*1232Skas  * line.  If the word is too long to fit all by itself on a line,
260*1232Skas  * just give it its own and hope for the best.
261*1232Skas  */
262*1232Skas 
263*1232Skas pack(word)
264*1232Skas 	char word[];
265*1232Skas {
266*1232Skas 	register char *cp;
267*1232Skas 	register int s, t;
268*1232Skas 
269*1232Skas 	if (outp == NOSTR)
270*1232Skas 		leadin();
271*1232Skas 	t = strlen(word);
272*1232Skas 	s = outp-outbuf;
273*1232Skas 	if (t+s <= LENGTH) {
274*1232Skas 
275*1232Skas 		/*
276*1232Skas 		 * In like flint!
277*1232Skas 		 */
278*1232Skas 
279*1232Skas 		for (cp = word; *cp; *outp++ = *cp++)
280*1232Skas 			;
281*1232Skas 		return;
282*1232Skas 	}
283*1232Skas 	if (s > pfx) {
284*1232Skas 		oflush();
285*1232Skas 		leadin();
286*1232Skas 	}
287*1232Skas 	for (cp = word; *cp; *outp++ = *cp++)
288*1232Skas 		;
289*1232Skas }
290*1232Skas 
291*1232Skas /*
292*1232Skas  * If there is anything on the current output line, send it on
293*1232Skas  * its way.  Set outp to NOSTR to indicate the absence of the current
294*1232Skas  * line prefix.
295*1232Skas  */
296*1232Skas 
297*1232Skas oflush()
298*1232Skas {
299*1232Skas 	if (outp == NOSTR)
300*1232Skas 		return;
301*1232Skas 	*outp = '\0';
302*1232Skas 	tabulate(outbuf);
303*1232Skas 	outp = NOSTR;
304*1232Skas }
305*1232Skas 
306*1232Skas /*
307*1232Skas  * Take the passed line buffer, insert leading tabs where possible, and
308*1232Skas  * output on standard output (finally).
309*1232Skas  */
310*1232Skas 
311*1232Skas tabulate(line)
312*1232Skas 	char line[];
313*1232Skas {
314*1232Skas 	register char *cp, *cp2;
315*1232Skas 	register int b, t;
316*1232Skas 
317*1232Skas 	/*
318*1232Skas 	 * Toss trailing blanks in the output line.
319*1232Skas 	 */
320*1232Skas 
321*1232Skas 	cp = line + strlen(line) - 1;
322*1232Skas 	while (cp >= line && *cp == ' ')
323*1232Skas 		cp--;
324*1232Skas 	*++cp = '\0';
325*1232Skas 
326*1232Skas 	/*
327*1232Skas 	 * Count the leading blank space and tabulate.
328*1232Skas 	 */
329*1232Skas 
330*1232Skas 	for (cp = line; *cp == ' '; cp++)
331*1232Skas 		;
332*1232Skas 	b = cp-line;
333*1232Skas 	t = b >> 3;
334*1232Skas 	b &= 07;
335*1232Skas 	if (t > 0)
336*1232Skas 		do
337*1232Skas 			putc('\t', stdout);
338*1232Skas 		while (--t);
339*1232Skas 	if (b > 0)
340*1232Skas 		do
341*1232Skas 			putc(' ', stdout);
342*1232Skas 		while (--b);
343*1232Skas 	while (*cp)
344*1232Skas 		putc(*cp++, stdout);
345*1232Skas 	putc('\n', stdout);
346*1232Skas }
347*1232Skas 
348*1232Skas /*
349*1232Skas  * Initialize the output line with the appropriate number of
350*1232Skas  * leading blanks.
351*1232Skas  */
352*1232Skas 
353*1232Skas leadin()
354*1232Skas {
355*1232Skas 	register int b;
356*1232Skas 	register char *cp;
357*1232Skas 
358*1232Skas 	for (b = 0, cp = outbuf; b < pfx; b++)
359*1232Skas 		*cp++ = ' ';
360*1232Skas 	outp = cp;
361*1232Skas }
362*1232Skas 
363*1232Skas /*
364*1232Skas  * Save a string in dynamic space.
365*1232Skas  * This little goodie is needed for
366*1232Skas  * a headline detector in head.c
367*1232Skas  */
368*1232Skas 
369*1232Skas char *
370*1232Skas savestr(str)
371*1232Skas 	char str[];
372*1232Skas {
373*1232Skas 	register char *top;
374*1232Skas 
375*1232Skas 	top = calloc(strlen(str) + 1, 1);
376*1232Skas 	if (top == NOSTR) {
377*1232Skas 		fprintf(stderr, "fmt:  Ran out of memory\n");
378*1232Skas 		exit(1);
379*1232Skas 	}
380*1232Skas 	copy(str, top);
381*1232Skas 	return(top);
382*1232Skas }
383*1232Skas 
384*1232Skas /*
385*1232Skas  * Is s1 a prefix of s2??
386*1232Skas  */
387*1232Skas 
388*1232Skas ispref(s1, s2)
389*1232Skas 	register char *s1, *s2;
390*1232Skas {
391*1232Skas 
392*1232Skas 	while (*s1++ == *s2)
393*1232Skas 		;
394*1232Skas 	return(*s1 == '\0');
395*1232Skas }
396