fmt.c revision 1.9 1 /* $NetBSD: fmt.c,v 1.9 1998/12/19 16:05:18 christos 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 #include <sys/cdefs.h>
37 #ifndef lint
38 __COPYRIGHT("@(#) 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 __RCSID("$NetBSD: fmt.c,v 1.9 1998/12/19 16:05:18 christos 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 void fmt __P((FILE *));
81 int ispref __P((char *, char *));
82 int ishead __P((char *));
83 void leadin __P((void));
84 int main __P((int, char **));
85 void oflush __P((void));
86 void pack __P((char *, int));
87 void prefix __P((char *));
88 char *savestr __P((char *));
89 void setout __P((void));
90 void split __P((char *));
91 void tabulate __P((char *));
92
93 /*
94 * Drive the whole formatter by managing input files. Also,
95 * cause initialization of the output stuff and flush it out
96 * at the end.
97 */
98
99 int
100 main(argc, argv)
101 int argc;
102 char **argv;
103 {
104 FILE *fi;
105 int errs = 0;
106 int number; /* LIZ@UOM 6/18/85 */
107
108 goal_length = GOAL_LENGTH;
109 max_length = MAX_LENGTH;
110 setout();
111 lineno = 1;
112 mark = -10;
113
114 setlocale(LC_ALL, "");
115
116 /*
117 * LIZ@UOM 6/18/85 -- Check for goal and max length arguments
118 */
119 if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) {
120 argv++;
121 argc--;
122 goal_length = abs(number);
123 if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) {
124 argv++;
125 argc--;
126 max_length = abs(number);
127 }
128 }
129 if (max_length <= goal_length) {
130 fprintf(stderr, "Max length must be greater than %s\n",
131 "goal length");
132 exit(1);
133 }
134 if (argc < 2) {
135 fmt(stdin);
136 oflush();
137 exit(0);
138 }
139 while (--argc) {
140 if ((fi = fopen(*++argv, "r")) == NULL) {
141 perror(*argv);
142 errs++;
143 continue;
144 }
145 fmt(fi);
146 fclose(fi);
147 }
148 oflush();
149 exit(errs);
150 }
151
152 /*
153 * Read up characters from the passed input file, forming lines,
154 * doing ^H processing, expanding tabs, stripping trailing blanks,
155 * and sending each line down for analysis.
156 */
157 void
158 fmt(fi)
159 FILE *fi;
160 {
161 char linebuf[BUFSIZ], canonb[BUFSIZ];
162 char *cp, *cp2;
163 int c, col;
164
165 c = getc(fi);
166 while (c != EOF) {
167 /*
168 * Collect a line, doing ^H processing.
169 * Leave tabs for now.
170 */
171 cp = linebuf;
172 while (c != '\n' && c != EOF && cp-linebuf < BUFSIZ-1) {
173 if (c == '\b') {
174 if (cp > linebuf)
175 cp--;
176 c = getc(fi);
177 continue;
178 }
179 if(!(isprint(c) || c == '\t')) {
180 c = getc(fi);
181 continue;
182 }
183 *cp++ = c;
184 c = getc(fi);
185 }
186 *cp = '\0';
187
188 /*
189 * Toss anything remaining on the input line.
190 */
191 while (c != '\n' && c != EOF)
192 c = getc(fi);
193
194 /*
195 * Expand tabs on the way to canonb.
196 */
197 col = 0;
198 cp = linebuf;
199 cp2 = canonb;
200 while ((c = *cp++) != 0) {
201 if (c != '\t') {
202 col++;
203 if (cp2-canonb < BUFSIZ-1)
204 *cp2++ = c;
205 continue;
206 }
207 do {
208 if (cp2-canonb < BUFSIZ-1)
209 *cp2++ = ' ';
210 col++;
211 } while ((col & 07) != 0);
212 }
213
214 /*
215 * Swipe trailing blanks from the line.
216 */
217 for (cp2--; cp2 >= canonb && *cp2 == ' '; cp2--)
218 ;
219 *++cp2 = '\0';
220 prefix(canonb);
221 if (c != EOF)
222 c = getc(fi);
223 }
224 }
225
226 /*
227 * Take a line devoid of tabs and other garbage and determine its
228 * blank prefix. If the indent changes, call for a linebreak.
229 * If the input line is blank, echo the blank line on the output.
230 * Finally, if the line minus the prefix is a mail header, try to keep
231 * it on a line by itself.
232 */
233 void
234 prefix(line)
235 char line[];
236 {
237 char *cp, **hp;
238 int np, h;
239
240 if (strlen(line) == 0) {
241 oflush();
242 putchar('\n');
243 return;
244 }
245 for (cp = line; *cp == ' '; cp++)
246 ;
247 np = cp - line;
248
249 /*
250 * The following horrible expression attempts to avoid linebreaks
251 * when the indent changes due to a paragraph.
252 */
253 if (np != pfx && (np > pfx || abs(pfx-np) > 8))
254 oflush();
255 if ((h = ishead(cp)) != 0)
256 oflush(), mark = lineno;
257 if (lineno - mark < 3 && lineno - mark > 0)
258 for (hp = &headnames[0]; *hp != (char *) 0; hp++)
259 if (ispref(*hp, cp)) {
260 h = 1;
261 oflush();
262 break;
263 }
264 if (!h && (h = (*cp == '.')))
265 oflush();
266 pfx = np;
267 if (h)
268 pack(cp, strlen(cp));
269 else split(cp);
270 if (h)
271 oflush();
272 lineno++;
273 }
274
275 /*
276 * Split up the passed line into output "words" which are
277 * maximal strings of non-blanks with the blank separation
278 * attached at the end. Pass these words along to the output
279 * line packer.
280 */
281 void
282 split(line)
283 char line[];
284 {
285 char *cp, *cp2;
286 char word[BUFSIZ];
287 int wordl; /* LIZ@UOM 6/18/85 */
288
289 cp = line;
290 while (*cp) {
291 cp2 = word;
292 wordl = 0; /* LIZ@UOM 6/18/85 */
293
294 /*
295 * Collect a 'word,' allowing it to contain escaped white
296 * space.
297 */
298 while (*cp && *cp != ' ') {
299 if (*cp == '\\' && isspace((unsigned char)cp[1]))
300 *cp2++ = *cp++;
301 *cp2++ = *cp++;
302 wordl++;/* LIZ@UOM 6/18/85 */
303 }
304
305 /*
306 * Guarantee a space at end of line. Two spaces after end of
307 * sentence punctuation.
308 */
309 if (*cp == '\0') {
310 *cp2++ = ' ';
311 if (strchr(".:!", cp[-1]))
312 *cp2++ = ' ';
313 }
314 while (*cp == ' ')
315 *cp2++ = *cp++;
316 *cp2 = '\0';
317 /*
318 * LIZ@UOM 6/18/85 pack(word);
319 */
320 pack(word, wordl);
321 }
322 }
323
324 /*
325 * Output section.
326 * Build up line images from the words passed in. Prefix
327 * each line with correct number of blanks. The buffer "outbuf"
328 * contains the current partial line image, including prefixed blanks.
329 * "outp" points to the next available space therein. When outp is NOSTR,
330 * there ain't nothing in there yet. At the bottom of this whole mess,
331 * leading tabs are reinserted.
332 */
333 char outbuf[BUFSIZ]; /* Sandbagged output line image */
334 char *outp; /* Pointer in above */
335
336 /*
337 * Initialize the output section.
338 */
339 void
340 setout()
341 {
342 outp = NOSTR;
343 }
344
345 /*
346 * Pack a word onto the output line. If this is the beginning of
347 * the line, push on the appropriately-sized string of blanks first.
348 * If the word won't fit on the current line, flush and begin a new
349 * line. If the word is too long to fit all by itself on a line,
350 * just give it its own and hope for the best.
351 *
352 * LIZ@UOM 6/18/85 -- If the new word will fit in at less than the
353 * goal length, take it. If not, then check to see if the line
354 * will be over the max length; if so put the word on the next
355 * line. If not, check to see if the line will be closer to the
356 * goal length with or without the word and take it or put it on
357 * the next line accordingly.
358 */
359
360 /*
361 * LIZ@UOM 6/18/85 -- pass in the length of the word as well
362 * pack(word)
363 * char word[];
364 */
365 void
366 pack(word,wl)
367 char word[];
368 int wl;
369 {
370 char *cp;
371 int s, t;
372
373 if (outp == NOSTR)
374 leadin();
375 /*
376 * LIZ@UOM 6/18/85 -- change condition to check goal_length; s is the
377 * length of the line before the word is added; t is now the length
378 * of the line after the word is added
379 * t = strlen(word);
380 * if (t+s <= LENGTH)
381 */
382 s = outp - outbuf;
383 t = wl + s;
384 if ((t <= goal_length) ||
385 ((t <= max_length) && (t - goal_length <= goal_length - s))) {
386 /*
387 * In like flint!
388 */
389 for (cp = word; *cp; *outp++ = *cp++);
390 return;
391 }
392 if (s > pfx) {
393 oflush();
394 leadin();
395 }
396 for (cp = word; *cp; *outp++ = *cp++);
397 }
398
399 /*
400 * If there is anything on the current output line, send it on
401 * its way. Set outp to NOSTR to indicate the absence of the current
402 * line prefix.
403 */
404 void
405 oflush()
406 {
407 if (outp == NOSTR)
408 return;
409 *outp = '\0';
410 tabulate(outbuf);
411 outp = NOSTR;
412 }
413
414 /*
415 * Take the passed line buffer, insert leading tabs where possible, and
416 * output on standard output (finally).
417 */
418 void
419 tabulate(line)
420 char line[];
421 {
422 char *cp;
423 int b, t;
424
425 /*
426 * Toss trailing blanks in the output line.
427 */
428 cp = line + strlen(line) - 1;
429 while (cp >= line && *cp == ' ')
430 cp--;
431 *++cp = '\0';
432
433 /*
434 * Count the leading blank space and tabulate.
435 */
436 for (cp = line; *cp == ' '; cp++)
437 ;
438 b = cp-line;
439 t = b >> 3;
440 b &= 07;
441 if (t > 0)
442 do
443 putc('\t', stdout);
444 while (--t);
445 if (b > 0)
446 do
447 putc(' ', stdout);
448 while (--b);
449 while (*cp)
450 putc(*cp++, stdout);
451 putc('\n', stdout);
452 }
453
454 /*
455 * Initialize the output line with the appropriate number of
456 * leading blanks.
457 */
458 void
459 leadin()
460 {
461 int b;
462 char *cp;
463
464 for (b = 0, cp = outbuf; b < pfx; b++)
465 *cp++ = ' ';
466 outp = cp;
467 }
468
469 /*
470 * Save a string in dynamic space.
471 * This little goodie is needed for
472 * a headline detector in head.c
473 */
474 char *
475 savestr(str)
476 char str[];
477 {
478 char *top;
479
480 top = malloc(strlen(str) + 1);
481 if (top == NOSTR) {
482 fprintf(stderr, "fmt: Ran out of memory\n");
483 exit(1);
484 }
485 strcpy(top, str);
486 return (top);
487 }
488
489 /*
490 * Is s1 a prefix of s2??
491 */
492 int
493 ispref(s1, s2)
494 char *s1, *s2;
495 {
496
497 while (*s1++ == *s2)
498 ;
499 return (*s1 == '\0');
500 }
501