1
/* Word-wrapping and line-truncating streams
2
Copyright (C) 1997-1999, 2001-2003, 2005, 2009-2010 Free Software
4
This file is part of the GNU C Library.
5
Written by Miles Bader <miles@gnu.ai.mit.edu>.
7
This program is free software: you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 3 of the License, or
10
(at your option) any later version.
12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with this program. If not, see <http://www.gnu.org/licenses/>. */
20
/* This package emulates glibc `line_wrap_stream' semantics for systems that
33
#include "argp-fmtstream.h"
34
#include "argp-namefrob.h"
36
#ifndef ARGP_FMTSTREAM_USE_LINEWRAP
39
#define isblank(ch) ((ch)==' ' || (ch)=='\t')
42
#if defined _LIBC && defined USE_IN_LIBIO
44
# include <libio/libioP.h>
45
# define __vsnprintf(s, l, f, a) _IO_vsnprintf (s, l, f, a)
48
#define INIT_BUF_SIZE 200
49
#define PRINTF_SIZE_GUESS 150
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. */
58
__argp_make_fmtstream (FILE *stream,
59
size_t lmargin, size_t rmargin, ssize_t wmargin)
63
fs = (struct argp_fmtstream *) malloc (sizeof (struct argp_fmtstream));
68
fs->lmargin = lmargin;
69
fs->rmargin = rmargin;
70
fs->wmargin = wmargin;
74
fs->buf = (char *) malloc (INIT_BUF_SIZE);
83
fs->end = fs->buf + INIT_BUF_SIZE;
92
weak_alias (__argp_make_fmtstream, argp_make_fmtstream)
96
/* Flush FS to its stream, and free it (but don't close the stream). */
98
__argp_fmtstream_free (argp_fmtstream_t fs)
100
__argp_fmtstream_update (fs);
104
__fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf);
106
fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream);
115
weak_alias (__argp_fmtstream_free, argp_fmtstream_free)
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. */
122
__argp_fmtstream_update (argp_fmtstream_t fs)
127
/* Scan the buffer for newlines. */
128
buf = fs->buf + fs->point_offs;
133
if (fs->point_col == 0 && fs->lmargin != 0)
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)
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. */
148
/* No buffer space for spaces. Must flush. */
150
for (i = 0; i < pad; i++)
153
if (_IO_fwide (fs->stream, 0) > 0)
154
putwc_unlocked (L' ', fs->stream);
157
putc_unlocked (' ', fs->stream);
164
nl = memchr (buf, '\n', len);
166
if (fs->point_col < 0)
171
/* The buffer ends in a partial line. */
173
if (fs->point_col + len < fs->rmargin)
175
/* The remaining buffer text is a partial line and fits
176
within the maximum line width. Advance point for the
177
characters to be written and stop scanning. */
178
fs->point_col += len;
182
/* Set the end-of-line pointer for the code below to
183
the end of the buffer. */
186
else if (fs->point_col + (nl - buf) < (ssize_t) fs->rmargin)
188
/* The buffer contains a full line that fits within the maximum
189
line width. Reset point and scan the next line. */
195
/* This line is too long. */
200
/* Truncate the line by overwriting the excess with the
201
newline and anything after it in the buffer. */
204
memmove (buf + (r - fs->point_col), nl, fs->p - nl);
205
fs->p -= buf + (r - fs->point_col) - nl;
206
/* Reset point for the next line and start scanning it. */
208
buf += r + 1; /* Skip full line plus \n. */
212
/* The buffer ends with a partial line that is beyond the
213
maximum line width. Advance point for the characters
214
written, and discard those past the max from the buffer. */
215
fs->point_col += len;
216
fs->p -= fs->point_col - r;
222
/* Do word wrap. Go to the column just past the maximum line
223
width and scan back for the beginning of the word there.
224
Then insert a line break. */
229
p = buf + (r + 1 - fs->point_col);
230
while (p >= buf && !isblank ((unsigned char) *p))
232
nextline = p + 1; /* This will begin the next line. */
236
/* Swallow separating blanks. */
240
while (p >= buf && isblank ((unsigned char) *p));
241
nl = p + 1; /* The newline will replace the first blank. */
245
/* A single word that is greater than the maximum line width.
246
Oh well. Put it on an overlong line by itself. */
247
p = buf + (r + 1 - fs->point_col);
248
/* Find the end of the long word. */
252
while (p < nl && !isblank ((unsigned char) *p));
255
/* It already ends a line. No fussing required. */
260
/* We will move the newline to replace the first blank. */
262
/* Swallow separating blanks. */
265
while (isblank ((unsigned char) *p));
266
/* The next line will start here. */
270
/* Note: There are a bunch of tests below for
271
NEXTLINE == BUF + LEN + 1; this case is where NL happens to fall
272
at the end of the buffer, and NEXTLINE is in fact empty (and so
273
we need not be careful to maintain its contents). */
275
if ((nextline == buf + len + 1
276
? fs->end - nl < fs->wmargin + 1
277
: nextline - (nl + 1) < fs->wmargin)
280
/* The margin needs more blanks than we removed. */
281
if (fs->end - fs->p > fs->wmargin + 1)
282
/* Make some space for them. */
284
size_t mv = fs->p - nextline;
285
memmove (nl + 1 + fs->wmargin, nextline, mv);
286
nextline = nl + 1 + fs->wmargin;
287
len = nextline + mv - buf;
291
/* Output the first line so we can use the space. */
294
__fxprintf (fs->stream, "%.*s\n",
295
(int) (nl - fs->buf), fs->buf);
298
fwrite_unlocked (fs->buf, 1, nl - fs->buf, fs->stream);
299
putc_unlocked ('\n', fs->stream);
302
len += buf - fs->buf;
307
/* We can fit the newline and blanks in before
311
if (nextline - nl >= fs->wmargin
312
|| (nextline == buf + len + 1 && fs->end - nextline >= fs->wmargin))
313
/* Add blanks up to the wrap margin column. */
314
for (i = 0; i < fs->wmargin; ++i)
317
for (i = 0; i < fs->wmargin; ++i)
319
if (_IO_fwide (fs->stream, 0) > 0)
320
putwc_unlocked (L' ', fs->stream);
323
putc_unlocked (' ', fs->stream);
325
/* Copy the tail of the original buffer into the current buffer
328
memmove (nl, nextline, buf + len - nextline);
329
len -= nextline - buf;
331
/* Continue the scan on the remaining lines in the buffer. */
334
/* Restore bufp to include all the remaining text. */
337
/* Reset the counter of what has been output this line. If wmargin
338
is 0, we want to avoid the lmargin getting added, so we set
339
point_col to a magic value of -1 in that case. */
340
fs->point_col = fs->wmargin ? fs->wmargin : -1;
344
/* Remember that we've scanned as far as the end of the buffer. */
345
fs->point_offs = fs->p - fs->buf;
348
/* Ensure that FS has space for AMOUNT more bytes in its buffer, either by
349
growing the buffer, or by flushing it. True is returned iff we succeed. */
351
__argp_fmtstream_ensure (struct argp_fmtstream *fs, size_t amount)
353
if ((size_t) (fs->end - fs->p) < amount)
357
/* Flush FS's buffer. */
358
__argp_fmtstream_update (fs);
361
__fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf);
362
wrote = fs->p - fs->buf;
364
wrote = fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream);
366
if (wrote == fs->p - fs->buf)
374
fs->point_offs -= wrote;
375
memmove (fs->buf, fs->buf + wrote, fs->p - fs->buf);
379
if ((size_t) (fs->end - fs->buf) < amount)
380
/* Gotta grow the buffer. */
382
size_t old_size = fs->end - fs->buf;
383
size_t new_size = old_size + amount;
386
if (new_size < old_size || ! (new_buf = realloc (fs->buf, new_size)))
388
__set_errno (ENOMEM);
393
fs->end = new_buf + new_size;
402
__argp_fmtstream_printf (struct argp_fmtstream *fs, const char *fmt, ...)
406
size_t size_guess = PRINTF_SIZE_GUESS; /* How much space to reserve. */
412
if (! __argp_fmtstream_ensure (fs, size_guess))
415
va_start (args, fmt);
416
avail = fs->end - fs->p;
417
out = __vsnprintf (fs->p, avail, fmt, args);
419
if ((size_t) out >= avail)
420
size_guess = out + 1;
422
while ((size_t) out >= avail);
431
weak_alias (__argp_fmtstream_printf, argp_fmtstream_printf)
435
#endif /* !ARGP_FMTSTREAM_USE_LINEWRAP */