WvStreams
argp-fmtstream.c
00001 /* Word-wrapping and line-truncating streams
00002    Copyright (C) 1997, 1998, 1999, 2001 Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004    Written by Miles Bader <miles@gnu.ai.mit.edu>.
00005 
00006    The GNU C Library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public License as
00008    published by the Free Software Foundation; either version 2 of the
00009    License, or (at your option) any later version.
00010 
00011    The GNU C Library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public
00017    License along with the GNU C Library; see the file COPYING.LIB.  If not,
00018    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00019    Boston, MA 02111-1307, USA.  */
00020 
00021 /* This package emulates glibc `line_wrap_stream' semantics for systems that
00022    don't have that.  */
00023 
00024 #ifdef HAVE_CONFIG_H
00025 #include <config.h>
00026 #endif
00027 
00028 #include <stdlib.h>
00029 #include <string.h>
00030 #include <errno.h>
00031 #include <stdarg.h>
00032 #include <ctype.h>
00033 
00034 #include "argp-fmtstream.h"
00035 #include "argp-namefrob.h"
00036 
00037 #ifndef ARGP_FMTSTREAM_USE_LINEWRAP
00038 
00039 #ifndef isblank
00040 #define isblank(ch) ((ch)==' ' || (ch)=='\t')
00041 #endif
00042 
00043 #if defined _LIBC && defined USE_IN_LIBIO
00044 # include <libio/libioP.h>
00045 # define __vsnprintf(s, l, f, a) _IO_vsnprintf (s, l, f, a)
00046 #endif
00047 
00048 #define INIT_BUF_SIZE 200
00049 #define PRINTF_SIZE_GUESS 150
00050 
00051 /* Return an argp_fmtstream that outputs to STREAM, and which prefixes lines
00052    written on it with LMARGIN spaces and limits them to RMARGIN columns
00053    total.  If WMARGIN >= 0, words that extend past RMARGIN are wrapped by
00054    replacing the whitespace before them with a newline and WMARGIN spaces.
00055    Otherwise, chars beyond RMARGIN are simply dropped until a newline.
00056    Returns NULL if there was an error.  */
00057 argp_fmtstream_t
00058 __argp_make_fmtstream (FILE *stream,
00059                        size_t lmargin, size_t rmargin, ssize_t wmargin)
00060 {
00061   argp_fmtstream_t fs = malloc (sizeof (struct argp_fmtstream));
00062   if (fs)
00063     {
00064       fs->stream = stream;
00065 
00066       fs->lmargin = lmargin;
00067       fs->rmargin = rmargin;
00068       fs->wmargin = wmargin;
00069       fs->point_col = 0;
00070       fs->point_offs = 0;
00071 
00072       fs->buf = malloc (INIT_BUF_SIZE);
00073       if (! fs->buf)
00074         {
00075           free (fs);
00076           fs = 0;
00077         }
00078       else
00079         {
00080           fs->p = fs->buf;
00081           fs->end = fs->buf + INIT_BUF_SIZE;
00082         }
00083     }
00084 
00085   return fs;
00086 }
00087 #ifdef weak_alias
00088 weak_alias (__argp_make_fmtstream, argp_make_fmtstream)
00089 #endif
00090 
00091 /* Flush FS to its stream, and free it (but don't close the stream).  */
00092 void
00093 __argp_fmtstream_free (argp_fmtstream_t fs)
00094 {
00095   __argp_fmtstream_update (fs);
00096   if (fs->p > fs->buf)
00097     FWRITE_UNLOCKED (fs->buf, 1, fs->p - fs->buf, fs->stream);
00098   free (fs->buf);
00099   free (fs);
00100 }
00101 #ifdef weak_alias
00102 weak_alias (__argp_fmtstream_free, argp_fmtstream_free)
00103 #endif
00104 
00105 /* Process FS's buffer so that line wrapping is done from POINT_OFFS to the
00106    end of its buffer.  This code is mostly from glibc stdio/linewrap.c.  */
00107 void
00108 __argp_fmtstream_update (argp_fmtstream_t fs)
00109 {
00110   char *buf, *nl;
00111   size_t len;
00112 
00113   /* Scan the buffer for newlines.  */
00114   buf = fs->buf + fs->point_offs;
00115   while (buf < fs->p)
00116     {
00117       size_t r;
00118 
00119       if (fs->point_col == 0 && fs->lmargin != 0)
00120         {
00121           /* We are starting a new line.  Print spaces to the left margin.  */
00122           const size_t pad = fs->lmargin;
00123           if (fs->p + pad < fs->end)
00124             {
00125               /* We can fit in them in the buffer by moving the
00126                  buffer text up and filling in the beginning.  */
00127               memmove (buf + pad, buf, fs->p - buf);
00128               fs->p += pad; /* Compensate for bigger buffer. */
00129               memset (buf, ' ', pad); /* Fill in the spaces.  */
00130               buf += pad; /* Don't bother searching them.  */
00131             }
00132           else
00133             {
00134               /* No buffer space for spaces.  Must flush.  */
00135               size_t i;
00136               for (i = 0; i < pad; i++)
00137                 PUTC_UNLOCKED (' ', fs->stream);
00138             }
00139           fs->point_col = pad;
00140         }
00141 
00142       len = fs->p - buf;
00143       nl = memchr (buf, '\n', len);
00144 
00145       if (fs->point_col < 0)
00146         fs->point_col = 0;
00147 
00148       if (!nl)
00149         {
00150           /* The buffer ends in a partial line.  */
00151 
00152           if (fs->point_col + len < fs->rmargin)
00153             {
00154               /* The remaining buffer text is a partial line and fits
00155                  within the maximum line width.  Advance point for the
00156                  characters to be written and stop scanning.  */
00157               fs->point_col += len;
00158               break;
00159             }
00160           else
00161             /* Set the end-of-line pointer for the code below to
00162                the end of the buffer.  */
00163             nl = fs->p;
00164         }
00165       else if (fs->point_col + (nl - buf) < (ssize_t) fs->rmargin)
00166         {
00167           /* The buffer contains a full line that fits within the maximum
00168              line width.  Reset point and scan the next line.  */
00169           fs->point_col = 0;
00170           buf = nl + 1;
00171           continue;
00172         }
00173 
00174       /* This line is too long.  */
00175       r = fs->rmargin - 1;
00176 
00177       if (fs->wmargin < 0)
00178         {
00179           /* Truncate the line by overwriting the excess with the
00180              newline and anything after it in the buffer.  */
00181           if (nl < fs->p)
00182             {
00183               memmove (buf + (r - fs->point_col), nl, fs->p - nl);
00184               fs->p -= buf + (r - fs->point_col) - nl;
00185               /* Reset point for the next line and start scanning it.  */
00186               fs->point_col = 0;
00187               buf += r + 1; /* Skip full line plus \n. */
00188             }
00189           else
00190             {
00191               /* The buffer ends with a partial line that is beyond the
00192                  maximum line width.  Advance point for the characters
00193                  written, and discard those past the max from the buffer.  */
00194               fs->point_col += len;
00195               fs->p -= fs->point_col - r;
00196               break;
00197             }
00198         }
00199       else
00200         {
00201           /* Do word wrap.  Go to the column just past the maximum line
00202              width and scan back for the beginning of the word there.
00203              Then insert a line break.  */
00204 
00205           char *p, *nextline;
00206           int i;
00207 
00208           p = buf + (r + 1 - fs->point_col);
00209           while (p >= buf && !isblank (*p))
00210             --p;
00211           nextline = p + 1;     /* This will begin the next line.  */
00212 
00213           if (nextline > buf)
00214             {
00215               /* Swallow separating blanks.  */
00216               if (p >= buf)
00217                 do
00218                   --p;
00219                 while (p >= buf && isblank (*p));
00220               nl = p + 1;       /* The newline will replace the first blank. */
00221             }
00222           else
00223             {
00224               /* A single word that is greater than the maximum line width.
00225                  Oh well.  Put it on an overlong line by itself.  */
00226               p = buf + (r + 1 - fs->point_col);
00227               /* Find the end of the long word.  */
00228               do
00229                 ++p;
00230               while (p < nl && !isblank (*p));
00231               if (p == nl)
00232                 {
00233                   /* It already ends a line.  No fussing required.  */
00234                   fs->point_col = 0;
00235                   buf = nl + 1;
00236                   continue;
00237                 }
00238               /* We will move the newline to replace the first blank.  */
00239               nl = p;
00240               /* Swallow separating blanks.  */
00241               do
00242                 ++p;
00243               while (isblank (*p));
00244               /* The next line will start here.  */
00245               nextline = p;
00246             }
00247 
00248           /* Note: There are a bunch of tests below for
00249              NEXTLINE == BUF + LEN + 1; this case is where NL happens to fall
00250              at the end of the buffer, and NEXTLINE is in fact empty (and so
00251              we need not be careful to maintain its contents).  */
00252 
00253           if (nextline == buf + len + 1
00254               ? fs->end - nl < fs->wmargin + 1
00255               : nextline - (nl + 1) < fs->wmargin)
00256             {
00257               /* The margin needs more blanks than we removed.  */
00258               if (fs->end - fs->p > fs->wmargin + 1)
00259                 /* Make some space for them.  */
00260                 {
00261                   size_t mv = fs->p - nextline;
00262                   memmove (nl + 1 + fs->wmargin, nextline, mv);
00263                   nextline = nl + 1 + fs->wmargin;
00264                   len = nextline + mv - buf;
00265                   *nl++ = '\n';
00266                 }
00267               else
00268                 /* Output the first line so we can use the space.  */
00269                 {
00270                   if (nl > fs->buf)
00271                     FWRITE_UNLOCKED (fs->buf, 1, nl - fs->buf, fs->stream);
00272                   PUTC_UNLOCKED ('\n', fs->stream);
00273                   len += buf - fs->buf;
00274                   nl = buf = fs->buf;
00275                 }
00276             }
00277           else
00278             /* We can fit the newline and blanks in before
00279                the next word.  */
00280             *nl++ = '\n';
00281 
00282           if (nextline - nl >= fs->wmargin
00283               || (nextline == buf + len + 1 && fs->end - nextline >= fs->wmargin))
00284             /* Add blanks up to the wrap margin column.  */
00285             for (i = 0; i < fs->wmargin; ++i)
00286               *nl++ = ' ';
00287           else
00288             for (i = 0; i < fs->wmargin; ++i)
00289               PUTC_UNLOCKED (' ', fs->stream);
00290 
00291           /* Copy the tail of the original buffer into the current buffer
00292              position.  */
00293           if (nl < nextline)
00294             memmove (nl, nextline, buf + len - nextline);
00295           len -= nextline - buf;
00296 
00297           /* Continue the scan on the remaining lines in the buffer.  */
00298           buf = nl;
00299 
00300           /* Restore bufp to include all the remaining text.  */
00301           fs->p = nl + len;
00302 
00303           /* Reset the counter of what has been output this line.  If wmargin
00304              is 0, we want to avoid the lmargin getting added, so we set
00305              point_col to a magic value of -1 in that case.  */
00306           fs->point_col = fs->wmargin ? fs->wmargin : -1;
00307         }
00308     }
00309 
00310   /* Remember that we've scanned as far as the end of the buffer.  */
00311   fs->point_offs = fs->p - fs->buf;
00312 }
00313 
00314 /* Ensure that FS has space for AMOUNT more bytes in its buffer, either by
00315    growing the buffer, or by flushing it.  True is returned iff we succeed. */
00316 int
00317 __argp_fmtstream_ensure (struct argp_fmtstream *fs, size_t amount)
00318 {
00319   if ((size_t) (fs->end - fs->p) < amount)
00320     {
00321       ssize_t wrote;
00322 
00323       /* Flush FS's buffer.  */
00324       __argp_fmtstream_update (fs);
00325 
00326       wrote = FWRITE_UNLOCKED (fs->buf, 1, fs->p - fs->buf, fs->stream);
00327       if (wrote == fs->p - fs->buf)
00328         {
00329           fs->p = fs->buf;
00330           fs->point_offs = 0;
00331         }
00332       else
00333         {
00334           fs->p -= wrote;
00335           fs->point_offs -= wrote;
00336           memmove (fs->buf, fs->buf + wrote, fs->p - fs->buf);
00337           return 0;
00338         }
00339 
00340       if ((size_t) (fs->end - fs->buf) < amount)
00341         /* Gotta grow the buffer.  */
00342         {
00343           size_t new_size = fs->end - fs->buf + amount;
00344           char *new_buf = realloc (fs->buf, new_size);
00345 
00346           if (! new_buf)
00347             {
00348               __set_errno (ENOMEM);
00349               return 0;
00350             }
00351 
00352           fs->buf = new_buf;
00353           fs->end = new_buf + new_size;
00354           fs->p = fs->buf;
00355         }
00356     }
00357 
00358   return 1;
00359 }
00360 
00361 ssize_t
00362 __argp_fmtstream_printf (struct argp_fmtstream *fs, const char *fmt, ...)
00363 {
00364   size_t out;
00365   size_t avail;
00366   size_t size_guess = PRINTF_SIZE_GUESS; /* How much space to reserve. */
00367 
00368   do
00369     {
00370       va_list args;
00371 
00372       if (! __argp_fmtstream_ensure (fs, size_guess))
00373         return -1;
00374 
00375       va_start (args, fmt);
00376       avail = fs->end - fs->p;
00377       out = __vsnprintf (fs->p, avail, fmt, args);
00378       va_end (args);
00379       if (out >= avail)
00380         size_guess = out + 1;
00381     }
00382   while (out >= avail);
00383 
00384   fs->p += out;
00385 
00386   return out;
00387 }
00388 #ifdef weak_alias
00389 weak_alias (__argp_fmtstream_printf, argp_fmtstream_printf)
00390 #endif
00391 
00392 /* Duplicate the inline definitions in argp-fmtstream.h, for compilers
00393  * that don't do inlining. */
00394 size_t
00395 __argp_fmtstream_write (argp_fmtstream_t __fs,
00396                         __const char *__str, size_t __len)
00397 {
00398   if (__fs->p + __len <= __fs->end || __argp_fmtstream_ensure (__fs, __len))
00399     {
00400       memcpy (__fs->p, __str, __len);
00401       __fs->p += __len;
00402       return __len;
00403     }
00404   else
00405     return 0;
00406 }
00407 
00408 int
00409 __argp_fmtstream_puts (argp_fmtstream_t __fs, __const char *__str)
00410 {
00411   size_t __len = strlen (__str);
00412   if (__len)
00413     {
00414       size_t __wrote = __argp_fmtstream_write (__fs, __str, __len);
00415       return __wrote == __len ? 0 : -1;
00416     }
00417   else
00418     return 0;
00419 }
00420 
00421 int
00422 __argp_fmtstream_putc (argp_fmtstream_t __fs, int __ch)
00423 {
00424   if (__fs->p < __fs->end || __argp_fmtstream_ensure (__fs, 1))
00425     return *__fs->p++ = __ch;
00426   else
00427     return EOF;
00428 }
00429 
00430 /* Set __FS's left margin to __LMARGIN and return the old value.  */
00431 size_t
00432 __argp_fmtstream_set_lmargin (argp_fmtstream_t __fs, size_t __lmargin)
00433 {
00434   size_t __old;
00435   if ((size_t) (__fs->p - __fs->buf) > __fs->point_offs)
00436     __argp_fmtstream_update (__fs);
00437   __old = __fs->lmargin;
00438   __fs->lmargin = __lmargin;
00439   return __old;
00440 }
00441 
00442 /* Set __FS's right margin to __RMARGIN and return the old value.  */
00443 size_t
00444 __argp_fmtstream_set_rmargin (argp_fmtstream_t __fs, size_t __rmargin)
00445 {
00446   size_t __old;
00447   if ((size_t) (__fs->p - __fs->buf) > __fs->point_offs)
00448     __argp_fmtstream_update (__fs);
00449   __old = __fs->rmargin;
00450   __fs->rmargin = __rmargin;
00451   return __old;
00452 }
00453 
00454 /* Set FS's wrap margin to __WMARGIN and return the old value.  */
00455 size_t
00456 __argp_fmtstream_set_wmargin (argp_fmtstream_t __fs, size_t __wmargin)
00457 {
00458   size_t __old;
00459   if ((size_t) (__fs->p - __fs->buf) > __fs->point_offs)
00460     __argp_fmtstream_update (__fs);
00461   __old = __fs->wmargin;
00462   __fs->wmargin = __wmargin;
00463   return __old;
00464 }
00465 
00466 /* Return the column number of the current output point in __FS.  */
00467 size_t
00468 __argp_fmtstream_point (argp_fmtstream_t __fs)
00469 {
00470   if ((size_t) (__fs->p - __fs->buf) > __fs->point_offs)
00471     __argp_fmtstream_update (__fs);
00472   return __fs->point_col >= 0 ? __fs->point_col : 0;
00473 }
00474 
00475 #endif /* !ARGP_FMTSTREAM_USE_LINEWRAP */