[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.20 and 1.21

version 1.20, 2003/12/01 15:34:26 version 1.21, 2004/04/01 23:14:19
Line 196 
Line 196 
  * (returning 0 instead), but we do complain about bad numbers.   * (returning 0 instead), but we do complain about bad numbers.
  */   */
 static size_t  static size_t
 get_positive(const char *s, const char *err_mess, int fussyP) {  get_positive(const char *s, const char *err_mess, int fussyP)
   char *t;  {
   long result = strtol(s,&t,0);          char *t;
   if (*t) { if (fussyP) goto Lose; else return 0; }          long result = strtol(s, &t, 0);
   if (result<=0) { Lose: errx(EX_USAGE, "%s", err_mess); }  
   return (size_t) result;          if (*t) {
                   if (fussyP)
                           goto Lose;
                   else
                           return 0;
           }
           if (result <= 0) {
   Lose:
                   errx(EX_USAGE, "%s", err_mess);
           }
   
           return (size_t) result;
 }  }
   
 /* Global variables */  /* Global variables */
   
 static int centerP=0;           /* Try to center lines? */  static int centerP = 0;                         /* Try to center lines? */
 static size_t goal_length=0;    /* Target length for output lines */  static size_t goal_length = 0;                  /* Target length for output lines */
 static size_t max_length=0;     /* Maximum length for output lines */  static size_t max_length = 0;                   /* Maximum length for output lines */
 static int coalesce_spaces_P=0; /* Coalesce multiple whitespace -> ' ' ? */  static int coalesce_spaces_P = 0;               /* Coalesce multiple whitespace -> ' ' ? */
 static int allow_indented_paragraphs=0; /* Can first line have diff. ind.? */  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 tab_width = 8;                       /* Number of spaces per tab stop */
 static size_t output_tab_width=0;       /* Ditto, when squashing leading spaces */  static size_t output_tab_width = 0;             /* Ditto, when squashing leading spaces */
 static const char *sentence_enders=".?!";       /* Double-space after these */  static const char *sentence_enders = ".?!";     /* Double-space after these */
 static int grok_mail_headers=0; /* treat embedded mail headers magically? */  static int grok_mail_headers = 0;               /* treat embedded mail headers magically? */
 static int format_troff=0;      /* Format troff? */  static int format_troff = 0;                    /* Format troff? */
   
 static int n_errors=0;          /* Number of failed files. Return on exit. */  static int n_errors = 0;                        /* Number of failed files. Return on exit. */
 static char *output_buffer=0;   /* Output line will be built here */  static char *output_buffer = NULL;              /* Output line will be built here */
 static size_t x;                /* Horizontal position in output line */  static size_t x;                                /* Horizontal position in output line */
 static size_t x0;               /* Ditto, ignoring leading whitespace */  static size_t x0;                               /* Ditto, ignoring leading whitespace */
 static size_t pending_spaces;   /* Spaces to add before next word */  static size_t pending_spaces;                   /* Spaces to add before next word */
 static int output_in_paragraph=0;       /* Any of current para written out yet? */  static int output_in_paragraph = 0;             /* Any of current para written out yet? */
   
 /* Prototypes */  /* Prototypes */
   
 static void process_named_file(const char *);  static void     process_named_file(const char *);
 static void     process_stream(FILE *, const char *);  static void     process_stream(FILE *, const char *);
 static size_t    indent_length(const char *, size_t);  static size_t   indent_length(const char *, size_t);
 static int     might_be_header(const unsigned char *);  static int      might_be_header(const unsigned char *);
 static void      new_paragraph(size_t, size_t);  static void     new_paragraph(size_t, size_t);
 static void        output_word(size_t, size_t, const char *, size_t, size_t);  static void     output_word(size_t, size_t, const char *, size_t, size_t);
 static void      output_indent(size_t);  static void     output_indent(size_t);
 static void      center_stream(FILE *, const char *);  static void     center_stream(FILE *, const char *);
 static char *         get_line(FILE *, size_t *);  static char     *get_line(FILE *, size_t *);
 static void *         xrealloc(void *, size_t);  static void     *xrealloc(void *, size_t);
   void            usage(void);
   
 #define XMALLOC(x) xrealloc(0,x)  #define XMALLOC(x) xrealloc(0, x)
   
 /* Here is perhaps the right place to mention that this code is  /* Here is perhaps the right place to mention that this code is
  * all in top-down order. Hence, |main| comes first.   * all in top-down order. Hence, |main| comes first.
  */   */
 int  int
 main(int argc, char *argv[]) {  main(int argc, char *argv[])
   int ch;                       /* used for |getopt| processing */  {
           int ch;                 /* used for |getopt| processing */
   
           (void)setlocale(LC_CTYPE, "");
   
   (void)setlocale(LC_CTYPE, "");          /* 1. Grok parameters. */
           while ((ch = getopt(argc, argv, "0123456789cd:hl:mnpst:w:")) != -1) {
                   switch (ch) {
                   case 'c':
                           centerP = 1;
                           break;
                   case 'd':
                           sentence_enders = optarg;
                           break;
                   case 'l':
                           output_tab_width
                                   = get_positive(optarg, "output tab width must be positive", 1);
                           break;
                   case 'm':
                           grok_mail_headers = 1;
                           break;
                   case 'n':
                           format_troff = 1;
                           break;
                   case 'p':
                           allow_indented_paragraphs = 1;
                           break;
                   case 's':
                           coalesce_spaces_P = 1;
                           break;
                   case 't':
                           tab_width = get_positive(optarg, "tab width must be positive", 1);
                           break;
                   case 'w':
                           goal_length = get_positive(optarg, "width must be positive", 1);
                           max_length = goal_length;
                           break;
                   case '0': case '1': case '2': case '3': case '4': case '5':
                   case '6': case '7': case '8': case '9':
                           /* XXX  this is not a stylistically approved use of getopt() */
                           if (goal_length == 0) {
                                   char *p;
   
   /* 1. Grok parameters. */                                  p = argv[optind - 1];
                                   if (p[0] == '-' && p[1] == ch && !p[2])
                                           goal_length = get_positive(++p, "width must be nonzero", 1);
                                   else
                                           goal_length = get_positive(argv[optind]+1,
                                                           "width must be nonzero", 1);
                                   max_length = goal_length;
                           }
                           break;
                   case 'h':
                   default:
                           usage();
                           /* NOT REACHED */
                   }
           }
   
   while ((ch = getopt(argc, argv, "0123456789cd:hl:mnpst:w:")) != -1)          argc -= optind;
   switch(ch) {          argv += optind;
     case 'c':  
       centerP = 1;  
       continue;  
     case 'd':  
       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 'n':  
       format_troff = 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 'w':  
       goal_length = get_positive(optarg, "width must be positive", 1);  
       max_length = goal_length;  
       continue;  
     case '0': case '1': case '2': case '3': case '4': case '5':  
     case '6': case '7': case '8': case '9':  
     /* XXX  this is not a stylistically approved use of getopt() */  
       if (goal_length==0) {  
         char *p;  
         p = argv[optind - 1];  
         if (p[0] == '-' && p[1] == ch && !p[2])  
              goal_length = get_positive(++p, "width must be nonzero", 1);  
         else  
              goal_length = get_positive(argv[optind]+1,  
                  "width must be nonzero", 1);  
         max_length = goal_length;  
       }  
       continue;  
     case 'h': default:  
       fprintf(stderr,  
 "Usage:   fmt [-cmps] [-d chars] [-l num] [-t num]\n"  
 "             [-w width | -width | 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"  
 "         -n     format lines beginning with a dot\n"  
 "         -p     allow indented paragraphs\n"  
 "         -s     coalesce whitespace inside lines\n"  
 "         -t <n> have tabs every <n> columns\n"  
 "         -w <n> set maximum width to <n>\n"  
 "         goal   set target width to goal\n");  
       exit(ch=='h' ? 0 : EX_USAGE);  
   }  
   argc -= optind; argv += optind;  
   
   /* [ goal [ maximum ] ] */          /* [ goal [ maximum ] ] */
           if (argc > 0 && goal_length == 0 &&
               (goal_length = get_positive(*argv,"goal length must be positive", 0)) != 0) {
                   --argc;
                   ++argv;
                   if (argc > 0 && (max_length = get_positive(*argv,"max length must be positive", 0)) != 0) {
                           --argc;
                           ++argv;
                           if (max_length < goal_length)
                                   errx(EX_USAGE, "max length must be >= goal length");
                   }
           }
   
   if (argc>0 && goal_length==0          if (goal_length == 0)
       && (goal_length=get_positive(*argv,"goal length must be positive", 0))                  goal_length = 65;
          != 0) {          if (max_length == 0)
     --argc; ++argv;                  max_length = goal_length+10;
     if (argc>0          output_buffer = XMALLOC(max_length+1);  /* really needn't be longer */
         && (max_length=get_positive(*argv,"max length must be positive", 0))  
            != 0) {  
       --argc; ++argv;  
       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 */  
   
   /* 2. Process files. */          /* 2. Process files. */
   
   if (argc>0) {          if (argc > 0) {
     while (argc-->0) process_named_file(*argv++);                  while (argc-- > 0)
   }                          process_named_file(*argv++);
   else {          } else {
     process_stream(stdin, "standard input");                  process_stream(stdin, "standard input");
   }          }
   
   /* We're done. */          /* We're done. */
           return n_errors ? EX_NOINPUT : 0;
   
   return n_errors ? EX_NOINPUT : 0;  
   
 }  }
   
 /* Process a single file, given its name.  /* Process a single file, given its name.
  */   */
 static void  static void
 process_named_file(const char *name) {  process_named_file(const char *name)
   FILE *f=fopen(name, "r");  {
   if (!f) { perror(name); ++n_errors; }          FILE *f;
   else {  
     process_stream(f, name);          if ((f = fopen(name, "r")) == NULL) {
     fclose(f);                  perror(name);
   }                  ++n_errors;
           } else {
                   process_stream(f, name);
                   fclose(f);
           }
 }  }
   
 /* Types of mail header continuation lines:  /* Types of mail header continuation lines:
  */   */
 typedef enum {  typedef enum {
   hdr_ParagraphStart = -1,          hdr_ParagraphStart      = -1,
   hdr_NonHeader      = 0,          hdr_NonHeader           = 0,
   hdr_Header         = 1,          hdr_Header              = 1,
   hdr_Continuation   = 2          hdr_Continuation        = 2
 } HdrType;  } HdrType;
   
 /* Process a stream. This is where the real work happens,  /* Process a stream. This is where the real work happens,
  * except that centering is handled separately.   * except that centering is handled separately.
  */   */
 static void  static void
 process_stream(FILE *stream, const char *name) {  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 n;
   size_t first_indent=SILLY;    /* indentation of line 0 of paragraph */          size_t np;
   HdrType prev_header_type=hdr_ParagraphStart;          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;
           HdrType header_type;
   
         /* ^-- header_type of previous line; -1 at para start */          /* ^-- header_type of previous line; -1 at para start */
   char *line;          char *line;
   size_t length;          size_t length;
   
   if (centerP) { center_stream(stream, name); return; }          if (centerP) {
   while ((line=get_line(stream,&length)) != NULL) {                  center_stream(stream, name);
     size_t np=indent_length(line, length);                  return;
     { 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 troff request,  
        *   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  
            || (line[0]=='.' && !format_troff)  
            || 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;  
         /* nroff compatibility */  
         if (length>0 && line[0]=='.' && !format_troff) {  
           printf("%.*s\n", (int)length, line);  
           continue;  
         }  
         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;          while ((line = get_line(stream, &length)) != NULL) {
       while (n<length) {                  np = indent_length(line, length);
         /* Find word end and count spaces after it */                  header_type = hdr_NonHeader;
         size_t word_length=0, space_length=0;                  if (grok_mail_headers && prev_header_type != hdr_NonHeader) {
         while (n+word_length < length && line[n+word_length] != ' ')                          if (np == 0 && might_be_header(line))
           ++word_length;                                  header_type = hdr_Header;
         space_length = word_length;                          else if (np > 0 && prev_header_type>hdr_NonHeader)
         while (n+space_length < length && line[n+space_length] == ' ')                                  header_type = hdr_Continuation;
           ++space_length;                  }
         /* Send the word to the output machinery. */  
         output_word(first_indent, last_indent,                  /* We need a new paragraph if and only if:
                     line+n, word_length, space_length-word_length);                   *   this line is blank,
         n += space_length;                   *   OR it's a troff request,
       }                   *   OR it's a mail header,
     }                   *   OR it's not a mail header AND the last line was one,
     ++para_line_number;                   *   OR the indentation has changed
   }                   *      AND the line isn't a mail header continuation line
   new_paragraph(output_in_paragraph ? last_indent : first_indent, 0);                   *      AND this isn't the second line of an indented paragraph.
   if (ferror(stream)) { perror(name); ++n_errors; }                   */
                   if (length == 0 || (line[0] == '.' && !format_troff) ||
                       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;
   
                           /* nroff compatibility */
                           if (length > 0 && line[0] == '.' && !format_troff) {
                                   printf("%.*s\n", (int)length, line);
                                   continue;
                           }
                           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;
                   }
   
                   n = np;
                   while (n < length) {
                           /* Find word end and count spaces after it */
                           size_t word_length = 0, space_length = 0;
                           while (n+word_length < length && line[n+word_length] != ' ')
                                   ++word_length;
                           space_length = word_length;
                           while (n+space_length < length && line[n+space_length] == ' ')
                                   ++space_length;
                           /* Send the word to the output machinery. */
                           output_word(first_indent, last_indent,
                                   line+n, word_length, space_length-word_length);
                           n += space_length;
                   }
                   ++para_line_number;
           }
   
           new_paragraph(output_in_paragraph ? last_indent : first_indent, 0);
           if (ferror(stream)) {
                   perror(name);
                   ++n_errors;
           }
 }  }
   
 /* How long is the indent on this line?  /* How long is the indent on this line?
  */   */
 static size_t  static size_t
 indent_length(const char *line, size_t length) {  indent_length(const char *line, size_t length)
   size_t n=0;  {
   while (n<length && *line++ == ' ') ++n;          size_t n = 0;
   return n;  
           while (n < length && *line++ == ' ')
                   ++n;
           return n;
 }  }
   
 /* Might this line be a mail header?  /* Might this line be a mail header?
Line 470 
Line 492 
  * conservative to avoid mangling ordinary civilised text.   * conservative to avoid mangling ordinary civilised text.
  */   */
 static int  static int
 might_be_header(const unsigned char *line) {  might_be_header(const unsigned char *line)
   if (!isupper(*line++)) return 0;  {
   while (*line && (isalnum(*line) || *line=='-')) ++line;  
   return (*line==':' && isspace(line[1]));          if (!isupper(*line++))
                   return 0;
           while (*line && (isalnum(*line) || *line == '-'))
                   ++line;
           return (*line == ':' && isspace(line[1]));
 }  }
   
 /* Begin a new paragraph with an indent of |indent| spaces.  /* Begin a new paragraph with an indent of |indent| spaces.
  */   */
 static void  static void
 new_paragraph(size_t old_indent, size_t indent) {  new_paragraph(size_t old_indent, size_t indent)
   if (x0) {  {
     if (old_indent>0) output_indent(old_indent);  
     fwrite(output_buffer, 1, x0, stdout);          if (x0) {
     putchar('\n');                  if (old_indent > 0)
   }                          output_indent(old_indent);
   x=indent; x0=0; pending_spaces=0;                  fwrite(output_buffer, 1, x0, stdout);
   output_in_paragraph = 0;                  putchar('\n');
           }
           x = indent;
           x0 = 0;
           pending_spaces = 0;
           output_in_paragraph = 0;
 }  }
   
 /* Output spaces or tabs for leading indentation.  /* Output spaces or tabs for leading indentation.
  */   */
 static void  static void
 output_indent(size_t n_spaces) {  output_indent(size_t n_spaces)
   if (output_tab_width) {  {
     while (n_spaces >= output_tab_width) {  
       putchar('\t');          if (output_tab_width) {
       n_spaces -= output_tab_width;                  while (n_spaces >= output_tab_width) {
     }                          putchar('\t');
   }                          n_spaces -= output_tab_width;
   while (n_spaces-- > 0) putchar(' ');                  }
           }
           while (n_spaces-- > 0)
                   putchar(' ');
 }  }
   
 /* Output a single word, or add it to the buffer.  /* Output a single word, or add it to the buffer.
Line 507 
Line 541 
  * lines of a paragraph. They'll often be the same, of course.   * lines of a paragraph. They'll often be the same, of course.
  */   */
 static void  static void
 output_word(size_t indent0, size_t indent1, const char *word, size_t length, size_t spaces) {  output_word(size_t indent0, size_t indent1, const char *word, size_t length, size_t spaces)
   size_t new_x = x+pending_spaces+length;  {
   size_t indent = output_in_paragraph ? indent1 : indent0;          size_t new_x = x + pending_spaces + length;
           size_t indent = output_in_paragraph ? indent1 : indent0;
   
   /* If either |spaces==0| (at end of line) or |coalesce_spaces_P|          /* If either |spaces==0| (at end of line) or |coalesce_spaces_P|
    * (squashing internal whitespace), then add just one space;           * (squashing internal whitespace), then add just one space;
    * except that if the last character was a sentence-ender we           * except that if the last character was a sentence-ender we
    * actually add two spaces.           * actually add two spaces.
    */           */
   if (coalesce_spaces_P || spaces==0)          if (coalesce_spaces_P || spaces == 0)
     spaces = strchr(sentence_enders, word[length-1]) ? 2 : 1;                  spaces = strchr(sentence_enders, word[length-1]) ? 2 : 1;
   
   if (new_x<=goal_length) {          if (new_x <= goal_length) {
     /* After adding the word we still aren't at the goal length,                  /* After adding the word we still aren't at the goal length,
      * so clearly we add it to the buffer rather than outputing it.                   * so clearly we add it to the buffer rather than outputing it.
      */                   */
     memset(output_buffer+x0, ' ', pending_spaces);                  memset(output_buffer+x0, ' ', pending_spaces);
     x0 += pending_spaces; x += pending_spaces;                  x0 += pending_spaces;
     memcpy(output_buffer+x0, word, length);                  x += pending_spaces;
     x0 += length; x += length;                  memcpy(output_buffer+x0, word, length);
     pending_spaces = spaces;                  x0 += length;
   }                  x += length;
   else {                  pending_spaces = spaces;
     /* Adding the word takes us past the goal. Print the line-so-far,          } else {
      * and the word too iff either (1) the lsf is empty or (2) that                  /* Adding the word takes us past the goal. Print the line-so-far,
      * makes us nearer the goal but doesn't take us over the limit,                   * and the word too iff either (1) the lsf is empty or (2) that
      * or (3) the word on its own takes us over the limit.                   * makes us nearer the goal but doesn't take us over the limit,
      * In case (3) we put a newline in between.                   * or (3) the word on its own takes us over the limit.
      */                   * In case (3) we put a newline in between.
     if (indent>0) output_indent(indent);                   */
     fwrite(output_buffer, 1, x0, stdout);                  if (indent > 0)
     if (x0==0 || (new_x <= max_length && new_x-goal_length <= goal_length-x)) {                          output_indent(indent);
       printf("%*s", (int)pending_spaces, "");                  fwrite(output_buffer, 1, x0, stdout);
       goto write_out_word;                  if (x0 == 0 || (new_x <= max_length && new_x-goal_length <= goal_length-x)) {
     }                          printf("%*s", (int)pending_spaces, "");
     else {                          goto write_out_word;
       /* If the word takes us over the limit on its own, just                  } else {
        * spit it out and don't bother buffering it.                          /* 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+length > max_length) {
         if (indent>0) output_indent(indent);                                  putchar('\n');
                                   if (indent > 0)
                                           output_indent(indent);
 write_out_word:  write_out_word:
         fwrite(word, 1, length, stdout);                                  fwrite(word, 1, length, stdout);
         x0 = 0; x = indent1; pending_spaces = 0;                                  x0 = 0;
       }                                  x = indent1;
       else {                                  pending_spaces = 0;
         memcpy(output_buffer, word, length);                          } else {
         x0 = length; x = length+indent1; pending_spaces = spaces;                                  memcpy(output_buffer, word, length);
       }                                  x0 = length;
     }                                  x = length+indent1;
     putchar('\n');                                  pending_spaces = spaces;
     output_in_paragraph = 1;                          }
   }                  }
   
                   putchar('\n');
                   output_in_paragraph = 1;
           }
 }  }
   
 /* Process a stream, but just center its lines rather than trying to  /* Process a stream, but just center its lines rather than trying to
  * format them neatly.   * format them neatly.
  */   */
 static void  static void
 center_stream(FILE *stream, const char *name) {  center_stream(FILE *stream, const char *name)
   char *line;  {
   size_t length;          char *line;
   while ((line=get_line(stream, &length)) != 0) {          size_t length;
     size_t l=length;          size_t l;
     while (l>0 && isspace(*line)) { ++line; --l; }  
     length=l;          while ((line = get_line(stream, &length)) != 0) {
     while (l<goal_length) { putchar(' '); l+=2; }                  l = length;
     fwrite(line, 1, length, stdout);                  while (l > 0 && isspace(*line)) {
     putchar('\n');                          ++line;
   }                          --l;
   if (ferror(stream)) { perror(name); ++n_errors; }                  }
   
                   length = l;
   
                   while (l < goal_length) {
                           putchar(' ');
                           l += 2;
                   }
   
                   fwrite(line, 1, length, stdout);
                   putchar('\n');
           }
   
           if (ferror(stream)) {
                   perror(name);
                   ++n_errors;
           }
 }  }
   
 /* Get a single line from a stream. Expand tabs, strip control  /* Get a single line from a stream. Expand tabs, strip control
Line 593 
Line 650 
  * |pending_spaces|.   * |pending_spaces|.
  */   */
 static char *  static char *
 get_line(FILE *stream, size_t *lengthp) {  get_line(FILE *stream, size_t *lengthp)
   static char *buf=NULL;  {
   static size_t length=0;          int ch;
   size_t len=0;          int troff = 0;
   int ch;          static char *buf = NULL;
   size_t spaces_pending=0;          static size_t length = 0;
   int troff=0;          size_t len = 0;
           size_t spaces_pending = 0;
   
   if (buf==NULL) { length=100; buf=XMALLOC(length); }          if (buf == NULL) {
   while ((ch=getc(stream)) != '\n' && ch != EOF) {                  length = 100;
     if (len+spaces_pending==0 && ch=='.' && !format_troff) troff=1;                  buf = XMALLOC(length);
     if (ch==' ') ++spaces_pending;          }
     else if (troff || !iscntrl(ch)) {  
       while (len+spaces_pending >= length) {          while ((ch = getc(stream)) != '\n' && ch != EOF) {
         length*=2; buf=xrealloc(buf, length);                  if ((len + spaces_pending == 0) && (ch == '.' && !format_troff))
       }                          troff = 1;
       while (spaces_pending > 0) { --spaces_pending; buf[len++]=' '; }                  if (ch == ' ') {
       buf[len++] = ch;                          ++spaces_pending;
     }                  } else if (troff || !iscntrl(ch)) {
     else if (ch=='\t')                          while (len + spaces_pending >= length) {
       spaces_pending += tab_width - (len+spaces_pending)%tab_width;                                  length *= 2;
     else if (ch=='\b') { if (len) --len; }                                  buf = xrealloc(buf, length);
   }                          }
   *lengthp=len;  
   return (len>0 || ch!=EOF) ? buf : 0;                          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.  /* (Re)allocate some memory, exiting with an error if we can't.
  */   */
 static void *  static void *
 xrealloc(void *ptr, size_t nbytes) {  xrealloc(void *ptr, size_t nbytes)
   void *p = realloc(ptr, nbytes);  {
   if (p == NULL) errx(EX_OSERR, "out of memory");          void *p;
   return p;  
           p  = realloc(ptr, nbytes);
           if (p == NULL)
                   errx(EX_OSERR, "out of memory");
           return p;
 }  }
   
   void
   usage(void)
   {
   
           fprintf(stderr,
                   "Usage:   fmt [-cmps] [-d chars] [-l num] [-t num]\n"
                   "             [-w width | -width | 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"
                   "         -n     format lines beginning with a dot\n"
                   "         -p     allow indented paragraphs\n"
                   "         -s     coalesce whitespace inside lines\n"
                   "         -t <n> have tabs every <n> columns\n"
                   "         -w <n> set maximum width to <n>\n"
                   "         goal   set target width to goal\n");
           exit (0);
   }
   

Legend:
Removed from v.1.20  
changed lines
  Added in v.1.21