Back to index

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