1 /* $NetBSD: fmt.c,v 1.32 2012/06/30 21:31:15 christos 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. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 __COPYRIGHT("@(#) Copyright (c) 1980, 1993\ 35 The Regents of the University of California. All rights reserved."); 36 #endif /* not lint */ 37 38 #ifndef lint 39 #if 0 40 static char sccsid[] = "@(#)fmt.c 8.1 (Berkeley) 7/20/93"; 41 #endif 42 __RCSID("$NetBSD: fmt.c,v 1.32 2012/06/30 21:31:15 christos Exp $"); 43 #endif /* not lint */ 44 45 #include <ctype.h> 46 #include <locale.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <unistd.h> 50 #include <errno.h> 51 #include <err.h> 52 #include <limits.h> 53 #include <string.h> 54 #include "buffer.h" 55 56 /* 57 * fmt -- format the concatenation of input files or standard input 58 * onto standard output. Designed for use with Mail ~| 59 * 60 * Syntax : fmt [ goal [ max ] ] [ name ... ] 61 * Authors: Kurt Shoens (UCB) 12/7/78; 62 * Liz Allen (UMCP) 2/24/83 [Addition of goal length concept]. 63 */ 64 65 /* LIZ@UOM 6/18/85 --New variables goal_length and max_length */ 66 #define GOAL_LENGTH 65 67 #define MAX_LENGTH 75 68 static size_t goal_length; /* Target or goal line length in output */ 69 static size_t max_length; /* Max line length in output */ 70 static size_t pfx; /* Current leading blank count */ 71 static int raw; /* Don't treat mail specially */ 72 static int lineno; /* Current input line */ 73 static int mark; /* Last place we saw a head line */ 74 static int center; 75 static struct buffer outbuf; 76 77 static const char *headnames[] = {"To", "Subject", "Cc", 0}; 78 79 static void usage(void) __dead; 80 static int getnum(const char *, const char *, size_t *, int); 81 static void fmt(FILE *); 82 static int ispref(const char *, const char *); 83 static void leadin(void); 84 static void oflush(void); 85 static void pack(const char *, size_t); 86 static void prefix(const struct buffer *, int); 87 static void split(const char *, int); 88 static void tabulate(struct buffer *); 89 90 91 int ishead(const 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(int argc, char **argv) 101 { 102 FILE *fi; 103 int errs = 0; 104 int compat = 1; 105 int c; 106 107 goal_length = GOAL_LENGTH; 108 max_length = MAX_LENGTH; 109 buf_init(&outbuf); 110 lineno = 1; 111 mark = -10; 112 113 setprogname(*argv); 114 (void)setlocale(LC_ALL, ""); 115 116 while ((c = getopt(argc, argv, "Cg:m:rw:")) != -1) 117 switch (c) { 118 case 'C': 119 center++; 120 break; 121 case 'g': 122 (void)getnum(optarg, "goal", &goal_length, 1); 123 compat = 0; 124 break; 125 case 'm': 126 case 'w': 127 (void)getnum(optarg, "max", &max_length, 1); 128 compat = 0; 129 break; 130 case 'r': 131 raw++; 132 break; 133 default: 134 usage(); 135 } 136 137 argc -= optind; 138 argv += optind; 139 140 /* 141 * compatibility with old usage. 142 */ 143 if (compat && argc > 0 && getnum(*argv, "goal", &goal_length, 0)) { 144 argv++; 145 argc--; 146 if (argc > 0 && getnum(*argv, "max", &max_length, 0)) { 147 argv++; 148 argc--; 149 } 150 } 151 152 if (max_length <= goal_length) { 153 errx(1, "Max length (%zu) must be greater than goal " 154 "length (%zu)", max_length, goal_length); 155 } 156 if (argc == 0) { 157 fmt(stdin); 158 oflush(); 159 return 0; 160 } 161 for (;argc; argc--, argv++) { 162 if ((fi = fopen(*argv, "r")) == NULL) { 163 warn("Cannot open `%s'", *argv); 164 errs++; 165 continue; 166 } 167 fmt(fi); 168 (void)fclose(fi); 169 } 170 oflush(); 171 buf_end(&outbuf); 172 return errs; 173 } 174 175 static void 176 usage(void) 177 { 178 (void)fprintf(stderr, 179 "Usage: %s [-Cr] [-g <goal>] [-m|w <max>] [<files>..]\n" 180 "\t %s [-Cr] [<goal>] [<max>] [<files>]\n", 181 getprogname(), getprogname()); 182 exit(1); 183 } 184 185 static int 186 getnum(const char *str, const char *what, size_t *res, int badnum) 187 { 188 unsigned long ul; 189 char *ep; 190 191 errno = 0; 192 ul = strtoul(str, &ep, 0); 193 if (*str != '\0' && *ep == '\0') { 194 if ((errno == ERANGE && ul == ULONG_MAX) || ul > SIZE_T_MAX) 195 errx(1, "%s number `%s' too big", what, str); 196 *res = (size_t)ul; 197 return 1; 198 } else if (badnum) 199 errx(1, "Bad %s number `%s'", what, str); 200 201 return 0; 202 } 203 204 /* 205 * Read up characters from the passed input file, forming lines, 206 * doing ^H processing, expanding tabs, stripping trailing blanks, 207 * and sending each line down for analysis. 208 */ 209 static void 210 fmt(FILE *fi) 211 { 212 struct buffer lbuf, cbuf; 213 char *cp, *cp2; 214 int c, add_space; 215 size_t len, col, i; 216 217 if (center) { 218 for (;;) { 219 cp = fgetln(fi, &len); 220 if (!cp) 221 return; 222 223 /* skip over leading space */ 224 while (len > 0) { 225 if (!isspace((unsigned char)*cp)) 226 break; 227 cp++; 228 len--; 229 } 230 231 /* clear trailing space */ 232 while (len > 0) { 233 if (!isspace((unsigned char)cp[len-1])) 234 break; 235 len--; 236 } 237 238 if (len == 0) { 239 /* blank line */ 240 (void)putchar('\n'); 241 continue; 242 } 243 244 if (goal_length > len) { 245 for (i = 0; i < (goal_length - len) / 2; i++) { 246 (void)putchar(' '); 247 } 248 } 249 for (i = 0; i < len; i++) { 250 (void)putchar(cp[i]); 251 } 252 (void)putchar('\n'); 253 } 254 } 255 256 buf_init(&lbuf); 257 buf_init(&cbuf); 258 c = getc(fi); 259 260 while (c != EOF) { 261 /* 262 * Collect a line, doing ^H processing. 263 * Leave tabs for now. 264 */ 265 buf_reset(&lbuf); 266 while (c != '\n' && c != EOF) { 267 if (c == '\b') { 268 (void)buf_unputc(&lbuf); 269 c = getc(fi); 270 continue; 271 } 272 if(!(isprint(c) || c == '\t' || c >= 160)) { 273 c = getc(fi); 274 continue; 275 } 276 buf_putc(&lbuf, c); 277 c = getc(fi); 278 } 279 buf_putc(&lbuf, '\0'); 280 (void)buf_unputc(&lbuf); 281 add_space = c != EOF; 282 283 /* 284 * Expand tabs on the way. 285 */ 286 col = 0; 287 cp = lbuf.bptr; 288 buf_reset(&cbuf); 289 while ((c = *cp++) != '\0') { 290 if (c != '\t') { 291 col++; 292 buf_putc(&cbuf, c); 293 continue; 294 } 295 do { 296 buf_putc(&cbuf, ' '); 297 col++; 298 } while ((col & 07) != 0); 299 } 300 301 /* 302 * Swipe trailing blanks from the line. 303 */ 304 for (cp2 = cbuf.ptr - 1; cp2 >= cbuf.bptr && *cp2 == ' '; cp2--) 305 continue; 306 cbuf.ptr = cp2 + 1; 307 buf_putc(&cbuf, '\0'); 308 (void)buf_unputc(&cbuf); 309 prefix(&cbuf, add_space); 310 if (c != EOF) 311 c = getc(fi); 312 } 313 buf_end(&cbuf); 314 buf_end(&lbuf); 315 } 316 317 /* 318 * Take a line devoid of tabs and other garbage and determine its 319 * blank prefix. If the indent changes, call for a linebreak. 320 * If the input line is blank, echo the blank line on the output. 321 * Finally, if the line minus the prefix is a mail header, try to keep 322 * it on a line by itself. 323 */ 324 static void 325 prefix(const struct buffer *buf, int add_space) 326 { 327 const char *cp; 328 const char **hp; 329 size_t np; 330 int h; 331 332 if (buf->ptr == buf->bptr) { 333 oflush(); 334 (void)putchar('\n'); 335 return; 336 } 337 for (cp = buf->bptr; *cp == ' '; cp++) 338 continue; 339 np = cp - buf->bptr; 340 341 /* 342 * The following horrible expression attempts to avoid linebreaks 343 * when the indent changes due to a paragraph. 344 */ 345 if (np != pfx && (np > pfx || abs((int)(pfx - np)) > 8)) 346 oflush(); 347 if (!raw) { 348 if ((h = ishead(cp)) != 0) { 349 oflush(); 350 mark = lineno; 351 } 352 if (lineno - mark < 3 && lineno - mark > 0) 353 for (hp = &headnames[0]; *hp != NULL; hp++) 354 if (ispref(*hp, cp)) { 355 h = 1; 356 oflush(); 357 break; 358 } 359 if (!h && (h = (*cp == '.'))) 360 oflush(); 361 } else 362 h = 0; 363 pfx = np; 364 if (h) { 365 pack(cp, (size_t)(buf->ptr - cp)); 366 oflush(); 367 } else 368 split(cp, add_space); 369 lineno++; 370 } 371 372 /* 373 * Split up the passed line into output "words" which are 374 * maximal strings of non-blanks with the blank separation 375 * attached at the end. Pass these words along to the output 376 * line packer. 377 */ 378 static void 379 split(const char line[], int add_space) 380 { 381 const char *cp; 382 struct buffer word; 383 size_t wlen; 384 385 buf_init(&word); 386 cp = line; 387 while (*cp) { 388 buf_reset(&word); 389 wlen = 0; 390 391 /* 392 * Collect a 'word,' allowing it to contain escaped white 393 * space. 394 */ 395 while (*cp && *cp != ' ') { 396 if (*cp == '\\' && isspace((unsigned char)cp[1])) 397 buf_putc(&word, *cp++); 398 buf_putc(&word, *cp++); 399 wlen++; 400 } 401 402 /* 403 * Guarantee a space at end of line. Two spaces after end of 404 * sentence punctuation. 405 */ 406 if (*cp == '\0' && add_space) { 407 buf_putc(&word, ' '); 408 if (strchr(".:!", cp[-1])) 409 buf_putc(&word, ' '); 410 } 411 while (*cp == ' ') 412 buf_putc(&word, *cp++); 413 414 buf_putc(&word, '\0'); 415 (void)buf_unputc(&word); 416 417 pack(word.bptr, wlen); 418 } 419 buf_end(&word); 420 } 421 422 /* 423 * Output section. 424 * Build up line images from the words passed in. Prefix 425 * each line with correct number of blanks. 426 * 427 * At the bottom of this whole mess, leading tabs are reinserted. 428 */ 429 430 /* 431 * Pack a word onto the output line. If this is the beginning of 432 * the line, push on the appropriately-sized string of blanks first. 433 * If the word won't fit on the current line, flush and begin a new 434 * line. If the word is too long to fit all by itself on a line, 435 * just give it its own and hope for the best. 436 * 437 * LIZ@UOM 6/18/85 -- If the new word will fit in at less than the 438 * goal length, take it. If not, then check to see if the line 439 * will be over the max length; if so put the word on the next 440 * line. If not, check to see if the line will be closer to the 441 * goal length with or without the word and take it or put it on 442 * the next line accordingly. 443 */ 444 445 static void 446 pack(const char *word, size_t wlen) 447 { 448 const char *cp; 449 size_t s, t; 450 451 if (outbuf.bptr == outbuf.ptr) 452 leadin(); 453 /* 454 * LIZ@UOM 6/18/85 -- change condition to check goal_length; s is the 455 * length of the line before the word is added; t is now the length 456 * of the line after the word is added 457 */ 458 s = outbuf.ptr - outbuf.bptr; 459 t = wlen + s; 460 if ((t <= goal_length) || ((t <= max_length) && 461 (s <= goal_length) && (t - goal_length <= goal_length - s))) { 462 /* 463 * In like flint! 464 */ 465 for (cp = word; *cp;) 466 buf_putc(&outbuf, *cp++); 467 return; 468 } 469 if (s > pfx) { 470 oflush(); 471 leadin(); 472 } 473 for (cp = word; *cp;) 474 buf_putc(&outbuf, *cp++); 475 } 476 477 /* 478 * If there is anything on the current output line, send it on 479 * its way. Reset outbuf. 480 */ 481 static void 482 oflush(void) 483 { 484 if (outbuf.bptr == outbuf.ptr) 485 return; 486 buf_putc(&outbuf, '\0'); 487 (void)buf_unputc(&outbuf); 488 tabulate(&outbuf); 489 buf_reset(&outbuf); 490 } 491 492 /* 493 * Take the passed line buffer, insert leading tabs where possible, and 494 * output on standard output (finally). 495 */ 496 static void 497 tabulate(struct buffer *buf) 498 { 499 char *cp; 500 size_t b, t; 501 502 /* 503 * Toss trailing blanks in the output line. 504 */ 505 for (cp = buf->ptr - 1; cp >= buf->bptr && *cp == ' '; cp--) 506 continue; 507 *++cp = '\0'; 508 509 /* 510 * Count the leading blank space and tabulate. 511 */ 512 for (cp = buf->bptr; *cp == ' '; cp++) 513 continue; 514 b = cp - buf->bptr; 515 t = b / 8; 516 b = b % 8; 517 if (t > 0) 518 do 519 (void)putchar('\t'); 520 while (--t); 521 if (b > 0) 522 do 523 (void)putchar(' '); 524 while (--b); 525 while (*cp) 526 (void)putchar(*cp++); 527 (void)putchar('\n'); 528 } 529 530 /* 531 * Initialize the output line with the appropriate number of 532 * leading blanks. 533 */ 534 static void 535 leadin(void) 536 { 537 size_t b; 538 539 buf_reset(&outbuf); 540 541 for (b = 0; b < pfx; b++) 542 buf_putc(&outbuf, ' '); 543 } 544 545 /* 546 * Is s1 a prefix of s2?? 547 */ 548 static int 549 ispref(const char *s1, const char *s2) 550 { 551 552 while (*s1++ == *s2) 553 continue; 554 return *s1 == '\0'; 555 } 556