summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2021-11-17 22:19:25 +0200
committerSergey Poznyakoff <gray@gnu.org>2021-11-18 12:01:11 +0200
commitbc2a36294a31e7e65004b129e2c8a5057e5f9bac (patch)
treee341d23b0d3683c33b0837dee1512f8e3aefd8f5
parent9b4ef43adf31250682625f8b482e659ea1e11413 (diff)
downloadgdbm-bc2a36294a31e7e65004b129e2c8a5057e5f9bac.tar.gz
Word wrapping output functions for gdbm apps
* src/Makefile.am (libgdbmapp_a_SOURCES): Add wordwrap.c * src/wordwrap.c: New file. * tests/Makefile.am: Add t_wordwrap and wordspit.at * tests/testsuite.at: Add new test. * tests/t_wordwrap.c: New file. * src/gdbmshell.c (help_handler): Use wordwrap functions. * src/parseopt.c: Rewrite help output using wordwrap. Add support for the ARGP_HELP_FMT environment variable. Make sure no empty strings are ever passed to gettext.
-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
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile.am5
-rw-r--r--tests/t_wordwrap.c181
-rw-r--r--tests/testsuite.at3
-rw-r--r--tests/wordwrap.at129
10 files changed, 1235 insertions, 155 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;
+}
+
+
+
diff --git a/tests/.gitignore b/tests/.gitignore
index 0addc86..f424120 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -31,3 +31,4 @@ testsuite.dir
*.sum
*.bak
site.exp
+t_wordwrap
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1323cab..3bd6b83 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -78,7 +78,8 @@ TESTSUITE_AT = \
setopt00.at\
setopt01.at\
setopt02.at\
- version.at
+ version.at\
+ wordwrap.at
TESTSUITE = $(srcdir)/testsuite
M4=m4
@@ -125,6 +126,7 @@ check_PROGRAMS = \
gtrecover\
gtver\
num2word\
+ t_wordwrap\
$(DBMPROGS)
AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_builddir)/src $(DBMINCLUDES)
@@ -138,5 +140,6 @@ dtdump_LDADD = ../src/libgdbm.la ../compat/libgdbm_compat.la
dtfetch_LDADD = ../src/libgdbm.la ../compat/libgdbm_compat.la
dtdel_LDADD = ../src/libgdbm.la ../compat/libgdbm_compat.la
d_creat_ce_LDADD = ../src/libgdbm.la ../compat/libgdbm_compat.la
+t_wordwrap_LDADD = ../src/libgdbmapp.a
SUBDIRS = gdbmtool
diff --git a/tests/t_wordwrap.c b/tests/t_wordwrap.c
new file mode 100644
index 0000000..baf0f5b
--- /dev/null
+++ b/tests/t_wordwrap.c
@@ -0,0 +1,181 @@
+/* This file is part of GDBM test suite.
+ Copyright (C) 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 2, 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+
+static void
+h_left_margin (WORDWRAP_FILE wf, char *arg)
+{
+ wordwrap_set_left_margin (wf, atoi (arg));
+}
+
+static void
+h_right_margin (WORDWRAP_FILE wf, char *arg)
+{
+ wordwrap_set_right_margin (wf, atoi (arg));
+}
+
+static void
+h_flush (WORDWRAP_FILE wf, char *arg)
+{
+ wordwrap_flush (wf);
+}
+
+static void
+h_file (WORDWRAP_FILE wf, char *arg)
+{
+ FILE *fp;
+ char buf[256];
+
+ fp = fopen (arg, "r");
+ if (!fp)
+ {
+ perror (arg);
+ exit (1);
+ }
+
+ while (fgets (buf, sizeof buf, fp))
+ {
+ wordwrap_write (wf, buf, strlen (buf));
+ }
+ fclose (fp);
+}
+
+static void
+h_newline (WORDWRAP_FILE wf, char *arg)
+{
+ wordwrap_putc (wf, '\n');
+}
+
+static void
+h_para (WORDWRAP_FILE wf, char *arg)
+{
+ wordwrap_para (wf);
+}
+
+
+struct wwt_option
+{
+ char *name;
+ int arg;
+ void (*handler) (WORDWRAP_FILE, char *);
+};
+
+struct wwt_option wwt_options[] = {
+ { "left", 1, h_left_margin },
+ { "right", 1, h_right_margin },
+ { "flush", 0, h_flush },
+ { "file", 1, h_file },
+ { "newline", 0, h_newline },
+ { "para", 0, h_para },
+ { NULL }
+};
+
+enum
+ {
+ WWT_ARG,
+ WWT_OPT,
+ WWT_ERR
+ };
+
+int
+wwt_getopt (WORDWRAP_FILE wf, char *arg)
+{
+ if (arg[0] == '-')
+ {
+ struct wwt_option *opt;
+ size_t len;
+
+ if (arg[1] == '-' && arg[2] == 0)
+ return WWT_ARG;
+
+ arg++;
+ len = strcspn (arg, "=");
+
+ for (opt = wwt_options; opt->name; opt++)
+ {
+ if (strlen (opt->name) == len && memcmp (opt->name, arg, len) == 0)
+ {
+ if (opt->arg && arg[len])
+ arg += len + 1;
+ else if (!opt->arg && !arg[len])
+ arg = NULL;
+ else
+ continue;
+ opt->handler (wf, arg);
+ return WWT_OPT;
+ }
+ }
+ return WWT_ERR;
+ }
+ return WWT_ARG;
+}
+
+int
+main (int argc, char **argv)
+{
+ WORDWRAP_FILE wf;
+ int escape = 0;
+
+ setlocale (LC_ALL, "");
+ if ((wf = wordwrap_fdopen (1)) == NULL)
+ {
+ perror ("wordwrap_open");
+ exit (1);
+ }
+
+ while (--argc)
+ {
+ char *arg = *++argv;
+
+ if (escape)
+ {
+ wordwrap_write (wf, arg, strlen (arg));
+ if (argc > 1)
+ wordwrap_write (wf, " ", 1);
+ escape = 0;
+ continue;
+ }
+
+ switch (wwt_getopt (wf, arg))
+ {
+ case WWT_ARG:
+ if (strcmp (arg, "--") == 0)
+ escape = 1;
+ else
+ {
+ wordwrap_write (wf, arg, strlen (arg));
+ if (argc > 1)
+ wordwrap_write (wf, " ", 1);
+ }
+ break;
+
+ case WWT_OPT:
+ break;
+
+ case WWT_ERR:
+ fprintf (stderr, "unrecognized option: %s\n", arg);
+ }
+ }
+
+ wordwrap_close (wf);
+}
diff --git a/tests/testsuite.at b/tests/testsuite.at
index bfc9747..8bfc8fd 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -78,6 +78,9 @@ m4_include([cloexec01.at])
m4_include([cloexec02.at])
m4_include([cloexec03.at])
+AT_BANNER([Wordwrap])
+m4_include([wordwrap.at])
+
AT_BANNER([gdbmtool])
m4_include([gdbmtool00.at])
m4_include([gdbmtool01.at])
diff --git a/tests/wordwrap.at b/tests/wordwrap.at
new file mode 100644
index 0000000..a3031b6
--- /dev/null
+++ b/tests/wordwrap.at
@@ -0,0 +1,129 @@
+# This file is part of GDBM. -*- autoconf -*-
+# Copyright (C) 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 2, 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/>. */
+
+AT_SETUP([wordwrap])
+
+AT_CHECK([t_wordwrap -right=10 abcdefghi],
+[0],
+[abcdefghi
+])
+
+AT_CHECK([t_wordwrap -right=10 abcdefghijklmn],
+[0],
+[abcdefghi
+jklmn
+])
+
+AT_CHECK([t_wordwrap -right=10 abcd efg hijklmn],
+[0],
+[abcd efg
+hijklmn
+])
+
+AT_CHECK([t_wordwrap -left=2 -right=10 Donec condimentum orciluctus facilisis interdum],
+[0],
+[ Donec
+ condime
+ ntum
+ orciluc
+ tus
+ facilis
+ is
+ interdu
+ m
+])
+
+AT_DATA([1.in],
+[Cras non est eleifend diam placerat aliquet. Phasellus sem dui, tincidunt eget interdum sed, auctor nec nibh. Donec condimentum orci luctus facilisis interdum. Nam sed rhoncus neque. Aliquam pellentesque ligula massa, et maximus augue commodo et. Sed et mi dolor.
+])
+
+AT_CHECK([t_wordwrap -left=10 -right=30 -file=1.in],
+[0],
+[ Cras non est
+ eleifend diam
+ placerat aliquet.
+ Phasellus sem dui,
+ tincidunt eget
+ interdum sed,
+ auctor nec nibh.
+ Donec condimentum
+ orci luctus
+ facilisis interdum.
+ Nam sed rhoncus
+ neque. Aliquam
+ pellentesque ligula
+ massa, et maximus
+ augue commodo et.
+ Sed et mi dolor.
+])
+
+AT_DATA([2.in],
+[Cras non est eleifend diam placerat aliquet. Phasellus sem dui,
+tincidunt eget interdum sed, auctor nec
+nibh.
+Donec condimentum orci luctus facilisis interdum.
+
+Nam sed rhoncus neque.
+Aliquam pellentesque ligula massa,
+et maximus augue commodo et. Sed et mi dolor.
+])
+
+AT_CHECK([t_wordwrap -left=10 -right=30 -file=2.in],
+[0],
+[ Cras non est
+ eleifend diam
+ placerat aliquet.
+ Phasellus sem dui,
+ tincidunt eget
+ interdum sed,
+ auctor nec
+ nibh.
+ Donec condimentum
+ orci luctus
+ facilisis interdum.
+
+ Nam sed rhoncus
+ neque.
+ Aliquam
+ pellentesque ligula
+ massa,
+ et maximus augue
+ commodo et. Sed et
+ mi dolor.
+])
+
+AT_CHECK([t_wordwrap -left=10 -right=64 \
+ -- '-a, --aenean' -left=30 'aenean quis posuere lacus a suscipit erat, donec sit amet pretium ligula, non ex et nisi placerat suscipit.' \
+ -left=10 \
+ -- -b \
+ -left=30 pulvinar tempor erat \
+ -left=10 \
+ -- '--porttitor, --ullamcore, --diam-velit-consecretur' \
+ -left=30 \
+ lorem ipsum uis aute irure dolor in reprehenderit in voluptate velit esse],
+[0],
+[ -a, --aenean aenean quis posuere lacus a
+ suscipit erat, donec sit amet
+ pretium ligula, non ex et nisi
+ placerat suscipit.
+ -b pulvinar tempor erat
+ --porttitor, --ullamcore, --diam-velit-consecretur
+ lorem ipsum uis aute irure dolor
+ in reprehenderit in voluptate
+ velit esse
+])
+
+AT_CLEANUP \ No newline at end of file