1N/A/***********************************************************************
1N/A* *
1N/A* This software is part of the ast package *
1N/A* Copyright (c) 1992-2011 AT&T Intellectual Property *
1N/A* and is licensed under the *
1N/A* Common Public License, Version 1.0 *
1N/A* by AT&T Intellectual Property *
1N/A* *
1N/A* A copy of the License is available at *
1N/A* http://www.opensource.org/licenses/cpl1.0.txt *
1N/A* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
1N/A* *
1N/A* Information and Software Systems Research *
1N/A* AT&T Research *
1N/A* Florham Park NJ *
1N/A* *
1N/A* Glenn Fowler <gsf@research.att.com> *
1N/A* David Korn <dgk@research.att.com> *
1N/A* *
1N/A***********************************************************************/
1N/A#pragma prototyped
1N/A
1N/Astatic const char usage[] =
1N/A"[-?\n@(#)$Id: fmt (AT&T Research) 2007-01-02 $\n]"
1N/AUSAGE_LICENSE
1N/A"[+NAME?fmt - simple text formatter]"
1N/A"[+DESCRIPTION?\bfmt\b reads the input files and left justifies space "
1N/A "separated words into lines \awidth\a characters or less in length and "
1N/A "writes the lines to the standard output. The standard input is read if "
1N/A "\b-\b or no files are specified. Blank lines and interword spacing are "
1N/A "preserved in the output. Indentation is preserved, and lines with "
1N/A "identical indentation are joined and justified.]"
1N/A"[+?\bfmt\b is meant to format mail messages prior to sending, but may "
1N/A "also be useful for other simple tasks. For example, in \bvi\b(1) the "
1N/A "command \b:!}fmt\b will justify the lines in the current paragraph.]"
1N/A"[c:crown-margin?Preserve the indentation of the first two lines within "
1N/A "a paragraph, and align the left margin of each subsequent line with "
1N/A "that of the second line.]"
1N/A"[o:optget?Format concatenated \boptget\b(3) usage strings.]"
1N/A"[s:split-only?Split lines only; do not join short lines to form longer "
1N/A "ones.]"
1N/A"[u:uniform-spacing?One space between words, two after sentences.]"
1N/A"[w:width?Set the output line width to \acolumns\a.]#[columns:=72]"
1N/A "\n\n"
1N/A"[ file ... ]"
1N/A "\n\n"
1N/A"[+SEE ALSO?\bmailx\b(1), \bnroff\b(1), \btroff\b(1), \bvi\b(1), "
1N/A "\boptget\b(3)]"
1N/A;
1N/A
1N/A#include <cmd.h>
1N/A#include <ctype.h>
1N/A
1N/Atypedef struct Fmt_s
1N/A{
1N/A long flags;
1N/A char* outp;
1N/A char* outbuf;
1N/A char* endbuf;
1N/A Sfio_t* in;
1N/A Sfio_t* out;
1N/A int indent;
1N/A int nextdent;
1N/A int nwords;
1N/A int prefix;
1N/A int quote;
1N/A int retain;
1N/A int section;
1N/A} Fmt_t;
1N/A
1N/A#define INDENT 4
1N/A#define TABSZ 8
1N/A
1N/A#define isoption(fp,c) ((fp)->flags&(1L<<((c)-'a')))
1N/A#define setoption(fp,c) ((fp)->flags|=(1L<<((c)-'a')))
1N/A#define clroption(fp,c) ((fp)->flags&=~(1L<<((c)-'a')))
1N/A
1N/Astatic void
1N/Aoutline(Fmt_t* fp)
1N/A{
1N/A register char* cp = fp->outbuf;
1N/A int n = 0;
1N/A int c;
1N/A int d;
1N/A
1N/A if (!fp->outp)
1N/A return;
1N/A while (fp->outp[-1] == ' ')
1N/A fp->outp--;
1N/A *fp->outp = 0;
1N/A while (*cp++ == ' ')
1N/A n++;
1N/A if (n >= TABSZ)
1N/A {
1N/A n /= TABSZ;
1N/A cp = &fp->outbuf[TABSZ*n];
1N/A while (n--)
1N/A *--cp = '\t';
1N/A }
1N/A else
1N/A cp = fp->outbuf;
1N/A fp->nwords = 0;
1N/A if (!isoption(fp, 'o'))
1N/A sfputr(fp->out, cp, '\n');
1N/A else if (*cp)
1N/A {
1N/A n = fp->indent;
1N/A if (*cp != '[')
1N/A {
1N/A if (*cp == ' ')
1N/A cp++;
1N/A n += INDENT;
1N/A }
1N/A while (n--)
1N/A sfputc(fp->out, ' ');
1N/A if (fp->quote)
1N/A {
1N/A if ((d = (fp->outp - cp)) <= 0)
1N/A c = 0;
1N/A else if ((c = fp->outp[-1]) == 'n' && d > 1 && fp->outp[-2] == '\\')
1N/A c = '}';
1N/A sfprintf(fp->out, "\"%s%s\"\n", cp, c == ']' || c == '{' || c == '}' ? "" : " ");
1N/A }
1N/A else
1N/A sfputr(fp->out, cp, '\n');
1N/A if (fp->nextdent)
1N/A {
1N/A fp->indent += fp->nextdent;
1N/A fp->endbuf -= fp->nextdent;
1N/A fp->nextdent = 0;
1N/A }
1N/A }
1N/A fp->outp = 0;
1N/A}
1N/A
1N/Astatic void
1N/Asplit(Fmt_t* fp, char* buf, int splice)
1N/A{
1N/A register char* cp;
1N/A register char* ep;
1N/A register char* qp;
1N/A register int c = 1;
1N/A register int q = 0;
1N/A register int n;
1N/A int prefix;
1N/A
1N/A for (ep = buf; *ep == ' '; ep++);
1N/A prefix = ep - buf;
1N/A
1N/A /*
1N/A * preserve blank lines
1N/A */
1N/A
1N/A if ((*ep == 0 || *buf == '.') && !isoption(fp, 'o'))
1N/A {
1N/A if (*ep)
1N/A prefix = strlen(buf);
1N/A outline(fp);
1N/A strcpy(fp->outbuf, buf);
1N/A fp->outp = fp->outbuf+prefix;
1N/A outline(fp);
1N/A return;
1N/A }
1N/A if (fp->prefix < prefix && !isoption(fp, 'c'))
1N/A outline(fp);
1N/A if (!fp->outp || prefix < fp->prefix)
1N/A fp->prefix = prefix;
1N/A while (c)
1N/A {
1N/A cp = ep;
1N/A while (*ep == ' ')
1N/A ep++;
1N/A if (cp != ep && isoption(fp, 'u'))
1N/A cp = ep-1;
1N/A while (c = *ep)
1N/A {
1N/A if (c == ' ')
1N/A break;
1N/A ep++;
1N/A
1N/A /*
1N/A * skip over \space
1N/A */
1N/A
1N/A if (c == '\\' && *ep)
1N/A ep++;
1N/A }
1N/A n = (ep-cp);
1N/A if (n && isoption(fp, 'o'))
1N/A {
1N/A for (qp = cp; qp < ep; qp++)
1N/A if (*qp == '\\')
1N/A qp++;
1N/A else if (*qp == '"')
1N/A q = !q;
1N/A if (*(ep-1) == '"')
1N/A goto skip;
1N/A }
1N/A if (fp->nwords > 0 && &fp->outp[n] >= fp->endbuf && !fp->retain && !q)
1N/A outline(fp);
1N/A skip:
1N/A if (fp->nwords == 0)
1N/A {
1N/A if (fp->prefix)
1N/A memset(fp->outbuf, ' ', fp->prefix);
1N/A fp->outp = &fp->outbuf[fp->prefix];
1N/A while (*cp == ' ')
1N/A cp++;
1N/A n = (ep-cp);
1N/A }
1N/A memcpy(fp->outp, cp, n);
1N/A fp->outp += n;
1N/A fp->nwords++;
1N/A }
1N/A if (isoption(fp, 's') || *buf == 0)
1N/A outline(fp);
1N/A else if (fp->outp)
1N/A {
1N/A /*
1N/A * two spaces at ends of sentences
1N/A */
1N/A
1N/A if (!isoption(fp, 'o') && strchr(".:!?", fp->outp[-1]))
1N/A *fp->outp++ = ' ';
1N/A if (!splice && !fp->retain && (!fp->quote || (fp->outp - fp->outbuf) < 2 || fp->outp[-2] != '\\' || fp->outp[-1] != 'n' && fp->outp[-1] != 't' && fp->outp[-1] != ' '))
1N/A *fp->outp++ = ' ';
1N/A }
1N/A}
1N/A
1N/Astatic int
1N/Adofmt(Fmt_t* fp)
1N/A{
1N/A register int c;
1N/A int b;
1N/A int x;
1N/A int splice;
1N/A char* cp;
1N/A char* dp;
1N/A char* ep;
1N/A char* lp;
1N/A char* tp;
1N/A char buf[8192];
1N/A
1N/A cp = 0;
1N/A while (cp || (cp = sfgetr(fp->in, '\n', 0)) && !(splice = 0) && (lp = cp + sfvalue(fp->in) - 1) || (cp = sfgetr(fp->in, '\n', SF_LASTR)) && (splice = 1) && (lp = cp + sfvalue(fp->in)))
1N/A {
1N/A if (isoption(fp, 'o'))
1N/A {
1N/A if (!isoption(fp, 'i'))
1N/A {
1N/A setoption(fp, 'i');
1N/A b = 0;
1N/A while (cp < lp)
1N/A {
1N/A if (*cp == ' ')
1N/A b += 1;
1N/A else if (*cp == '\t')
1N/A b += INDENT;
1N/A else
1N/A break;
1N/A cp++;
1N/A }
1N/A fp->indent = roundof(b, INDENT);
1N/A }
1N/A else
1N/A while (cp < lp && (*cp == ' ' || *cp == '\t'))
1N/A cp++;
1N/A if (!isoption(fp, 'q') && cp < lp)
1N/A {
1N/A setoption(fp, 'q');
1N/A if (*cp == '"')
1N/A {
1N/A ep = lp;
1N/A while (--ep > cp)
1N/A if (*ep == '"')
1N/A {
1N/A fp->quote = 1;
1N/A break;
1N/A }
1N/A else if (*ep != ' ' && *ep != '\t')
1N/A break;
1N/A }
1N/A }
1N/A }
1N/A again:
1N/A dp = buf;
1N/A ep = 0;
1N/A for (b = 1;; b = 0)
1N/A {
1N/A if (cp >= lp)
1N/A {
1N/A cp = 0;
1N/A break;
1N/A }
1N/A c = *cp++;
1N/A if (isoption(fp, 'o'))
1N/A {
1N/A if (c == '\\')
1N/A {
1N/A x = 0;
1N/A c = ' ';
1N/A cp--;
1N/A while (cp < lp)
1N/A {
1N/A if (*cp == '\\')
1N/A {
1N/A cp++;
1N/A if ((lp - cp) < 1)
1N/A {
1N/A c = '\\';
1N/A break;
1N/A }
1N/A if (*cp == 'n')
1N/A {
1N/A cp++;
1N/A c = '\n';
1N/A if ((lp - cp) > 2)
1N/A {
1N/A if (*cp == ']' || *cp == '@' && *(cp + 1) == '(')
1N/A {
1N/A *dp++ = '\\';
1N/A *dp++ = 'n';
1N/A c = *cp++;
1N/A break;
1N/A }
1N/A if (*cp == '\\' && *(cp + 1) == 'n')
1N/A {
1N/A cp += 2;
1N/A *dp++ = '\n';
1N/A break;
1N/A }
1N/A }
1N/A }
1N/A else if (*cp == 't' || *cp == ' ')
1N/A {
1N/A cp++;
1N/A x = 1;
1N/A c = ' ';
1N/A }
1N/A else
1N/A {
1N/A if (x && dp != buf && *(dp - 1) != ' ')
1N/A *dp++ = ' ';
1N/A *dp++ = '\\';
1N/A c = *cp++;
1N/A break;
1N/A }
1N/A }
1N/A else if (*cp == ' ' || *cp == '\t')
1N/A {
1N/A cp++;
1N/A c = ' ';
1N/A x = 1;
1N/A }
1N/A else
1N/A {
1N/A if (x && c != '\n' && dp != buf && *(dp - 1) != ' ')
1N/A *dp++ = ' ';
1N/A break;
1N/A }
1N/A }
1N/A if (c == '\n')
1N/A {
1N/A c = 0;
1N/A goto flush;
1N/A }
1N/A if (c == ' ' && (dp == buf || *(dp - 1) == ' '))
1N/A continue;
1N/A }
1N/A else if (c == '"')
1N/A {
1N/A if (b || cp >= lp)
1N/A {
1N/A if (fp->quote)
1N/A continue;
1N/A fp->section = 0;
1N/A }
1N/A }
1N/A else if (c == '\a')
1N/A {
1N/A *dp++ = '\\';
1N/A c = 'a';
1N/A }
1N/A else if (c == '\b')
1N/A {
1N/A *dp++ = '\\';
1N/A c = 'b';
1N/A }
1N/A else if (c == '\f')
1N/A {
1N/A *dp++ = '\\';
1N/A c = 'f';
1N/A }
1N/A else if (c == '\v')
1N/A {
1N/A *dp++ = '\\';
1N/A c = 'v';
1N/A }
1N/A else if (c == ']' && (cp >= lp || *cp != ':' && *cp != '#' && *cp != '!'))
1N/A {
1N/A if (cp < lp && *cp == ']')
1N/A {
1N/A cp++;
1N/A *dp++ = c;
1N/A }
1N/A else
1N/A {
1N/A fp->section = 1;
1N/A fp->retain = 0;
1N/A flush:
1N/A *dp++ = c;
1N/A *dp = 0;
1N/A split(fp, buf, 0);
1N/A outline(fp);
1N/A goto again;
1N/A }
1N/A }
1N/A else if (fp->section)
1N/A {
1N/A if (c == '[')
1N/A {
1N/A if (b)
1N/A fp->retain = 1;
1N/A else
1N/A {
1N/A cp--;
1N/A c = 0;
1N/A goto flush;
1N/A }
1N/A fp->section = 0;
1N/A }
1N/A else if (c == '{')
1N/A {
1N/A x = 1;
1N/A for (tp = cp; tp < lp; tp++)
1N/A {
1N/A if (*tp == '[' || *tp == '\n')
1N/A break;
1N/A if (*tp == ' ' || *tp == '\t' || *tp == '"')
1N/A continue;
1N/A if (*tp == '\\' && (lp - tp) > 1)
1N/A {
1N/A if (*++tp == 'n')
1N/A break;
1N/A if (*tp == 't' || *tp == '\n')
1N/A continue;
1N/A }
1N/A x = 0;
1N/A break;
1N/A }
1N/A if (x)
1N/A {
1N/A if (fp->endbuf > (fp->outbuf + fp->indent + 2*INDENT))
1N/A fp->nextdent = 2*INDENT;
1N/A goto flush;
1N/A }
1N/A else
1N/A fp->section = 0;
1N/A }
1N/A else if (c == '}')
1N/A {
1N/A if (fp->indent && (b || *(cp - 2) != 'f'))
1N/A {
1N/A if (b)
1N/A {
1N/A fp->indent -= 2*INDENT;
1N/A fp->endbuf += 2*INDENT;
1N/A }
1N/A else
1N/A {
1N/A cp--;
1N/A c = 0;
1N/A }
1N/A goto flush;
1N/A }
1N/A else
1N/A fp->section = 0;
1N/A }
1N/A else if (c == ' ' || c == '\t')
1N/A continue;
1N/A else
1N/A fp->section = 0;
1N/A }
1N/A else if (c == '?' && (cp >= lp || *cp != '?'))
1N/A {
1N/A if (fp->retain)
1N/A {
1N/A cp--;
1N/A while (cp < lp && *cp != ' ' && *cp != '\t' && *cp != ']' && dp < &buf[sizeof(buf)-3])
1N/A *dp++ = *cp++;
1N/A if (cp < lp && (*cp == ' ' || *cp == '\t'))
1N/A *dp++ = *cp++;
1N/A *dp = 0;
1N/A split(fp, buf, 0);
1N/A dp = buf;
1N/A ep = 0;
1N/A fp->retain = 0;
1N/A if (fp->outp >= fp->endbuf)
1N/A outline(fp);
1N/A continue;
1N/A }
1N/A }
1N/A else if (c == ' ' || c == '\t')
1N/A for (c = ' '; *cp == ' ' || *cp == '\t'; cp++);
1N/A }
1N/A else if (c == '\b')
1N/A {
1N/A if (dp > buf)
1N/A {
1N/A dp--;
1N/A if (ep)
1N/A ep--;
1N/A }
1N/A continue;
1N/A }
1N/A else if (c == '\t')
1N/A {
1N/A /*
1N/A * expand tabs
1N/A */
1N/A
1N/A if (!ep)
1N/A ep = dp;
1N/A c = isoption(fp, 'o') ? 1 : TABSZ - (dp - buf) % TABSZ;
1N/A if (dp >= &buf[sizeof(buf) - c - 3])
1N/A {
1N/A cp--;
1N/A break;
1N/A }
1N/A while (c-- > 0)
1N/A *dp++ = ' ';
1N/A continue;
1N/A }
1N/A else if (!isprint(c))
1N/A continue;
1N/A if (dp >= &buf[sizeof(buf) - 3])
1N/A {
1N/A tp = dp;
1N/A while (--tp > buf)
1N/A if (isspace(*tp))
1N/A {
1N/A cp -= dp - tp;
1N/A dp = tp;
1N/A break;
1N/A }
1N/A ep = 0;
1N/A break;
1N/A }
1N/A if (c != ' ')
1N/A ep = 0;
1N/A else if (!ep)
1N/A ep = dp;
1N/A *dp++ = c;
1N/A }
1N/A if (ep)
1N/A *ep = 0;
1N/A else
1N/A *dp = 0;
1N/A split(fp, buf, splice);
1N/A }
1N/A return 0;
1N/A}
1N/A
1N/Aint
1N/Ab_fmt(int argc, char** argv, void *context)
1N/A{
1N/A register int n;
1N/A char* cp;
1N/A Fmt_t fmt;
1N/A char outbuf[8 * 1024];
1N/A
1N/A fmt.flags = 0;
1N/A fmt.out = sfstdout;
1N/A fmt.outbuf = outbuf;
1N/A fmt.outp = 0;
1N/A fmt.endbuf = &outbuf[72];
1N/A fmt.indent = 0;
1N/A fmt.nextdent = 0;
1N/A fmt.nwords = 0;
1N/A fmt.prefix = 0;
1N/A fmt.quote = 0;
1N/A fmt.retain = 0;
1N/A fmt.section = 1;
1N/A cmdinit(argc, argv, context, ERROR_CATALOG, 0);
1N/A for (;;)
1N/A {
1N/A switch (n = optget(argv, usage))
1N/A {
1N/A case 'c':
1N/A case 'o':
1N/A case 's':
1N/A case 'u':
1N/A setoption(&fmt, n);
1N/A continue;
1N/A case 'w':
1N/A if (opt_info.num < TABSZ || opt_info.num>= sizeof(outbuf))
1N/A error(2, "width out of range");
1N/A fmt.endbuf = &outbuf[opt_info.num];
1N/A continue;
1N/A case ':':
1N/A error(2, "%s", opt_info.arg);
1N/A break;
1N/A case '?':
1N/A error(ERROR_usage(2), "%s", opt_info.arg);
1N/A break;
1N/A }
1N/A break;
1N/A }
1N/A argv += opt_info.index;
1N/A if (error_info.errors)
1N/A error(ERROR_usage(2), "%s", optusage(NiL));
1N/A if (isoption(&fmt, 'o'))
1N/A setoption(&fmt, 'c');
1N/A if (isoption(&fmt, 's'))
1N/A clroption(&fmt, 'u');
1N/A if (cp = *argv)
1N/A argv++;
1N/A do {
1N/A if (!cp || streq(cp, "-"))
1N/A fmt.in = sfstdin;
1N/A else if (!(fmt.in = sfopen(NiL, cp, "r")))
1N/A {
1N/A error(ERROR_system(0), "%s: cannot open", cp);
1N/A error_info.errors = 1;
1N/A continue;
1N/A }
1N/A dofmt(&fmt);
1N/A if (fmt.in != sfstdin)
1N/A sfclose(fmt.in);
1N/A } while (cp = *argv++);
1N/A outline(&fmt);
1N/A if (sfsync(sfstdout))
1N/A error(ERROR_system(0), "write error");
1N/A return error_info.errors != 0;
1N/A}