help: Improve compatibility with old compilers.
[dxcommon.git] / src / help.c
blob2e6043bea760b74c58d3b644358f48808a768324
1 /*
2 * Copyright © 2021-2024 Nick Bowler
4 * Helper functions for formatting --help program output.
6 * In order to support localized output, this depends on the Gnulib gettext-h
7 * and mbswidth modules. However, if ENABLE_NLS is not defined (or defined to
8 * 0) then these modules are not required.
10 * License WTFPL2: Do What The Fuck You Want To Public License, version 2.
11 * This is free software: you are free to do what the fuck you want to.
12 * There is NO WARRANTY, to the extent permitted by law.
15 #if HAVE_CONFIG_H
16 # include <config.h>
17 #endif
18 #include <stdio.h>
19 #include <stddef.h>
20 #include <string.h>
21 #include <assert.h>
22 #include <limits.h>
23 #include <getopt.h>
25 #include "help.h"
27 #ifndef ENABLE_NLS
28 # define ENABLE_NLS 0
29 #endif
30 #ifndef HELP_GETOPT_LONG_ONLY
31 # define HELP_GETOPT_LONG_ONLY 0
32 #endif
34 #if ENABLE_NLS
35 # include <locale.h>
36 # include <gettext.h>
37 # include <mbswidth.h>
38 #else
39 # define gettext(s) (s)
40 # define pgettext_expr(c, s) (s)
41 # define mbsnwidth(a, b, c) (assert(0), 0)
42 #endif
44 #define STR_L10N_(x)
45 #define N_(s) s
47 /* Returns a single numeric value depending on the type of option:
49 * 6 - if the option has a short option character and an optional argument
50 * 5 - if the option has a short option character and a mandatory argument
51 * 4 - if the option has a short option charater and no argument
52 * 3 - N/A
53 * 2 - if the option has no short option character and an optional argument
54 * 1 - if the option has no short option character and a mandatory argument
55 * 0 - if the option has no short option character and no argument
57 static int option_type(const struct option *opt)
59 int ret = opt->has_arg & 3;
61 #if !HELP_GETOPT_LONG_ONLY
62 if (!opt->flag)
63 ret |= (opt->val <= CHAR_MAX) << 2;
64 #endif
66 return ret;
69 enum {
70 OPT_SHORT_WITH_OPTIONAL_ARG = 6,
71 OPT_SHORT_WITH_MANDATORY_ARG = 5,
72 OPT_SHORT_WITHOUT_ARG = 4,
73 OPT_LONG_WITH_OPTIONAL_ARG = 2,
74 OPT_LONG_WITH_MANDATORY_ARG = 1,
75 OPT_LONG_WITHOUT_ARG = 0
78 #if HELP_GETOPT_LONG_ONLY
79 /* String table generated by gen-strtab.awk from help-glo.str */
80 #define STRTAB_INITIALIZER \
81 N_(" -%s [%s]") \
82 "\0" "%*s%.*s\n" \
83 "\0" N_(" -%s %s") \
84 "\0" N_(" -%s") \
86 enum {
87 fmt_long_optional_arg = 0,
88 fmt_long_mandatory_arg = 19,
89 fmt_long_without_arg = 27,
90 fmt_desc = 10,
91 fmt_nl = 17
93 #else
94 /* String table generated by gen-strtab.awk from help-std.str */
95 #define STRTAB_INITIALIZER \
96 STR_L10N_(N_(" --%s[=%s]")) \
97 STR_L10N_(N_(" --%s=%s")) \
98 STR_L10N_(N_(" --%s")) \
99 N_(" -%c, --%s[=%s]") \
100 "\0" N_(" -%c, --%s=%s") \
101 "\0" N_(" -%c, --%s") \
102 "\0" "%*s%.*s\n" \
104 enum {
105 fmt_short_optional_arg = 0,
106 fmt_long_optional_arg = 5,
107 fmt_short_mandatory_arg = 16,
108 fmt_long_mandatory_arg = 21,
109 fmt_short_without_arg = 30,
110 fmt_long_without_arg = 35,
111 fmt_desc = 41,
112 fmt_nl = 48
114 #endif
116 static const struct optstr {
117 unsigned char map[HELP_GETOPT_LONG_ONLY ? 3 : 7];
118 char tab[sizeof (STRTAB_INITIALIZER)];
119 } optstr = {
121 offsetof(struct optstr, tab) + fmt_long_without_arg,
122 offsetof(struct optstr, tab) + fmt_long_mandatory_arg,
123 offsetof(struct optstr, tab) + fmt_long_optional_arg,
124 #if !HELP_GETOPT_LONG_ONLY
126 offsetof(struct optstr, tab) + fmt_short_without_arg,
127 offsetof(struct optstr, tab) + fmt_short_mandatory_arg,
128 offsetof(struct optstr, tab) + fmt_short_optional_arg,
129 #endif
131 STRTAB_INITIALIZER
135 * When NLS is enabled, we assume snprintf is available. The GNU libintl
136 * library should provide a suitable fallback if necessary.
138 #if ENABLE_NLS && !defined(help_do_snprintf)
139 #define help_do_snprintf snprintf
140 #elif !ENABLE_NLS
141 #define help_do_snprintf fake_snprintf
142 static inline int fake_snprintf(char *buf, size_t n, const char *fmt, ...)
144 return -1;
146 #endif
148 #define has_short_char(type) (!HELP_GETOPT_LONG_ONLY && (type & 4))
150 int help_print_optstring(const struct option *opt, const char *argname, int l)
152 int type = option_type(opt);
153 const char *fmt, *nls_arg;
154 char optstring[256];
155 int w;
157 fmt = (char *)&optstr + optstr.map[type];
158 if (!ENABLE_NLS)
159 goto no_translate;
161 nls_arg = argname ? pgettext_expr(opt->name, argname) : argname;
162 if (has_short_char(type)) {
163 w = help_do_snprintf(optstring, sizeof optstring,
164 gettext(fmt), opt->val, opt->name,
165 nls_arg);
166 } else {
167 w = help_do_snprintf(optstring, sizeof optstring,
168 gettext(fmt), opt->name,
169 nls_arg);
172 if (w < 0 || w >= sizeof optstring)
173 goto no_translate;
175 w = mbsnwidth(optstring, w, 0);
176 printf("%s", optstring);
177 goto out;
179 no_translate:
180 if (has_short_char(type)) {
181 w = printf(fmt, opt->val, opt->name, argname);
182 } else {
183 w = printf(fmt, opt->name, argname);
185 out:
186 if (w <= 0 || w > l) {
187 putchar('\n');
188 return 0;
191 return w;
194 void help_print_desc(const struct option *opt, const char *s, int i, int w)
196 if (opt)
197 s = pgettext_expr(opt->name, s);
199 do {
200 size_t n = strcspn(s, &optstr.tab[fmt_nl]);
202 printf(&optstr.tab[fmt_desc], n ? i-w : 0, "", (int)n, s);
203 s += n + (s[n] != '\0');
204 w = 0;
205 } while (*s);