fmt.c revision 1.5 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