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