/* * Copyright © 2021-2024 Nick Bowler * * Helper functions for formatting --help program output. * * In order to support localized output, this depends on the Gnulib gettext-h * and mbswidth modules. However, if ENABLE_NLS is not defined (or defined to * 0) then these modules are not required. * * License WTFPL2: Do What The Fuck You Want To Public License, version 2. * This is free software: you are free to do what the fuck you want to. * There is NO WARRANTY, to the extent permitted by law. */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include "help.h" #ifndef ENABLE_NLS # define ENABLE_NLS 0 #endif #ifndef HELP_GETOPT_LONG_ONLY # define HELP_GETOPT_LONG_ONLY 0 #endif #if ENABLE_NLS # include # include # include #else # define gettext(s) (s) # define pgettext_expr(c, s) (s) # define mbsnwidth(a, b, c) (assert(0), 0) #endif #define STR_L10N_(x) #define N_(s) s /* Returns a single numeric value depending on the type of option: * * 6 - if the option has a short option character and an optional argument * 5 - if the option has a short option character and a mandatory argument * 4 - if the option has a short option charater and no argument * 3 - N/A * 2 - if the option has no short option character and an optional argument * 1 - if the option has no short option character and a mandatory argument * 0 - if the option has no short option character and no argument */ 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; } enum { OPT_SHORT_WITH_OPTIONAL_ARG = 6, OPT_SHORT_WITH_MANDATORY_ARG = 5, OPT_SHORT_WITHOUT_ARG = 4, OPT_LONG_WITH_OPTIONAL_ARG = 2, OPT_LONG_WITH_MANDATORY_ARG = 1, 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) { 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; 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 || w >= sizeof optstring) goto no_translate; w = mbsnwidth(optstring, w, 0); printf("%s", optstring); goto out; no_translate: 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) { putchar('\n'); return 0; } return w; } void help_print_desc(const struct option *opt, const char *s, int i, int w) { if (opt) s = pgettext_expr(opt->name, s); do { size_t n = strcspn(s, &optstr.tab[fmt_nl]); printf(&optstr.tab[fmt_desc], n ? i-w : 0, "", (int)n, s); s += n + (s[n] != '\0'); w = 0; } while (*s); }