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