]> git.draconx.ca Git - dxcommon.git/blob - src/help.c
DX_C_ALIGNAS: Work around bash-5 parsing bug.
[dxcommon.git] / src / help.c
1 /*
2  * Copyright © 2021-2024 Nick Bowler
3  *
4  * Helper functions for formatting --help program output.
5  *
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.
9  *
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.
13  */
14
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>
24
25 #include "help.h"
26
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
33
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
43
44 #define STR_L10N_(x)
45 #define N_(s) s
46
47 /* Returns a single numeric value depending on the type of option:
48  *
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
56  */
57 static int option_type(const struct option *opt)
58 {
59         int ret = opt->has_arg & 3;
60
61 #if !HELP_GETOPT_LONG_ONLY
62         if (!opt->flag)
63                 ret |= (opt->val <= CHAR_MAX) << 2;
64 #endif
65
66         return ret;
67 }
68
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
76 };
77
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") \
85         ""
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
92 };
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" \
103         ""
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
113 };
114 #endif
115
116 static const struct optstr {
117         unsigned char map[HELP_GETOPT_LONG_ONLY ? 3 : 7];
118         char tab[sizeof (STRTAB_INITIALIZER)];
119 } optstr = {
120         {
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
125                 0,
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
130         },
131         STRTAB_INITIALIZER
132 };
133
134 /*
135  * When NLS is enabled, we assume snprintf is available.  The GNU libintl
136  * library should provide a suitable fallback if necessary.
137  */
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, ...)
143 {
144         return -1;
145 }
146 #endif
147
148 #define has_short_char(type) (!HELP_GETOPT_LONG_ONLY && (type & 4))
149
150 int help_print_optstring(const struct option *opt, const char *argname, int l)
151 {
152         int type = option_type(opt);
153         const char *fmt, *nls_arg;
154         char optstring[256];
155         int w;
156
157         fmt = (char *)&optstr + optstr.map[type];
158         if (!ENABLE_NLS)
159                 goto no_translate;
160
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);
170         }
171
172         if (w < 0 || w >= sizeof optstring)
173                 goto no_translate;
174
175         w = mbsnwidth(optstring, w, 0);
176         printf("%s", optstring);
177         goto out;
178
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);
184         }
185 out:
186         if (w <= 0 || w > l) {
187                 putchar('\n');
188                 return 0;
189         }
190
191         return w;
192 }
193
194 void help_print_desc(const struct option *opt, const char *s, int i, int w)
195 {
196         if (opt)
197                 s = pgettext_expr(opt->name, s);
198
199         do {
200                 size_t n = strcspn(s, &optstr.tab[fmt_nl]);
201
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);
206 }