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