122453Sdist /* 222453Sdist * Copyright (c) 1980 Regents of the University of California. 322453Sdist * All rights reserved. The Berkeley software License Agreement 422453Sdist * specifies the terms and conditions for redistribution. 522453Sdist */ 622453Sdist 714531Ssam #ifndef lint 823638Smckusick char *copyright = 922453Sdist "@(#) Copyright (c) 1980 Regents of the University of California.\n\ 1022453Sdist All rights reserved.\n"; 1122453Sdist #endif not lint 121232Skas 1322453Sdist #ifndef lint 14*31142Sedward static char *sccsid = "@(#)fmt.c 5.4 (Berkeley) 05/18/87"; 1522453Sdist #endif not lint 1622453Sdist 171232Skas #include <stdio.h> 181232Skas #include <ctype.h> 191232Skas 201232Skas /* 211232Skas * fmt -- format the concatenation of input files or standard input 221232Skas * onto standard output. Designed for use with Mail ~| 231232Skas * 2429545Smckusick * Syntax : fmt [ goal [ max ] ] [ name ... ] 2529545Smckusick * Authors: Kurt Shoens (UCB) 12/7/78; 2629545Smckusick * Liz Allen (UMCP) 2/24/83 [Addition of goal length concept]. 271232Skas */ 281232Skas 2929545Smckusick /* LIZ@UOM 6/18/85 -- Don't need LENGTH any more. 3029545Smckusick * #define LENGTH 72 Max line length in output 3129545Smckusick */ 321232Skas #define NOSTR ((char *) 0) /* Null string pointer for lint */ 331232Skas 3429545Smckusick /* LIZ@UOM 6/18/85 --New variables goal_length and max_length */ 3529545Smckusick int goal_length = 65; /* Target or goal line length in output */ 3629545Smckusick int max_length = 75; /* Max line length in output */ 371232Skas int pfx; /* Current leading blank count */ 381232Skas int lineno; /* Current input line */ 391232Skas int mark; /* Last place we saw a head line */ 401232Skas 41*31142Sedward char *malloc(); /* for lint . . . */ 421232Skas char *headnames[] = {"To", "Subject", "Cc", 0}; 431232Skas 441232Skas /* 451232Skas * Drive the whole formatter by managing input files. Also, 461232Skas * cause initialization of the output stuff and flush it out 471232Skas * at the end. 481232Skas */ 491232Skas 501232Skas main(argc, argv) 5129545Smckusick int argc; 521232Skas char **argv; 531232Skas { 541232Skas register FILE *fi; 551232Skas register int errs = 0; 5629545Smckusick int number; /* LIZ@UOM 6/18/85 */ 571232Skas 581232Skas setout(); 591232Skas lineno = 1; 601232Skas mark = -10; 6129545Smckusick /* 6229545Smckusick * LIZ@UOM 6/18/85 -- Check for goal and max length arguments 6329545Smckusick */ 6429545Smckusick if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) { 6529545Smckusick argv++; 6629545Smckusick argc--; 6729545Smckusick goal_length = number; 6829545Smckusick if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) { 6929545Smckusick argv++; 7029545Smckusick argc--; 7129545Smckusick max_length = number; 7229545Smckusick } 7329545Smckusick } 7429545Smckusick if (max_length <= goal_length) { 7529545Smckusick fprintf(stderr, "Max length must be greater than %s\n", 7629545Smckusick "goal length"); 7729545Smckusick exit(1); 7829545Smckusick } 791232Skas if (argc < 2) { 801232Skas fmt(stdin); 811232Skas oflush(); 821232Skas exit(0); 831232Skas } 841232Skas while (--argc) { 8529545Smckusick if ((fi = fopen(*++argv, "r")) == NULL) { 8629545Smckusick perror(*argv); 871232Skas errs++; 881232Skas continue; 891232Skas } 901232Skas fmt(fi); 911232Skas fclose(fi); 921232Skas } 931232Skas oflush(); 941232Skas exit(errs); 951232Skas } 961232Skas 971232Skas /* 981232Skas * Read up characters from the passed input file, forming lines, 991232Skas * doing ^H processing, expanding tabs, stripping trailing blanks, 1001232Skas * and sending each line down for analysis. 1011232Skas */ 1021232Skas fmt(fi) 1031232Skas FILE *fi; 1041232Skas { 1051232Skas char linebuf[BUFSIZ], canonb[BUFSIZ]; 1061232Skas register char *cp, *cp2; 1071232Skas register int c, col; 1081232Skas 1091232Skas c = getc(fi); 1101232Skas while (c != EOF) { 1111232Skas /* 1121232Skas * Collect a line, doing ^H processing. 1131232Skas * Leave tabs for now. 1141232Skas */ 1151232Skas cp = linebuf; 1161232Skas while (c != '\n' && c != EOF && cp-linebuf < BUFSIZ-1) { 1171232Skas if (c == '\b') { 1181232Skas if (cp > linebuf) 1191232Skas cp--; 1201232Skas c = getc(fi); 1211232Skas continue; 1221232Skas } 1231232Skas if ((c < ' ' || c >= 0177) && c != '\t') { 1241232Skas c = getc(fi); 1251232Skas continue; 1261232Skas } 1271232Skas *cp++ = c; 1281232Skas c = getc(fi); 1291232Skas } 1301232Skas *cp = '\0'; 1311232Skas 1321232Skas /* 1331232Skas * Toss anything remaining on the input line. 1341232Skas */ 1351232Skas while (c != '\n' && c != EOF) 1361232Skas c = getc(fi); 1371232Skas 1381232Skas /* 1391232Skas * Expand tabs on the way to canonb. 1401232Skas */ 1411232Skas col = 0; 1421232Skas cp = linebuf; 1431232Skas cp2 = canonb; 1441232Skas while (c = *cp++) { 1451232Skas if (c != '\t') { 1461232Skas col++; 1471232Skas if (cp2-canonb < BUFSIZ-1) 1481232Skas *cp2++ = c; 1491232Skas continue; 1501232Skas } 1511232Skas do { 1521232Skas if (cp2-canonb < BUFSIZ-1) 1531232Skas *cp2++ = ' '; 1541232Skas col++; 1551232Skas } while ((col & 07) != 0); 1561232Skas } 1571232Skas 1581232Skas /* 1591232Skas * Swipe trailing blanks from the line. 1601232Skas */ 1611232Skas for (cp2--; cp2 >= canonb && *cp2 == ' '; cp2--) 1621232Skas ; 1631232Skas *++cp2 = '\0'; 1641232Skas prefix(canonb); 1651232Skas if (c != EOF) 1661232Skas c = getc(fi); 1671232Skas } 1681232Skas } 1691232Skas 1701232Skas /* 1711232Skas * Take a line devoid of tabs and other garbage and determine its 1721232Skas * blank prefix. If the indent changes, call for a linebreak. 1731232Skas * If the input line is blank, echo the blank line on the output. 1741232Skas * Finally, if the line minus the prefix is a mail header, try to keep 1751232Skas * it on a line by itself. 1761232Skas */ 1771232Skas prefix(line) 1781232Skas char line[]; 1791232Skas { 1801232Skas register char *cp, **hp; 1811232Skas register int np, h; 1821232Skas 1831232Skas if (strlen(line) == 0) { 1841232Skas oflush(); 1851232Skas putchar('\n'); 1861232Skas return; 1871232Skas } 1881232Skas for (cp = line; *cp == ' '; cp++) 1891232Skas ; 1901232Skas np = cp - line; 1911232Skas 1921232Skas /* 1931232Skas * The following horrible expression attempts to avoid linebreaks 1941232Skas * when the indent changes due to a paragraph. 1951232Skas */ 1961232Skas if (np != pfx && (np > pfx || abs(pfx-np) > 8)) 1971232Skas oflush(); 1981232Skas if (h = ishead(cp)) 1991232Skas oflush(), mark = lineno; 2001232Skas if (lineno - mark < 3 && lineno - mark > 0) 2011232Skas for (hp = &headnames[0]; *hp != (char *) 0; hp++) 2021232Skas if (ispref(*hp, cp)) { 2031232Skas h = 1; 2041232Skas oflush(); 2051232Skas break; 2061232Skas } 2071232Skas if (!h && (h = (*cp == '.'))) 2081232Skas oflush(); 2091232Skas pfx = np; 2101232Skas split(cp); 2111232Skas if (h) 2121232Skas oflush(); 2131232Skas lineno++; 2141232Skas } 2151232Skas 2161232Skas /* 2171232Skas * Split up the passed line into output "words" which are 2181232Skas * maximal strings of non-blanks with the blank separation 2191232Skas * attached at the end. Pass these words along to the output 2201232Skas * line packer. 2211232Skas */ 2221232Skas split(line) 2231232Skas char line[]; 2241232Skas { 2251232Skas register char *cp, *cp2; 2261232Skas char word[BUFSIZ]; 22729545Smckusick int wordl; /* LIZ@UOM 6/18/85 */ 2281232Skas 2291232Skas cp = line; 2301232Skas while (*cp) { 2311232Skas cp2 = word; 23229545Smckusick wordl = 0; /* LIZ@UOM 6/18/85 */ 2331232Skas 2341232Skas /* 23529545Smckusick * Collect a 'word,' allowing it to contain escaped white 23629545Smckusick * space. 2371232Skas */ 2381232Skas while (*cp && *cp != ' ') { 2391232Skas if (*cp == '\\' && isspace(cp[1])) 2401232Skas *cp2++ = *cp++; 2411232Skas *cp2++ = *cp++; 24229545Smckusick wordl++;/* LIZ@UOM 6/18/85 */ 2431232Skas } 2441232Skas 2451232Skas /* 24629545Smckusick * Guarantee a space at end of line. Two spaces after end of 24729545Smckusick * sentence punctuation. 2481232Skas */ 2491232Skas if (*cp == '\0') { 2501232Skas *cp2++ = ' '; 25129545Smckusick if (any(cp[-1], ".:!")) 2521232Skas *cp2++ = ' '; 2531232Skas } 2541232Skas while (*cp == ' ') 2551232Skas *cp2++ = *cp++; 2561232Skas *cp2 = '\0'; 25729545Smckusick /* 25829545Smckusick * LIZ@UOM 6/18/85 pack(word); 25929545Smckusick */ 26029545Smckusick pack(word, wordl); 2611232Skas } 2621232Skas } 2631232Skas 2641232Skas /* 2651232Skas * Output section. 2661232Skas * Build up line images from the words passed in. Prefix 2671232Skas * each line with correct number of blanks. The buffer "outbuf" 2681232Skas * contains the current partial line image, including prefixed blanks. 2691232Skas * "outp" points to the next available space therein. When outp is NOSTR, 2701232Skas * there ain't nothing in there yet. At the bottom of this whole mess, 2711232Skas * leading tabs are reinserted. 2721232Skas */ 2731232Skas char outbuf[BUFSIZ]; /* Sandbagged output line image */ 2741232Skas char *outp; /* Pointer in above */ 2751232Skas 2761232Skas /* 2771232Skas * Initialize the output section. 2781232Skas */ 2791232Skas setout() 2801232Skas { 2811232Skas outp = NOSTR; 2821232Skas } 2831232Skas 2841232Skas /* 2851232Skas * Pack a word onto the output line. If this is the beginning of 2861232Skas * the line, push on the appropriately-sized string of blanks first. 2871232Skas * If the word won't fit on the current line, flush and begin a new 2881232Skas * line. If the word is too long to fit all by itself on a line, 2891232Skas * just give it its own and hope for the best. 29029545Smckusick * 29129545Smckusick * LIZ@UOM 6/18/85 -- If the new word will fit in at less than the 29229545Smckusick * goal length, take it. If not, then check to see if the line 29329545Smckusick * will be over the max length; if so put the word on the next 29429545Smckusick * line. If not, check to see if the line will be closer to the 29529545Smckusick * goal length with or without the word and take it or put it on 29629545Smckusick * the next line accordingly. 2971232Skas */ 2981232Skas 29929545Smckusick /* 30029545Smckusick * LIZ@UOM 6/18/85 -- pass in the length of the word as well 30129545Smckusick * pack(word) 30229545Smckusick * char word[]; 30329545Smckusick */ 30429545Smckusick pack(word,wl) 3051232Skas char word[]; 30629545Smckusick int wl; 3071232Skas { 3081232Skas register char *cp; 3091232Skas register int s, t; 3101232Skas 3111232Skas if (outp == NOSTR) 3121232Skas leadin(); 31329545Smckusick /* 31429545Smckusick * LIZ@UOM 6/18/85 -- change condition to check goal_length; s is the 31529545Smckusick * length of the line before the word is added; t is now the length 31629545Smckusick * of the line after the word is added 31729545Smckusick * t = strlen(word); 31829545Smckusick * if (t+s <= LENGTH) 31929545Smckusick */ 32029545Smckusick s = outp - outbuf; 32129545Smckusick t = wl + s; 32229545Smckusick if ((t <= goal_length) || 32329545Smckusick ((t <= max_length) && (t - goal_length <= goal_length - s))) { 3241232Skas /* 32529545Smckusick * In like flint! 3261232Skas */ 32729545Smckusick for (cp = word; *cp; *outp++ = *cp++); 3281232Skas return; 3291232Skas } 3301232Skas if (s > pfx) { 3311232Skas oflush(); 3321232Skas leadin(); 3331232Skas } 33429545Smckusick for (cp = word; *cp; *outp++ = *cp++); 3351232Skas } 3361232Skas 3371232Skas /* 3381232Skas * If there is anything on the current output line, send it on 3391232Skas * its way. Set outp to NOSTR to indicate the absence of the current 3401232Skas * line prefix. 3411232Skas */ 3421232Skas oflush() 3431232Skas { 3441232Skas if (outp == NOSTR) 3451232Skas return; 3461232Skas *outp = '\0'; 3471232Skas tabulate(outbuf); 3481232Skas outp = NOSTR; 3491232Skas } 3501232Skas 3511232Skas /* 3521232Skas * Take the passed line buffer, insert leading tabs where possible, and 3531232Skas * output on standard output (finally). 3541232Skas */ 3551232Skas tabulate(line) 3561232Skas char line[]; 3571232Skas { 3581232Skas register char *cp, *cp2; 3591232Skas register int b, t; 3601232Skas 3611232Skas /* 3621232Skas * Toss trailing blanks in the output line. 3631232Skas */ 3641232Skas cp = line + strlen(line) - 1; 3651232Skas while (cp >= line && *cp == ' ') 3661232Skas cp--; 3671232Skas *++cp = '\0'; 3681232Skas 3691232Skas /* 3701232Skas * Count the leading blank space and tabulate. 3711232Skas */ 3721232Skas for (cp = line; *cp == ' '; cp++) 3731232Skas ; 3741232Skas b = cp-line; 3751232Skas t = b >> 3; 3761232Skas b &= 07; 3771232Skas if (t > 0) 3781232Skas do 3791232Skas putc('\t', stdout); 3801232Skas while (--t); 3811232Skas if (b > 0) 3821232Skas do 3831232Skas putc(' ', stdout); 3841232Skas while (--b); 3851232Skas while (*cp) 3861232Skas putc(*cp++, stdout); 3871232Skas putc('\n', stdout); 3881232Skas } 3891232Skas 3901232Skas /* 3911232Skas * Initialize the output line with the appropriate number of 3921232Skas * leading blanks. 3931232Skas */ 3941232Skas leadin() 3951232Skas { 3961232Skas register int b; 3971232Skas register char *cp; 3981232Skas 3991232Skas for (b = 0, cp = outbuf; b < pfx; b++) 4001232Skas *cp++ = ' '; 4011232Skas outp = cp; 4021232Skas } 4031232Skas 4041232Skas /* 4051232Skas * Save a string in dynamic space. 4061232Skas * This little goodie is needed for 4071232Skas * a headline detector in head.c 4081232Skas */ 4091232Skas char * 4101232Skas savestr(str) 4111232Skas char str[]; 4121232Skas { 4131232Skas register char *top; 4141232Skas 415*31142Sedward top = malloc(strlen(str) + 1); 4161232Skas if (top == NOSTR) { 4171232Skas fprintf(stderr, "fmt: Ran out of memory\n"); 4181232Skas exit(1); 4191232Skas } 420*31142Sedward strcpy(top, str); 42129545Smckusick return (top); 4221232Skas } 4231232Skas 4241232Skas /* 4251232Skas * Is s1 a prefix of s2?? 4261232Skas */ 4271232Skas ispref(s1, s2) 4281232Skas register char *s1, *s2; 4291232Skas { 4301232Skas 4311232Skas while (*s1++ == *s2) 4321232Skas ; 43329545Smckusick return (*s1 == '\0'); 4341232Skas } 435