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