xref: /netbsd-src/usr.bin/fmt/fmt.c (revision fdecd6a253f999ae92b139670d9e15cc9df4497c)
1 /*	$NetBSD: fmt.c,v 1.5 1997/05/31 15:13:49 kleink Exp $	*/
2 
3 /*
4  * Copyright (c) 1980, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifndef lint
37 static char copyright[] =
38 "@(#) Copyright (c) 1980, 1993\n\
39 	The Regents of the University of California.  All rights reserved.\n";
40 #endif /* not lint */
41 
42 #ifndef lint
43 #if 0
44 static char sccsid[] = "@(#)fmt.c	8.1 (Berkeley) 7/20/93";
45 #endif
46 static char rcsid[] = "$NetBSD: fmt.c,v 1.5 1997/05/31 15:13:49 kleink Exp $";
47 #endif /* not lint */
48 
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <ctype.h>
53 #include <locale.h>
54 
55 /*
56  * fmt -- format the concatenation of input files or standard input
57  * onto standard output.  Designed for use with Mail ~|
58  *
59  * Syntax : fmt [ goal [ max ] ] [ name ... ]
60  * Authors: Kurt Shoens (UCB) 12/7/78;
61  *          Liz Allen (UMCP) 2/24/83 [Addition of goal length concept].
62  */
63 
64 /* LIZ@UOM 6/18/85 -- Don't need LENGTH any more.
65  * #define	LENGTH	72		Max line length in output
66  */
67 #define	NOSTR	((char *) 0)	/* Null string pointer for lint */
68 
69 /* LIZ@UOM 6/18/85 --New variables goal_length and max_length */
70 #define GOAL_LENGTH 65
71 #define MAX_LENGTH 75
72 int	goal_length;		/* Target or goal line length in output */
73 int	max_length;		/* Max line length in output */
74 int	pfx;			/* Current leading blank count */
75 int	lineno;			/* Current input line */
76 int	mark;			/* Last place we saw a head line */
77 
78 char	*headnames[] = {"To", "Subject", "Cc", 0};
79 
80 /*
81  * Drive the whole formatter by managing input files.  Also,
82  * cause initialization of the output stuff and flush it out
83  * at the end.
84  */
85 
86 main(argc, argv)
87 	int argc;
88 	char **argv;
89 {
90 	register FILE *fi;
91 	register int errs = 0;
92 	int number;		/* LIZ@UOM 6/18/85 */
93 
94 	goal_length = GOAL_LENGTH;
95 	max_length = MAX_LENGTH;
96 	setout();
97 	lineno = 1;
98 	mark = -10;
99 
100 	setlocale(LC_ALL, "");
101 
102 	/*
103 	 * LIZ@UOM 6/18/85 -- Check for goal and max length arguments
104 	 */
105 	if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) {
106 		argv++;
107 		argc--;
108 		goal_length = number;
109 		if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) {
110 			argv++;
111 			argc--;
112 			max_length = number;
113 		}
114 	}
115 	if (max_length <= goal_length) {
116 		fprintf(stderr, "Max length must be greater than %s\n",
117 			"goal length");
118 		exit(1);
119 	}
120 	if (argc < 2) {
121 		fmt(stdin);
122 		oflush();
123 		exit(0);
124 	}
125 	while (--argc) {
126 		if ((fi = fopen(*++argv, "r")) == NULL) {
127 			perror(*argv);
128 			errs++;
129 			continue;
130 		}
131 		fmt(fi);
132 		fclose(fi);
133 	}
134 	oflush();
135 	exit(errs);
136 }
137 
138 /*
139  * Read up characters from the passed input file, forming lines,
140  * doing ^H processing, expanding tabs, stripping trailing blanks,
141  * and sending each line down for analysis.
142  */
143 fmt(fi)
144 	FILE *fi;
145 {
146 	char linebuf[BUFSIZ], canonb[BUFSIZ];
147 	register char *cp, *cp2;
148 	register int c, col;
149 
150 	c = getc(fi);
151 	while (c != EOF) {
152 		/*
153 		 * Collect a line, doing ^H processing.
154 		 * Leave tabs for now.
155 		 */
156 		cp = linebuf;
157 		while (c != '\n' && c != EOF && cp-linebuf < BUFSIZ-1) {
158 			if (c == '\b') {
159 				if (cp > linebuf)
160 					cp--;
161 				c = getc(fi);
162 				continue;
163 			}
164 			if(!(isprint(c) || c == '\t')) {
165 				c = getc(fi);
166 				continue;
167 			}
168 			*cp++ = c;
169 			c = getc(fi);
170 		}
171 		*cp = '\0';
172 
173 		/*
174 		 * Toss anything remaining on the input line.
175 		 */
176 		while (c != '\n' && c != EOF)
177 			c = getc(fi);
178 
179 		/*
180 		 * Expand tabs on the way to canonb.
181 		 */
182 		col = 0;
183 		cp = linebuf;
184 		cp2 = canonb;
185 		while (c = *cp++) {
186 			if (c != '\t') {
187 				col++;
188 				if (cp2-canonb < BUFSIZ-1)
189 					*cp2++ = c;
190 				continue;
191 			}
192 			do {
193 				if (cp2-canonb < BUFSIZ-1)
194 					*cp2++ = ' ';
195 				col++;
196 			} while ((col & 07) != 0);
197 		}
198 
199 		/*
200 		 * Swipe trailing blanks from the line.
201 		 */
202 		for (cp2--; cp2 >= canonb && *cp2 == ' '; cp2--)
203 			;
204 		*++cp2 = '\0';
205 		prefix(canonb);
206 		if (c != EOF)
207 			c = getc(fi);
208 	}
209 }
210 
211 /*
212  * Take a line devoid of tabs and other garbage and determine its
213  * blank prefix.  If the indent changes, call for a linebreak.
214  * If the input line is blank, echo the blank line on the output.
215  * Finally, if the line minus the prefix is a mail header, try to keep
216  * it on a line by itself.
217  */
218 prefix(line)
219 	char line[];
220 {
221 	register char *cp, **hp;
222 	register int np, h;
223 
224 	if (strlen(line) == 0) {
225 		oflush();
226 		putchar('\n');
227 		return;
228 	}
229 	for (cp = line; *cp == ' '; cp++)
230 		;
231 	np = cp - line;
232 
233 	/*
234 	 * The following horrible expression attempts to avoid linebreaks
235 	 * when the indent changes due to a paragraph.
236 	 */
237 	if (np != pfx && (np > pfx || abs(pfx-np) > 8))
238 		oflush();
239 	if (h = ishead(cp))
240 		oflush(), mark = lineno;
241 	if (lineno - mark < 3 && lineno - mark > 0)
242 		for (hp = &headnames[0]; *hp != (char *) 0; hp++)
243 			if (ispref(*hp, cp)) {
244 				h = 1;
245 				oflush();
246 				break;
247 			}
248 	if (!h && (h = (*cp == '.')))
249 		oflush();
250 	pfx = np;
251 	if (h)
252 		pack(cp, strlen(cp));
253 	else	split(cp);
254 	if (h)
255 		oflush();
256 	lineno++;
257 }
258 
259 /*
260  * Split up the passed line into output "words" which are
261  * maximal strings of non-blanks with the blank separation
262  * attached at the end.  Pass these words along to the output
263  * line packer.
264  */
265 split(line)
266 	char line[];
267 {
268 	register char *cp, *cp2;
269 	char word[BUFSIZ];
270 	int wordl;		/* LIZ@UOM 6/18/85 */
271 
272 	cp = line;
273 	while (*cp) {
274 		cp2 = word;
275 		wordl = 0;	/* LIZ@UOM 6/18/85 */
276 
277 		/*
278 		 * Collect a 'word,' allowing it to contain escaped white
279 		 * space.
280 		 */
281 		while (*cp && *cp != ' ') {
282 			if (*cp == '\\' && isspace(cp[1]))
283 				*cp2++ = *cp++;
284 			*cp2++ = *cp++;
285 			wordl++;/* LIZ@UOM 6/18/85 */
286 		}
287 
288 		/*
289 		 * Guarantee a space at end of line. Two spaces after end of
290 		 * sentence punctuation.
291 		 */
292 		if (*cp == '\0') {
293 			*cp2++ = ' ';
294 			if (index(".:!", cp[-1]))
295 				*cp2++ = ' ';
296 		}
297 		while (*cp == ' ')
298 			*cp2++ = *cp++;
299 		*cp2 = '\0';
300 		/*
301 		 * LIZ@UOM 6/18/85 pack(word);
302 		 */
303 		pack(word, wordl);
304 	}
305 }
306 
307 /*
308  * Output section.
309  * Build up line images from the words passed in.  Prefix
310  * each line with correct number of blanks.  The buffer "outbuf"
311  * contains the current partial line image, including prefixed blanks.
312  * "outp" points to the next available space therein.  When outp is NOSTR,
313  * there ain't nothing in there yet.  At the bottom of this whole mess,
314  * leading tabs are reinserted.
315  */
316 char	outbuf[BUFSIZ];			/* Sandbagged output line image */
317 char	*outp;				/* Pointer in above */
318 
319 /*
320  * Initialize the output section.
321  */
322 setout()
323 {
324 	outp = NOSTR;
325 }
326 
327 /*
328  * Pack a word onto the output line.  If this is the beginning of
329  * the line, push on the appropriately-sized string of blanks first.
330  * If the word won't fit on the current line, flush and begin a new
331  * line.  If the word is too long to fit all by itself on a line,
332  * just give it its own and hope for the best.
333  *
334  * LIZ@UOM 6/18/85 -- If the new word will fit in at less than the
335  *	goal length, take it.  If not, then check to see if the line
336  *	will be over the max length; if so put the word on the next
337  *	line.  If not, check to see if the line will be closer to the
338  *	goal length with or without the word and take it or put it on
339  *	the next line accordingly.
340  */
341 
342 /*
343  * LIZ@UOM 6/18/85 -- pass in the length of the word as well
344  * pack(word)
345  *	char word[];
346  */
347 pack(word,wl)
348 	char word[];
349 	int wl;
350 {
351 	register char *cp;
352 	register int s, t;
353 
354 	if (outp == NOSTR)
355 		leadin();
356 	/*
357 	 * LIZ@UOM 6/18/85 -- change condition to check goal_length; s is the
358 	 * length of the line before the word is added; t is now the length
359 	 * of the line after the word is added
360 	 *	t = strlen(word);
361 	 *	if (t+s <= LENGTH)
362 	 */
363 	s = outp - outbuf;
364 	t = wl + s;
365 	if ((t <= goal_length) ||
366 	    ((t <= max_length) && (t - goal_length <= goal_length - s))) {
367 		/*
368 		 * In like flint!
369 		 */
370 		for (cp = word; *cp; *outp++ = *cp++);
371 		return;
372 	}
373 	if (s > pfx) {
374 		oflush();
375 		leadin();
376 	}
377 	for (cp = word; *cp; *outp++ = *cp++);
378 }
379 
380 /*
381  * If there is anything on the current output line, send it on
382  * its way.  Set outp to NOSTR to indicate the absence of the current
383  * line prefix.
384  */
385 oflush()
386 {
387 	if (outp == NOSTR)
388 		return;
389 	*outp = '\0';
390 	tabulate(outbuf);
391 	outp = NOSTR;
392 }
393 
394 /*
395  * Take the passed line buffer, insert leading tabs where possible, and
396  * output on standard output (finally).
397  */
398 tabulate(line)
399 	char line[];
400 {
401 	register char *cp;
402 	register int b, t;
403 
404 	/*
405 	 * Toss trailing blanks in the output line.
406 	 */
407 	cp = line + strlen(line) - 1;
408 	while (cp >= line && *cp == ' ')
409 		cp--;
410 	*++cp = '\0';
411 
412 	/*
413 	 * Count the leading blank space and tabulate.
414 	 */
415 	for (cp = line; *cp == ' '; cp++)
416 		;
417 	b = cp-line;
418 	t = b >> 3;
419 	b &= 07;
420 	if (t > 0)
421 		do
422 			putc('\t', stdout);
423 		while (--t);
424 	if (b > 0)
425 		do
426 			putc(' ', stdout);
427 		while (--b);
428 	while (*cp)
429 		putc(*cp++, stdout);
430 	putc('\n', stdout);
431 }
432 
433 /*
434  * Initialize the output line with the appropriate number of
435  * leading blanks.
436  */
437 leadin()
438 {
439 	register int b;
440 	register char *cp;
441 
442 	for (b = 0, cp = outbuf; b < pfx; b++)
443 		*cp++ = ' ';
444 	outp = cp;
445 }
446 
447 /*
448  * Save a string in dynamic space.
449  * This little goodie is needed for
450  * a headline detector in head.c
451  */
452 char *
453 savestr(str)
454 	char str[];
455 {
456 	register char *top;
457 
458 	top = malloc(strlen(str) + 1);
459 	if (top == NOSTR) {
460 		fprintf(stderr, "fmt:  Ran out of memory\n");
461 		exit(1);
462 	}
463 	strcpy(top, str);
464 	return (top);
465 }
466 
467 /*
468  * Is s1 a prefix of s2??
469  */
470 ispref(s1, s2)
471 	register char *s1, *s2;
472 {
473 
474 	while (*s1++ == *s2)
475 		;
476 	return (*s1 == '\0');
477 }
478