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