home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_200 / 264_01 / fmt.c < prev    next >
Text File  |  1979-12-31  |  10KB  |  382 lines

  1. /* xc
  2. % cc -O fmt.c -o fmt
  3. % strip fmt
  4.  */
  5. /*
  6.  * fmt - simple text formatter
  7.  *
  8.  * Usage: fmt [-#] [file...]
  9.  *
  10.  * Flags:
  11.  * -#   set maximum line width to # (default 72)
  12.  *
  13.  * Fmt produces, from the concatenation of the input files, or from the
  14.  * standard input if no files are specified, standard output with each
  15.  * line as close as possible to width characters wide.  It preserves
  16.  * blank lines, the spaces at the beginnings of lines and between words,
  17.  * start of paragraph indentations, and dot command lines (used by many
  18.  * more complex text formatters).
  19.  * It is useful for formatting mail messages, and can be used to justify
  20.  * a paragraph from within vi(1) with the key sequence {!}fmt.
  21.  *
  22.  * This program is in the public domain.
  23.  * David MacKenzie
  24.  * 6522 Elgin Lane
  25.  * Bethesda, MD 20817
  26.  *
  27.  * Latest revision: 05/08/88
  28.  */
  29.  
  30. /* Define only one OS as nonzero. */
  31. #define UNIX 0
  32. #define MSDOS 1            /* Specifically, Aztec C. */
  33.  
  34. /* Define as nonzero for debugging. */
  35. #define DEBUG 0
  36. #define DEBUGFILE "debug.out"
  37.  
  38. #define DEF_WIDTH 72        /* Default formatted line width. */
  39. #define DEF_TWIDTH 8        /* Default width of tab stops. */
  40.  
  41. /* Characters which end sentences and are followed by two spaces. */
  42. #define SENT_ENDERS ".!?\""
  43.  
  44. /* The states that the formatter can be in, describing what's read. */
  45. #define STATE_INDENT 1
  46. #define STATE_WORD 2
  47. #define STATE_SPACE 3
  48.  
  49. #include <stdio.h>
  50.  
  51. #if MSDOS
  52. #define getc(f) agetc(f)
  53.  
  54. _main(argc, argv)
  55. #else
  56. main(argc, argv)
  57. #endif
  58.     int     argc;
  59.     char  **argv;
  60. {
  61.     void    fmt();
  62.  
  63. #if UNIX
  64.     char   *rindex();
  65.  
  66. #endif
  67.     char   *prog;        /* Base of name we were called with. */
  68.     int     lwid = DEF_WIDTH;    /* Output line width. */
  69.     int     twid = DEF_TWIDTH;    /* Assumed width of tabs. */
  70.     int     optind;        /* Loop index. */
  71.  
  72. #if UNIX
  73.     prog = rindex(argv[0], '/');
  74.     prog = prog ? prog + 1 : argv[0];
  75. #endif
  76. #if MSDOS
  77.     prog = "fmt";
  78. #endif
  79.  
  80.     for (optind = 1; optind < argc && *argv[optind] == '-'; ++optind)
  81.     if ((lwid = atoi(&argv[optind][1])) <= 0) {
  82.         fprintf(stderr, "%d: illegal line width\n", lwid);
  83.         exit(1);
  84.     }
  85.  
  86.     if (optind == argc)
  87.     fmt("-", lwid, twid);
  88.     else
  89.     for (; optind < argc; ++optind)
  90.         fmt(argv[optind], lwid, twid);
  91.  
  92.     exit(0);
  93. }
  94.  
  95. /*
  96.  * Send the formatted contents of file ("-" for stdin) to stdout.
  97.  * Except when reading an indent, we increment out_pos when characters
  98.  * are read into the pend_ buffers, not when they are printed.
  99.  * Whenever we're done with using the current value of a variable, we
  100.  * reset it.
  101.  * Terminology: "white characters" consist of spaces (' ' and '\t') and
  102.  * newline.  The '\r' character is treated, perhaps wrongly, as an
  103.  * ordinary nonwhite character.
  104.  */
  105.  
  106. void
  107. fmt(file, lwid, twid)
  108.     char   *file;
  109.     int     lwid;
  110.     int     twid;
  111. {
  112.     char   *malloc();
  113.  
  114. #if DEBUG
  115.     FILE   *errp;
  116.  
  117. #endif
  118.     FILE   *fp = stdin;        /* Input file pointer. */
  119.     int     c;            /* One character of input. */
  120.     int     last_nw;        /* Last nonwhite character read. */
  121.     int     out_pos;        /* Logical output column. */
  122.     int     pos_incr;        /* Temporary for tabs calculation. */
  123.     int     old_indent_pos;    /* For breaking at indented paragraphs. */
  124.     int     i;            /* Loop counter for printing strings. */
  125.     char    initial;        /* To prevent newline before first line. */
  126.     char    dot_line;        /* For preserving text formatter dot cmds. */
  127.     char    state;        /* Type of characters currently reading. */
  128.  
  129.     /*
  130.      * Instead of null terminating these strings, we keep length counts. That
  131.      * way we can add single characters to the ends of them easily. The _len
  132.      * variables count physical characters (string length); the _pos
  133.      * variables count screen length (adjusting for tab width). 
  134.      */
  135.     char   *indent;        /* Indentation. */
  136.     int     indent_len;
  137.     int     indent_pos;
  138.     char   *pend_spaces;    /* Buffered inter-word spaces/tabs. */
  139.     int     spaces_len;
  140.     int     spaces_pos;
  141.     char   *pend_word;        /* Buffered nonwhite characters. */
  142.     int     word_len;
  143.  
  144. #if DEBUG
  145.     errp = fopen(DEBUGFILE, "a");
  146.     fprintf(errp, "File: %s\n", file);
  147. #endif
  148.  
  149.     if (strcmp(file, "-") && !(fp = fopen(file, "r"))) {
  150.     perror(file);
  151.     exit(1);
  152.     }
  153.     if (!(indent = malloc(lwid)) ||
  154.     !(pend_spaces = malloc(lwid)) ||
  155.     !(pend_word = malloc(lwid))) {
  156.     perror("malloc");
  157.     exit(1);
  158.     }
  159.     /* Set up by pretending we just received "\n\n". */
  160.     out_pos = lwid;
  161.     old_indent_pos = 0;
  162.     indent_len = 0;
  163.     indent_pos = 0;
  164.     spaces_len = 0;
  165.     spaces_pos = 0;
  166.     word_len = 0;
  167.     initial = 1;
  168.     dot_line = 0;
  169.     state = STATE_INDENT;
  170.  
  171.     while ((c = getc(fp)) != EOF) {
  172.     switch (state) {
  173.     case STATE_INDENT:
  174. #if DEBUG
  175.         fprintf(errp,
  176.         "INDENT: out_pos=%d, dot_line=%d, indent_pos=%d, c=%c\n",
  177.         out_pos, dot_line, indent_pos, c);
  178. #endif
  179.         /*
  180.          * When in this state, we're reading the indentation at the start
  181.          * of an input line into indent, but we're not printing it. That
  182.          * only happens when the end of an *output* line is reached.
  183.          * Thus, this indent might never get printed if it's one of a
  184.          * string of short lines. 
  185.          */
  186.         switch (c) {
  187.         case ' ':
  188.         case '\t':
  189.         pos_incr = c == ' ' ? 1 : twid - indent_pos % twid;
  190.         /* If indent > max. line wid-1, ignore rest of it. */
  191.         if (indent_pos + pos_incr < lwid) {
  192.             indent[indent_len++] = c;
  193.             indent_pos += pos_incr;
  194.         }
  195.         break;
  196.         case '\n':
  197.         /*
  198.          * We were reading the indent, but got no printable
  199.          * characters before the newline.  We basically just read a
  200.          * blank line. 
  201.          */
  202.         /* Terminate previous partial line. */
  203.         if (spaces_len)
  204.             putchar('\n');
  205.         spaces_len = 0;
  206.         spaces_pos = 0;
  207.         putchar('\n');
  208.         initial = 1;
  209.         out_pos = lwid;
  210.         indent_len = 0;
  211.         indent_pos = 0;
  212.         break;
  213.         default:
  214.         /* First nonwhite character on this input line. */
  215.         /* If char ends sentence, add extra space. */
  216.         if (index(SENT_ENDERS, last_nw) && c >= 'A' && c <= 'Z') {
  217.             pend_spaces[spaces_len++] = ' ';
  218.             ++spaces_pos;
  219.             ++out_pos;
  220.         }
  221.         pend_word[word_len++] = c;
  222.         last_nw = c;
  223.         ++out_pos;
  224.         if (indent_pos > old_indent_pos)
  225.             /* New para. - force output line break at end of word. */
  226.             out_pos = lwid;
  227.         /* Preserve text formatter command lines (start with '.'). */
  228.         if (c == '.' && indent_pos == 0) {
  229.             out_pos = lwid;
  230.             dot_line = 1;
  231.         }
  232.         state = STATE_WORD;
  233.         break;
  234.         }
  235.         break;
  236.     case STATE_WORD:
  237. #if DEBUG
  238.         fprintf(errp,
  239.         "WORD:   out_pos=%d, dot_line=%d, indent_pos=%d, c=%c\n",
  240.         out_pos, dot_line, indent_pos, c);
  241. #endif
  242.         /*
  243.          * When in this state, we have read at least one nonwhite
  244.          * character and are buffering them in pend_word until we read a
  245.          * white character, at which point we can determine whether to
  246.          * break the output line. 
  247.          */
  248.         switch (c) {
  249.         case ' ':
  250.         case '\t':
  251.         case '\n':
  252.     white:
  253.         if (out_pos >= lwid) {
  254.             /* Wrap. */
  255.             if (initial)
  256.             initial = 0;
  257.             else
  258.             putchar('\n');
  259.             for (i = 0; i < indent_len; ++i)
  260.             putchar(indent[i]);
  261.             out_pos = indent_pos + word_len;
  262.         } else {
  263.             for (i = 0; i < spaces_len; ++i)
  264.             putchar(pend_spaces[i]);
  265.         }
  266.         spaces_len = 0;
  267.         spaces_pos = 0;
  268.         for (i = 0; i < word_len; ++i)
  269.             putchar(pend_word[i]);
  270.         word_len = 0;
  271.         if (c == '\n') {
  272.             /* If we read end of a dot cmd line, force line break. */
  273.             if (dot_line)
  274.             out_pos = lwid;
  275.             old_indent_pos = indent_pos;
  276.             indent_len = 0;
  277.             indent_pos = 0;
  278.             pend_spaces[spaces_len++] = ' ';
  279.             ++spaces_pos;
  280.             ++out_pos;
  281.             dot_line = 0;
  282.             state = STATE_INDENT;
  283.         } else {
  284.             pend_spaces[spaces_len++] = c;
  285.             pos_incr = c == ' ' ? 1 : twid - out_pos % twid;
  286.             spaces_pos += pos_incr;
  287.             out_pos += pos_incr;
  288.             state = STATE_SPACE;
  289.         }
  290.         break;
  291.         default:
  292.         /* If word is longer than line max., wrap it. */
  293.         if (word_len < lwid) {
  294.             pend_word[word_len++] = c;
  295.             last_nw = c;
  296.             ++out_pos;
  297.         } else {
  298.             ungetc(c, fp);
  299.             c = ' ';
  300.             goto white;    /* Ick! Ick! */
  301.         }
  302.         break;
  303.         }
  304.         break;
  305.     case STATE_SPACE:
  306. #if DEBUG
  307.         fprintf(errp,
  308.         "SPACE:  out_pos=%d, dot_line=%d, indent_pos=%d, c=%c\n",
  309.         out_pos, dot_line, indent_pos, c);
  310. #endif
  311.         /*
  312.          * When in this state, we have read at least one space or tab
  313.          * after having read at least one nonwhite character on the line,
  314.          * and are buffering them into pend_spaces. 
  315.          */
  316.         switch (c) {
  317.         case ' ':
  318.         case '\t':
  319.         pos_incr = c == ' ' ? 1 : twid - out_pos % twid;
  320.         /* If space length > max. line wid-1, ignore rest of it. */
  321.         if (spaces_pos + pos_incr < lwid) {
  322.             pend_spaces[spaces_len++] = c;
  323.             spaces_pos += pos_incr;
  324.             out_pos += pos_incr;
  325.         }
  326.         break;
  327.         case '\n':
  328.         /*
  329.          * These spaces were at the end of an input line.  Since it
  330.          * might not be the end of an output line, we ignore them in
  331.          * favor of this rule: if the last nonwhite character was one
  332.          * that ends sentences, we add two spaces, otherwise one. 
  333.          */
  334.         /* We read the end of a dot cmd line ending with spaces. */
  335.         if (dot_line)
  336.             out_pos = lwid;
  337.         old_indent_pos = indent_pos;
  338.         indent_len = 0;
  339.         indent_pos = 0;
  340.         spaces_len = 0;
  341.         spaces_pos = 0;
  342.         pend_spaces[spaces_len++] = ' ';
  343.         ++spaces_pos;
  344.         dot_line = 0;
  345.         state = STATE_INDENT;
  346.         break;
  347.         default:
  348.         pend_word[word_len++] = c;
  349.         last_nw = c;
  350.         ++out_pos;
  351.         state = STATE_WORD;
  352.         break;
  353.         }
  354.         break;
  355.     }
  356.     }
  357.     if (word_len) {
  358.     if (out_pos >= lwid) {
  359.         /* Wrap. */
  360.         putchar('\n');
  361.         for (i = 0; i < indent_len; ++i)
  362.         putchar(indent[i]);
  363.     } else {
  364.         for (i = 0; i < spaces_len; ++i)
  365.         putchar(pend_spaces[i]);
  366.     }
  367.     for (i = 0; i < word_len; ++i)
  368.         putchar(pend_word[i]);
  369.     }
  370.     /* Don't print newline if file ended with several newlines. */
  371.     if (word_len || spaces_len)
  372.     putchar('\n');
  373.     free(pend_word);
  374.     free(pend_spaces);
  375.     free(indent);
  376.     if (fp != stdin)
  377.     fclose(fp);
  378. #if DEBUG
  379.     fclose(errp);
  380. #endif
  381. }
  382.