122453Sdist /*
2*63942Sbostic * Copyright (c) 1980, 1993
3*63942Sbostic * The Regents of the University of California. All rights reserved.
433499Sbostic *
542731Sbostic * %sccs.include.redist.c%
622453Sdist */
722453Sdist
814531Ssam #ifndef lint
9*63942Sbostic static char copyright[] =
10*63942Sbostic "@(#) Copyright (c) 1980, 1993\n\
11*63942Sbostic The Regents of the University of California. All rights reserved.\n";
1233499Sbostic #endif /* not lint */
131232Skas
1422453Sdist #ifndef lint
15*63942Sbostic static char sccsid[] = "@(#)fmt.c 8.1 (Berkeley) 07/20/93";
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
main(argc,argv)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 */
fmt(fi)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 */
prefix(line)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)
21663941Sbostic pack(cp, strlen(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 */
split(line)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 */
setout()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 */
pack(word,wl)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 */
oflush()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 */
tabulate(line)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 */
leadin()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 *
savestr(str)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 */
ispref(s1,s2)4341232Skas ispref(s1, s2)
4351232Skas register char *s1, *s2;
4361232Skas {
4371232Skas
4381232Skas while (*s1++ == *s2)
4391232Skas ;
44029545Smckusick return (*s1 == '\0');
4411232Skas }
442