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

Annotation of src/usr.bin/fmt/fmt.c, Revision 1.10

1.10    ! millert     1: /*     $OpenBSD$       */
1.1       deraadt     2:
1.10    ! millert     3: /* Sensible version of fmt
        !             4:  *
        !             5:  * Syntax: fmt [ options ] [ goal [ max ] ] [ filename ... ]
        !             6:  *
        !             7:  * Since the documentation for the original fmt is so poor, here
        !             8:  * is an accurate description of what this one does. It's usually
        !             9:  * the same. The *mechanism* used may differ from that suggested
        !            10:  * here. Note that we are *not* entirely compatible with fmt,
        !            11:  * because fmt gets so many things wrong.
        !            12:  *
        !            13:  * 1. Tabs are expanded, assuming 8-space tab stops.
        !            14:  *    If the `-t <n>' option is given, we assume <n>-space
        !            15:  *    tab stops instead.
        !            16:  *    Trailing blanks are removed from all lines.
        !            17:  *    x\b == nothing, for any x other than \b.
        !            18:  *    Other control characters are simply stripped. This
        !            19:  *    includes \r.
        !            20:  * 2. Each line is split into leading whitespace and
        !            21:  *    everything else. Maximal consecutive sequences of
        !            22:  *    lines with the same leading whitespace are considered
        !            23:  *    to form paragraphs, except that a blank line is always
        !            24:  *    a paragraph to itself.
        !            25:  *    If the `-p' option is given then the first line of a
        !            26:  *    paragraph is permitted to have indentation different
        !            27:  *    from that of the other lines.
        !            28:  *    If the `-m' option is given then a line that looks
        !            29:  *    like a mail message header, if it is not immediately
        !            30:  *    preceded by a non-blank non-message-header line, is
        !            31:  *    taken to start a new paragraph, which also contains
        !            32:  *    any subsequent lines with non-empty leading whitespace.
        !            33:  * 3. The "everything else" is split into words; a word
        !            34:  *    includes its trailing whitespace, and a word at the
        !            35:  *    end of a line is deemed to be followed by a single
        !            36:  *    space, or two spaces if it ends with a sentence-end
        !            37:  *    character. (See the `-d' option for how to change that.)
        !            38:  *    If the `-s' option has been given, then a word's trailing
        !            39:  *    whitespace is replaced by what it would have had if it
        !            40:  *    had occurred at end of line.
        !            41:  * 4. Each paragraph is sent to standard output as follows.
        !            42:  *    We output the leading whitespace, and then enough words
        !            43:  *    to make the line length as near as possible to the goal
        !            44:  *    without exceeding the maximum. (If a single word would
        !            45:  *    exceed the maximum, we output that anyway.) Of course
        !            46:  *    the trailing whitespace of the last word is ignored.
        !            47:  *    We then emit a newline and start again if there are any
        !            48:  *    words left.
        !            49:  *    Note that for a blank line this translates as "We emit
        !            50:  *    a newline".
        !            51:  *    If the `-l <n>' option is given, then leading whitespace
        !            52:  *    is modified slightly: <n> spaces are replaced by a tab.
        !            53:  *    Indented paragraphs (see above under `-p') make matters
        !            54:  *    more complicated than this suggests. Actually every paragraph
        !            55:  *    has two `leading whitespace' values; the value for the first
        !            56:  *    line, and the value for the most recent line. (While processing
        !            57:  *    the first line, the two are equal. When `-p' has not been
        !            58:  *    given, they are always equal.) The leading whitespace
        !            59:  *    actually output is that of the first line (for the first
        !            60:  *    line of *output*) or that of the most recent line (for
        !            61:  *    all other lines of output).
        !            62:  *    When `-m' has been given, message header paragraphs are
        !            63:  *    taken as having first-leading-whitespace empty and
        !            64:  *    subsequent-leading-whitespace two spaces.
        !            65:  *
        !            66:  * Multiple input files are formatted one at a time, so that a file
        !            67:  * never ends in the middle of a line.
        !            68:  *
        !            69:  * There's an alternative mode of operation, invoked by giving
        !            70:  * the `-c' option. In that case we just center every line,
        !            71:  * and most of the other options are ignored. This should
        !            72:  * really be in a separate program, but we must stay compatible
        !            73:  * with old `fmt'.
        !            74:  *
        !            75:  * QUERY: Should `-m' also try to do the right thing with quoted text?
        !            76:  * QUERY: `-b' to treat backslashed whitespace as old `fmt' does?
        !            77:  * QUERY: Option meaning `never join lines'?
        !            78:  * QUERY: Option meaning `split in mid-word to avoid overlong lines'?
        !            79:  * (Those last two might not be useful, since we have `fold'.)
        !            80:  *
        !            81:  * Differences from old `fmt':
        !            82:  *
        !            83:  *   - We have many more options. Options that aren't understood
        !            84:  *     generate a lengthy usage message, rather than being
        !            85:  *     treated as filenames.
        !            86:  *   - Even with `-m', our handling of message headers is
        !            87:  *     significantly different. (And much better.)
        !            88:  *   - We don't treat `\ ' as non-word-breaking.
        !            89:  *   - Downward changes of indentation start new paragraphs
        !            90:  *     for us, as well as upward. (I think old `fmt' behaves
        !            91:  *     in the way it does in order to allow indented paragraphs,
        !            92:  *     but this is a broken way of making indented paragraphs
        !            93:  *     behave right.)
        !            94:  *   - Given the choice of going over or under |goal_length|
        !            95:  *     by the same amount, we go over; old `fmt' goes under.
        !            96:  *   - We treat `?' as ending a sentence, and not `:'. Old `fmt'
        !            97:  *     does the reverse.
        !            98:  *   - We return approved return codes. Old `fmt' returns
        !            99:  *     1 for some errors, and *the number of unopenable files*
        !           100:  *     when that was all that went wrong.
        !           101:  *   - We have fewer crashes and more helpful error messages.
        !           102:  *   - We don't turn spaces into tabs at starts of lines unless
        !           103:  *     specifically requested.
        !           104:  *   - New `fmt' is somewhat smaller and slightly faster than
        !           105:  *     old `fmt'.
        !           106:  *
        !           107:  * Bugs:
        !           108:  *
        !           109:  *   None known. There probably are some, though.
        !           110:  *
        !           111:  * Portability:
        !           112:  *
        !           113:  *   I believe this code to be pretty portable. It does require
        !           114:  *   that you have `getopt'. If you need to include "getopt.h"
        !           115:  *   for this (e.g., if your system didn't come with `getopt'
        !           116:  *   and you installed it yourself) then you should arrange for
        !           117:  *   NEED_getopt_h to be #defined.
        !           118:  *
        !           119:  *   Everything here should work OK even on nasty 16-bit
        !           120:  *   machines and nice 64-bit ones. However, it's only really
        !           121:  *   been tested on my FreeBSD machine. Your mileage may vary.
        !           122:  */
        !           123:
        !           124: /* Copyright (c) 1997 Gareth McCaughan. All rights reserved.
        !           125:  *
        !           126:  * Redistribution and use of this code, in source or binary forms,
        !           127:  * with or without modification, are permitted subject to the following
        !           128:  * conditions:
        !           129:  *
        !           130:  *  - Redistribution of source code must retain the above copyright
1.1       deraadt   131:  *    notice, this list of conditions and the following disclaimer.
1.10    ! millert   132:  *
        !           133:  *  - If you distribute modified source code it must also include
        !           134:  *    a notice saying that it has been modified, and giving a brief
        !           135:  *    description of what changes have been made.
        !           136:  *
        !           137:  * Disclaimer: I am not responsible for the results of using this code.
        !           138:  *             If it formats your hard disc, sends obscene messages to
        !           139:  *             your boss and kills your children then that's your problem
        !           140:  *             not mine. I give absolutely no warranty of any sort as to
        !           141:  *             what the program will do, and absolutely refuse to be held
        !           142:  *             liable for any consequences of your using it.
        !           143:  *             Thank you. Have a nice day.
        !           144:  */
        !           145:
        !           146: /* RCS change log:
        !           147:  * Revision 1.5  1998/03/02 18:02:21  gjm11
        !           148:  * Minor changes for portability.
        !           149:  *
        !           150:  * Revision 1.4  1997/10/01 11:51:28  gjm11
        !           151:  * Repair broken indented-paragraph handling.
        !           152:  * Add mail message header stuff.
        !           153:  * Improve comments and layout.
        !           154:  * Make usable with non-BSD systems.
        !           155:  * Add revision display to usage message.
        !           156:  *
        !           157:  * Revision 1.3  1997/09/30 16:24:47  gjm11
        !           158:  * Add copyright notice, rcsid string and log message.
        !           159:  *
        !           160:  * Revision 1.2  1997/09/30 16:13:39  gjm11
        !           161:  * Add options: -d <chars>, -l <width>, -p, -s, -t <width>, -h .
        !           162:  * Parse options with `getopt'. Clean up code generally.
        !           163:  * Make comments more accurate.
        !           164:  *
        !           165:  * Revision 1.1  1997/09/30 11:29:57  gjm11
        !           166:  * Initial revision
1.1       deraadt   167:  */
                    168:
                    169: #ifndef lint
1.10    ! millert   170: static const char rcsid[] =
        !           171:   "$OpenBSD: fmt.c,v 1.5 1998/03/02 18:02:21 gjm11 Exp $";
        !           172: static const char copyright[] =
        !           173:   "Copyright (c) 1997 Gareth McCaughan. All rights reserved.\n";
1.1       deraadt   174: #endif /* not lint */
                    175:
1.10    ! millert   176: /* Cater for BSD and non-BSD systems.
        !           177:  * I hate the C preprocessor.
        !           178:  */
        !           179:
        !           180: #undef HAVE_errx
        !           181: #undef HAVE_sysexits
        !           182:
        !           183: #ifdef unix
        !           184: # include <sys/param.h>
        !           185: # ifdef BSD
        !           186: #  define HAVE_errx
        !           187: #  if BSD >= 199306
        !           188: #   define HAVE_sysexits
        !           189: #  endif
        !           190: # endif
        !           191: #endif
        !           192:
        !           193: #ifdef HAVE_errx
        !           194: # include <err.h>
        !           195: #else
        !           196: # define errx(rc,str) { fprintf(stderr,"fmt: %s\n",str); exit(rc); }
        !           197: #endif
        !           198:
        !           199: #ifdef HAVE_sysexits
        !           200: # include <sysexits.h>
1.4       millert   201: #else
1.10    ! millert   202: # define EX_USAGE 1
        !           203: # define EX_NOINPUT 1
        !           204: # define EX_SOFTWARE 1
        !           205: # define EX_OSERR 1
1.1       deraadt   206: #endif
                    207:
1.4       millert   208: #include <ctype.h>
1.1       deraadt   209: #include <stdio.h>
                    210: #include <stdlib.h>
                    211: #include <string.h>
                    212:
1.10    ! millert   213: #ifdef NEED_getopt_h
        !           214: # include "getopt.h"
        !           215: #endif
        !           216:
        !           217: /* Something that, we hope, will never be a genuine line length,
        !           218:  * indentation etc.
1.1       deraadt   219:  */
1.10    ! millert   220: #define SILLY ((size_t)-1)
1.1       deraadt   221:
1.10    ! millert   222: /* I used to use |strtoul| for this, but (1) not all systems have it
        !           223:  * and (2) it's probably better to use |strtol| to detect negative
        !           224:  * numbers better.
        !           225:  * If |fussyp==0| then we don't complain about non-numbers
        !           226:  * (returning 0 instead), but we do complain about bad numbers.
        !           227:  */
        !           228: size_t get_positive(const char *s, const char *err_mess, int fussyP) {
        !           229:   char *t;
        !           230:   long result = strtol(s,&t,0);
        !           231:   if (*t) { if (fussyP) goto Lose; else return 0; }
        !           232:   if (result<=0) { Lose: errx(EX_USAGE, err_mess); }
        !           233:   return (size_t) result;
1.1       deraadt   234: }
                    235:
1.10    ! millert   236: /* Just for the sake of linguistic purity: */
        !           237:
        !           238: #ifdef BRITISH
        !           239: # define CENTER "centre"
        !           240: #else
        !           241: # define CENTER "center"
        !           242: #endif
        !           243:
        !           244: /* Global variables */
        !           245:
        !           246: static int centerP=0;          /* Try to center lines? */
        !           247: static size_t goal_length=0;   /* Target length for output lines */
        !           248: static size_t max_length=0;    /* Maximum length for output lines */
        !           249: static int coalesce_spaces_P=0;        /* Coalesce multiple whitespace -> ' ' ? */
        !           250: static int allow_indented_paragraphs=0;        /* Can first line have diff. ind.? */
        !           251: static int tab_width=8;                /* Number of spaces per tab stop */
        !           252: static int output_tab_width=0; /* Ditto, when squashing leading spaces */
        !           253: static char *sentence_enders=".?!";    /* Double-space after these */
        !           254: static int grok_mail_headers=0;        /* treat embedded mail headers magically? */
        !           255:
        !           256: static int n_errors=0;         /* Number of failed files. Return on exit. */
        !           257: static char *output_buffer=0;  /* Output line will be built here */
        !           258: static size_t x;               /* Horizontal position in output line */
        !           259: static size_t x0;              /* Ditto, ignoring leading whitespace */
        !           260: static size_t pending_spaces;  /* Spaces to add before next word */
        !           261: static int output_in_paragraph=0;      /* Any of current para written out yet? */
        !           262:
        !           263: /* Prototypes */
        !           264:
        !           265: static void process_named_file (const char *);
        !           266: static void     process_stream (FILE *, const char *);
        !           267: static size_t    indent_length (const char *, size_t);
        !           268: static int     might_be_header (const char *);
        !           269: static void      new_paragraph (size_t, size_t);
        !           270: static void        output_word (size_t, size_t, const char *, size_t, size_t);
        !           271: static void      output_indent (size_t);
        !           272: static void      center_stream (FILE *, const char *);
        !           273: static char *         get_line (FILE *, size_t *);
        !           274: static void *         xrealloc (void *, size_t);
        !           275:
        !           276: #define XMALLOC(x) xrealloc(0,x)
        !           277:
        !           278: /* Here is perhaps the right place to mention that this code is
        !           279:  * all in top-down order. Hence, |main| comes first.
1.1       deraadt   280:  */
1.4       millert   281: int
1.10    ! millert   282: main(int argc, char *argv[]) {
        !           283:   int ch;                      /* used for |getopt| processing */
        !           284:
        !           285:   /* 1. Grok parameters. */
        !           286:
        !           287:   while ((ch = getopt(argc, argv, "cd:hl:mpst:")) != EOF) switch(ch) {
        !           288:     case 'c':
        !           289:       centerP = 1;
        !           290:       continue;
        !           291:     case 'd':
        !           292:       sentence_enders = XMALLOC(strlen(optarg)+1);
        !           293:       strcpy(sentence_enders, optarg);
        !           294:       continue;
        !           295:     case 'l':
        !           296:       output_tab_width
        !           297:         = get_positive(optarg, "output tab width must be positive", 1);
        !           298:       continue;
        !           299:     case 'm':
        !           300:       grok_mail_headers = 1;
        !           301:       continue;
        !           302:     case 'p':
        !           303:       allow_indented_paragraphs = 1;
        !           304:       continue;
        !           305:     case 's':
        !           306:       coalesce_spaces_P = 1;
        !           307:       continue;
        !           308:     case 't':
        !           309:       tab_width = get_positive(optarg, "tab width must be positive", 1);
        !           310:       continue;
        !           311:     case 'h': default:
        !           312:       fprintf(stderr,
        !           313: "Usage:   fmt [-cmps] [-d chars] [-l num] [-t num] [goal [maximum]] [file...]\n"
        !           314: "Options: -c     " CENTER " each line instead of formatting\n"
        !           315: "         -d <chars> double-space after <chars> at line end\n"
        !           316: "         -l <n> turn each <n> spaces at start of line into a tab\n"
        !           317: "         -m     try to make sure mail header lines stay separate\n"
        !           318: "         -p     allow indented paragraphs\n"
        !           319: "         -s     coalesce whitespace inside lines\n"
        !           320: "         -t <n> have tabs every <n> columns\n");
        !           321:       exit(ch=='h' ? 0 : EX_USAGE);
        !           322:   }
        !           323:   argc -= optind; argv += optind;
        !           324:
        !           325:   /* [ goal [ maximum ] ] */
        !           326:
        !           327:   if (argc>0
        !           328:       && (goal_length=get_positive(*argv,"goal length must be positive", 0))
        !           329:          != 0) {
        !           330:     --argc; ++argv;
        !           331:     if (argc>0
        !           332:         && (goal_length=get_positive(*argv,"max length must be positive", 0))
        !           333:            != 0) {
        !           334:       if (max_length<goal_length)
        !           335:         errx(EX_USAGE, "max length must be >= goal length");
        !           336:     }
        !           337:   }
        !           338:   if (goal_length==0) goal_length = 65;
        !           339:   if (max_length==0) max_length = goal_length+10;
        !           340:   output_buffer = XMALLOC(max_length+1);       /* really needn't be longer */
        !           341:
        !           342:   /* 2. Process files. */
        !           343:
        !           344:   if (argc>0) {
        !           345:     while (argc-->0) process_named_file(*argv++);
        !           346:   }
        !           347:   else {
        !           348:     process_stream(stdin, "standard input");
        !           349:   }
        !           350:
        !           351:   /* We're done. */
        !           352:
        !           353:   return n_errors ? EX_NOINPUT : 0;
        !           354:
        !           355: }
        !           356:
        !           357: /* Process a single file, given its name.
        !           358:  */
        !           359: static void
        !           360: process_named_file(const char *name) {
        !           361:   FILE *f=fopen(name, "r");
        !           362:   if (!f) { perror(name); ++n_errors; }
        !           363:   else {
        !           364:     process_stream(f, name);
        !           365:     fclose(f);
        !           366:   }
        !           367: }
        !           368:
        !           369: /* Types of mail header continuation lines:
        !           370:  */
        !           371: typedef enum {
        !           372:   hdr_ParagraphStart = -1,
        !           373:   hdr_NonHeader      = 0,
        !           374:   hdr_Header         = 1,
        !           375:   hdr_Continuation   = 2
        !           376: } HdrType;
        !           377:
        !           378: /* Process a stream. This is where the real work happens,
        !           379:  * except that centering is handled separately.
        !           380:  */
        !           381: static void
        !           382: process_stream(FILE *stream, const char *name) {
        !           383:   size_t last_indent=SILLY;    /* how many spaces in last indent? */
        !           384:   size_t para_line_number=0;   /* how many lines already read in this para? */
        !           385:   size_t first_indent=SILLY;   /* indentation of line 0 of paragraph */
        !           386:   HdrType prev_header_type=hdr_ParagraphStart;
        !           387:        /* ^-- header_type of previous line; -1 at para start */
        !           388:   char *line;
        !           389:   size_t length;
        !           390:
        !           391:   if (centerP) { center_stream(stream, name); return; }
        !           392:   while ((line=get_line(stream,&length)) != NULL) {
        !           393:     size_t np=indent_length(line, length);
        !           394:     { HdrType header_type=hdr_NonHeader;
        !           395:       if (grok_mail_headers && prev_header_type!=hdr_NonHeader) {
        !           396:         if (np==0 && might_be_header(line))
        !           397:           header_type = hdr_Header;
        !           398:         else if (np>0 && prev_header_type>hdr_NonHeader)
        !           399:           header_type = hdr_Continuation;
        !           400:       }
        !           401:       /* We need a new paragraph if and only if:
        !           402:        *   this line is blank,
        !           403:        *   OR it's a mail header,
        !           404:        *   OR it's not a mail header AND the last line was one,
        !           405:        *   OR the indentation has changed
        !           406:        *      AND the line isn't a mail header continuation line
        !           407:        *      AND this isn't the second line of an indented paragraph.
        !           408:        */
        !           409:       if ( length==0
        !           410:            || header_type==hdr_Header
        !           411:            || (header_type==hdr_NonHeader && prev_header_type>hdr_NonHeader)
        !           412:            || (np!=last_indent
        !           413:                && header_type != hdr_Continuation
        !           414:                && (!allow_indented_paragraphs || para_line_number != 1)) ) {
        !           415:         new_paragraph(output_in_paragraph ? last_indent : first_indent, np);
        !           416:         para_line_number = 0;
        !           417:         first_indent = np;
        !           418:         last_indent = np;
        !           419:         if (header_type==hdr_Header) last_indent=2;    /* for cont. lines */
        !           420:         if (length==0) {
        !           421:           putchar('\n');
        !           422:           prev_header_type=hdr_ParagraphStart;
        !           423:           continue;
        !           424:         }
        !           425:       }
        !           426:       else {
        !           427:         /* If this is an indented paragraph other than a mail header
        !           428:          * continuation, set |last_indent|.
        !           429:          */
        !           430:         if (np != last_indent && header_type != hdr_Continuation)
        !           431:           last_indent=np;
        !           432:       }
        !           433:       prev_header_type = header_type;
        !           434:     }
        !           435:
        !           436:     { size_t n=np;
        !           437:       while (n<length) {
        !           438:         /* Find word end and count spaces after it */
        !           439:         size_t word_length=0, space_length=0;
        !           440:         while (n+word_length < length && line[n+word_length] != ' ')
        !           441:           ++word_length;
        !           442:         space_length = word_length;
        !           443:         while (n+space_length < length && line[n+space_length] == ' ')
        !           444:           ++space_length;
        !           445:         /* Send the word to the output machinery. */
        !           446:         output_word(first_indent, last_indent,
        !           447:                     line+n, word_length, space_length-word_length);
        !           448:         n += space_length;
        !           449:       }
        !           450:     }
        !           451:     ++para_line_number;
        !           452:   }
        !           453:   new_paragraph(output_in_paragraph ? last_indent : first_indent, 0);
        !           454:   if (ferror(stream)) { perror(name); ++n_errors; }
        !           455: }
        !           456:
        !           457: /* How long is the indent on this line?
        !           458:  */
        !           459: static size_t
        !           460: indent_length(const char *line, size_t length) {
        !           461:   size_t n=0;
        !           462:   while (n<length && *line++ == ' ') ++n;
        !           463:   return n;
        !           464: }
        !           465:
        !           466: /* Might this line be a mail header?
        !           467:  * We deem a line to be a possible header if it matches the
        !           468:  * Perl regexp /^[A-Z][-A-Za-z0-9]*:\s/. This is *not* the same
        !           469:  * as in RFC whatever-number-it-is; we want to be gratuitously
        !           470:  * conservative to avoid mangling ordinary civilised text.
        !           471:  */
        !           472: static int
        !           473: might_be_header(const char *line) {
        !           474:   if (!isupper(*line++)) return 0;
        !           475:   while (*line && (isalnum(*line) || *line=='-')) ++line;
        !           476:   return (*line==':' && isspace(line[1]));
        !           477: }
        !           478:
        !           479: /* Begin a new paragraph with an indent of |indent| spaces.
        !           480:  */
        !           481: static void
        !           482: new_paragraph(size_t old_indent, size_t indent) {
        !           483:   if (x0) {
        !           484:     if (old_indent>0) output_indent(old_indent);
        !           485:     fwrite(output_buffer, 1, x0, stdout);
        !           486:     putchar('\n');
        !           487:   }
        !           488:   x=indent; x0=0; pending_spaces=0;
        !           489:   output_in_paragraph = 0;
        !           490: }
        !           491:
        !           492: /* Output spaces or tabs for leading indentation.
        !           493:  */
        !           494: static void
        !           495: output_indent(size_t n_spaces) {
        !           496:   if (output_tab_width) {
        !           497:     while (n_spaces >= output_tab_width) {
        !           498:       putchar('\t');
        !           499:       n_spaces -= output_tab_width;
        !           500:     }
        !           501:   }
        !           502:   while (n_spaces-- > 0) putchar(' ');
        !           503: }
        !           504:
        !           505: /* Output a single word, or add it to the buffer.
        !           506:  * indent0 and indent1 are the indents to use on the first and subsequent
        !           507:  * lines of a paragraph. They'll often be the same, of course.
        !           508:  */
        !           509: static void
        !           510: output_word(size_t indent0, size_t indent1, const char *word, size_t length, size_t spaces) {
        !           511:   size_t new_x = x+pending_spaces+length;
        !           512:   size_t indent = output_in_paragraph ? indent1 : indent0;
        !           513:
        !           514:   /* If either |spaces==0| (at end of line) or |coalesce_spaces_P|
        !           515:    * (squashing internal whitespace), then add just one space;
        !           516:    * except that if the last character was a sentence-ender we
        !           517:    * actually add two spaces.
        !           518:    */
        !           519:   if (coalesce_spaces_P || spaces==0)
        !           520:     spaces = strchr(sentence_enders, word[length-1]) ? 2 : 1;
        !           521:
        !           522:   if (new_x<=goal_length) {
        !           523:     /* After adding the word we still aren't at the goal length,
        !           524:      * so clearly we add it to the buffer rather than outputing it.
        !           525:      */
        !           526:     memset(output_buffer+x0, ' ', pending_spaces);
        !           527:     x0 += pending_spaces; x += pending_spaces;
        !           528:     memcpy(output_buffer+x0, word, length);
        !           529:     x0 += length; x += length;
        !           530:     pending_spaces = spaces;
        !           531:   }
        !           532:   else {
        !           533:     /* Adding the word takes us past the goal. Print the line-so-far,
        !           534:      * and the word too iff either (1) the lsf is empty or (2) that
        !           535:      * makes us nearer the goal but doesn't take us over the limit,
        !           536:      * or (3) the word on its own takes us over the limit.
        !           537:      * In case (3) we put a newline in between.
        !           538:      */
        !           539:     if (indent>0) output_indent(indent);
        !           540:     fwrite(output_buffer, 1, x0, stdout);
        !           541:     if (x0==0 || (new_x <= max_length && new_x-goal_length <= goal_length-x)) {
        !           542:       printf("%*s", pending_spaces, "");
        !           543:       goto write_out_word;
        !           544:     }
        !           545:     else {
        !           546:       /* If the word takes us over the limit on its own, just
        !           547:        * spit it out and don't bother buffering it.
        !           548:        */
        !           549:       if (indent+length > max_length) {
        !           550:         putchar('\n');
        !           551:         if (indent>0) output_indent(indent);
        !           552: write_out_word:
        !           553:         fwrite(word, 1, length, stdout);
        !           554:         x0 = 0; x = indent1; pending_spaces = 0;
        !           555:       }
        !           556:       else {
        !           557:         memcpy(output_buffer, word, length);
        !           558:         x0 = length; x = length+indent1; pending_spaces = spaces;
        !           559:       }
        !           560:     }
        !           561:     putchar('\n');
        !           562:     output_in_paragraph = 1;
        !           563:   }
        !           564: }
        !           565:
        !           566: /* Process a stream, but just center its lines rather than trying to
        !           567:  * format them neatly.
        !           568:  */
        !           569: static void
        !           570: center_stream(FILE *stream, const char *name) {
        !           571:   char *line;
        !           572:   size_t length;
        !           573:   while ((line=get_line(stream, &length)) != 0) {
        !           574:     size_t l=length;
        !           575:     while (l>0 && isspace(*line)) { ++line; --l; }
        !           576:     length=l;
        !           577:     while (l<goal_length) { putchar(' '); l+=2; }
        !           578:     fwrite(line, 1, length, stdout);
        !           579:     putchar('\n');
        !           580:   }
        !           581:   if (ferror(stream)) { perror(name); ++n_errors; }
        !           582: }
        !           583:
        !           584: /* Get a single line from a stream. Expand tabs, strip control
        !           585:  * characters and trailing whitespace, and handle backspaces.
        !           586:  * Return the address of the buffer containing the line, and
        !           587:  * put the length of the line in |lengthp|.
        !           588:  * This can cope with arbitrarily long lines, and with lines
        !           589:  * without terminating \n.
        !           590:  * If there are no characters left or an error happens, we
        !           591:  * return 0.
        !           592:  * Don't confuse |spaces_pending| here with the global
        !           593:  * |pending_spaces|.
        !           594:  */
        !           595: static char *
        !           596: get_line(FILE *stream, size_t *lengthp) {
        !           597:   static char *buf=NULL;
        !           598:   static size_t length=0;
        !           599:   size_t len=0;
        !           600:   int ch;
        !           601:   size_t spaces_pending=0;
        !           602:
        !           603:   if (buf==NULL) { length=100; buf=XMALLOC(length); }
        !           604:   while ((ch=getc(stream)) != '\n' && ch != EOF) {
        !           605:     if (ch==' ') ++spaces_pending;
        !           606:     else if (isprint(ch)) {
        !           607:       while (len+spaces_pending >= length) {
        !           608:         length*=2; buf=xrealloc(buf, length);
        !           609:       }
        !           610:       while (spaces_pending > 0) { --spaces_pending; buf[len++]=' '; }
        !           611:       buf[len++] = ch;
        !           612:     }
        !           613:     else if (ch=='\t')
        !           614:       spaces_pending += tab_width - (len+spaces_pending)%tab_width;
        !           615:     else if (ch=='\b') { if (len) --len; }
        !           616:   }
        !           617:   *lengthp=len;
        !           618:   return (len>0 || ch!=EOF) ? buf : 0;
        !           619: }
        !           620:
        !           621: /* (Re)allocate some memory, exiting with an error if we can't.
        !           622:  */
        !           623: static void *
        !           624: xrealloc(void *ptr, size_t nbytes) {
        !           625:   void *p = realloc(ptr, nbytes);
        !           626:   if (p == NULL) errx(EX_OSERR, "out of memory");
        !           627:   return p;
1.1       deraadt   628: }