[BACK]Return to fmt.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / fmt

Diff for /src/usr.bin/fmt/fmt.c between version 1.9 and 1.10

version 1.9, 1998/02/16 07:54:29 version 1.10, 1998/04/25 23:02:28
Line 1 
Line 1 
 /*      $OpenBSD$       */  /*      $OpenBSD$       */
 /*      $NetBSD: fmt.c,v 1.4 1995/09/01 01:29:41 jtc Exp $      */  
   
 /*  /* Sensible version of fmt
  * Copyright (c) 1980, 1993  
  *      The Regents of the University of California.  All rights reserved.  
  *   *
  * Redistribution and use in source and binary forms, with or without   * Syntax: fmt [ options ] [ goal [ max ] ] [ filename ... ]
  * modification, are permitted provided that the following conditions   *
  * are met:   * Since the documentation for the original fmt is so poor, here
  * 1. Redistributions of source code must retain the above copyright   * is an accurate description of what this one does. It's usually
    * the same. The *mechanism* used may differ from that suggested
    * here. Note that we are *not* entirely compatible with fmt,
    * because fmt gets so many things wrong.
    *
    * 1. Tabs are expanded, assuming 8-space tab stops.
    *    If the `-t <n>' option is given, we assume <n>-space
    *    tab stops instead.
    *    Trailing blanks are removed from all lines.
    *    x\b == nothing, for any x other than \b.
    *    Other control characters are simply stripped. This
    *    includes \r.
    * 2. Each line is split into leading whitespace and
    *    everything else. Maximal consecutive sequences of
    *    lines with the same leading whitespace are considered
    *    to form paragraphs, except that a blank line is always
    *    a paragraph to itself.
    *    If the `-p' option is given then the first line of a
    *    paragraph is permitted to have indentation different
    *    from that of the other lines.
    *    If the `-m' option is given then a line that looks
    *    like a mail message header, if it is not immediately
    *    preceded by a non-blank non-message-header line, is
    *    taken to start a new paragraph, which also contains
    *    any subsequent lines with non-empty leading whitespace.
    * 3. The "everything else" is split into words; a word
    *    includes its trailing whitespace, and a word at the
    *    end of a line is deemed to be followed by a single
    *    space, or two spaces if it ends with a sentence-end
    *    character. (See the `-d' option for how to change that.)
    *    If the `-s' option has been given, then a word's trailing
    *    whitespace is replaced by what it would have had if it
    *    had occurred at end of line.
    * 4. Each paragraph is sent to standard output as follows.
    *    We output the leading whitespace, and then enough words
    *    to make the line length as near as possible to the goal
    *    without exceeding the maximum. (If a single word would
    *    exceed the maximum, we output that anyway.) Of course
    *    the trailing whitespace of the last word is ignored.
    *    We then emit a newline and start again if there are any
    *    words left.
    *    Note that for a blank line this translates as "We emit
    *    a newline".
    *    If the `-l <n>' option is given, then leading whitespace
    *    is modified slightly: <n> spaces are replaced by a tab.
    *    Indented paragraphs (see above under `-p') make matters
    *    more complicated than this suggests. Actually every paragraph
    *    has two `leading whitespace' values; the value for the first
    *    line, and the value for the most recent line. (While processing
    *    the first line, the two are equal. When `-p' has not been
    *    given, they are always equal.) The leading whitespace
    *    actually output is that of the first line (for the first
    *    line of *output*) or that of the most recent line (for
    *    all other lines of output).
    *    When `-m' has been given, message header paragraphs are
    *    taken as having first-leading-whitespace empty and
    *    subsequent-leading-whitespace two spaces.
    *
    * Multiple input files are formatted one at a time, so that a file
    * never ends in the middle of a line.
    *
    * There's an alternative mode of operation, invoked by giving
    * the `-c' option. In that case we just center every line,
    * and most of the other options are ignored. This should
    * really be in a separate program, but we must stay compatible
    * with old `fmt'.
    *
    * QUERY: Should `-m' also try to do the right thing with quoted text?
    * QUERY: `-b' to treat backslashed whitespace as old `fmt' does?
    * QUERY: Option meaning `never join lines'?
    * QUERY: Option meaning `split in mid-word to avoid overlong lines'?
    * (Those last two might not be useful, since we have `fold'.)
    *
    * Differences from old `fmt':
    *
    *   - We have many more options. Options that aren't understood
    *     generate a lengthy usage message, rather than being
    *     treated as filenames.
    *   - Even with `-m', our handling of message headers is
    *     significantly different. (And much better.)
    *   - We don't treat `\ ' as non-word-breaking.
    *   - Downward changes of indentation start new paragraphs
    *     for us, as well as upward. (I think old `fmt' behaves
    *     in the way it does in order to allow indented paragraphs,
    *     but this is a broken way of making indented paragraphs
    *     behave right.)
    *   - Given the choice of going over or under |goal_length|
    *     by the same amount, we go over; old `fmt' goes under.
    *   - We treat `?' as ending a sentence, and not `:'. Old `fmt'
    *     does the reverse.
    *   - We return approved return codes. Old `fmt' returns
    *     1 for some errors, and *the number of unopenable files*
    *     when that was all that went wrong.
    *   - We have fewer crashes and more helpful error messages.
    *   - We don't turn spaces into tabs at starts of lines unless
    *     specifically requested.
    *   - New `fmt' is somewhat smaller and slightly faster than
    *     old `fmt'.
    *
    * Bugs:
    *
    *   None known. There probably are some, though.
    *
    * Portability:
    *
    *   I believe this code to be pretty portable. It does require
    *   that you have `getopt'. If you need to include "getopt.h"
    *   for this (e.g., if your system didn't come with `getopt'
    *   and you installed it yourself) then you should arrange for
    *   NEED_getopt_h to be #defined.
    *
    *   Everything here should work OK even on nasty 16-bit
    *   machines and nice 64-bit ones. However, it's only really
    *   been tested on my FreeBSD machine. Your mileage may vary.
    */
   
   /* Copyright (c) 1997 Gareth McCaughan. All rights reserved.
    *
    * Redistribution and use of this code, in source or binary forms,
    * with or without modification, are permitted subject to the following
    * conditions:
    *
    *  - Redistribution of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.   *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright  
  *    notice, this list of conditions and the following disclaimer in the  
  *    documentation and/or other materials provided with the distribution.  
  * 3. All advertising materials mentioning features or use of this software  
  *    must display the following acknowledgement:  
  *      This product includes software developed by the University of  
  *      California, Berkeley and its contributors.  
  * 4. Neither the name of the University nor the names of its contributors  
  *    may be used to endorse or promote products derived from this software  
  *    without specific prior written permission.  
  *   *
  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   *  - If you distribute modified source code it must also include
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE   *    a notice saying that it has been modified, and giving a brief
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   *    description of what changes have been made.
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE   *
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL   * Disclaimer: I am not responsible for the results of using this code.
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS   *             If it formats your hard disc, sends obscene messages to
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)   *             your boss and kills your children then that's your problem
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT   *             not mine. I give absolutely no warranty of any sort as to
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY   *             what the program will do, and absolutely refuse to be held
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF   *             liable for any consequences of your using it.
  * SUCH DAMAGE.   *             Thank you. Have a nice day.
  */   */
   
   /* RCS change log:
    * Revision 1.5  1998/03/02 18:02:21  gjm11
    * Minor changes for portability.
    *
    * Revision 1.4  1997/10/01 11:51:28  gjm11
    * Repair broken indented-paragraph handling.
    * Add mail message header stuff.
    * Improve comments and layout.
    * Make usable with non-BSD systems.
    * Add revision display to usage message.
    *
    * Revision 1.3  1997/09/30 16:24:47  gjm11
    * Add copyright notice, rcsid string and log message.
    *
    * Revision 1.2  1997/09/30 16:13:39  gjm11
    * Add options: -d <chars>, -l <width>, -p, -s, -t <width>, -h .
    * Parse options with `getopt'. Clean up code generally.
    * Make comments more accurate.
    *
    * Revision 1.1  1997/09/30 11:29:57  gjm11
    * Initial revision
    */
   
 #ifndef lint  #ifndef lint
 static char copyright[] =  static const char rcsid[] =
 "@(#) Copyright (c) 1980, 1993\n\    "$OpenBSD$";
         The Regents of the University of California.  All rights reserved.\n";  static const char copyright[] =
     "Copyright (c) 1997 Gareth McCaughan. All rights reserved.\n";
 #endif /* not lint */  #endif /* not lint */
   
 #ifndef lint  /* Cater for BSD and non-BSD systems.
 #if 0   * I hate the C preprocessor.
 static char sccsid[] = "@(#)fmt.c       8.1 (Berkeley) 7/20/93";   */
   
   #undef HAVE_errx
   #undef HAVE_sysexits
   
   #ifdef unix
   # include <sys/param.h>
   # ifdef BSD
   #  define HAVE_errx
   #  if BSD >= 199306
   #   define HAVE_sysexits
   #  endif
   # endif
   #endif
   
   #ifdef HAVE_errx
   # include <err.h>
 #else  #else
 static char rcsid[] = "$OpenBSD$";  # define errx(rc,str) { fprintf(stderr,"fmt: %s\n",str); exit(rc); }
 #endif  #endif
 #endif /* not lint */  
   
   #ifdef HAVE_sysexits
   # include <sysexits.h>
   #else
   # define EX_USAGE 1
   # define EX_NOINPUT 1
   # define EX_SOFTWARE 1
   # define EX_OSERR 1
   #endif
   
 #include <ctype.h>  #include <ctype.h>
 #include <err.h>  
 #include <locale.h>  
 #include <stdio.h>  #include <stdio.h>
 #include <stdlib.h>  #include <stdlib.h>
 #include <string.h>  #include <string.h>
   
 #ifdef  __GNUC__  #ifdef NEED_getopt_h
 #define inline  __inline  # include "getopt.h"
 #else   /* !__GNUC__ */  #endif
 #define inline  
 #endif  /* !__GNUC__ */  
   
 /*  /* Something that, we hope, will never be a genuine line length,
  * fmt -- format the concatenation of input files or standard input   * indentation etc.
  * onto standard output.  Designed for use with Mail ~|  
  *  
  * Syntax : fmt [ goal [ max ] ] [ name ... ]  
  * Authors: Kurt Shoens (UCB) 12/7/78;  
  *          Liz Allen (UMCP) 2/24/83 [Addition of goal length concept].  
  */   */
   #define SILLY ((size_t)-1)
   
 /* LIZ@UOM 6/18/85 -- Don't need LENGTH any more.  /* I used to use |strtoul| for this, but (1) not all systems have it
  * #define      LENGTH  72              Max line length in output   * and (2) it's probably better to use |strtol| to detect negative
    * numbers better.
    * If |fussyp==0| then we don't complain about non-numbers
    * (returning 0 instead), but we do complain about bad numbers.
  */   */
 #define NOSTR   ((char *) 0)    /* Null string pointer for lint */  size_t get_positive(const char *s, const char *err_mess, int fussyP) {
     char *t;
     long result = strtol(s,&t,0);
     if (*t) { if (fussyP) goto Lose; else return 0; }
     if (result<=0) { Lose: errx(EX_USAGE, err_mess); }
     return (size_t) result;
   }
   
 /* LIZ@UOM 6/18/85 --New variables goal_length and max_length */  /* Just for the sake of linguistic purity: */
 #define GOAL_LENGTH 65  
 #define MAX_LENGTH 75  
 int     goal_length;            /* Target or goal line length in output */  
 int     max_length;             /* Max line length in output */  
 int     pfx;                    /* Current leading blank count */  
 int     lineno;                 /* Current input line */  
 int     mark;                   /* Last place we saw a head line */  
 int     center;                 /* Did they ask to center lines? */  
   
 char    *headnames[] = {"To", "Subject", "Cc", 0};  #ifdef BRITISH
   # define CENTER "centre"
   #else
   # define CENTER "center"
   #endif
   
 void fmt __P((FILE *));  /* Global variables */
 void setout __P((void));  
 void prefix __P((char *));  
 void split __P((char *));  
 void pack __P((char *, int));  
 void oflush __P((void));  
 void tabulate __P((char *));  
 void leadin __P((void));  
 char *savestr __P((char *));  
 inline char *extstr __P((char *, int *, int));  
 int  ispref __P((char *, char *));  
 int  ishead __P((char *));  
   
 /*  static int centerP=0;           /* Try to center lines? */
  * Drive the whole formatter by managing input files.  Also,  static size_t goal_length=0;    /* Target length for output lines */
  * cause initialization of the output stuff and flush it out  static size_t max_length=0;     /* Maximum length for output lines */
  * at the end.  static int coalesce_spaces_P=0; /* Coalesce multiple whitespace -> ' ' ? */
  */  static int allow_indented_paragraphs=0; /* Can first line have diff. ind.? */
   static int tab_width=8;         /* Number of spaces per tab stop */
   static int output_tab_width=0;  /* Ditto, when squashing leading spaces */
   static char *sentence_enders=".?!";     /* Double-space after these */
   static int grok_mail_headers=0; /* treat embedded mail headers magically? */
   
 int  static int n_errors=0;          /* Number of failed files. Return on exit. */
 main(argc, argv)  static char *output_buffer=0;   /* Output line will be built here */
         int argc;  static size_t x;                /* Horizontal position in output line */
         char **argv;  static size_t x0;               /* Ditto, ignoring leading whitespace */
 {  static size_t pending_spaces;   /* Spaces to add before next word */
         register FILE *fi;  static int output_in_paragraph=0;       /* Any of current para written out yet? */
         register int errs = 0;  
         int number;             /* LIZ@UOM 6/18/85 */  
   
         (void) setlocale(LC_CTYPE, "");  /* Prototypes */
   
         goal_length = GOAL_LENGTH;  static void process_named_file (const char *);
         max_length = MAX_LENGTH;  static void     process_stream (FILE *, const char *);
         setout();  static size_t    indent_length (const char *, size_t);
         lineno = 1;  static int     might_be_header (const char *);
         mark = -10;  static void      new_paragraph (size_t, size_t);
         /*  static void        output_word (size_t, size_t, const char *, size_t, size_t);
          * LIZ@UOM 6/18/85 -- Check for goal and max length arguments  static void      output_indent (size_t);
          */  static void      center_stream (FILE *, const char *);
         if (argc > 1 && !strcmp(argv[1], "-c")) {  static char *         get_line (FILE *, size_t *);
                 center++;  static void *         xrealloc (void *, size_t);
                 argc--;  
                 argv++;  
         }  
         if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) {  
                 argv++;  
                 argc--;  
                 goal_length = abs(number);  
                 if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) {  
                         argv++;  
                         argc--;  
                         max_length = abs(number);  
                 }  
         }  
         if (max_length <= goal_length)  
                 errx(1, "Max length (%d) must be greater than goal length: %d",  
                      max_length, goal_length);  
         if (argc < 2) {  
                 fmt(stdin);  
                 oflush();  
                 exit(0);  
         }  
         while (--argc) {  
                 if ((fi = fopen(*++argv, "r")) == NULL) {  
                         perror(*argv);  
                         errs++;  
                         continue;  
                 }  
                 fmt(fi);  
                 fclose(fi);  
         }  
         oflush();  
         exit(errs);  
 }  
   
 /*  #define XMALLOC(x) xrealloc(0,x)
  * Read up characters from the passed input file, forming lines,  
  * doing ^H processing, expanding tabs, stripping trailing blanks,  /* Here is perhaps the right place to mention that this code is
  * and sending each line down for analysis.   * all in top-down order. Hence, |main| comes first.
  */   */
 void  int
 fmt(fi)  main(int argc, char *argv[]) {
         FILE *fi;    int ch;                       /* used for |getopt| processing */
 {  
         static char *linebuf, *canonb;  
         static int lbufsize, cbufsize;  
         register char *cp, *cp2, cc;  
         register int c, col;  
 #define CHUNKSIZE 1024  
   
         canonb = malloc(CHUNKSIZE);    /* 1. Grok parameters. */
         if (canonb == 0)  
                 errx(1, "Ran out of memory");  
   
         if (center) {    while ((ch = getopt(argc, argv, "cd:hl:mpst:")) != EOF) switch(ch) {
                 register int len;      case 'c':
         centerP = 1;
         continue;
       case 'd':
         sentence_enders = XMALLOC(strlen(optarg)+1);
         strcpy(sentence_enders, optarg);
         continue;
       case 'l':
         output_tab_width
           = get_positive(optarg, "output tab width must be positive", 1);
         continue;
       case 'm':
         grok_mail_headers = 1;
         continue;
       case 'p':
         allow_indented_paragraphs = 1;
         continue;
       case 's':
         coalesce_spaces_P = 1;
         continue;
       case 't':
         tab_width = get_positive(optarg, "tab width must be positive", 1);
         continue;
       case 'h': default:
         fprintf(stderr,
   "Usage:   fmt [-cmps] [-d chars] [-l num] [-t num] [goal [maximum]] [file...]\n"
   "Options: -c     " CENTER " each line instead of formatting\n"
   "         -d <chars> double-space after <chars> at line end\n"
   "         -l <n> turn each <n> spaces at start of line into a tab\n"
   "         -m     try to make sure mail header lines stay separate\n"
   "         -p     allow indented paragraphs\n"
   "         -s     coalesce whitespace inside lines\n"
   "         -t <n> have tabs every <n> columns\n");
         exit(ch=='h' ? 0 : EX_USAGE);
     }
     argc -= optind; argv += optind;
   
                 linebuf = extstr(linebuf, &lbufsize, NULL);    /* [ goal [ maximum ] ] */
                 for (;;) {  
                         len = 0;  
                         for (;;) {  
                                 if (!fgets(linebuf + len, lbufsize - len, fi))  
                                         break;  
                                 len = strlen(linebuf);  
                                 if (linebuf[len-1] == '\n' || feof(fi))  
                                         break;  
                                 linebuf = extstr(linebuf, &lbufsize, NULL);  
                         }  
                         if (len == 0)  
                                 return;  
                         cp = linebuf;  
                         while (*cp && isspace(*cp))  
                                 cp++;  
                         cp2 = cp + strlen(cp) - 1;  
                         while (cp2 > cp && isspace(*cp2))  
                                 cp2--;  
                         if (cp == cp2)  
                                 putchar('\n');  
                         col = cp2 - cp;  
                         for (c = 0; c < (goal_length-col)/2; c++)  
                                 putchar(' ');  
                         while (cp <= cp2)  
                                 putchar(*cp++);  
                         putchar('\n');  
                 }  
         }  
         c = getc(fi);  
         while (c != EOF) {  
                 /*  
                  * Collect a line, doing ^H processing.  
                  * Leave tabs for now.  
                  */  
                 cp = linebuf;  
                 while (c != '\n' && c != EOF) {  
                         if (cp - linebuf >= lbufsize) {  
                                 int offset = cp - linebuf;  
                                 linebuf = extstr(linebuf, &lbufsize, NULL);  
                                 cp = linebuf + offset;  
                         }  
                         if (c == '\b') {  
                                 if (cp > linebuf)  
                                         cp--;  
                                 c = getc(fi);  
                                 continue;  
                         }  
                         if (!isprint(c) && c != '\t') {  
                                 c = getc(fi);  
                                 continue;  
                         }  
                         *cp++ = c;  
                         c = getc(fi);  
                 }  
   
                 /*    if (argc>0
                  * Toss anything remaining on the input line.        && (goal_length=get_positive(*argv,"goal length must be positive", 0))
                  */           != 0) {
                 while (c != '\n' && c != EOF)      --argc; ++argv;
                         c = getc(fi);      if (argc>0
           && (goal_length=get_positive(*argv,"max length must be positive", 0))
              != 0) {
         if (max_length<goal_length)
           errx(EX_USAGE, "max length must be >= goal length");
       }
     }
     if (goal_length==0) goal_length = 65;
     if (max_length==0) max_length = goal_length+10;
     output_buffer = XMALLOC(max_length+1);        /* really needn't be longer */
   
                 if (cp != NULL) {    /* 2. Process files. */
                         *cp = '\0';  
                 } else {  
                         putchar('\n');  
                         c = getc(fi);  
                         continue;  
                 }  
   
                 /*    if (argc>0) {
                  * Expand tabs on the way to canonb.      while (argc-->0) process_named_file(*argv++);
                  */    }
                 col = 0;    else {
                 cp = linebuf;      process_stream(stdin, "standard input");
                 cp2 = canonb;    }
                 while ((cc = *cp++)) {  
                         if (cc != '\t') {  
                                 col++;  
                                 if (cp2 - canonb >= cbufsize) {  
                                         int offset = cp2 - canonb;  
                                         canonb = extstr(canonb, &cbufsize, NULL);  
                                         cp2 = canonb + offset;  
                                 }  
                                 *cp2++ = cc;  
                                 continue;  
                         }  
                         do {  
                                 if (cp2 - canonb >= cbufsize) {  
                                         int offset = cp2 - canonb;  
                                         canonb = extstr(canonb, &cbufsize, NULL);  
                                         cp2 = canonb + offset;  
                                 }  
                                 *cp2++ = ' ';  
                                 col++;  
                         } while ((col & 07) != 0);  
                 }  
   
                 /*    /* We're done. */
                  * Swipe trailing blanks from the line.  
                  */  
                 for (cp2--; cp2 >= canonb && *cp2 == ' '; cp2--)  
                         ;  
                 *++cp2 = '\0';  
                 prefix(canonb);  
                 if (c != EOF)  
                         c = getc(fi);  
         }  
 }  
   
 /*    return n_errors ? EX_NOINPUT : 0;
  * Take a line devoid of tabs and other garbage and determine its  
  * blank prefix.  If the indent changes, call for a linebreak.  
  * If the input line is blank, echo the blank line on the output.  
  * Finally, if the line minus the prefix is a mail header, try to keep  
  * it on a line by itself.  
  */  
 void  
 prefix(line)  
         char line[];  
 {  
         register char *cp, **hp;  
         register int np, h;  
   
         if (*line == '\0') {  }
                 oflush();  
                 putchar('\n');  
                 return;  
         }  
         for (cp = line; *cp == ' '; cp++)  
                 ;  
         np = cp - line;  
   
         /*  /* Process a single file, given its name.
          * The following horrible expression attempts to avoid linebreaks   */
          * when the indent changes due to a paragraph.  static void
          */  process_named_file(const char *name) {
         if (np != pfx && (np > pfx || abs(pfx-np) > 8))    FILE *f=fopen(name, "r");
                 oflush();    if (!f) { perror(name); ++n_errors; }
         if ((h = ishead(cp)))    else {
                 oflush(), mark = lineno;      process_stream(f, name);
         if (lineno - mark < 3 && lineno - mark > 0)      fclose(f);
                 for (hp = &headnames[0]; *hp != NULL; hp++)    }
                         if (ispref(*hp, cp)) {  
                                 h = 1;  
                                 oflush();  
                                 break;  
                         }  
         if (!h && (h = (*cp == '.')))  
                 oflush();  
         pfx = np;  
         if (h)  
                 pack(cp, strlen(cp));  
         else  
                 split(cp);  
         if (h)  
                 oflush();  
         lineno++;  
 }  }
   
 /*  /* Types of mail header continuation lines:
  * Split up the passed line into output "words" which are  
  * maximal strings of non-blanks with the blank separation  
  * attached at the end.  Pass these words along to the output  
  * line packer.  
  */   */
 void  typedef enum {
 split(line)    hdr_ParagraphStart = -1,
         char line[];    hdr_NonHeader      = 0,
 {    hdr_Header         = 1,
         register char *cp, *cp2;    hdr_Continuation   = 2
         static char *word;  } HdrType;
         static int wordsize;  
         int wordl;              /* LIZ@UOM 6/18/85 */  
   
         if (strlen(line) >= wordsize)  /* Process a stream. This is where the real work happens,
                 word = extstr(word, &wordsize, strlen(line) + 1);   * except that centering is handled separately.
    */
   static void
   process_stream(FILE *stream, const char *name) {
     size_t last_indent=SILLY;     /* how many spaces in last indent? */
     size_t para_line_number=0;    /* how many lines already read in this para? */
     size_t first_indent=SILLY;    /* indentation of line 0 of paragraph */
     HdrType prev_header_type=hdr_ParagraphStart;
           /* ^-- header_type of previous line; -1 at para start */
     char *line;
     size_t length;
   
         cp = line;    if (centerP) { center_stream(stream, name); return; }
         while (*cp) {    while ((line=get_line(stream,&length)) != NULL) {
                 cp2 = word;      size_t np=indent_length(line, length);
                 wordl = 0;      /* LIZ@UOM 6/18/85 */      { HdrType header_type=hdr_NonHeader;
         if (grok_mail_headers && prev_header_type!=hdr_NonHeader) {
           if (np==0 && might_be_header(line))
             header_type = hdr_Header;
           else if (np>0 && prev_header_type>hdr_NonHeader)
             header_type = hdr_Continuation;
         }
         /* We need a new paragraph if and only if:
          *   this line is blank,
          *   OR it's a mail header,
          *   OR it's not a mail header AND the last line was one,
          *   OR the indentation has changed
          *      AND the line isn't a mail header continuation line
          *      AND this isn't the second line of an indented paragraph.
          */
         if ( length==0
              || header_type==hdr_Header
              || (header_type==hdr_NonHeader && prev_header_type>hdr_NonHeader)
              || (np!=last_indent
                  && header_type != hdr_Continuation
                  && (!allow_indented_paragraphs || para_line_number != 1)) ) {
           new_paragraph(output_in_paragraph ? last_indent : first_indent, np);
           para_line_number = 0;
           first_indent = np;
           last_indent = np;
           if (header_type==hdr_Header) last_indent=2;     /* for cont. lines */
           if (length==0) {
             putchar('\n');
             prev_header_type=hdr_ParagraphStart;
             continue;
           }
         }
         else {
           /* If this is an indented paragraph other than a mail header
            * continuation, set |last_indent|.
            */
           if (np != last_indent && header_type != hdr_Continuation)
             last_indent=np;
         }
         prev_header_type = header_type;
       }
   
                 /*      { size_t n=np;
                  * Collect a 'word,' allowing it to contain escaped white        while (n<length) {
                  * space.          /* Find word end and count spaces after it */
                  */          size_t word_length=0, space_length=0;
                 while (*cp && *cp != ' ') {          while (n+word_length < length && line[n+word_length] != ' ')
                         if (*cp == '\\' && isspace(cp[1]))            ++word_length;
                                 *cp2++ = *cp++, wordl++;          space_length = word_length;
                         *cp2++ = *cp++;          while (n+space_length < length && line[n+space_length] == ' ')
                         wordl++;/* LIZ@UOM 6/18/85 */            ++space_length;
                 }          /* Send the word to the output machinery. */
           output_word(first_indent, last_indent,
                 /*                      line+n, word_length, space_length-word_length);
                  * Guarantee a space at end of line. Two spaces after end of          n += space_length;
                  * sentence punctuation.        }
                  */      }
                 if (*cp == '\0') {      ++para_line_number;
                         *cp2++ = ' ';    }
                         if (strchr(".:!", cp[-1]))    new_paragraph(output_in_paragraph ? last_indent : first_indent, 0);
                                 *cp2++ = ' ';    if (ferror(stream)) { perror(name); ++n_errors; }
                 }  
                 while (*cp == ' ')  
                         *cp2++ = *cp++;  
                 *cp2 = '\0';  
                 /*  
                  * LIZ@UOM 6/18/85 pack(word);  
                  */  
                 pack(word, wordl);  
         }  
 }  }
   
 /*  /* How long is the indent on this line?
  * Output section.  
  * Build up line images from the words passed in.  Prefix  
  * each line with correct number of blanks.  The buffer "outbuf"  
  * contains the current partial line image, including prefixed blanks.  
  * "outp" points to the next available space therein.  When outp is NOSTR,  
  * there ain't nothing in there yet.  At the bottom of this whole mess,  
  * leading tabs are reinserted.  
  */   */
 static char *outbuf;                    /* Sandbagged output line image */  static size_t
 static int   obufsize;                  /* Size of outbuf */  indent_length(const char *line, size_t length) {
 static char *outp;                      /* Pointer in above */    size_t n=0;
     while (n<length && *line++ == ' ') ++n;
     return n;
   }
   
 /*  /* Might this line be a mail header?
  * Initialize the output section.   * We deem a line to be a possible header if it matches the
    * Perl regexp /^[A-Z][-A-Za-z0-9]*:\s/. This is *not* the same
    * as in RFC whatever-number-it-is; we want to be gratuitously
    * conservative to avoid mangling ordinary civilised text.
  */   */
 void  static int
 setout()  might_be_header(const char *line) {
 {    if (!isupper(*line++)) return 0;
         outp = NOSTR;    while (*line && (isalnum(*line) || *line=='-')) ++line;
     return (*line==':' && isspace(line[1]));
 }  }
   
 /*  /* Begin a new paragraph with an indent of |indent| spaces.
  * Pack a word onto the output line.  If this is the beginning of  
  * the line, push on the appropriately-sized string of blanks first.  
  * If the word won't fit on the current line, flush and begin a new  
  * line.  If the word is too long to fit all by itself on a line,  
  * just give it its own and hope for the best.  
  *  
  * LIZ@UOM 6/18/85 -- If the new word will fit in at less than the  
  *      goal length, take it.  If not, then check to see if the line  
  *      will be over the max length; if so put the word on the next  
  *      line.  If not, check to see if the line will be closer to the  
  *      goal length with or without the word and take it or put it on  
  *      the next line accordingly.  
  */   */
   static void
 /*  new_paragraph(size_t old_indent, size_t indent) {
  * LIZ@UOM 6/18/85 -- pass in the length of the word as well    if (x0) {
  * pack(word)      if (old_indent>0) output_indent(old_indent);
  *      char word[];      fwrite(output_buffer, 1, x0, stdout);
  */      putchar('\n');
 void    }
 pack(word, wl)    x=indent; x0=0; pending_spaces=0;
         char word[];    output_in_paragraph = 0;
         int wl;  
 {  
         register char *cp;  
         register int s, t;  
   
         if (outp == NOSTR)  
                 leadin();  
         /*  
          * LIZ@UOM 6/18/85 -- change condition to check goal_length; s is the  
          * length of the line before the word is added; t is now the length  
          * of the line after the word is added  
          *      t = strlen(word);  
          *      if (t+s <= LENGTH)  
          */  
         s = outp - outbuf;  
         t = wl + s;  
         if (t + 1 > obufsize) {  
                 outbuf = extstr(outbuf, &obufsize, t + 1);  
                 outp = outbuf + s;  
         }  
         if ((t <= goal_length) ||  
             ((t <= max_length) && (t - goal_length <= goal_length - s))) {  
                 /*  
                  * In like flint!  
                  */  
                 for (cp = word; *cp; *outp++ = *cp++)  
                         ;  
                 return;  
         }  
         if (s > pfx) {  
                 oflush();  
                 leadin();  
         }  
         for (cp = word; *cp; *outp++ = *cp++)  
                 ;  
 }  }
   
 /*  /* Output spaces or tabs for leading indentation.
  * If there is anything on the current output line, send it on  
  * its way.  Set outp to NOSTR to indicate the absence of the current  
  * line prefix.  
  */   */
 void  static void
 oflush()  output_indent(size_t n_spaces) {
 {    if (output_tab_width) {
         if (outp == NOSTR)      while (n_spaces >= output_tab_width) {
                 return;        putchar('\t');
         *outp = '\0';        n_spaces -= output_tab_width;
         tabulate(outbuf);      }
         outp = NOSTR;    }
     while (n_spaces-- > 0) putchar(' ');
 }  }
   
 /*  /* Output a single word, or add it to the buffer.
  * Take the passed line buffer, insert leading tabs where possible, and   * indent0 and indent1 are the indents to use on the first and subsequent
  * output on standard output (finally).   * lines of a paragraph. They'll often be the same, of course.
  */   */
 void  static void
 tabulate(line)  output_word(size_t indent0, size_t indent1, const char *word, size_t length, size_t spaces) {
         char line[];    size_t new_x = x+pending_spaces+length;
 {    size_t indent = output_in_paragraph ? indent1 : indent0;
         register char *cp;  
         register int b, t;  
   
         /*    /* If either |spaces==0| (at end of line) or |coalesce_spaces_P|
          * Toss trailing blanks in the output line.     * (squashing internal whitespace), then add just one space;
          */     * except that if the last character was a sentence-ender we
         cp = line + strlen(line) - 1;     * actually add two spaces.
         while (cp >= line && *cp == ' ')     */
                 cp--;    if (coalesce_spaces_P || spaces==0)
         *++cp = '\0';      spaces = strchr(sentence_enders, word[length-1]) ? 2 : 1;
   
         /*    if (new_x<=goal_length) {
          * Count the leading blank space and tabulate.      /* After adding the word we still aren't at the goal length,
          */       * so clearly we add it to the buffer rather than outputing it.
         for (cp = line; *cp == ' '; cp++)       */
                 ;      memset(output_buffer+x0, ' ', pending_spaces);
         b = cp-line;      x0 += pending_spaces; x += pending_spaces;
         t = b >> 3;      memcpy(output_buffer+x0, word, length);
         b &= 07;      x0 += length; x += length;
         if (t > 0)      pending_spaces = spaces;
                 do    }
                         putc('\t', stdout);    else {
                 while (--t);      /* Adding the word takes us past the goal. Print the line-so-far,
         if (b > 0)       * and the word too iff either (1) the lsf is empty or (2) that
                 do       * makes us nearer the goal but doesn't take us over the limit,
                         putc(' ', stdout);       * or (3) the word on its own takes us over the limit.
                 while (--b);       * In case (3) we put a newline in between.
         while (*cp)       */
                 putc(*cp++, stdout);      if (indent>0) output_indent(indent);
         putc('\n', stdout);      fwrite(output_buffer, 1, x0, stdout);
       if (x0==0 || (new_x <= max_length && new_x-goal_length <= goal_length-x)) {
         printf("%*s", pending_spaces, "");
         goto write_out_word;
       }
       else {
         /* If the word takes us over the limit on its own, just
          * spit it out and don't bother buffering it.
          */
         if (indent+length > max_length) {
           putchar('\n');
           if (indent>0) output_indent(indent);
   write_out_word:
           fwrite(word, 1, length, stdout);
           x0 = 0; x = indent1; pending_spaces = 0;
         }
         else {
           memcpy(output_buffer, word, length);
           x0 = length; x = length+indent1; pending_spaces = spaces;
         }
       }
       putchar('\n');
       output_in_paragraph = 1;
     }
 }  }
   
 /*  /* Process a stream, but just center its lines rather than trying to
  * Initialize the output line with the appropriate number of   * format them neatly.
  * leading blanks.  
  */   */
 void  static void
 leadin()  center_stream(FILE *stream, const char *name) {
 {    char *line;
         register int b;    size_t length;
         register char *cp;    while ((line=get_line(stream, &length)) != 0) {
       size_t l=length;
         if (obufsize == 0 || (outp != NULL && outp - outbuf <= pfx))      while (l>0 && isspace(*line)) { ++line; --l; }
                 outbuf = extstr(outbuf, &obufsize, pfx);      length=l;
         for (b = 0, cp = outbuf; b < pfx; b++)      while (l<goal_length) { putchar(' '); l+=2; }
                 *cp++ = ' ';      fwrite(line, 1, length, stdout);
         outp = cp;      putchar('\n');
     }
     if (ferror(stream)) { perror(name); ++n_errors; }
 }  }
   
 /*  /* Get a single line from a stream. Expand tabs, strip control
  * Save a string in dynamic space.   * characters and trailing whitespace, and handle backspaces.
  * This little goodie is needed for   * Return the address of the buffer containing the line, and
  * a headline detector in head.c   * put the length of the line in |lengthp|.
    * This can cope with arbitrarily long lines, and with lines
    * without terminating \n.
    * If there are no characters left or an error happens, we
    * return 0.
    * Don't confuse |spaces_pending| here with the global
    * |pending_spaces|.
  */   */
 char *  static char *
 savestr(str)  get_line(FILE *stream, size_t *lengthp) {
         char str[];    static char *buf=NULL;
 {    static size_t length=0;
         char *top;    size_t len=0;
     int ch;
     size_t spaces_pending=0;
   
         top = strdup(str);    if (buf==NULL) { length=100; buf=XMALLOC(length); }
         if (top == NOSTR)    while ((ch=getc(stream)) != '\n' && ch != EOF) {
                 errx(1, "Ran out of memory");      if (ch==' ') ++spaces_pending;
         return (top);      else if (isprint(ch)) {
         while (len+spaces_pending >= length) {
           length*=2; buf=xrealloc(buf, length);
         }
         while (spaces_pending > 0) { --spaces_pending; buf[len++]=' '; }
         buf[len++] = ch;
       }
       else if (ch=='\t')
         spaces_pending += tab_width - (len+spaces_pending)%tab_width;
       else if (ch=='\b') { if (len) --len; }
     }
     *lengthp=len;
     return (len>0 || ch!=EOF) ? buf : 0;
 }  }
   
 /*  /* (Re)allocate some memory, exiting with an error if we can't.
  * Is s1 a prefix of s2??  
  */   */
 int  static void *
 ispref(s1, s2)  xrealloc(void *ptr, size_t nbytes) {
         register char *s1, *s2;    void *p = realloc(ptr, nbytes);
 {    if (p == NULL) errx(EX_OSERR, "out of memory");
     return p;
         while (*s1++ == *s2)  
                 ;  
         return (*s1 == '\0');  
 }  
   
 inline char *  
 extstr(str, size, gsize)  
         char *str;  
         int *size;  
         int gsize;  
 {  
         do {  
                 *size += CHUNKSIZE;  
         } while (gsize && *size < gsize);  
   
         if ((str = realloc(str, *size)) == NULL)  
                 errx(1, "Ran out of memory");  
   
         return(str);  
 }  }

Legend:
Removed from v.1.9  
changed lines
  Added in v.1.10