122453Sdist /* 222453Sdist * Copyright (c) 1980 Regents of the University of California. 333499Sbostic * All rights reserved. 433499Sbostic * 5*42731Sbostic * %sccs.include.redist.c% 622453Sdist */ 722453Sdist 814531Ssam #ifndef lint 933499Sbostic char copyright[] = 1022453Sdist "@(#) Copyright (c) 1980 Regents of the University of California.\n\ 1122453Sdist All rights reserved.\n"; 1233499Sbostic #endif /* not lint */ 131232Skas 1422453Sdist #ifndef lint 15*42731Sbostic static char sccsid[] = "@(#)fmt.c 5.10 (Berkeley) 06/01/90"; 1633499Sbostic #endif /* not lint */ 1722453Sdist 181232Skas #include <stdio.h> 191232Skas #include <ctype.h> 201232Skas 211232Skas /* 221232Skas * fmt -- format the concatenation of input files or standard input 231232Skas * onto standard output. Designed for use with Mail ~| 241232Skas * 2529545Smckusick * Syntax : fmt [ goal [ max ] ] [ name ... ] 2629545Smckusick * Authors: Kurt Shoens (UCB) 12/7/78; 2729545Smckusick * Liz Allen (UMCP) 2/24/83 [Addition of goal length concept]. 281232Skas */ 291232Skas 3029545Smckusick /* LIZ@UOM 6/18/85 -- Don't need LENGTH any more. 3129545Smckusick * #define LENGTH 72 Max line length in output 3229545Smckusick */ 331232Skas #define NOSTR ((char *) 0) /* Null string pointer for lint */ 341232Skas 3529545Smckusick /* LIZ@UOM 6/18/85 --New variables goal_length and max_length */ 3634965Sedward #define GOAL_LENGTH 65 3734965Sedward #define MAX_LENGTH 75 3834965Sedward int goal_length; /* Target or goal line length in output */ 3934965Sedward int max_length; /* Max line length in output */ 401232Skas int pfx; /* Current leading blank count */ 411232Skas int lineno; /* Current input line */ 421232Skas int mark; /* Last place we saw a head line */ 431232Skas 4431142Sedward char *malloc(); /* for lint . . . */ 451232Skas char *headnames[] = {"To", "Subject", "Cc", 0}; 461232Skas 471232Skas /* 481232Skas * Drive the whole formatter by managing input files. Also, 491232Skas * cause initialization of the output stuff and flush it out 501232Skas * at the end. 511232Skas */ 521232Skas 531232Skas main(argc, argv) 5429545Smckusick int argc; 551232Skas char **argv; 561232Skas { 571232Skas register FILE *fi; 581232Skas register int errs = 0; 5929545Smckusick int number; /* LIZ@UOM 6/18/85 */ 601232Skas 6134965Sedward goal_length = GOAL_LENGTH; 6234965Sedward max_length = MAX_LENGTH; 631232Skas setout(); 641232Skas lineno = 1; 651232Skas mark = -10; 6629545Smckusick /* 6729545Smckusick * LIZ@UOM 6/18/85 -- Check for goal and max length arguments 6829545Smckusick */ 6929545Smckusick if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) { 7029545Smckusick argv++; 7129545Smckusick argc--; 7229545Smckusick goal_length = number; 7329545Smckusick if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) { 7429545Smckusick argv++; 7529545Smckusick argc--; 7629545Smckusick max_length = number; 7729545Smckusick } 7829545Smckusick } 7929545Smckusick if (max_length <= goal_length) { 8029545Smckusick fprintf(stderr, "Max length must be greater than %s\n", 8129545Smckusick "goal length"); 8229545Smckusick exit(1); 8329545Smckusick } 841232Skas if (argc < 2) { 851232Skas fmt(stdin); 861232Skas oflush(); 871232Skas exit(0); 881232Skas } 891232Skas while (--argc) { 9029545Smckusick if ((fi = fopen(*++argv, "r")) == NULL) { 9129545Smckusick perror(*argv); 921232Skas errs++; 931232Skas continue; 941232Skas } 951232Skas fmt(fi); 961232Skas fclose(fi); 971232Skas } 981232Skas oflush(); 991232Skas exit(errs); 1001232Skas } 1011232Skas 1021232Skas /* 1031232Skas * Read up characters from the passed input file, forming lines, 1041232Skas * doing ^H processing, expanding tabs, stripping trailing blanks, 1051232Skas * and sending each line down for analysis. 1061232Skas */ 1071232Skas fmt(fi) 1081232Skas FILE *fi; 1091232Skas { 1101232Skas char linebuf[BUFSIZ], canonb[BUFSIZ]; 1111232Skas register char *cp, *cp2; 1121232Skas register int c, col; 1131232Skas 1141232Skas c = getc(fi); 1151232Skas while (c != EOF) { 1161232Skas /* 1171232Skas * Collect a line, doing ^H processing. 1181232Skas * Leave tabs for now. 1191232Skas */ 1201232Skas cp = linebuf; 1211232Skas while (c != '\n' && c != EOF && cp-linebuf < BUFSIZ-1) { 1221232Skas if (c == '\b') { 1231232Skas if (cp > linebuf) 1241232Skas cp--; 1251232Skas c = getc(fi); 1261232Skas continue; 1271232Skas } 1281232Skas if ((c < ' ' || c >= 0177) && c != '\t') { 1291232Skas c = getc(fi); 1301232Skas continue; 1311232Skas } 1321232Skas *cp++ = c; 1331232Skas c = getc(fi); 1341232Skas } 1351232Skas *cp = '\0'; 1361232Skas 1371232Skas /* 1381232Skas * Toss anything remaining on the input line. 1391232Skas */ 1401232Skas while (c != '\n' && c != EOF) 1411232Skas c = getc(fi); 1421232Skas 1431232Skas /* 1441232Skas * Expand tabs on the way to canonb. 1451232Skas */ 1461232Skas col = 0; 1471232Skas cp = linebuf; 1481232Skas cp2 = canonb; 1491232Skas while (c = *cp++) { 1501232Skas if (c != '\t') { 1511232Skas col++; 1521232Skas if (cp2-canonb < BUFSIZ-1) 1531232Skas *cp2++ = c; 1541232Skas continue; 1551232Skas } 1561232Skas do { 1571232Skas if (cp2-canonb < BUFSIZ-1) 1581232Skas *cp2++ = ' '; 1591232Skas col++; 1601232Skas } while ((col & 07) != 0); 1611232Skas } 1621232Skas 1631232Skas /* 1641232Skas * Swipe trailing blanks from the line. 1651232Skas */ 1661232Skas for (cp2--; cp2 >= canonb && *cp2 == ' '; cp2--) 1671232Skas ; 1681232Skas *++cp2 = '\0'; 1691232Skas prefix(canonb); 1701232Skas if (c != EOF) 1711232Skas c = getc(fi); 1721232Skas } 1731232Skas } 1741232Skas 1751232Skas /* 1761232Skas * Take a line devoid of tabs and other garbage and determine its 1771232Skas * blank prefix. If the indent changes, call for a linebreak. 1781232Skas * If the input line is blank, echo the blank line on the output. 1791232Skas * Finally, if the line minus the prefix is a mail header, try to keep 1801232Skas * it on a line by itself. 1811232Skas */ 1821232Skas prefix(line) 1831232Skas char line[]; 1841232Skas { 1851232Skas register char *cp, **hp; 1861232Skas register int np, h; 1871232Skas 1881232Skas if (strlen(line) == 0) { 1891232Skas oflush(); 1901232Skas putchar('\n'); 1911232Skas return; 1921232Skas } 1931232Skas for (cp = line; *cp == ' '; cp++) 1941232Skas ; 1951232Skas np = cp - line; 1961232Skas 1971232Skas /* 1981232Skas * The following horrible expression attempts to avoid linebreaks 1991232Skas * when the indent changes due to a paragraph. 2001232Skas */ 2011232Skas if (np != pfx && (np > pfx || abs(pfx-np) > 8)) 2021232Skas oflush(); 2031232Skas if (h = ishead(cp)) 2041232Skas oflush(), mark = lineno; 2051232Skas if (lineno - mark < 3 && lineno - mark > 0) 2061232Skas for (hp = &headnames[0]; *hp != (char *) 0; hp++) 2071232Skas if (ispref(*hp, cp)) { 2081232Skas h = 1; 2091232Skas oflush(); 2101232Skas break; 2111232Skas } 2121232Skas if (!h && (h = (*cp == '.'))) 2131232Skas oflush(); 2141232Skas pfx = np; 2151232Skas if (h) 21640367Smckusick pack(cp); 21740367Smckusick else split(cp); 21840367Smckusick if (h) 2191232Skas oflush(); 2201232Skas lineno++; 2211232Skas } 2221232Skas 2231232Skas /* 2241232Skas * Split up the passed line into output "words" which are 2251232Skas * maximal strings of non-blanks with the blank separation 2261232Skas * attached at the end. Pass these words along to the output 2271232Skas * line packer. 2281232Skas */ 2291232Skas split(line) 2301232Skas char line[]; 2311232Skas { 2321232Skas register char *cp, *cp2; 2331232Skas char word[BUFSIZ]; 23429545Smckusick int wordl; /* LIZ@UOM 6/18/85 */ 2351232Skas 2361232Skas cp = line; 2371232Skas while (*cp) { 2381232Skas cp2 = word; 23929545Smckusick wordl = 0; /* LIZ@UOM 6/18/85 */ 2401232Skas 2411232Skas /* 24229545Smckusick * Collect a 'word,' allowing it to contain escaped white 24329545Smckusick * space. 2441232Skas */ 2451232Skas while (*cp && *cp != ' ') { 2461232Skas if (*cp == '\\' && isspace(cp[1])) 2471232Skas *cp2++ = *cp++; 2481232Skas *cp2++ = *cp++; 24929545Smckusick wordl++;/* LIZ@UOM 6/18/85 */ 2501232Skas } 2511232Skas 2521232Skas /* 25329545Smckusick * Guarantee a space at end of line. Two spaces after end of 25429545Smckusick * sentence punctuation. 2551232Skas */ 2561232Skas if (*cp == '\0') { 2571232Skas *cp2++ = ' '; 25834987Sedward if (index(".:!", cp[-1])) 2591232Skas *cp2++ = ' '; 2601232Skas } 2611232Skas while (*cp == ' ') 2621232Skas *cp2++ = *cp++; 2631232Skas *cp2 = '\0'; 26429545Smckusick /* 26529545Smckusick * LIZ@UOM 6/18/85 pack(word); 26629545Smckusick */ 26729545Smckusick pack(word, wordl); 2681232Skas } 2691232Skas } 2701232Skas 2711232Skas /* 2721232Skas * Output section. 2731232Skas * Build up line images from the words passed in. Prefix 2741232Skas * each line with correct number of blanks. The buffer "outbuf" 2751232Skas * contains the current partial line image, including prefixed blanks. 2761232Skas * "outp" points to the next available space therein. When outp is NOSTR, 2771232Skas * there ain't nothing in there yet. At the bottom of this whole mess, 2781232Skas * leading tabs are reinserted. 2791232Skas */ 2801232Skas char outbuf[BUFSIZ]; /* Sandbagged output line image */ 2811232Skas char *outp; /* Pointer in above */ 2821232Skas 2831232Skas /* 2841232Skas * Initialize the output section. 2851232Skas */ 2861232Skas setout() 2871232Skas { 2881232Skas outp = NOSTR; 2891232Skas } 2901232Skas 2911232Skas /* 2921232Skas * Pack a word onto the output line. If this is the beginning of 2931232Skas * the line, push on the appropriately-sized string of blanks first. 2941232Skas * If the word won't fit on the current line, flush and begin a new 2951232Skas * line. If the word is too long to fit all by itself on a line, 2961232Skas * just give it its own and hope for the best. 29729545Smckusick * 29829545Smckusick * LIZ@UOM 6/18/85 -- If the new word will fit in at less than the 29929545Smckusick * goal length, take it. If not, then check to see if the line 30029545Smckusick * will be over the max length; if so put the word on the next 30129545Smckusick * line. If not, check to see if the line will be closer to the 30229545Smckusick * goal length with or without the word and take it or put it on 30329545Smckusick * the next line accordingly. 3041232Skas */ 3051232Skas 30629545Smckusick /* 30729545Smckusick * LIZ@UOM 6/18/85 -- pass in the length of the word as well 30829545Smckusick * pack(word) 30929545Smckusick * char word[]; 31029545Smckusick */ 31129545Smckusick pack(word,wl) 3121232Skas char word[]; 31329545Smckusick int wl; 3141232Skas { 3151232Skas register char *cp; 3161232Skas register int s, t; 3171232Skas 3181232Skas if (outp == NOSTR) 3191232Skas leadin(); 32029545Smckusick /* 32129545Smckusick * LIZ@UOM 6/18/85 -- change condition to check goal_length; s is the 32229545Smckusick * length of the line before the word is added; t is now the length 32329545Smckusick * of the line after the word is added 32429545Smckusick * t = strlen(word); 32529545Smckusick * if (t+s <= LENGTH) 32629545Smckusick */ 32729545Smckusick s = outp - outbuf; 32829545Smckusick t = wl + s; 32929545Smckusick if ((t <= goal_length) || 33029545Smckusick ((t <= max_length) && (t - goal_length <= goal_length - s))) { 3311232Skas /* 33229545Smckusick * In like flint! 3331232Skas */ 33429545Smckusick for (cp = word; *cp; *outp++ = *cp++); 3351232Skas return; 3361232Skas } 3371232Skas if (s > pfx) { 3381232Skas oflush(); 3391232Skas leadin(); 3401232Skas } 34129545Smckusick for (cp = word; *cp; *outp++ = *cp++); 3421232Skas } 3431232Skas 3441232Skas /* 3451232Skas * If there is anything on the current output line, send it on 3461232Skas * its way. Set outp to NOSTR to indicate the absence of the current 3471232Skas * line prefix. 3481232Skas */ 3491232Skas oflush() 3501232Skas { 3511232Skas if (outp == NOSTR) 3521232Skas return; 3531232Skas *outp = '\0'; 3541232Skas tabulate(outbuf); 3551232Skas outp = NOSTR; 3561232Skas } 3571232Skas 3581232Skas /* 3591232Skas * Take the passed line buffer, insert leading tabs where possible, and 3601232Skas * output on standard output (finally). 3611232Skas */ 3621232Skas tabulate(line) 3631232Skas char line[]; 3641232Skas { 36534987Sedward register char *cp; 3661232Skas register int b, t; 3671232Skas 3681232Skas /* 3691232Skas * Toss trailing blanks in the output line. 3701232Skas */ 3711232Skas cp = line + strlen(line) - 1; 3721232Skas while (cp >= line && *cp == ' ') 3731232Skas cp--; 3741232Skas *++cp = '\0'; 3751232Skas 3761232Skas /* 3771232Skas * Count the leading blank space and tabulate. 3781232Skas */ 3791232Skas for (cp = line; *cp == ' '; cp++) 3801232Skas ; 3811232Skas b = cp-line; 3821232Skas t = b >> 3; 3831232Skas b &= 07; 3841232Skas if (t > 0) 3851232Skas do 3861232Skas putc('\t', stdout); 3871232Skas while (--t); 3881232Skas if (b > 0) 3891232Skas do 3901232Skas putc(' ', stdout); 3911232Skas while (--b); 3921232Skas while (*cp) 3931232Skas putc(*cp++, stdout); 3941232Skas putc('\n', stdout); 3951232Skas } 3961232Skas 3971232Skas /* 3981232Skas * Initialize the output line with the appropriate number of 3991232Skas * leading blanks. 4001232Skas */ 4011232Skas leadin() 4021232Skas { 4031232Skas register int b; 4041232Skas register char *cp; 4051232Skas 4061232Skas for (b = 0, cp = outbuf; b < pfx; b++) 4071232Skas *cp++ = ' '; 4081232Skas outp = cp; 4091232Skas } 4101232Skas 4111232Skas /* 4121232Skas * Save a string in dynamic space. 4131232Skas * This little goodie is needed for 4141232Skas * a headline detector in head.c 4151232Skas */ 4161232Skas char * 4171232Skas savestr(str) 4181232Skas char str[]; 4191232Skas { 4201232Skas register char *top; 4211232Skas 42231142Sedward top = malloc(strlen(str) + 1); 4231232Skas if (top == NOSTR) { 4241232Skas fprintf(stderr, "fmt: Ran out of memory\n"); 4251232Skas exit(1); 4261232Skas } 42731142Sedward strcpy(top, str); 42829545Smckusick return (top); 4291232Skas } 4301232Skas 4311232Skas /* 4321232Skas * Is s1 a prefix of s2?? 4331232Skas */ 4341232Skas ispref(s1, s2) 4351232Skas register char *s1, *s2; 4361232Skas { 4371232Skas 4381232Skas while (*s1++ == *s2) 4391232Skas ; 44029545Smckusick return (*s1 == '\0'); 4411232Skas } 442