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