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