date and time created 80/10/30 00:36:28 by mckusick
[unix-history] / usr / src / usr.bin / fmt / fmt.c
CommitLineData
79e55e87
KS
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
14static char *SccsId = "@(#)fmt.c 1.1 %G%";
15
16#define LENGTH 72 /* Max line length in output */
17#define NOSTR ((char *) 0) /* Null string pointer for lint */
18
19int pfx; /* Current leading blank count */
20int lineno; /* Current input line */
21int mark; /* Last place we saw a head line */
22
23char *calloc(); /* for lint . . . */
24char *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
32main(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
67fmt(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
148prefix(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
195split(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
243char outbuf[BUFSIZ]; /* Sandbagged output line image */
244char *outp; /* Pointer in above */
245
246/*
247 * Initialize the output section.
248 */
249
250setout()
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
263pack(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
297oflush()
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
311tabulate(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
353leadin()
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
369char *
370savestr(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
388ispref(s1, s2)
389 register char *s1, *s2;
390{
391
392 while (*s1++ == *s2)
393 ;
394 return(*s1 == '\0');
395}