| xf.li | bdd93d5 | 2023-05-12 07:10:14 -0700 | [diff] [blame] | 1 | /* Word-wrapping and line-truncating streams | 
|  | 2 | Copyright (C) 1997-2016 Free Software Foundation, Inc. | 
|  | 3 | This file is part of the GNU C Library. | 
|  | 4 | Written by Miles Bader <miles@gnu.ai.mit.edu>. | 
|  | 5 |  | 
|  | 6 | The GNU C Library is free software; you can redistribute it and/or | 
|  | 7 | modify it under the terms of the GNU Lesser General Public | 
|  | 8 | License as published by the Free Software Foundation; either | 
|  | 9 | version 2.1 of the License, or (at your option) any later version. | 
|  | 10 |  | 
|  | 11 | The GNU C Library is distributed in the hope that it will be useful, | 
|  | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | 14 | Lesser General Public License for more details. | 
|  | 15 |  | 
|  | 16 | You should have received a copy of the GNU Lesser General Public | 
|  | 17 | License along with the GNU C Library; if not, see | 
|  | 18 | <http://www.gnu.org/licenses/>.  */ | 
|  | 19 |  | 
|  | 20 | /* This package emulates glibc `line_wrap_stream' semantics for systems that | 
|  | 21 | don't have that.  */ | 
|  | 22 |  | 
|  | 23 | #ifdef HAVE_CONFIG_H | 
|  | 24 | # include <config.h> | 
|  | 25 | #endif | 
|  | 26 |  | 
|  | 27 | #include <stdlib.h> | 
|  | 28 | #include <string.h> | 
|  | 29 | #include <errno.h> | 
|  | 30 | #include <stdarg.h> | 
|  | 31 | #include <ctype.h> | 
|  | 32 |  | 
|  | 33 | #include "argp-fmtstream.h" | 
|  | 34 | #include "argp-namefrob.h" | 
|  | 35 |  | 
|  | 36 | #ifndef ARGP_FMTSTREAM_USE_LINEWRAP | 
|  | 37 |  | 
|  | 38 | #ifndef isblank | 
|  | 39 | #define isblank(ch) ((ch)==' ' || (ch)=='\t') | 
|  | 40 | #endif | 
|  | 41 |  | 
|  | 42 | #ifdef _LIBC | 
|  | 43 | # include <wchar.h> | 
|  | 44 | # include <libio/libioP.h> | 
|  | 45 | # define __vsnprintf(s, l, f, a) _IO_vsnprintf (s, l, f, a) | 
|  | 46 | #endif | 
|  | 47 |  | 
|  | 48 | #define INIT_BUF_SIZE 200 | 
|  | 49 | #define PRINTF_SIZE_GUESS 150 | 
|  | 50 |  | 
|  | 51 | /* Return an argp_fmtstream that outputs to STREAM, and which prefixes lines | 
|  | 52 | written on it with LMARGIN spaces and limits them to RMARGIN columns | 
|  | 53 | total.  If WMARGIN >= 0, words that extend past RMARGIN are wrapped by | 
|  | 54 | replacing the whitespace before them with a newline and WMARGIN spaces. | 
|  | 55 | Otherwise, chars beyond RMARGIN are simply dropped until a newline. | 
|  | 56 | Returns NULL if there was an error.  */ | 
|  | 57 | argp_fmtstream_t | 
|  | 58 | __argp_make_fmtstream (FILE *stream, | 
|  | 59 | size_t lmargin, size_t rmargin, ssize_t wmargin) | 
|  | 60 | { | 
|  | 61 | argp_fmtstream_t fs; | 
|  | 62 |  | 
|  | 63 | fs = (struct argp_fmtstream *) malloc (sizeof (struct argp_fmtstream)); | 
|  | 64 | if (fs != NULL) | 
|  | 65 | { | 
|  | 66 | fs->stream = stream; | 
|  | 67 |  | 
|  | 68 | fs->lmargin = lmargin; | 
|  | 69 | fs->rmargin = rmargin; | 
|  | 70 | fs->wmargin = wmargin; | 
|  | 71 | fs->point_col = 0; | 
|  | 72 | fs->point_offs = 0; | 
|  | 73 |  | 
|  | 74 | fs->buf = (char *) malloc (INIT_BUF_SIZE); | 
|  | 75 | if (! fs->buf) | 
|  | 76 | { | 
|  | 77 | free (fs); | 
|  | 78 | fs = 0; | 
|  | 79 | } | 
|  | 80 | else | 
|  | 81 | { | 
|  | 82 | fs->p = fs->buf; | 
|  | 83 | fs->end = fs->buf + INIT_BUF_SIZE; | 
|  | 84 | } | 
|  | 85 | } | 
|  | 86 |  | 
|  | 87 | return fs; | 
|  | 88 | } | 
|  | 89 | #if 0 | 
|  | 90 | /* Not exported.  */ | 
|  | 91 | #ifdef weak_alias | 
|  | 92 | weak_alias (__argp_make_fmtstream, argp_make_fmtstream) | 
|  | 93 | #endif | 
|  | 94 | #endif | 
|  | 95 |  | 
|  | 96 | /* Flush FS to its stream, and free it (but don't close the stream).  */ | 
|  | 97 | void | 
|  | 98 | __argp_fmtstream_free (argp_fmtstream_t fs) | 
|  | 99 | { | 
|  | 100 | __argp_fmtstream_update (fs); | 
|  | 101 | if (fs->p > fs->buf) | 
|  | 102 | { | 
|  | 103 | #ifdef _LIBC | 
|  | 104 | __fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf); | 
|  | 105 | #else | 
|  | 106 | fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream); | 
|  | 107 | #endif | 
|  | 108 | } | 
|  | 109 | free (fs->buf); | 
|  | 110 | free (fs); | 
|  | 111 | } | 
|  | 112 | #if 0 | 
|  | 113 | /* Not exported.  */ | 
|  | 114 | #ifdef weak_alias | 
|  | 115 | weak_alias (__argp_fmtstream_free, argp_fmtstream_free) | 
|  | 116 | #endif | 
|  | 117 | #endif | 
|  | 118 |  | 
|  | 119 | /* Process FS's buffer so that line wrapping is done from POINT_OFFS to the | 
|  | 120 | end of its buffer.  This code is mostly from glibc stdio/linewrap.c.  */ | 
|  | 121 | void | 
|  | 122 | __argp_fmtstream_update (argp_fmtstream_t fs) | 
|  | 123 | { | 
|  | 124 | char *buf, *nl; | 
|  | 125 | size_t len; | 
|  | 126 |  | 
|  | 127 | /* Scan the buffer for newlines.  */ | 
|  | 128 | buf = fs->buf + fs->point_offs; | 
|  | 129 | while (buf < fs->p) | 
|  | 130 | { | 
|  | 131 | size_t r; | 
|  | 132 |  | 
|  | 133 | if (fs->point_col == 0 && fs->lmargin != 0) | 
|  | 134 | { | 
|  | 135 | /* We are starting a new line.  Print spaces to the left margin.  */ | 
|  | 136 | const size_t pad = fs->lmargin; | 
|  | 137 | if (fs->p + pad < fs->end) | 
|  | 138 | { | 
|  | 139 | /* We can fit in them in the buffer by moving the | 
|  | 140 | buffer text up and filling in the beginning.  */ | 
|  | 141 | memmove (buf + pad, buf, fs->p - buf); | 
|  | 142 | fs->p += pad; /* Compensate for bigger buffer. */ | 
|  | 143 | memset (buf, ' ', pad); /* Fill in the spaces.  */ | 
|  | 144 | buf += pad; /* Don't bother searching them.  */ | 
|  | 145 | } | 
|  | 146 | else | 
|  | 147 | { | 
|  | 148 | /* No buffer space for spaces.  Must flush.  */ | 
|  | 149 | size_t i; | 
|  | 150 | for (i = 0; i < pad; i++) | 
|  | 151 | { | 
|  | 152 | if (_IO_fwide (fs->stream, 0) > 0) | 
|  | 153 | putwc_unlocked (L' ', fs->stream); | 
|  | 154 | else | 
|  | 155 | putc_unlocked (' ', fs->stream); | 
|  | 156 | } | 
|  | 157 | } | 
|  | 158 | fs->point_col = pad; | 
|  | 159 | } | 
|  | 160 |  | 
|  | 161 | len = fs->p - buf; | 
|  | 162 | nl = memchr (buf, '\n', len); | 
|  | 163 |  | 
|  | 164 | if (fs->point_col < 0) | 
|  | 165 | fs->point_col = 0; | 
|  | 166 |  | 
|  | 167 | if (!nl) | 
|  | 168 | { | 
|  | 169 | /* The buffer ends in a partial line.  */ | 
|  | 170 |  | 
|  | 171 | if (fs->point_col + len < fs->rmargin) | 
|  | 172 | { | 
|  | 173 | /* The remaining buffer text is a partial line and fits | 
|  | 174 | within the maximum line width.  Advance point for the | 
|  | 175 | characters to be written and stop scanning.  */ | 
|  | 176 | fs->point_col += len; | 
|  | 177 | break; | 
|  | 178 | } | 
|  | 179 | else | 
|  | 180 | /* Set the end-of-line pointer for the code below to | 
|  | 181 | the end of the buffer.  */ | 
|  | 182 | nl = fs->p; | 
|  | 183 | } | 
|  | 184 | else if (fs->point_col + (nl - buf) < (ssize_t) fs->rmargin) | 
|  | 185 | { | 
|  | 186 | /* The buffer contains a full line that fits within the maximum | 
|  | 187 | line width.  Reset point and scan the next line.  */ | 
|  | 188 | fs->point_col = 0; | 
|  | 189 | buf = nl + 1; | 
|  | 190 | continue; | 
|  | 191 | } | 
|  | 192 |  | 
|  | 193 | /* This line is too long.  */ | 
|  | 194 | r = fs->rmargin - 1; | 
|  | 195 |  | 
|  | 196 | if (fs->wmargin < 0) | 
|  | 197 | { | 
|  | 198 | /* Truncate the line by overwriting the excess with the | 
|  | 199 | newline and anything after it in the buffer.  */ | 
|  | 200 | if (nl < fs->p) | 
|  | 201 | { | 
|  | 202 | memmove (buf + (r - fs->point_col), nl, fs->p - nl); | 
|  | 203 | fs->p -= buf + (r - fs->point_col) - nl; | 
|  | 204 | /* Reset point for the next line and start scanning it.  */ | 
|  | 205 | fs->point_col = 0; | 
|  | 206 | buf += r + 1; /* Skip full line plus \n. */ | 
|  | 207 | } | 
|  | 208 | else | 
|  | 209 | { | 
|  | 210 | /* The buffer ends with a partial line that is beyond the | 
|  | 211 | maximum line width.  Advance point for the characters | 
|  | 212 | written, and discard those past the max from the buffer.  */ | 
|  | 213 | fs->point_col += len; | 
|  | 214 | fs->p -= fs->point_col - r; | 
|  | 215 | break; | 
|  | 216 | } | 
|  | 217 | } | 
|  | 218 | else | 
|  | 219 | { | 
|  | 220 | /* Do word wrap.  Go to the column just past the maximum line | 
|  | 221 | width and scan back for the beginning of the word there. | 
|  | 222 | Then insert a line break.  */ | 
|  | 223 |  | 
|  | 224 | char *p, *nextline; | 
|  | 225 | int i; | 
|  | 226 |  | 
|  | 227 | p = buf + (r + 1 - fs->point_col); | 
|  | 228 | while (p >= buf && !isblank (*p)) | 
|  | 229 | --p; | 
|  | 230 | nextline = p + 1;	/* This will begin the next line.  */ | 
|  | 231 |  | 
|  | 232 | if (nextline > buf) | 
|  | 233 | { | 
|  | 234 | /* Swallow separating blanks.  */ | 
|  | 235 | if (p >= buf) | 
|  | 236 | do | 
|  | 237 | --p; | 
|  | 238 | while (p >= buf && isblank (*p)); | 
|  | 239 | nl = p + 1;	/* The newline will replace the first blank. */ | 
|  | 240 | } | 
|  | 241 | else | 
|  | 242 | { | 
|  | 243 | /* A single word that is greater than the maximum line width. | 
|  | 244 | Oh well.  Put it on an overlong line by itself.  */ | 
|  | 245 | p = buf + (r + 1 - fs->point_col); | 
|  | 246 | /* Find the end of the long word.  */ | 
|  | 247 | do | 
|  | 248 | ++p; | 
|  | 249 | while (p < nl && !isblank (*p)); | 
|  | 250 | if (p == nl) | 
|  | 251 | { | 
|  | 252 | /* It already ends a line.  No fussing required.  */ | 
|  | 253 | fs->point_col = 0; | 
|  | 254 | buf = nl + 1; | 
|  | 255 | continue; | 
|  | 256 | } | 
|  | 257 | /* We will move the newline to replace the first blank.  */ | 
|  | 258 | nl = p; | 
|  | 259 | /* Swallow separating blanks.  */ | 
|  | 260 | do | 
|  | 261 | ++p; | 
|  | 262 | while (isblank (*p)); | 
|  | 263 | /* The next line will start here.  */ | 
|  | 264 | nextline = p; | 
|  | 265 | } | 
|  | 266 |  | 
|  | 267 | /* Note: There are a bunch of tests below for | 
|  | 268 | NEXTLINE == BUF + LEN + 1; this case is where NL happens to fall | 
|  | 269 | at the end of the buffer, and NEXTLINE is in fact empty (and so | 
|  | 270 | we need not be careful to maintain its contents).  */ | 
|  | 271 |  | 
|  | 272 | if ((nextline == buf + len + 1 | 
|  | 273 | ? fs->end - nl < fs->wmargin + 1 | 
|  | 274 | : nextline - (nl + 1) < fs->wmargin) | 
|  | 275 | && fs->p > nextline) | 
|  | 276 | { | 
|  | 277 | /* The margin needs more blanks than we removed.  */ | 
|  | 278 | if (fs->end - fs->p > fs->wmargin + 1) | 
|  | 279 | /* Make some space for them.  */ | 
|  | 280 | { | 
|  | 281 | size_t mv = fs->p - nextline; | 
|  | 282 | memmove (nl + 1 + fs->wmargin, nextline, mv); | 
|  | 283 | nextline = nl + 1 + fs->wmargin; | 
|  | 284 | len = nextline + mv - buf; | 
|  | 285 | *nl++ = '\n'; | 
|  | 286 | } | 
|  | 287 | else | 
|  | 288 | /* Output the first line so we can use the space.  */ | 
|  | 289 | { | 
|  | 290 | #ifdef _LIBC | 
|  | 291 | __fxprintf (fs->stream, "%.*s\n", | 
|  | 292 | (int) (nl - fs->buf), fs->buf); | 
|  | 293 | #else | 
|  | 294 | if (nl > fs->buf) | 
|  | 295 | fwrite_unlocked (fs->buf, 1, nl - fs->buf, fs->stream); | 
|  | 296 | putc_unlocked ('\n', fs->stream); | 
|  | 297 | #endif | 
|  | 298 |  | 
|  | 299 | len += buf - fs->buf; | 
|  | 300 | nl = buf = fs->buf; | 
|  | 301 | } | 
|  | 302 | } | 
|  | 303 | else | 
|  | 304 | /* We can fit the newline and blanks in before | 
|  | 305 | the next word.  */ | 
|  | 306 | *nl++ = '\n'; | 
|  | 307 |  | 
|  | 308 | if (nextline - nl >= fs->wmargin | 
|  | 309 | || (nextline == buf + len + 1 && fs->end - nextline >= fs->wmargin)) | 
|  | 310 | /* Add blanks up to the wrap margin column.  */ | 
|  | 311 | for (i = 0; i < fs->wmargin; ++i) | 
|  | 312 | *nl++ = ' '; | 
|  | 313 | else | 
|  | 314 | for (i = 0; i < fs->wmargin; ++i) | 
|  | 315 | if (_IO_fwide (fs->stream, 0) > 0) | 
|  | 316 | putwc_unlocked (L' ', fs->stream); | 
|  | 317 | else | 
|  | 318 | putc_unlocked (' ', fs->stream); | 
|  | 319 |  | 
|  | 320 | /* Copy the tail of the original buffer into the current buffer | 
|  | 321 | position.  */ | 
|  | 322 | if (nl < nextline) | 
|  | 323 | memmove (nl, nextline, buf + len - nextline); | 
|  | 324 | len -= nextline - buf; | 
|  | 325 |  | 
|  | 326 | /* Continue the scan on the remaining lines in the buffer.  */ | 
|  | 327 | buf = nl; | 
|  | 328 |  | 
|  | 329 | /* Restore bufp to include all the remaining text.  */ | 
|  | 330 | fs->p = nl + len; | 
|  | 331 |  | 
|  | 332 | /* Reset the counter of what has been output this line.  If wmargin | 
|  | 333 | is 0, we want to avoid the lmargin getting added, so we set | 
|  | 334 | point_col to a magic value of -1 in that case.  */ | 
|  | 335 | fs->point_col = fs->wmargin ? fs->wmargin : -1; | 
|  | 336 | } | 
|  | 337 | } | 
|  | 338 |  | 
|  | 339 | /* Remember that we've scanned as far as the end of the buffer.  */ | 
|  | 340 | fs->point_offs = fs->p - fs->buf; | 
|  | 341 | } | 
|  | 342 |  | 
|  | 343 | /* Ensure that FS has space for AMOUNT more bytes in its buffer, either by | 
|  | 344 | growing the buffer, or by flushing it.  True is returned iff we succeed. */ | 
|  | 345 | int | 
|  | 346 | __argp_fmtstream_ensure (struct argp_fmtstream *fs, size_t amount) | 
|  | 347 | { | 
|  | 348 | if ((size_t) (fs->end - fs->p) < amount) | 
|  | 349 | { | 
|  | 350 | ssize_t wrote; | 
|  | 351 |  | 
|  | 352 | /* Flush FS's buffer.  */ | 
|  | 353 | __argp_fmtstream_update (fs); | 
|  | 354 |  | 
|  | 355 | #ifdef _LIBC | 
|  | 356 | __fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf); | 
|  | 357 | wrote = fs->p - fs->buf; | 
|  | 358 | #else | 
|  | 359 | wrote = fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream); | 
|  | 360 | #endif | 
|  | 361 | if (wrote == fs->p - fs->buf) | 
|  | 362 | { | 
|  | 363 | fs->p = fs->buf; | 
|  | 364 | fs->point_offs = 0; | 
|  | 365 | } | 
|  | 366 | else | 
|  | 367 | { | 
|  | 368 | fs->p -= wrote; | 
|  | 369 | fs->point_offs -= wrote; | 
|  | 370 | memmove (fs->buf, fs->buf + wrote, fs->p - fs->buf); | 
|  | 371 | return 0; | 
|  | 372 | } | 
|  | 373 |  | 
|  | 374 | if ((size_t) (fs->end - fs->buf) < amount) | 
|  | 375 | /* Gotta grow the buffer.  */ | 
|  | 376 | { | 
|  | 377 | size_t old_size = fs->end - fs->buf; | 
|  | 378 | size_t new_size = old_size + amount; | 
|  | 379 | char *new_buf; | 
|  | 380 |  | 
|  | 381 | if (new_size < old_size || ! (new_buf = realloc (fs->buf, new_size))) | 
|  | 382 | { | 
|  | 383 | __set_errno (ENOMEM); | 
|  | 384 | return 0; | 
|  | 385 | } | 
|  | 386 |  | 
|  | 387 | fs->buf = new_buf; | 
|  | 388 | fs->end = new_buf + new_size; | 
|  | 389 | fs->p = fs->buf; | 
|  | 390 | } | 
|  | 391 | } | 
|  | 392 |  | 
|  | 393 | return 1; | 
|  | 394 | } | 
|  | 395 |  | 
|  | 396 | ssize_t | 
|  | 397 | __argp_fmtstream_printf (struct argp_fmtstream *fs, const char *fmt, ...) | 
|  | 398 | { | 
|  | 399 | int out; | 
|  | 400 | size_t avail; | 
|  | 401 | size_t size_guess = PRINTF_SIZE_GUESS; /* How much space to reserve. */ | 
|  | 402 |  | 
|  | 403 | do | 
|  | 404 | { | 
|  | 405 | va_list args; | 
|  | 406 |  | 
|  | 407 | if (! __argp_fmtstream_ensure (fs, size_guess)) | 
|  | 408 | return -1; | 
|  | 409 |  | 
|  | 410 | va_start (args, fmt); | 
|  | 411 | avail = fs->end - fs->p; | 
|  | 412 | out = __vsnprintf (fs->p, avail, fmt, args); | 
|  | 413 | va_end (args); | 
|  | 414 | if ((size_t) out >= avail) | 
|  | 415 | size_guess = out + 1; | 
|  | 416 | } | 
|  | 417 | while ((size_t) out >= avail); | 
|  | 418 |  | 
|  | 419 | fs->p += out; | 
|  | 420 |  | 
|  | 421 | return out; | 
|  | 422 | } | 
|  | 423 | #if 0 | 
|  | 424 | /* Not exported.  */ | 
|  | 425 | #ifdef weak_alias | 
|  | 426 | weak_alias (__argp_fmtstream_printf, argp_fmtstream_printf) | 
|  | 427 | #endif | 
|  | 428 | #endif | 
|  | 429 |  | 
|  | 430 | #endif /* !ARGP_FMTSTREAM_USE_LINEWRAP */ |