argp-fmtstream.c

00001 /* Word-wrapping and line-truncating streams
00002    Copyright (C) 1997-1999,2001,2002,2003,2005 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    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU Lesser General Public License as published by
00008    the Free Software Foundation; either version 2.1, or (at your option)
00009    any later version.
00010 
00011    This program 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
00014    GNU Lesser General Public License for more details.
00015 
00016    You should have received a copy of the GNU Lesser General Public License along
00017    with this program; if not, write to the Free Software Foundation,
00018    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
00019 
00020 /* This package emulates glibc `line_wrap_stream' semantics for systems that
00021    don't have that.  */
00022 
00023 #ifdef HAVE_CONFIG_H
00024 # include <config.h>
00025 #endif
00026 
00027 #include <stdlib.h>
00028 #include <string.h>
00029 #include <errno.h>
00030 #include <stdarg.h>
00031 #include <ctype.h>
00032 
00033 #include "argp-fmtstream.h"
00034 #include "argp-namefrob.h"
00035 
00036 #ifndef ARGP_FMTSTREAM_USE_LINEWRAP
00037 
00038 #ifndef isblank
00039 #define isblank(ch) ((ch)==' ' || (ch)=='\t')
00040 #endif
00041 
00042 #if defined _LIBC && defined USE_IN_LIBIO
00043 # include <wchar.h>
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;
00062 
00063   fs = (struct argp_fmtstream *) malloc (sizeof (struct argp_fmtstream));
00064   if (fs != NULL)
00065     {
00066       fs->stream = stream;
00067 
00068       fs->lmargin = lmargin;
00069       fs->rmargin = rmargin;
00070       fs->wmargin = wmargin;
00071       fs->point_col = 0;
00072       fs->point_offs = 0;
00073 
00074       fs->buf = (char *) malloc (INIT_BUF_SIZE);
00075       if (! fs->buf)
00076         {
00077           free (fs);
00078           fs = 0;
00079         }
00080       else
00081         {
00082           fs->p = fs->buf;
00083           fs->end = fs->buf + INIT_BUF_SIZE;
00084         }
00085     }
00086 
00087   return fs;
00088 }
00089 #if 0
00090 /* Not exported.  */
00091 #ifdef weak_alias
00092 weak_alias (__argp_make_fmtstream, argp_make_fmtstream)
00093 #endif
00094 #endif
00095 
00096 /* Flush FS to its stream, and free it (but don't close the stream).  */
00097 void
00098 __argp_fmtstream_free (argp_fmtstream_t fs)
00099 {
00100   __argp_fmtstream_update (fs);
00101   if (fs->p > fs->buf)
00102     {
00103 #ifdef USE_IN_LIBIO
00104       __fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf);
00105 #else
00106       fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream);
00107 #endif
00108     }
00109   free (fs->buf);
00110   free (fs);
00111 }
00112 #if 0
00113 /* Not exported.  */
00114 #ifdef weak_alias
00115 weak_alias (__argp_fmtstream_free, argp_fmtstream_free)
00116 #endif
00117 #endif
00118 
00119 /* Process FS's buffer so that line wrapping is done from POINT_OFFS to the
00120    end of its buffer.  This code is mostly from glibc stdio/linewrap.c.  */
00121 void
00122 __argp_fmtstream_update (argp_fmtstream_t fs)
00123 {
00124   char *buf, *nl;
00125   size_t len;
00126 
00127   /* Scan the buffer for newlines.  */
00128   buf = fs->buf + fs->point_offs;
00129   while (buf < fs->p)
00130     {
00131       size_t r;
00132 
00133       if (fs->point_col == 0 && fs->lmargin != 0)
00134         {
00135           /* We are starting a new line.  Print spaces to the left margin.  */
00136           const size_t pad = fs->lmargin;
00137           if (fs->p + pad < fs->end)
00138             {
00139               /* We can fit in them in the buffer by moving the
00140                  buffer text up and filling in the beginning.  */
00141               memmove (buf + pad, buf, fs->p - buf);
00142               fs->p += pad; /* Compensate for bigger buffer. */
00143               memset (buf, ' ', pad); /* Fill in the spaces.  */
00144               buf += pad; /* Don't bother searching them.  */
00145             }
00146           else
00147             {
00148               /* No buffer space for spaces.  Must flush.  */
00149               size_t i;
00150               for (i = 0; i < pad; i++)
00151                 {
00152 #ifdef USE_IN_LIBIO
00153                   if (_IO_fwide (fs->stream, 0) > 0)
00154                     putwc_unlocked (L' ', fs->stream);
00155                   else
00156 #endif
00157                     putc_unlocked (' ', fs->stream);
00158                 }
00159             }
00160           fs->point_col = pad;
00161         }
00162 
00163       len = fs->p - buf;
00164       nl = memchr (buf, '\n', len);
00165 
00166       if (fs->point_col < 0)
00167         fs->point_col = 0;
00168 
00169       if (!nl)
00170         {
00171           /* The buffer ends in a partial line.  */
00172 
00173           if (fs->point_col + len < fs->rmargin)
00174             {
00175               /* The remaining buffer text is a partial line and fits
00176                  within the maximum line width.  Advance point for the
00177                  characters to be written and stop scanning.  */
00178               fs->point_col += len;
00179               break;
00180             }
00181           else
00182             /* Set the end-of-line pointer for the code below to
00183                the end of the buffer.  */
00184             nl = fs->p;
00185         }
00186       else if (fs->point_col + (nl - buf) < (ssize_t) fs->rmargin)
00187         {
00188           /* The buffer contains a full line that fits within the maximum
00189              line width.  Reset point and scan the next line.  */
00190           fs->point_col = 0;
00191           buf = nl + 1;
00192           continue;
00193         }
00194 
00195       /* This line is too long.  */
00196       r = fs->rmargin - 1;
00197 
00198       if (fs->wmargin < 0)
00199         {
00200           /* Truncate the line by overwriting the excess with the
00201              newline and anything after it in the buffer.  */
00202           if (nl < fs->p)
00203             {
00204               memmove (buf + (r - fs->point_col), nl, fs->p - nl);
00205               fs->p -= buf + (r - fs->point_col) - nl;
00206               /* Reset point for the next line and start scanning it.  */
00207               fs->point_col = 0;
00208               buf += r + 1; /* Skip full line plus \n. */
00209             }
00210           else
00211             {
00212               /* The buffer ends with a partial line that is beyond the
00213                  maximum line width.  Advance point for the characters
00214                  written, and discard those past the max from the buffer.  */
00215               fs->point_col += len;
00216               fs->p -= fs->point_col - r;
00217               break;
00218             }
00219         }
00220       else
00221         {
00222           /* Do word wrap.  Go to the column just past the maximum line
00223              width and scan back for the beginning of the word there.
00224              Then insert a line break.  */
00225 
00226           char *p, *nextline;
00227           int i;
00228 
00229           p = buf + (r + 1 - fs->point_col);
00230           while (p >= buf && !isblank (*p))
00231             --p;
00232           nextline = p + 1;     /* This will begin the next line.  */
00233 
00234           if (nextline > buf)
00235             {
00236               /* Swallow separating blanks.  */
00237               if (p >= buf)
00238                 do
00239                   --p;
00240                 while (p >= buf && isblank (*p));
00241               nl = p + 1;       /* The newline will replace the first blank. */
00242             }
00243           else
00244             {
00245               /* A single word that is greater than the maximum line width.
00246                  Oh well.  Put it on an overlong line by itself.  */
00247               p = buf + (r + 1 - fs->point_col);
00248               /* Find the end of the long word.  */
00249               do
00250                 ++p;
00251               while (p < nl && !isblank (*p));
00252               if (p == nl)
00253                 {
00254                   /* It already ends a line.  No fussing required.  */
00255                   fs->point_col = 0;
00256                   buf = nl + 1;
00257                   continue;
00258                 }
00259               /* We will move the newline to replace the first blank.  */
00260               nl = p;
00261               /* Swallow separating blanks.  */
00262               do
00263                 ++p;
00264               while (isblank (*p));
00265               /* The next line will start here.  */
00266               nextline = p;
00267             }
00268 
00269           /* Note: There are a bunch of tests below for
00270              NEXTLINE == BUF + LEN + 1; this case is where NL happens to fall
00271              at the end of the buffer, and NEXTLINE is in fact empty (and so
00272              we need not be careful to maintain its contents).  */
00273 
00274           if ((nextline == buf + len + 1
00275                ? fs->end - nl < fs->wmargin + 1
00276                : nextline - (nl + 1) < fs->wmargin)
00277               && fs->p > nextline)
00278             {
00279               /* The margin needs more blanks than we removed.  */
00280               if (fs->end - fs->p > fs->wmargin + 1)
00281                 /* Make some space for them.  */
00282                 {
00283                   size_t mv = fs->p - nextline;
00284                   memmove (nl + 1 + fs->wmargin, nextline, mv);
00285                   nextline = nl + 1 + fs->wmargin;
00286                   len = nextline + mv - buf;
00287                   *nl++ = '\n';
00288                 }
00289               else
00290                 /* Output the first line so we can use the space.  */
00291                 {
00292 #ifdef _LIBC
00293                   __fxprintf (fs->stream, "%.*s\n",
00294                               (int) (nl - fs->buf), fs->buf);
00295 #else
00296                   if (nl > fs->buf)
00297                     fwrite_unlocked (fs->buf, 1, nl - fs->buf, fs->stream);
00298                   putc_unlocked ('\n', fs->stream);
00299 #endif
00300 
00301                   len += buf - fs->buf;
00302                   nl = buf = fs->buf;
00303                 }
00304             }
00305           else
00306             /* We can fit the newline and blanks in before
00307                the next word.  */
00308             *nl++ = '\n';
00309 
00310           if (nextline - nl >= fs->wmargin
00311               || (nextline == buf + len + 1 && fs->end - nextline >= fs->wmargin))
00312             /* Add blanks up to the wrap margin column.  */
00313             for (i = 0; i < fs->wmargin; ++i)
00314               *nl++ = ' ';
00315           else
00316             for (i = 0; i < fs->wmargin; ++i)
00317 #ifdef USE_IN_LIBIO
00318               if (_IO_fwide (fs->stream, 0) > 0)
00319                 putwc_unlocked (L' ', fs->stream);
00320               else
00321 #endif
00322                 putc_unlocked (' ', fs->stream);
00323 
00324           /* Copy the tail of the original buffer into the current buffer
00325              position.  */
00326           if (nl < nextline)
00327             memmove (nl, nextline, buf + len - nextline);
00328           len -= nextline - buf;
00329 
00330           /* Continue the scan on the remaining lines in the buffer.  */
00331           buf = nl;
00332 
00333           /* Restore bufp to include all the remaining text.  */
00334           fs->p = nl + len;
00335 
00336           /* Reset the counter of what has been output this line.  If wmargin
00337              is 0, we want to avoid the lmargin getting added, so we set
00338              point_col to a magic value of -1 in that case.  */
00339           fs->point_col = fs->wmargin ? fs->wmargin : -1;
00340         }
00341     }
00342 
00343   /* Remember that we've scanned as far as the end of the buffer.  */
00344   fs->point_offs = fs->p - fs->buf;
00345 }
00346 
00347 /* Ensure that FS has space for AMOUNT more bytes in its buffer, either by
00348    growing the buffer, or by flushing it.  True is returned iff we succeed. */
00349 int
00350 __argp_fmtstream_ensure (struct argp_fmtstream *fs, size_t amount)
00351 {
00352   if ((size_t) (fs->end - fs->p) < amount)
00353     {
00354       ssize_t wrote;
00355 
00356       /* Flush FS's buffer.  */
00357       __argp_fmtstream_update (fs);
00358 
00359 #ifdef _LIBC
00360       __fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf);
00361       wrote = fs->p - fs->buf;
00362 #else
00363       wrote = fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream);
00364 #endif
00365       if (wrote == fs->p - fs->buf)
00366         {
00367           fs->p = fs->buf;
00368           fs->point_offs = 0;
00369         }
00370       else
00371         {
00372           fs->p -= wrote;
00373           fs->point_offs -= wrote;
00374           memmove (fs->buf, fs->buf + wrote, fs->p - fs->buf);
00375           return 0;
00376         }
00377 
00378       if ((size_t) (fs->end - fs->buf) < amount)
00379         /* Gotta grow the buffer.  */
00380         {
00381           size_t old_size = fs->end - fs->buf;
00382           size_t new_size = old_size + amount;
00383           char *new_buf;
00384 
00385           if (new_size < old_size || ! (new_buf = realloc (fs->buf, new_size)))
00386             {
00387               __set_errno (ENOMEM);
00388               return 0;
00389             }
00390 
00391           fs->buf = new_buf;
00392           fs->end = new_buf + new_size;
00393           fs->p = fs->buf;
00394         }
00395     }
00396 
00397   return 1;
00398 }
00399 
00400 ssize_t
00401 __argp_fmtstream_printf (struct argp_fmtstream *fs, const char *fmt, ...)
00402 {
00403   int out;
00404   size_t avail;
00405   size_t size_guess = PRINTF_SIZE_GUESS; /* How much space to reserve. */
00406 
00407   do
00408     {
00409       va_list args;
00410 
00411       if (! __argp_fmtstream_ensure (fs, size_guess))
00412         return -1;
00413 
00414       va_start (args, fmt);
00415       avail = fs->end - fs->p;
00416       out = __vsnprintf (fs->p, avail, fmt, args);
00417       va_end (args);
00418       if ((size_t) out >= avail)
00419         size_guess = out + 1;
00420     }
00421   while ((size_t) out >= avail);
00422 
00423   fs->p += out;
00424 
00425   return out;
00426 }
00427 #if 0
00428 /* Not exported.  */
00429 #ifdef weak_alias
00430 weak_alias (__argp_fmtstream_printf, argp_fmtstream_printf)
00431 #endif
00432 #endif
00433 
00434 #endif /* !ARGP_FMTSTREAM_USE_LINEWRAP */

Generated on Fri Oct 5 18:20:25 2007 for WvStreams by  doxygen 1.5.3