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