WvStreams
|
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 */