summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am3
-rw-r--r--src/gdbmapp.h21
-rw-r--r--src/gdbmshell.c36
-rw-r--r--src/parseopt.c375
-rw-r--r--src/wordwrap.c636
5 files changed, 917 insertions, 154 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index afd9abd..316bfcc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -95,7 +95,8 @@ libgdbmapp_a_SOURCES =\
lex.c\
gdbmshell.c\
var.c\
- util.c
+ util.c\
+ wordwrap.c
if GDBM_COND_READLINE
libgdbmapp_a_SOURCES += input-rl.c
diff --git a/src/gdbmapp.h b/src/gdbmapp.h
index 5c60e7b..d766358 100644
--- a/src/gdbmapp.h
+++ b/src/gdbmapp.h
@@ -61,4 +61,25 @@ extern char *parseopt_program_args;
#define EXIT_FATAL 1
#define EXIT_MILD 2
#define EXIT_USAGE 3
+
+/* Word-wrapping stream. */
+typedef struct wordwrap_file *WORDWRAP_FILE;
+
+WORDWRAP_FILE wordwrap_fdopen (int fd);
+int wordwrap_close (WORDWRAP_FILE wf);
+int wordwrap_flush (WORDWRAP_FILE wf);
+int wordwrap_error (WORDWRAP_FILE wf);
+int wordwrap_set_left_margin (WORDWRAP_FILE wf, unsigned left);
+int wordwrap_next_left_margin (WORDWRAP_FILE wf, unsigned left);
+int wordwrap_set_right_margin (WORDWRAP_FILE wf, unsigned right);
+int wordwrap_write (WORDWRAP_FILE wf, char const *str, size_t len);
+int wordwrap_puts (WORDWRAP_FILE wf, char const *str);
+int wordwrap_putc (WORDWRAP_FILE wf, int c);
+int wordwrap_para (WORDWRAP_FILE wf);
+int wordwrap_vprintf (WORDWRAP_FILE wf, char const *fmt, va_list ap);
+int wordwrap_printf (WORDWRAP_FILE wf, char const *fmt, ...);
+int wordwrap_at_bol (WORDWRAP_FILE wf);
+int wordwrap_at_eol (WORDWRAP_FILE wf);
+void wordwrap_word_start (WORDWRAP_FILE wf);
+void wordwrap_word_end (WORDWRAP_FILE wf);
diff --git a/src/gdbmshell.c b/src/gdbmshell.c
index 45e3f9f..08042ce 100644
--- a/src/gdbmshell.c
+++ b/src/gdbmshell.c
@@ -2415,34 +2415,34 @@ help_handler (struct command_param *param GDBM_ARG_UNUSED,
struct command_environ *cenv)
{
struct command *cmd;
- FILE *fp = cenv->fp;
+ WORDWRAP_FILE wf;
+
+ fflush (cenv->fp);
+ wf = wordwrap_fdopen (fileno (cenv->fp));
for (cmd = command_tab; cmd->name; cmd++)
{
int i;
int n;
- int optoff;
-
- n = fprintf (fp, " %s", cmd->name);
- optoff = n;
-
+
+ wordwrap_set_left_margin (wf, 1);
+ wordwrap_set_right_margin (wf, 0);
+ n = strlen (cmd->name);
+ wordwrap_write (wf, cmd->name, n);
+
+ wordwrap_set_left_margin (wf, n + 2);
for (i = 0; i < NARGS && cmd->args[i].name; i++)
{
- if (n >= CMDCOLS)
- {
- fputc ('\n', fp);
- n = fprintf (fp, "%*.*s", optoff, optoff, "");
- }
- n += fprintf (fp, " %s", gettext (cmd->args[i].name));
+ wordwrap_printf (wf, " %s", gettext (cmd->args[i].name));
}
- if (n < CMDCOLS)
- fprintf (fp, "%*.s", CMDCOLS-n, "");
- else
- fprintf (fp, "\n%*.*s", CMDCOLS, CMDCOLS, "");
- fprintf (fp, " %s", gettext (cmd->doc));
- fputc ('\n', fp);
+ wordwrap_set_right_margin (wf, 0);
+ wordwrap_set_left_margin (wf, CMDCOLS);
+
+ wordwrap_printf (wf, " %s", gettext (cmd->doc));
+ wordwrap_flush (wf);
}
+ wordwrap_close (wf);
return 0;
}
diff --git a/src/parseopt.c b/src/parseopt.c
index 42cef26..0169ae5 100644
--- a/src/parseopt.c
+++ b/src/parseopt.c
@@ -216,50 +216,159 @@ parseopt_first (int pc, char **pv, struct gdbm_option *opts)
return parseopt_next ();
}
-#define LMARGIN 2
-#define DESCRCOLUMN 30
-#define RMARGIN 79
-#define GROUPCOLUMN 2
-#define USAGECOLUMN 13
-
-static void
-indent (size_t start, size_t col)
+static unsigned short_opt_col = 2;
+static unsigned long_opt_col = 6;
+static unsigned doc_opt_col = 2; /* FIXME: Not used: there are no doc
+ options in this implementation */
+static unsigned header_col = 1;
+static unsigned opt_doc_col = 29;
+static unsigned usage_indent = 12;
+static unsigned rmargin = 79;
+
+static unsigned dup_args = 0;
+static unsigned dup_args_note = 1;
+
+enum usage_var_type
+ {
+ usage_var_column,
+ usage_var_bool
+ };
+
+struct usage_var_def
{
- for (; start < col; start++)
- putchar (' ');
-}
+ char *name;
+ unsigned *valptr;
+ enum usage_var_type type;
+};
+
+static struct usage_var_def usage_var[] = {
+ { "short-opt-col", &short_opt_col, usage_var_column },
+ { "header-col", &header_col, usage_var_column },
+ { "opt-doc-col", &opt_doc_col, usage_var_column },
+ { "usage-indent", &usage_indent, usage_var_column },
+ { "rmargin", &rmargin, usage_var_column },
+ { "dup-args", &dup_args, usage_var_bool },
+ { "dup-args-note", &dup_args_note, usage_var_bool },
+ { "long-opt-col", &long_opt_col, usage_var_column },
+ { "doc-opt-col", &doc_opt_col, usage_var_column },
+ { NULL }
+};
static void
-print_option_descr (const char *descr, size_t lmargin, size_t rmargin)
+set_usage_var (char const *text, char **end)
{
- if (!(descr && descr[0]))
- return;
- descr = gettext (descr);
- while (*descr)
+ struct usage_var_def *p;
+ int boolval = 1;
+ char const *prog_name = parseopt_program_name ? parseopt_program_name : progname;
+ size_t len = strcspn (text, ",=");
+ char *endp;
+
+ if (len > 3 && memcmp (text, "no-", 3) == 0)
{
- size_t s = 0;
- size_t i;
- size_t width = rmargin - lmargin;
-
- for (i = 0; ; i++)
+ text += 3;
+ len -= 3;
+ boolval = 0;
+ }
+
+ for (p = usage_var; p->name; p++)
+ {
+ if (strlen (p->name) == len && memcmp (p->name, text, len) == 0)
+ break;
+ }
+
+ endp = (char*) text + len;
+ if (p)
+ {
+ if (p->type == usage_var_bool)
{
- if (descr[i] == 0 || descr[i] == ' ' || descr[i] == '\t')
+ if (*endp == '=')
{
- if (i > width)
- break;
- s = i;
- if (descr[i] == 0)
- break;
+ if (prog_name)
+ fprintf (stderr, "%s: ", prog_name);
+ fprintf (stderr,
+ _("error in ARGP_HELP_FMT: improper usage of [no-]%s\n"),
+ p->name);
+ endp = strchr (text + len, ',');
}
+ else
+ *p->valptr = boolval;
}
- fwrite (descr, 1, s, stdout);
- fputc ('\n', stdout);
- descr += s;
- if (*descr)
+ else if (*endp == '=')
{
- indent (0, lmargin);
- descr++;
+ unsigned long val;
+
+ errno = 0;
+ val = strtoul (text + len + 1, &endp, 10);
+ if (errno || (*endp && *endp != ','))
+ {
+ if (prog_name)
+ fprintf (stderr, "%s: ", prog_name);
+ fprintf (stderr,
+ _("error in ARGP_HELP_FMT: bad value for %s"),
+ p->name);
+ if (endp)
+ {
+ fprintf (stderr, _(" (near %s)"), endp);
+ }
+ fputc ('\n', stderr);
+ }
+ else if (val > UINT_MAX)
+ {
+ if (prog_name)
+ fprintf (stderr, "%s: ", prog_name);
+ fprintf (stderr,
+ _("error in ARGP_HELP_FMT: %s value is out of range\n"),
+ p->name);
+ }
+ else
+ *p->valptr = val;
}
+ else
+ {
+ if (prog_name)
+ fprintf (stderr, "%s: ", prog_name);
+ fprintf (stderr,
+ _("%s: ARGP_HELP_FMT parameter requires a value\n"),
+ p->name);
+ }
+ }
+ else
+ {
+ if (prog_name)
+ fprintf (stderr, "%s: ", prog_name);
+ fprintf (stderr,
+ _("%s: Unknown ARGP_HELP_FMT parameter\n"),
+ text);
+ }
+ *end = endp;
+}
+
+static void
+init_usage_vars (void)
+{
+ char *fmt, *p;
+
+ fmt = getenv ("ARGP_HELP_FMT");
+ if (!fmt || !*fmt)
+ return;
+
+ while (1)
+ {
+ set_usage_var (fmt, &p);
+ if (*p == 0)
+ break;
+ else if (*p == ',')
+ p++;
+ else
+ {
+ char const *prog_name = parseopt_program_name ? parseopt_program_name : progname;
+ if (prog_name)
+ fprintf (stderr, "%s: ", prog_name);
+ fprintf (stderr, _("ARGP_HELP_FMT: missing delimiter near %s\n"),
+ p);
+ break;
+ }
+ fmt = p;
}
}
@@ -269,8 +378,20 @@ void (*parseopt_help_hook) (FILE *stream);
static int argsused;
+static int
+print_arg (WORDWRAP_FILE wf, struct gdbm_option *opt, int delim)
+{
+ if (opt->opt_arg)
+ {
+ argsused = 1;
+ return wordwrap_printf (wf, "%c%s", delim,
+ opt->opt_arg[0] ? gettext (opt->opt_arg) : "");
+ }
+ return 0;
+}
+
size_t
-print_option (size_t num)
+print_option (WORDWRAP_FILE wf, size_t num)
{
struct gdbm_option *opt = option_tab + num;
size_t next, i;
@@ -279,9 +400,11 @@ print_option (size_t num)
if (IS_GROUP_HEADER (opt))
{
- indent (0, GROUPCOLUMN);
- print_option_descr (opt->opt_descr, GROUPCOLUMN, RMARGIN);
- putchar ('\n');
+ wordwrap_set_left_margin (wf, header_col);
+ wordwrap_set_right_margin (wf, rmargin);
+ if (opt->opt_descr[0])
+ wordwrap_puts (wf, gettext (opt->opt_descr));
+ wordwrap_putc (wf, '\n');
return num + 1;
}
@@ -293,55 +416,46 @@ print_option (size_t num)
if (opt->opt_flags & PARSEOPT_HIDDEN)
return next;
+ wordwrap_set_left_margin (wf, short_opt_col);
w = 0;
for (i = num; i < next; i++)
{
if (IS_VALID_SHORT_OPTION (&option_tab[i]))
{
- if (w == 0)
- {
- indent (0, LMARGIN);
- w = LMARGIN;
- }
- else
- w += printf (", ");
- w += printf ("-%c", option_tab[i].opt_short);
+ if (w)
+ wordwrap_write (wf, ", ", 2);
+ wordwrap_printf (wf, "-%c", option_tab[i].opt_short);
delim = ' ';
+ if (dup_args)
+ print_arg (wf, opt, delim);
+ w = 1;
}
}
+
#ifdef HAVE_GETOPT_LONG
+ w = 0;
+ wordwrap_set_left_margin (wf, long_opt_col);
for (i = num; i < next; i++)
{
if (IS_VALID_LONG_OPTION (&option_tab[i]))
{
- if (w == 0)
- {
- indent (0, LMARGIN);
- w = LMARGIN;
- }
- else
- w += printf (", ");
- w += printf ("--%s", option_tab[i].opt_long);
+ if (w)
+ wordwrap_write (wf, ", ", 2);
+ wordwrap_printf (wf, "--%s", option_tab[i].opt_long);
delim = '=';
+ if (dup_args)
+ print_arg (wf, opt, delim);
+ w = 1;
}
}
-#else
- if (!w)
- return next;
#endif
- if (opt->opt_arg)
- {
- argsused = 1;
- w += printf ("%c%s", delim, gettext (opt->opt_arg));
- }
- if (w >= DESCRCOLUMN)
- {
- putchar ('\n');
- w = 0;
- }
- indent (w, DESCRCOLUMN);
- print_option_descr (opt->opt_descr, DESCRCOLUMN, RMARGIN);
-
+ if (!dup_args)
+ print_arg (wf, opt, delim);
+
+ wordwrap_set_left_margin (wf, opt_doc_col);
+ if (opt->opt_descr[0])
+ wordwrap_puts (wf, gettext (opt->opt_descr));
+
return next;
}
@@ -349,40 +463,53 @@ void
parseopt_print_help (void)
{
unsigned i;
-
+ WORDWRAP_FILE wf;
+
argsused = 0;
- printf ("%s %s [%s]... %s\n", _("Usage:"),
- parseopt_program_name ? parseopt_program_name : progname,
- _("OPTION"),
- gettext (parseopt_program_args));
- print_option_descr (parseopt_program_doc, 0, RMARGIN);
- putchar ('\n');
+ init_usage_vars ();
+ wf = wordwrap_fdopen (1);
+
+ wordwrap_printf (wf, "%s %s [%s]... %s\n", _("Usage:"),
+ parseopt_program_name ? parseopt_program_name : progname,
+ _("OPTION"),
+ gettext (parseopt_program_args));
+
+ wordwrap_set_right_margin (wf, rmargin);
+ if (parseopt_program_doc && parseopt_program_doc[0])
+ wordwrap_puts (wf, gettext (parseopt_program_doc));
+ wordwrap_para (wf);
+
sort_all_options ();
for (i = 0; i < option_count; )
{
- i = print_option (i);
+ i = print_option (wf, i);
}
- putchar ('\n');
+ wordwrap_para (wf);
+
#ifdef HAVE_GETOPT_LONG
- if (argsused)
+ if (argsused && dup_args_note)
{
- print_option_descr (N_("Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options."), 0, RMARGIN);
- putchar ('\n');
+ wordwrap_set_left_margin (wf, 0);
+ wordwrap_set_right_margin (wf, rmargin);
+ wordwrap_puts (wf, _("Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options."));
+ wordwrap_para (wf);
}
#endif
if (parseopt_help_hook)
- parseopt_help_hook (stdout);
+ parseopt_help_hook (stdout);//FIXME
+ wordwrap_set_left_margin (wf, 0);
+ wordwrap_set_right_margin (wf, rmargin);
/* TRANSLATORS: The placeholder indicates the bug-reporting address
for this package. Please add _another line_ saying
"Report translation bugs to <...>\n" with the address for translation
bugs (typically your translation team's web or email address). */
- printf (_("Report bugs to %s.\n"), program_bug_address);
+ wordwrap_printf (wf, _("Report bugs to %s.\n"), program_bug_address);
#ifdef PACKAGE_URL
- printf (_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+ wordwrap_printf (wf, _("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
#endif
}
@@ -410,34 +537,21 @@ cmpidx_long (const void *a, const void *b)
void
print_usage (void)
{
+ WORDWRAP_FILE wf;
unsigned i;
- unsigned n;
- char buf[RMARGIN+1];
unsigned *idxbuf;
unsigned nidx;
-
-#define FLUSH \
- do \
- { \
- buf[n] = 0; \
- printf ("%s\n", buf); \
- n = USAGECOLUMN; \
- memset (buf, ' ', n); \
- } \
- while (0)
-#define ADDC(c) \
- do \
- { \
- if (n == RMARGIN) FLUSH; \
- buf[n++] = c; \
- } \
- while (0)
+ init_usage_vars ();
+
idxbuf = ecalloc (option_count, sizeof (idxbuf[0]));
- n = snprintf (buf, sizeof buf, "%s %s ", _("Usage:"),
- parseopt_program_name ? parseopt_program_name : progname);
-
+ wf = wordwrap_fdopen (1);
+ wordwrap_set_right_margin (wf, rmargin);
+ wordwrap_printf (wf, "%s %s ", _("Usage:"),
+ parseopt_program_name ? parseopt_program_name : progname);
+ wordwrap_next_left_margin (wf, usage_indent);
+
/* Print a list of short options without arguments. */
for (i = nidx = 0; i < option_count; i++)
if (IS_VALID_SHORT_OPTION (&option_tab[i]) && !option_tab[i].opt_arg)
@@ -447,13 +561,12 @@ print_usage (void)
{
qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short);
- ADDC ('[');
- ADDC ('-');
+ wordwrap_puts (wf, "[-");
for (i = 0; i < nidx; i++)
{
- ADDC (option_tab[idxbuf[i]].opt_short);
+ wordwrap_putc (wf, option_tab[idxbuf[i]].opt_short);
}
- ADDC (']');
+ wordwrap_putc (wf, ']');
}
/* Print a list of short options with arguments. */
@@ -471,17 +584,14 @@ print_usage (void)
{
struct gdbm_option *opt = option_tab + idxbuf[i];
const char *arg = gettext (opt->opt_arg);
- size_t len = 5 + strlen (arg) + 1;
-
- if (n + len > RMARGIN) FLUSH;
- buf[n++] = ' ';
- buf[n++] = '[';
- buf[n++] = '-';
- buf[n++] = opt->opt_short;
- buf[n++] = ' ';
- strcpy (&buf[n], arg);
- n += strlen (arg);
- buf[n++] = ']';
+
+ wordwrap_word_start (wf);
+ wordwrap_puts (wf, " [-");
+ wordwrap_putc (wf, opt->opt_short);
+ wordwrap_putc (wf, ' ');
+ wordwrap_puts (wf, arg);
+ wordwrap_putc (wf, ']');
+ wordwrap_word_end (wf);
}
}
@@ -501,26 +611,21 @@ print_usage (void)
{
struct gdbm_option *opt = option_tab + idxbuf[i];
const char *arg = opt->opt_arg ? gettext (opt->opt_arg) : NULL;
- size_t len = 5 + strlen (opt->opt_long)
- + (arg ? 1 + strlen (arg) : 0);
- if (n + len > RMARGIN) FLUSH;
- buf[n++] = ' ';
- buf[n++] = '[';
- buf[n++] = '-';
- buf[n++] = '-';
- strcpy (&buf[n], opt->opt_long);
- n += strlen (opt->opt_long);
+
+ wordwrap_word_start (wf);
+ wordwrap_write (wf, " [--", 4);
+ wordwrap_puts (wf, opt->opt_long);
if (opt->opt_arg)
{
- buf[n++] = '=';
- strcpy (&buf[n], arg);
- n += strlen (arg);
+ wordwrap_putc (wf, '=');
+ wordwrap_write (wf, arg, strlen (arg));
}
- buf[n++] = ']';
+ wordwrap_putc (wf, ']');
+ wordwrap_word_end (wf);
}
}
#endif
- FLUSH;
+ wordwrap_close (wf);
free (idxbuf);
}
diff --git a/src/wordwrap.c b/src/wordwrap.c
new file mode 100644
index 0000000..890291e
--- /dev/null
+++ b/src/wordwrap.c
@@ -0,0 +1,636 @@
+/* This file is part of GDBM, the GNU data base manager.
+ Copyright (C) 2011-2021 Free Software Foundation, Inc.
+
+ GDBM is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ GDBM is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with GDBM. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "autoconf.h"
+#include "gdbmapp.h"
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <wctype.h>
+#include <wchar.h>
+#include <errno.h>
+#include <limits.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#define UNSET ((unsigned)-1)
+#define ISSET(c) (c != UNSET)
+#define DEFAULT_RIGHT_MARGIN 80
+
+struct wordwrap_file
+{
+ int fd; /* Output file descriptor. */
+ unsigned left_margin; /* Left margin. */
+ unsigned right_margin; /* Right margin. */
+ char *buffer; /* Output buffer. */
+ size_t bufsize; /* Size of buffer in bytes. */
+ unsigned offset; /* Offset of the writing point in the buffer */
+ unsigned column; /* Number of screen column, i.e. the (multibyte)
+ character corresponding to the offset. */
+ unsigned last_ws; /* Offset of the beginning of the last whitespace
+ sequence written to the buffer. */
+ unsigned ws_run; /* Number of characters in the whitespace sequence. */
+ unsigned word_start; /* Start of a sequence that should be treated as a
+ single word. */
+ unsigned next_left_margin; /* Left margin to be set after next flush. */
+
+ int indent; /* If 1, reindent next line. */
+ int unibyte; /* 0: Normal operation.
+ 1: multibyte functions disabled for this line. */
+ int err; /* Last errno value associated with this file. */
+};
+
+/*
+ * Reset the file for the next input line.
+ */
+static void
+wordwrap_line_init (WORDWRAP_FILE wf)
+{
+ wf->offset = wf->column = wf->left_margin;
+ wf->last_ws = UNSET;
+ wf->ws_run = 0;
+ wf->unibyte = 0;
+}
+
+/*
+ * Detect the value of the right margin. Use TIOCGWINSZ ioctl, the COLUMNS
+ * environment variable, or the default value, in that order.
+ */
+static unsigned
+detect_right_margin (WORDWRAP_FILE wf)
+{
+ struct winsize ws;
+ unsigned r = 0;
+
+ ws.ws_col = ws.ws_row = 0;
+ if ((ioctl (wf->fd, TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_col == 0)
+ {
+ char *p = getenv ("COLUMNS");
+ if (p)
+ {
+ unsigned long n;
+ char *ep;
+ errno = 0;
+ n = strtoul (p, &ep, 10);
+ if (!(errno || *ep || n > UINT_MAX))
+ r = n;
+ }
+ else
+ r = DEFAULT_RIGHT_MARGIN;
+ }
+ else
+ r = ws.ws_col;
+ return r;
+}
+
+/*
+ * Create a wordwrap file operating on file descriptor FD.
+ * In the contrast to the libc fdopen, the descriptor is dup'ed.
+ * Left margin is set to 0, right margin is auto detected.
+ */
+WORDWRAP_FILE
+wordwrap_fdopen (int fd)
+{
+ struct wordwrap_file *wf;
+ int ec;
+
+ if ((wf = calloc (1, sizeof (*wf))) == NULL)
+ return NULL;
+ if ((wf->fd = dup (fd)) == -1)
+ {
+ ec = errno;
+ free (wf);
+ errno = ec;
+ return NULL;
+ }
+
+ wf->last_ws = UNSET;
+ wf->word_start = UNSET;
+ wf->next_left_margin = UNSET;
+
+ wordwrap_set_right_margin (wf, 0);
+
+ return wf;
+}
+
+/*
+ * Close the descriptor associated with the wordwrap file, and deallocate
+ * the memory.
+ */
+int
+wordwrap_close (WORDWRAP_FILE wf)
+{
+ int rc;
+
+ rc = wordwrap_flush (wf);
+ close (wf->fd);
+ free (wf->buffer);
+ free (wf);
+
+ return rc;
+}
+
+/*
+ * Return true if wordwrap file is at the beginning of line.
+ */
+int
+wordwrap_at_bol (WORDWRAP_FILE wf)
+{
+ return wf->column == wf->left_margin;
+}
+
+/*
+ * Return true if wordwrap file is at the end of line.
+ */
+int
+wordwrap_at_eol (WORDWRAP_FILE wf)
+{
+ return wf->column == wf->right_margin;
+}
+
+/*
+ * Write SIZE bytes from the buffer to the file.
+ * Return the number of bytes written.
+ * Set the file error indicator on error.
+ */
+static ssize_t
+full_write (WORDWRAP_FILE wf, size_t size)
+{
+ ssize_t total = 0;
+
+ while (total < size)
+ {
+ ssize_t n = write (wf->fd, wf->buffer + total, size - total);
+ if (n == -1)
+ {
+ wf->err = errno;
+ break;
+ }
+ if (n == 0)
+ {
+ wf->err = ENOSPC;
+ break;
+ }
+ total += n;
+ }
+ return total;
+}
+
+/*
+ * A fail-safe version of mbrtowc. If the call to mbrtowc, fails,
+ * switches the stream to the unibyte mode.
+ */
+static inline size_t
+safe_mbrtowc (WORDWRAP_FILE wf, wchar_t *wc, const char *s, mbstate_t *ps)
+{
+ if (!wf->unibyte)
+ {
+ size_t n = mbrtowc (wc, s, MB_CUR_MAX, ps);
+ if (n == (size_t) -1 || n == (size_t) -2)
+ wf->unibyte = 1;
+ else
+ return n;
+ }
+ *wc = *(unsigned char *)s;
+ return 1;
+}
+
+/*
+ * Return length of the whitespace prefix in STR.
+ */
+static size_t
+wsprefix (WORDWRAP_FILE wf, char const *str, size_t size)
+{
+ size_t i;
+ mbstate_t mbs;
+ wchar_t wc;
+
+ memset (&mbs, 0, sizeof (mbs));
+ for (i = 0; i < size; )
+ {
+ size_t n = safe_mbrtowc (wf, &wc, &str[i], &mbs);
+
+ if (!iswblank (wc))
+ break;
+
+ i += n;
+ }
+
+ return i;
+}
+
+/*
+ * Rescan N bytes from the current buffer from the current offset.
+ * Update offset, column, and whitespace segment counters.
+ */
+static void
+wordwrap_rescan (WORDWRAP_FILE wf, size_t n)
+{
+ mbstate_t mbs;
+ wchar_t wc;
+
+ wordwrap_line_init (wf);
+
+ memset (&mbs, 0, sizeof (mbs));
+ while (wf->offset < n)
+ {
+ size_t n = safe_mbrtowc (wf, &wc, &wf->buffer[wf->offset], &mbs);
+
+ if (iswblank (wc))
+ {
+ if (ISSET (wf->last_ws) && wf->last_ws + wf->ws_run == wf->offset)
+ wf->ws_run++;
+ else
+ {
+ wf->last_ws = wf->offset;
+ wf->ws_run = 1;
+ }
+ }
+
+ wf->offset += n;
+ wf->column++;
+ }
+}
+
+/*
+ * Flush SIZE bytes from the current buffer to the FD.
+ * Reinitialize WF for the next line.
+ */
+static int
+flush_line (WORDWRAP_FILE wf, size_t size)
+{
+ ssize_t n;
+ size_t len;
+ char c;
+
+ if (ISSET (wf->last_ws) && size == wf->last_ws + wf->ws_run)
+ len = wf->last_ws;
+ else
+ len = size;
+
+ if (len > wf->left_margin)
+ {
+ n = full_write (wf, len);
+ if (n == -1)
+ return -1;
+
+ if (n < len)
+ {
+ //FIXME: this breaks column and ws tracking
+ abort ();
+ }
+ }
+
+ c = '\n';
+ write (wf->fd, &c, 1);
+
+ if (ISSET (wf->next_left_margin))
+ {
+ wf->left_margin = wf->next_left_margin;
+ wf->next_left_margin = UNSET;
+ }
+
+ n = wf->offset - size;
+ if (n > 0)
+ {
+ size_t wsn;
+
+ wsn = wsprefix (wf, wf->buffer + size, n);
+
+ size += wsn;
+ n -= wsn;
+
+ if (n)
+ memmove (wf->buffer + wf->left_margin, wf->buffer + size, n);
+ }
+
+ if (wf->indent)
+ {
+ memset (wf->buffer, ' ', wf->left_margin);
+ wf->indent = 0;
+ }
+ wordwrap_rescan (wf, wf->left_margin + n);
+
+ return 0;
+}
+
+/*
+ * Flush the wordwrap file buffer.
+ */
+int
+wordwrap_flush (WORDWRAP_FILE wf)
+{
+ if (wf->offset > wf->left_margin)
+ return flush_line (wf, wf->offset);
+ return 0;
+}
+
+/*
+ * Return error indicator (last errno value).
+ */
+int
+wordwrap_error (WORDWRAP_FILE wf)
+{
+ return wf->err;
+}
+
+/*
+ * Set left margin value.
+ */
+int
+wordwrap_set_left_margin (WORDWRAP_FILE wf, unsigned left)
+{
+ int bol;
+
+ if (left == wf->left_margin)
+ return 0;
+ else if (left >= wf->right_margin)
+ {
+ wf->err = errno = EINVAL;
+ return -1;
+ }
+
+ bol = wordwrap_at_bol (wf);
+ wf->left_margin = left;
+ if (left < wf->offset)
+ {
+ wf->indent = 1;
+ if (!bol)
+ flush_line (wf, wf->offset);//FIXME: remove trailing ws
+ }
+ else
+ {
+ wf->indent = left > wf->offset;
+ if (wf->indent)
+ memset (wf->buffer + wf->offset, ' ', wf->left_margin - wf->offset);
+ }
+ wordwrap_line_init (wf);
+
+ return 0;
+}
+
+/*
+ * Set delayed left margin value. The new value will take effect after the
+ * current line is flushed.
+ */
+int
+wordwrap_next_left_margin (WORDWRAP_FILE wf, unsigned left)
+{
+ if (left == wf->left_margin)
+ return 0;
+ else if (left >= wf->right_margin)
+ {
+ wf->err = errno = EINVAL;
+ return -1;
+ }
+ wf->next_left_margin = left;
+ wf->indent = 1;
+ return 0;
+}
+
+/*
+ * Set right margin for the file.
+ */
+int
+wordwrap_set_right_margin (WORDWRAP_FILE wf, unsigned right)
+{
+ if (right == 0)
+ right = detect_right_margin (wf);
+
+ if (right == wf->right_margin)
+ return 0;
+ else if (right <= wf->left_margin)
+ {
+ wf->err = errno = EINVAL;
+ return -1;
+ }
+ else
+ {
+ char *p;
+ size_t size;
+
+ if (right < wf->offset)
+ {
+ if (wordwrap_flush (wf))
+ return -1;
+ }
+
+ size = MB_CUR_MAX * (right + 1);
+ p = realloc (wf->buffer, size);
+ if (!p)
+ {
+ wf->err = errno;
+ return -1;
+ }
+
+ wf->buffer = p;
+ wf->bufsize = size;
+ wf->right_margin = right;
+ }
+
+ return 0;
+}
+
+/*
+ * Mark current output position as the word start. The normal whitespace
+ * splitting is disabled, until wordwrap_word_end is called or the current
+ * buffer is flushed, whichever happens first.
+ * The functions wordwrap_word_start () / wordwrap_word_end () mark the
+ * sequence of characters that should not be split on whitespace, such as,
+ * e.g. option name with argument in help output ("-f FILE").
+ */
+void
+wordwrap_word_start (WORDWRAP_FILE wf)
+{
+ wf->word_start = wf->offset;
+}
+
+/*
+ * Disable word marker.
+ */
+void
+wordwrap_word_end (WORDWRAP_FILE wf)
+{
+ wf->word_start = UNSET;
+}
+
+/*
+ * Write LEN bytes from the string STR to the wordwrap file.
+ */
+int
+wordwrap_write (WORDWRAP_FILE wf, char const *str, size_t len)
+{
+ size_t i;
+ wchar_t wc;
+ mbstate_t mbs;
+
+ memset (&mbs, 0, sizeof (mbs));
+ for (i = 0; i < len; )
+ {
+ size_t n = safe_mbrtowc (wf, &wc, &str[i], &mbs);
+
+ if (wf->column + 1 == wf->right_margin || wc == '\n')
+ {
+ size_t len;
+
+ if (ISSET (wf->word_start))
+ {
+ len = wf->word_start;
+ wf->word_start = UNSET;
+ }
+ else if (!iswspace (wc) && ISSET (wf->last_ws))
+ len = wf->last_ws;
+ else
+ len = wf->offset;
+
+ flush_line (wf, len);
+ if (wc == '\n')
+ {
+ i += n;
+ continue;
+ }
+ }
+
+ if (iswblank (wc))
+ {
+ if (wf->offset == wf->left_margin)
+ {
+ /* Skip leading whitespace */
+ i += n;
+ continue;
+ }
+ else if (ISSET (wf->last_ws) && wf->last_ws + wf->ws_run == wf->offset)
+ wf->ws_run++;
+ else
+ {
+ wf->last_ws = wf->offset;
+ wf->ws_run = 1;
+ }
+ }
+
+ memcpy (wf->buffer + wf->offset, str + i, n);
+
+ wf->offset += n;
+ wf->column++;
+
+ i += n;
+ }
+ return 0;
+}
+
+/*
+ * Write a nul-terminated string STR to the file (terminating \0 not
+ * included).
+ */
+int
+wordwrap_puts (WORDWRAP_FILE wf, char const *str)
+{
+ return wordwrap_write (wf, str, strlen (str));
+}
+
+/*
+ * Write a single character to the file.
+ */
+int
+wordwrap_putc (WORDWRAP_FILE wf, int c)
+{
+ char ch = c;
+ return wordwrap_write (wf, &ch, 1);
+}
+
+/*
+ * Insert a paragraph (empty line).
+ */
+int
+wordwrap_para (WORDWRAP_FILE wf)
+{
+ return wordwrap_write (wf, "\n\n", 2);
+}
+
+/*
+ * Format AP according to FMT and write the formatted output to file.
+ */
+int
+wordwrap_vprintf (WORDWRAP_FILE wf, char const *fmt, va_list ap)
+{
+ size_t buflen = 64;
+ char *buf;
+ ssize_t n;
+ int rc;
+
+ buf = malloc (buflen);
+ if (!buf)
+ {
+ wf->err = errno;
+ return -1;
+ }
+
+ for (;;)
+ {
+ va_list aq;
+
+ va_copy (aq, ap);
+ n = vsnprintf (buf, buflen, fmt, aq);
+ va_end (aq);
+
+ if (n < 0 || n >= buflen || !memchr(buf, '\0', n + 1))
+ {
+ char *p;
+
+ if ((size_t) -1 / 3 * 2 <= buflen)
+ {
+ wf->err = ENOMEM;
+ free (buf);
+ return -1;
+ }
+
+ buflen += (buflen + 1) / 2;
+ p = realloc (buf, buflen);
+ if (!p)
+ {
+ wf->err = errno;
+ free (buf);
+ return -1;
+ }
+ buf = p;
+ }
+ else
+ break;
+ }
+
+ rc = wordwrap_write (wf, buf, n);
+ free (buf);
+ return rc;
+}
+
+/*
+ * Format argument list according to FMT and write the formatted output
+ * to file.
+ */
+int
+wordwrap_printf (WORDWRAP_FILE wf, char const *fmt, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start (ap, fmt);
+ rc = wordwrap_vprintf (wf, fmt, ap);
+ va_end (ap);
+ return rc;
+}
+
+
+