diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/gdbmapp.h | 21 | ||||
-rw-r--r-- | src/gdbmshell.c | 36 | ||||
-rw-r--r-- | src/parseopt.c | 375 | ||||
-rw-r--r-- | src/wordwrap.c | 636 |
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; +} + + + |