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