From 34fe796806d03a93958831642f89545dd060c839 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Sat, 29 Jul 2023 21:32:34 +0100 Subject: [PATCH] Refactored "--help" to support multi-byte character locales when word wrapping, and clear all "splint" and "flawfinder" warnings. --- autoconf/configure.in | 1 + autoconf/header.in | 3 + src/include/library/gettext.h | 20 +- src/library/gettext.c | 18 +- src/main/help.c | 702 ++++++++++++++++++++++++++-------- src/nls/de.po | 4 +- src/nls/fr.po | 4 +- src/nls/pl.po | 4 +- src/nls/pt.po | 4 +- src/nls/pv.pot | 2 +- 10 files changed, 569 insertions(+), 193 deletions(-) diff --git a/autoconf/configure.in b/autoconf/configure.in index 72dd87d..aedcb4b 100644 --- a/autoconf/configure.in +++ b/autoconf/configure.in @@ -40,6 +40,7 @@ AC_CHECK_FUNCS(memcpy basename vsnprintf strlcat) AC_CHECK_FUNCS(fdatasync) AC_CHECK_FUNCS(fpathconf sysconf posix_memalign) AC_CHECK_HEADERS(limits.h) +AC_CHECK_HEADERS(wctype.h) AC_ARG_ENABLE(debugging, [ --enable-debugging compile with debugging support], diff --git a/autoconf/header.in b/autoconf/header.in index 0510a81..b65c481 100644 --- a/autoconf/header.in +++ b/autoconf/header.in @@ -117,6 +117,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_WCHAR_H +/* Define to 1 if you have the header file. */ +#undef HAVE_WCTYPE_H + /* Define to 1 if the system has the type `_Bool'. */ #undef HAVE__BOOL diff --git a/src/include/library/gettext.h b/src/include/library/gettext.h index 821e0eb..06e51af 100644 --- a/src/include/library/gettext.h +++ b/src/include/library/gettext.h @@ -29,22 +29,26 @@ #else # define _(String) minigettext (String) # define N_(String) (String) -# define setlocale minisetlocale +# ifdef HAVE_LOCALE_H +# include +# else +# define setlocale minisetlocale +# ifndef LC_ALL +# define LC_ALL "" +# endif +# endif # define bindtextdomain minibindtextdomain # define textdomain minitextdomain -# ifndef LC_ALL -# define LC_ALL "" -# endif #endif #ifdef __cplusplus extern "C" { #endif -char *minisetlocale(char *, char *); -char *minibindtextdomain(char *, char *); -char *minitextdomain(char *); -char *minigettext(char *); +char *minisetlocale(const char *, const char *); +char *minibindtextdomain(const char *, const char *); +char *minitextdomain(const char *); +char *minigettext(const char *); #ifdef __cplusplus } diff --git a/src/library/gettext.c b/src/library/gettext.c index f417281..80e100f 100644 --- a/src/library/gettext.c +++ b/src/library/gettext.c @@ -38,25 +38,25 @@ struct msgtable_s *minigettext__gettable(char *a) } #endif /* ENABLE_NLS */ -char *minisetlocale(char *a, char *b) +char *minisetlocale(const char *a, const char *b) { return NULL; } -char *minibindtextdomain(char *a, char *b) +char *minibindtextdomain(const char *a, const char *b) { return NULL; } -char *minitextdomain(char *a) +char *minitextdomain(const char *a) { return NULL; } -char *minigettext(char *msgid) +char *minigettext(const char *msgid) { static struct msgtable_s *table = NULL; static int tried_lang = 0; @@ -64,7 +64,7 @@ char *minigettext(char *msgid) int i; if (NULL == msgid) - return msgid; + return NULL; if (0 == tried_lang) { lang = getenv("LANGUAGE"); @@ -93,19 +93,19 @@ char *minigettext(char *msgid) } if (NULL == table) - return msgid; + return (char *) msgid; for (i = 0; table[i].msgid; i++) { if (0 == strcmp(table[i].msgid, msgid)) { if (0 == table[i].msgstr) - return msgid; + return (char *) msgid; if (0 == table[i].msgstr[0]) - return msgid; + return (char *) msgid; return table[i].msgstr; } } - return msgid; + return (char *) msgid; } #endif /* HAVE_GETTEXT */ diff --git a/src/main/help.c b/src/main/help.c index 5824d88..29ce32d 100644 --- a/src/main/help.c +++ b/src/main/help.c @@ -12,12 +12,264 @@ #include #include #include +#if defined(ENABLE_NLS) && defined(HAVE_WCHAR_H) +#include +#if defined(HAVE_WCTYPE_H) +#include +#endif +#endif -struct optdesc_s { - char *optshort; - char *optlong; - char *param; - char *description; + +/* + * Return the number of display columns needed to show the given string. + * + * To do this, we convert it to a wide character string, and use the wide + * character display width function "wcswidth()" on it. + * + * If NLS is disabled, or the string cannot be converted, this is just the + * same as "strlen()". + */ +static size_t display_width(const char *string) +{ + size_t width; + +#if defined(ENABLE_NLS) && defined(HAVE_WCHAR_H) + size_t wide_char_count; + size_t wide_string_buffer_size; + wchar_t *wide_string; + + if (NULL == string) + return 0; + + /*@-nullpass@ */ + /* + * splint note: mbstowcs() manual page on Linux explicitly says it + * takes NULL. + */ + wide_char_count = mbstowcs(NULL, string, 0); + /*@+nullpass@ */ + if (wide_char_count == (size_t) -1) { + debug("%s: %s: %s", "mbstowcs", string, strerror(errno)); + return strlen(string); /* flawfinder: ignore */ + /* + * flawfinder rationale: we have already checked for NULL, + * and we don't know the size of the originating buffer so + * can't use strnlen(); it is up to the caller to provide a + * terminated string. + */ + } + + wide_string_buffer_size = sizeof(*wide_string) * (1 + wide_char_count); + wide_string = malloc(wide_string_buffer_size); + if (NULL == wide_string) { + perror("malloc"); + return strlen(string); /* flawfinder: ignore */ + /* flawfinder rationale: see above. */ + } + memset(wide_string, 0, wide_string_buffer_size); + + if (mbstowcs(wide_string, string, 1 + wide_char_count) == (size_t) -1) { + debug("%s: %s: %s", "mbstowcs", string, strerror(errno)); + width = strlen(string); /* flawfinder: ignore */ + /* flawfinder rationale: see above. */ + } else if (NULL != wide_string) { + /*@-unrecog @ */ + /* splint seems unable to see the prototype. */ + width = wcswidth(wide_string, wide_char_count); + /*@+unrecog @ */ + } else { + width = 0; + } + + free(wide_string); + +#else /* ! defined(ENABLE_NLS) && defined(HAVE_WCHAR_H) */ + if (NULL == string) + return 0; + + width = strlen(string); /* flawfinder: ignore */ + /* flawfinder rationale: see above. */ +#endif /* defined(ENABLE_NLS) && defined(HAVE_WCHAR_H) */ + + return width; +} + + +/* + * The 7-bit ASCII version of display_word_wrap() below - does not + * understand multi-byte characters. + */ +static void display_word_wrap_7bit(const char *string, size_t display_width, size_t left_margin) +{ + const char *start; + const char *end; + + if (NULL == string) + return; + + start = string; + + while (strlen(start) > display_width) { /* flawfinder: ignore */ + /* flawfinder rationale: see above. */ + + end = start + display_width; + while ((end > start) && (end[0] != ' ')) + end--; + if (end == start) { + end = start + display_width; + } else { + end++; + } + printf("%.*s", (int) (end - start), start); + if (end == start) + end++; + start = end; + if (start[0] != '\0') + printf("\n%*s", (int) left_margin, ""); + } + + printf("%s", start); +} + + +/* + * Output a string to standard output, word wrapping to "display_width" + * display character positions, and left-padding any new lines after the + * first one with "left_margin" spaces. + * + * Wide characters are handled if NLS is enabled, but if they can't be, this + * falls back to a version which just counts bytes as characters. + */ +static void display_word_wrap(const char *string, size_t display_width, size_t left_margin) +{ +#if defined(ENABLE_NLS) && defined(HAVE_WCHAR_H) + size_t wide_char_count; + size_t wide_string_buffer_size; + wchar_t *wide_string; + size_t start_idx, end_idx, chars_remaining; + + if (NULL == string) + return; + + /*@-nullpass@ */ + /* splint note: see earlier mbstowcs() call. */ + wide_char_count = mbstowcs(NULL, string, 0); + /*@+nullpass@ */ + if (wide_char_count == (size_t) -1) { + debug("%s: %s: %s", "mbstowcs", string, strerror(errno)); + display_word_wrap_7bit(string, display_width, left_margin); + return; + } + + wide_string_buffer_size = sizeof(*wide_string) * (1 + wide_char_count); + wide_string = malloc(wide_string_buffer_size); + if (NULL == wide_string) { + perror("malloc"); + display_word_wrap_7bit(string, display_width, left_margin); + return; + } + memset(wide_string, 0, wide_string_buffer_size); + + if (mbstowcs(wide_string, string, 1 + wide_char_count) == (size_t) -1) { + debug("%s: %s: %s", "mbstowcs", string, strerror(errno)); + free(wide_string); + display_word_wrap_7bit(string, display_width, left_margin); + return; + } + + start_idx = 0; + chars_remaining = wide_char_count; + + while (chars_remaining > 0 && wcswidth(&(wide_string[start_idx]), chars_remaining) > display_width) { + size_t next_idx; + + end_idx = start_idx + display_width; + while ((end_idx > start_idx) && (!iswspace(wide_string[end_idx]))) + end_idx--; + if (end_idx == start_idx) { + end_idx = start_idx + display_width; + } else { + end_idx++; + } + + next_idx = end_idx; + if (end_idx == start_idx) + next_idx++; + + while (start_idx < end_idx && start_idx < wide_char_count) { + char multi_byte_string[MB_CUR_MAX + 1]; /* flawfinder: ignore */ + /* + * flawfinder rationale: array is explicitly + * cleared, large enough according to the wctomb() + * manual, and we explicitly terminate the string. + */ + memset(multi_byte_string, 0, MB_CUR_MAX + 1); + if (wctomb(multi_byte_string, wide_string[start_idx]) >= 0) { + multi_byte_string[MB_CUR_MAX] = '\0'; + printf("%s", multi_byte_string); + } + start_idx++; + } + + start_idx = next_idx; + if (start_idx < wide_char_count) + printf("\n%*s", (int) left_margin, ""); + + chars_remaining = wide_char_count - start_idx; + } + + while (start_idx < wide_char_count) { + char multi_byte_string[MB_CUR_MAX + 1]; /* flawfinder: ignore */ + /* flawfinder rationale as above. */ + memset(multi_byte_string, 0, MB_CUR_MAX + 1); + if (wctomb(multi_byte_string, wide_string[start_idx]) >= 0) { + multi_byte_string[MB_CUR_MAX] = '\0'; + printf("%s", multi_byte_string); + } + start_idx++; + } + + free(wide_string); + +#else /* ! defined(ENABLE_NLS) && defined(HAVE_WCHAR_H) */ + display_word_wrap_7bit(string, display_width, left_margin); +#endif /* defined(ENABLE_NLS) && defined(HAVE_WCHAR_H) */ +} + + +/* + * Structure holding the displayed descriptions of each option - the short + * option such as "-s", its long counterpart such as "--size", the name of + * its argument such as "SIZE", and the description of what the option does. + * Any of them may be NULL. + * + * In the initialiser for this structure, translatable strings are wrapped + * with N_() to indicate that they are to be translated into the operator's + * language by this function at run-time. + * + * Note that "opt_short" and "opt_long" should never be marked as + * translatable, as they must remain consistent across all locales. + * + * The list is terminated with a NULL opt_short value - to leave a gap, set + * opt_short to an empty string instead. + */ +struct option_definition_s { + /*@null@ */ const char *opt_short; + /*@null@ */ const char *opt_long; + /*@null@ *//*@observer@ */ const char *opt_argument; + /*@null@ *//*@observer@ */ const char *opt_description; + struct { + /* + * Structure holding the width, in display character + * positions, of the individual parts of the description of + * each option - calculated after initialisation, so that + * translation can be performed. + */ + size_t opt_short; + size_t opt_long; + size_t opt_argument; + size_t opt_description; + } width; }; @@ -26,195 +278,311 @@ struct optdesc_s { */ void display_help(void) { - struct optdesc_s optlist[] = { - { "-p", "--progress", 0, - N_("show progress bar") }, - { "-t", "--timer", 0, - N_("show elapsed time") }, - { "-e", "--eta", 0, - N_("show estimated time of arrival (completion)") }, - { "-I", "--fineta", 0, - N_("show absolute estimated time of arrival (completion)") - }, - { "-r", "--rate", 0, - N_("show data transfer rate counter") }, - { "-a", "--average-rate", 0, - N_("show data transfer average rate counter") }, + struct option_definition_s option_definitions[] = { + { "-p", "--progress", NULL, + N_("show progress bar"), + { 0, 0, 0, 0} }, + { "-t", "--timer", NULL, + N_("show elapsed time"), + { 0, 0, 0, 0} }, + { "-e", "--eta", NULL, + N_("show estimated time of arrival (completion)"), + { 0, 0, 0, 0} }, + { "-I", "--fineta", NULL, + N_("show absolute estimated time of arrival (completion)"), + { 0, 0, 0, 0} }, + { "-r", "--rate", NULL, + N_("show data transfer rate counter"), + { 0, 0, 0, 0} }, + { "-a", "--average-rate", NULL, + N_("show data transfer average rate counter"), + { 0, 0, 0, 0} }, { "-m", "--average-rate-window", N_("SEC"), - N_ - ("compute average rate over past SEC seconds (default 30s)") - }, - { "-b", "--bytes", 0, - N_("show number of bytes transferred") }, - { "-8", "--bits", 0, - N_("show number of bits transferred") }, - { "-T", "--buffer-percent", 0, - N_("show percentage of transfer buffer in use") }, + N_("compute average rate over past SEC seconds (default 30s)"), + { 0, 0, 0, 0} }, + { "-b", "--bytes", NULL, + N_("show number of bytes transferred"), + { 0, 0, 0, 0} }, + { "-8", "--bits", NULL, + N_("show number of bits transferred"), + { 0, 0, 0, 0} }, + { "-T", "--buffer-percent", NULL, + N_("show percentage of transfer buffer in use"), + { 0, 0, 0, 0} }, { "-A", "--last-written", _("NUM"), - N_("show NUM bytes last written") }, + N_("show NUM bytes last written"), + { 0, 0, 0, 0} }, { "-F", "--format", N_("FORMAT"), - N_("set output format to FORMAT") }, - { "-n", "--numeric", 0, - N_("output percentages, not visual information") }, - { "-q", "--quiet", 0, - N_("do not output any transfer information at all") }, - { "", 0, 0, 0 }, - { "-W", "--wait", 0, - N_("display nothing until first byte transferred") }, + N_("set output format to FORMAT"), + { 0, 0, 0, 0} }, + { "-n", "--numeric", NULL, + N_("output percentages, not visual information"), + { 0, 0, 0, 0} }, + { "-q", "--quiet", NULL, + N_("do not output any transfer information at all"), + { 0, 0, 0, 0} }, + { "", NULL, NULL, NULL, { 0, 0, 0, 0} }, + { "-W", "--wait", NULL, + N_("display nothing until first byte transferred"), + { 0, 0, 0, 0} }, { "-D", "--delay-start", N_("SEC"), - N_("display nothing until SEC seconds have passed") }, + N_("display nothing until SEC seconds have passed"), + { 0, 0, 0, 0} }, { "-s", "--size", N_("SIZE"), - N_("set estimated data size to SIZE bytes") }, - { "-l", "--line-mode", 0, - N_("count lines instead of bytes") }, - { "-0", "--null", 0, - N_("lines are null-terminated") }, + N_("set estimated data size to SIZE bytes"), + { 0, 0, 0, 0} }, + { "-l", "--line-mode", NULL, + N_("count lines instead of bytes"), + { 0, 0, 0, 0} }, + { "-0", "--null", NULL, + N_("lines are null-terminated"), + { 0, 0, 0, 0} }, { "-i", "--interval", N_("SEC"), - N_("update every SEC seconds") }, + N_("update every SEC seconds"), + { 0, 0, 0, 0} }, { "-w", "--width", N_("WIDTH"), - N_("assume terminal is WIDTH characters wide") }, + N_("assume terminal is WIDTH characters wide"), + { 0, 0, 0, 0} }, { "-H", "--height", N_("HEIGHT"), - N_("assume terminal is HEIGHT rows high") }, + N_("assume terminal is HEIGHT rows high"), + { 0, 0, 0, 0} }, { "-N", "--name", N_("NAME"), - N_("prefix visual information with NAME") }, - { "-f", "--force", 0, - N_("output even if standard error is not a terminal") }, - { "-c", "--cursor", 0, - N_("use cursor positioning escape sequences") }, - { "", 0, 0, 0 }, + N_("prefix visual information with NAME"), + { 0, 0, 0, 0} }, + { "-f", "--force", NULL, + N_("output even if standard error is not a terminal"), + { 0, 0, 0, 0} }, + { "-c", "--cursor", NULL, + N_("use cursor positioning escape sequences"), + { 0, 0, 0, 0} }, + { "", NULL, NULL, NULL, { 0, 0, 0, 0} }, { "-L", "--rate-limit", N_("RATE"), - N_("limit transfer to RATE bytes per second") }, + N_("limit transfer to RATE bytes per second"), + { 0, 0, 0, 0} }, { "-B", "--buffer-size", N_("BYTES"), - N_("use a buffer size of BYTES") }, - { "-C", "--no-splice", 0, - N_("never use splice(), always use read/write") }, - { "-E", "--skip-errors", 0, - N_("skip read errors in input") }, - { "-S", "--stop-at-size", 0, - N_("stop after --size bytes have been transferred") }, - { "-Y", "--sync", 0, - N_("flush cache to disk after every write") }, - { "-K", "--direct-io", 0, - N_("use direct I/O to bypass cache") }, + N_("use a buffer size of BYTES"), + { 0, 0, 0, 0} }, + { "-C", "--no-splice", NULL, + N_("never use splice(), always use read/write"), + { 0, 0, 0, 0} }, + { "-E", "--skip-errors", NULL, + N_("skip read errors in input"), + { 0, 0, 0, 0} }, + { "-S", "--stop-at-size", NULL, + N_("stop after --size bytes have been transferred"), + { 0, 0, 0, 0} }, + { "-Y", "--sync", NULL, + N_("flush cache to disk after every write"), + { 0, 0, 0, 0} }, + { "-K", "--direct-io", NULL, + N_("use direct I/O to bypass cache"), + { 0, 0, 0, 0} }, #ifdef HAVE_IPC { "-R", "--remote", N_("PID"), - N_("update settings of process PID") }, + N_("update settings of process PID"), + { 0, 0, 0, 0} }, #endif /* HAVE_IPC */ - { "", 0, 0, 0 }, + { "", NULL, NULL, NULL, { 0, 0, 0, 0} }, { "-P", "--pidfile", N_("FILE"), - N_("save process ID in FILE") }, - { "", 0, 0, 0 }, + N_("save process ID in FILE"), + { 0, 0, 0, 0} }, + { "", NULL, NULL, NULL, { 0, 0, 0, 0} }, { "-d", "--watchfd", N_("PID[:FD]"), - N_("watch file FD opened by process PID") }, - { "", 0, 0, 0 }, - { "-h", "--help", 0, - N_("show this help and exit") }, - { "-V", "--version", 0, - N_("show version information and exit") }, + N_("watch file FD opened by process PID"), + { 0, 0, 0, 0} }, + { "", NULL, NULL, NULL, { 0, 0, 0, 0} }, + { "-h", "--help", NULL, + N_("show this help and exit"), + { 0, 0, 0, 0} }, + { "-V", "--version", NULL, + N_("show version information and exit"), + { 0, 0, 0, 0} }, #ifdef ENABLE_DEBUGGING { "-!", "--debug", N_("FILE"), - N_("write debug logs to FILE") }, + N_("write debug logs to FILE"), + { 0, 0, 0, 0} }, #endif - { 0, 0, 0, 0 } + { NULL, NULL, NULL, NULL, { 0, 0, 0, 0} } }; - unsigned int i, col1max = 0, tw = 77; - char *optbuf; - size_t optbuf_size; + unsigned int option_index; + size_t widest_option_width = 0; + size_t description_left_margin = 0; + size_t min_description_width = 50; + size_t right_margin = 77; + const char *program_description; + const char *bug_report_note; + unsigned int terminal_width = 0, terminal_height = 0; + pv_screensize(&terminal_width, &terminal_height); + if (terminal_width > 5) { + right_margin = (size_t) (terminal_width - 3); + } + + /*@-formatconst@ */ + /* + * splint note: unavoidable use of %s in translated string. Should + * be hard to exploit - the message catalogue would have to be + * replaced or forced to load from another location. + */ printf(_("Usage: %s [OPTION] [FILE]..."), PROGRAM_NAME); - printf("\n%s\n\n", - _ - ("Concatenate FILE(s), or standard input, to standard output,\n" - "with monitoring.")); + /*@+formatconst@ */ - for (i = 0; optlist[i].optshort; i++) { - unsigned int width = 0; - char *param; + printf("\n"); - width = 2 + (unsigned int) strlen(optlist[i].optshort); -#ifdef HAVE_GETOPT_LONG - if (optlist[i].optlong) - width += - 2 + (unsigned int) strlen(optlist[i].optlong); -#endif - param = optlist[i].param; - if (param) - param = _(param); - if (param) - width += 1 + strlen(param); - - if (width > col1max) - col1max = width; - } - - col1max++; - - optbuf_size = 16 + col1max; - optbuf = malloc(optbuf_size); - if (NULL == optbuf) { - fprintf(stderr, "%s: %s\n", PROGRAM_NAME, strerror(errno)); - exit(EXIT_FAILURE); - } - - for (i = 0; optlist[i].optshort; i++) { - char *param; - char *description; - char *start; - char *end; - - if ('\0' == optlist[i].optshort[0]) { - printf("\n"); - continue; - } - - param = optlist[i].param; - if (param) - param = _(param); - description = optlist[i].description; - if (description) - description = _(description); - - (void) pv_snprintf(optbuf, optbuf_size, "%s%s%s%s%s", - optlist[i].optshort, -#ifdef HAVE_GETOPT_LONG - optlist[i].optlong ? ", " : "", - optlist[i]. - optlong ? optlist[i].optlong : "", -#else - "", "", -#endif - param ? " " : "", param ? param : ""); - - printf(" %-*s ", (int) (col1max - 2), optbuf); - - if (NULL == description) { - printf("\n"); - continue; - } - - start = description; - - while ((unsigned int) strlen(start) > tw - col1max) { - end = start + tw - col1max; - while ((end > start) && (end[0] != ' ')) - end--; - if (end == start) { - end = start + tw - col1max; - } else { - end++; - } - printf("%.*s\n%*s ", (int) (end - start), start, - (int) col1max, ""); - if (end == start) - end++; - start = end; - } - - printf("%s\n", start); + /*@-mustfreefresh@ */ + /* + * splint note: the gettext calls made by _() cause memory leak + * warnings, but in this case it's unavoidable, and mitigated by the + * fact we only translate each string once. + */ + program_description = _("Concatenate FILE(s), or standard input, to standard output, with monitoring."); + if (NULL != program_description) { + display_word_wrap(program_description, right_margin, 0); + printf("\n"); } printf("\n"); - printf(_("Please report any bugs to %s."), BUG_REPORTS_TO); + + /* + * Translate the help text, and calculate the displayed width of + * each part of each option definition. The total display width of + * the short option, long option, and option argument together form + * the "option" width - we look for the widest one to calculate the + * left margin for all of the descriptions to start at. + */ + for (option_index = 0; NULL != option_definitions[option_index].opt_short; option_index++) { + struct option_definition_s *definition; + size_t option_width; + + definition = &(option_definitions[option_index]); + option_width = 0; + + definition->width.opt_short = display_width(definition->opt_short); + definition->width.opt_long = 0; + definition->width.opt_argument = 0; + definition->width.opt_description = 0; + + if (NULL != definition->opt_long) { + definition->width.opt_long = display_width(definition->opt_long); + } + + if (NULL != definition->opt_argument) { + /*@observer@ */ const char *translated; + translated = _(definition->opt_argument); + if (NULL != translated) { + definition->opt_argument = translated; + } + definition->width.opt_argument = display_width(definition->opt_argument); + } + + if (NULL != definition->opt_description) { + /*@observer@ */ const char *translated; + translated = _(definition->opt_description); + if (NULL != translated) { + definition->opt_description = translated; + } + definition->width.opt_description = display_width(definition->opt_description); + } + + /* + * The option_width is padded with a left margin of 2 + * spaces, a ", " between the short and long options, a + * space between the long option and the argument, and two + * spaces after the argument: + * + * " , " + * + * If we don't have getopt_long() then ", " is omitted. + */ + option_width += 2 + definition->width.opt_short; /* " short" */ +#ifdef HAVE_GETOPT_LONG + option_width += 2 + definition->width.opt_long; /* ", " */ +#endif + option_width += 1 + definition->width.opt_argument; /* " ARG" */ + option_width += 2; /* final 2 spaces */ + + if (option_width > widest_option_width) { + widest_option_width = option_width; + } + } + + /* + * Set the left margin for the option descriptions, based on the + * widest option width, or (right margin - min_description_width), + * whichever is less, so that there is always room for + * "min_description_width" characters of description. + */ + description_left_margin = right_margin - min_description_width; + if (widest_option_width < description_left_margin) { + description_left_margin = widest_option_width; + } + + debug("%s: description_left_margin=%d, widest_option_width=%d, right_margin=%d", "help display", + (int) description_left_margin, (int) widest_option_width, (int) right_margin); + + /* + * Display each of the option definitions, word wrapping the + * descriptions. + */ + for (option_index = 0; NULL != option_definitions[option_index].opt_short; option_index++) { + struct option_definition_s *definition; + size_t option_width; + + definition = &(option_definitions[option_index]); + option_width = 0; + + if (definition->width.opt_short > 0 && NULL != definition->opt_short) { + printf(" %s", definition->opt_short); + option_width += 2 + definition->width.opt_short; + } +#ifdef HAVE_GETOPT_LONG + if (definition->width.opt_long > 0 && NULL != definition->opt_long) { + printf(", %s", definition->opt_long); + option_width += 2 + definition->width.opt_long; + } +#endif + if (definition->width.opt_argument > 0 && NULL != definition->opt_argument) { + printf(" %s", definition->opt_argument); + option_width += 1 + definition->width.opt_argument; + } + + /* Just start a new line if there's no description. */ + if ((0 == definition->width.opt_description) || (NULL == definition->opt_description)) { + printf("\n"); + continue; + } + + /* + * If the option is too wide, start a new line for the + * description. In both cases, pad with spaces up to the + * description left margin. + */ + if (option_width > description_left_margin) { + printf("\n%*s", (int) description_left_margin, ""); + } else if (option_width < description_left_margin) { + printf("%*s", (int) (description_left_margin - option_width), ""); + } + + /* Output the description, word wrapped. */ + display_word_wrap(definition->opt_description, right_margin - description_left_margin, + description_left_margin); + + printf("\n"); + } + + printf("\n"); + + bug_report_note = _("Please report any bugs to %s."); + if (NULL != bug_report_note) { + /*@-formatconst@ */ + /* + * splint note: see earlier "formatconst" note. + * flawfinder: same reason. + */ + printf(bug_report_note, BUG_REPORTS_TO); /* flawfinder: ignore */ + /*@+formatconst@ */ + } + printf("\n"); } diff --git a/src/nls/de.po b/src/nls/de.po index a527165..157ebed 100644 --- a/src/nls/de.po +++ b/src/nls/de.po @@ -377,10 +377,10 @@ msgstr "Syntax: %s [OPTION] [DATEI]..." #: src/main/help.c:122 msgid "" -"Concatenate FILE(s), or standard input, to standard output,\n" +"Concatenate FILE(s), or standard input, to standard output, " "with monitoring." msgstr "" -"Verbindet DATEI(en) oder den Standard-Eingabe-Kanal mit dem\n" +"Verbindet DATEI(en) oder den Standard-Eingabe-Kanal mit dem " "Standard-Ausgabe-Kanal und misst den Datenstrom." #: src/main/help.c:215 diff --git a/src/nls/fr.po b/src/nls/fr.po index 40a1f10..ab632c1 100644 --- a/src/nls/fr.po +++ b/src/nls/fr.po @@ -376,10 +376,10 @@ msgstr "Utilisation : %s [OPTIONS] [FICHIER]..." #: src/main/help.c:122 msgid "" -"Concatenate FILE(s), or standard input, to standard output,\n" +"Concatenate FILE(s), or standard input, to standard output, " "with monitoring." msgstr "" -"Concatène FICHIER(s), ou l'entrée standard, sur la sortie standard\n" +"Concatène FICHIER(s), ou l'entrée standard, sur la sortie standard " "avec monitorage." #: src/main/help.c:215 diff --git a/src/nls/pl.po b/src/nls/pl.po index b0756b2..e946f29 100644 --- a/src/nls/pl.po +++ b/src/nls/pl.po @@ -374,10 +374,10 @@ msgstr "Sposób użycia: %s [OPCJA] [PLIK]..." #: src/main/help.c:122 msgid "" -"Concatenate FILE(s), or standard input, to standard output,\n" +"Concatenate FILE(s), or standard input, to standard output, " "with monitoring." msgstr "" -"Łączenie PLIK(ów) lub wejścia standardowego, do wyjścia standardowego\n" +"Łączenie PLIK(ów) lub wejścia standardowego, do wyjścia standardowego " "z monitoringiem." #: src/main/help.c:215 diff --git a/src/nls/pt.po b/src/nls/pt.po index 5233faf..fbff1e2 100644 --- a/src/nls/pt.po +++ b/src/nls/pt.po @@ -372,10 +372,10 @@ msgstr "Uso: %s [OPÇÕES] [ARQUIVOS]..." #: src/main/help.c:122 msgid "" -"Concatenate FILE(s), or standard input, to standard output,\n" +"Concatenate FILE(s), or standard input, to standard output, " "with monitoring." msgstr "" -"Concatena ARQUIVO(s) ou a entrada padrão e grava na saída padrão,\n" +"Concatena ARQUIVO(s) ou a entrada padrão e grava na saída padrão, " "com monitoramento." #: src/main/help.c:215 diff --git a/src/nls/pv.pot b/src/nls/pv.pot index 558cff1..2ee8263 100644 --- a/src/nls/pv.pot +++ b/src/nls/pv.pot @@ -373,7 +373,7 @@ msgstr "" #: src/main/help.c:122 msgid "" -"Concatenate FILE(s), or standard input, to standard output,\n" +"Concatenate FILE(s), or standard input, to standard output, " "with monitoring." msgstr ""