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