]> git.draconx.ca Git - dxcommon.git/blobdiff - src/help.c
Import getline helper from cdecl99.
[dxcommon.git] / src / help.c
index 43bb266d4df4dc8c5dc17ed7519abd49d1bdb088..2e6043bea760b74c58d3b644358f48808a768324 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021-2022 Nick Bowler
+ * Copyright © 2021-2024 Nick Bowler
  *
  * Helper functions for formatting --help program output.
  *
@@ -16,6 +16,7 @@
 #      include <config.h>
 #endif
 #include <stdio.h>
+#include <stddef.h>
 #include <string.h>
 #include <assert.h>
 #include <limits.h>
@@ -26,6 +27,9 @@
 #ifndef ENABLE_NLS
 #      define ENABLE_NLS 0
 #endif
+#ifndef HELP_GETOPT_LONG_ONLY
+#      define HELP_GETOPT_LONG_ONLY 0
+#endif
 
 #if ENABLE_NLS
 #      include <locale.h>
@@ -37,7 +41,8 @@
 #      define mbsnwidth(a, b, c) (assert(0), 0)
 #endif
 
-#define _(s) gettext(s)
+#define STR_L10N_(x)
+#define N_(s) s
 
 /* Returns a single numeric value depending on the type of option:
  *
@@ -53,8 +58,10 @@ static int option_type(const struct option *opt)
 {
        int ret = opt->has_arg & 3;
 
+#if !HELP_GETOPT_LONG_ONLY
        if (!opt->flag)
                ret |= (opt->val <= CHAR_MAX) << 2;
+#endif
 
        return ret;
 }
@@ -65,71 +72,104 @@ enum {
        OPT_SHORT_WITHOUT_ARG        = 4,
        OPT_LONG_WITH_OPTIONAL_ARG   = 2,
        OPT_LONG_WITH_MANDATORY_ARG  = 1,
-       OPT_LONG_WITHOUT_ARG         = 0,
+       OPT_LONG_WITHOUT_ARG         = 0
+};
+
+#if HELP_GETOPT_LONG_ONLY
+/* String table generated by gen-strtab.awk from help-glo.str */
+#define STRTAB_INITIALIZER \
+            N_(" -%s [%s]") \
+       "\0" "%*s%.*s\n" \
+       "\0" N_(" -%s %s") \
+       "\0" N_(" -%s") \
+       ""
+enum {
+       fmt_long_optional_arg = 0,
+       fmt_long_mandatory_arg = 19,
+       fmt_long_without_arg = 27,
+       fmt_desc = 10,
+       fmt_nl = 17
+};
+#else
+/* String table generated by gen-strtab.awk from help-std.str */
+#define STRTAB_INITIALIZER \
+       STR_L10N_(N_(" --%s[=%s]")) \
+       STR_L10N_(N_(" --%s=%s")) \
+       STR_L10N_(N_(" --%s")) \
+            N_(" -%c, --%s[=%s]") \
+       "\0" N_(" -%c, --%s=%s") \
+       "\0" N_(" -%c, --%s") \
+       "\0" "%*s%.*s\n" \
+       ""
+enum {
+       fmt_short_optional_arg = 0,
+       fmt_long_optional_arg = 5,
+       fmt_short_mandatory_arg = 16,
+       fmt_long_mandatory_arg = 21,
+       fmt_short_without_arg = 30,
+       fmt_long_without_arg = 35,
+       fmt_desc = 41,
+       fmt_nl = 48
 };
+#endif
+
+static const struct optstr {
+       unsigned char map[HELP_GETOPT_LONG_ONLY ? 3 : 7];
+       char tab[sizeof (STRTAB_INITIALIZER)];
+} optstr = {
+       {
+               offsetof(struct optstr, tab) + fmt_long_without_arg,
+               offsetof(struct optstr, tab) + fmt_long_mandatory_arg,
+               offsetof(struct optstr, tab) + fmt_long_optional_arg,
+#if !HELP_GETOPT_LONG_ONLY
+               0,
+               offsetof(struct optstr, tab) + fmt_short_without_arg,
+               offsetof(struct optstr, tab) + fmt_short_mandatory_arg,
+               offsetof(struct optstr, tab) + fmt_short_optional_arg,
+#endif
+       },
+       STRTAB_INITIALIZER
+};
+
+/*
+ * When NLS is enabled, we assume snprintf is available.  The GNU libintl
+ * library should provide a suitable fallback if necessary.
+ */
+#if ENABLE_NLS && !defined(help_do_snprintf)
+#define help_do_snprintf snprintf
+#elif !ENABLE_NLS
+#define help_do_snprintf fake_snprintf
+static inline int fake_snprintf(char *buf, size_t n, const char *fmt, ...)
+{
+       return -1;
+}
+#endif
+
+#define has_short_char(type) (!HELP_GETOPT_LONG_ONLY && (type & 4))
 
 int help_print_optstring(const struct option *opt, const char *argname, int l)
 {
-       char optstring[100];
+       int type = option_type(opt);
+       const char *fmt, *nls_arg;
+       char optstring[256];
        int w;
 
+       fmt = (char *)&optstr + optstr.map[type];
        if (!ENABLE_NLS)
                goto no_translate;
 
-       switch (option_type(opt)) {
-#if HELP_GETOPT_LONG_ONLY
-       case OPT_SHORT_WITH_OPTIONAL_ARG:
-       case OPT_LONG_WITH_OPTIONAL_ARG:
-               w = snprintf(optstring, sizeof optstring,
-                            _("  -%s [%s]"), opt->name,
-                            pgettext_expr(opt->name, argname));
-               break;
-       case OPT_SHORT_WITH_MANDATORY_ARG:
-       case OPT_LONG_WITH_MANDATORY_ARG:
-               w = snprintf(optstring, sizeof optstring,
-                            _("  -%s %s"), opt->name,
-                            pgettext_expr(opt->name, argname));
-               break;
-       case OPT_SHORT_WITHOUT_ARG:
-       case OPT_LONG_WITHOUT_ARG:
-               w = snprintf(optstring, sizeof optstring,
-                            _("  -%s"), opt->name);
-               break;
-#else
-       case OPT_SHORT_WITH_OPTIONAL_ARG:
-               w = snprintf(optstring, sizeof optstring,
-                            _("  -%c, --%s[=%s]"), opt->val, opt->name,
-                            pgettext_expr(opt->name, argname));
-               break;
-       case OPT_LONG_WITH_OPTIONAL_ARG:
-               w = snprintf(optstring, sizeof optstring,
-                            _("  --%s[=%s]"), opt->name,
-                            pgettext_expr(opt->name, argname));
-               break;
-       case OPT_SHORT_WITH_MANDATORY_ARG:
-               w = snprintf(optstring, sizeof optstring,
-                            _("  -%c, --%s=%s"), opt->val, opt->name,
-                            pgettext_expr(opt->name, argname));
-               break;
-       case OPT_LONG_WITH_MANDATORY_ARG:
-               w = snprintf(optstring, sizeof optstring,
-                            _("  --%s=%s"), opt->name,
-                            pgettext_expr(opt->name, argname));
-               break;
-       case OPT_SHORT_WITHOUT_ARG:
-               w = snprintf(optstring, sizeof optstring,
-                            _("  -%c, --%s"), opt->val, opt->name);
-               break;
-       case OPT_LONG_WITHOUT_ARG:
-               w = snprintf(optstring, sizeof optstring,
-                            _("  --%s"), opt->name);
-               break;
-#endif
-       default:
-               assert(0);
+       nls_arg = argname ? pgettext_expr(opt->name, argname) : argname;
+       if (has_short_char(type)) {
+               w = help_do_snprintf(optstring, sizeof optstring,
+                                    gettext(fmt), opt->val, opt->name,
+                                    nls_arg);
+       } else {
+               w = help_do_snprintf(optstring, sizeof optstring,
+                                    gettext(fmt), opt->name,
+                                    nls_arg);
        }
 
-       if (w < 0)
+       if (w < 0 || w >= sizeof optstring)
                goto no_translate;
 
        w = mbsnwidth(optstring, w, 0);
@@ -137,45 +177,13 @@ int help_print_optstring(const struct option *opt, const char *argname, int l)
        goto out;
 
 no_translate:
-       switch (option_type(opt)) {
-#if HELP_GETOPT_LONG_ONLY
-       case OPT_SHORT_WITH_OPTIONAL_ARG:
-       case OPT_LONG_WITH_OPTIONAL_ARG:
-               w = printf("  -%s [%s]", opt->name, argname);
-               break;
-       case OPT_SHORT_WITH_MANDATORY_ARG:
-       case OPT_LONG_WITH_MANDATORY_ARG:
-               w = printf("  -%s %s", opt->name, argname);
-               break;
-       case OPT_SHORT_WITHOUT_ARG:
-       case OPT_LONG_WITHOUT_ARG:
-               w = printf("  -%s", opt->name);
-               break;
-#else
-       case OPT_SHORT_WITH_OPTIONAL_ARG:
-               w = printf("  -%c, --%s[=%s]", opt->val, opt->name, argname);
-               break;
-       case OPT_LONG_WITH_OPTIONAL_ARG:
-               w = printf("  --%s[=%s]", opt->name, argname);
-               break;
-       case OPT_SHORT_WITH_MANDATORY_ARG:
-               w = printf("  -%c, --%s=%s", opt->val, opt->name, argname);
-               break;
-       case OPT_LONG_WITH_MANDATORY_ARG:
-               w = printf("  --%s=%s", opt->name, argname);
-               break;
-       case OPT_SHORT_WITHOUT_ARG:
-               w = printf("  -%c, --%s", opt->val, opt->name);
-               break;
-       case OPT_LONG_WITHOUT_ARG:
-               w = printf("  --%s", opt->name);
-               break;
-#endif
-       default:
-               assert(0);
+       if (has_short_char(type)) {
+               w = printf(fmt, opt->val, opt->name, argname);
+       } else {
+               w = printf(fmt, opt->name, argname);
        }
 out:
-       if (w < 0 || w > l) {
+       if (w <= 0 || w > l) {
                putchar('\n');
                return 0;
        }
@@ -188,14 +196,11 @@ void help_print_desc(const struct option *opt, const char *s, int i, int w)
        if (opt)
                s = pgettext_expr(opt->name, s);
 
-       for (; *s; w = 0) {
-               const char *nl = strchr(s, '\n');
-               int n = (nl ? nl-s : -1);
-
-               printf("%*s%.*s\n", i-w, "", n, s);
-               if (!nl)
-                       break;
+       do {
+               size_t n = strcspn(s, &optstr.tab[fmt_nl]);
 
-               s = nl+1;
-       }
+               printf(&optstr.tab[fmt_desc], n ? i-w : 0, "", (int)n, s);
+               s += n + (s[n] != '\0');
+               w = 0;
+       } while (*s);
 }