]> git.draconx.ca Git - dxcommon.git/blob - src/help.c
help_print_optstring: New table-based implementation.
[dxcommon.git] / src / help.c
1 /*
2  * Copyright © 2021-2023 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" N_("  -%s %s") \
83         "\0" N_("  -%s") \
84         ""
85 enum {
86         fmt_long_optional_arg = 0,
87         fmt_long_mandatory_arg = 11,
88         fmt_long_without_arg = 20
89 };
90 #else
91 /* String table generated by gen-strtab.awk from help-std.str */
92 #define STRTAB_INITIALIZER \
93              N_("  -%c, --%s[=%s]") \
94         "\0" N_("  -%c, --%s=%s") \
95         "\0" N_("  --%s[=%s]") \
96         "\0" N_("  -%c, --%s") \
97         "\0" N_("  --%s=%s") \
98         "\0" N_("  --%s") \
99         ""
100 enum {
101         fmt_short_optional_arg = 0,
102         fmt_long_optional_arg = 32,
103         fmt_short_mandatory_arg = 17,
104         fmt_long_mandatory_arg = 56,
105         fmt_short_without_arg = 44,
106         fmt_long_without_arg = 66
107 };
108 #endif
109
110 static const struct optstr {
111         unsigned char map[HELP_GETOPT_LONG_ONLY ? 3 : 7];
112         char tab[sizeof (STRTAB_INITIALIZER)];
113 } optstr = {
114         {
115                 offsetof(struct optstr, tab) + fmt_long_without_arg,
116                 offsetof(struct optstr, tab) + fmt_long_mandatory_arg,
117                 offsetof(struct optstr, tab) + fmt_long_optional_arg,
118 #if !HELP_GETOPT_LONG_ONLY
119                 0,
120                 offsetof(struct optstr, tab) + fmt_short_without_arg,
121                 offsetof(struct optstr, tab) + fmt_short_mandatory_arg,
122                 offsetof(struct optstr, tab) + fmt_short_optional_arg,
123 #endif
124         },
125         STRTAB_INITIALIZER
126 };
127
128 /*
129  * When NLS is enabled, we assume snprintf is available.  The GNU libintl
130  * library should provide a suitable fallback if necessary.
131  */
132 #if ENABLE_NLS && !defined(help_do_snprintf)
133 #define help_do_snprintf snprintf
134 #elif !ENABLE_NLS
135 #define help_do_snprintf fake_snprintf
136 static inline int fake_snprintf(char *buf, size_t n, const char *fmt, ...)
137 {
138         return -1;
139 }
140 #endif
141
142 #define has_short_char(type) (!HELP_GETOPT_LONG_ONLY && (type & 4))
143
144 int help_print_optstring(const struct option *opt, const char *argname, int l)
145 {
146         int type = option_type(opt);
147         const char *fmt, *nls_arg;
148         char optstring[256];
149         int w;
150
151         fmt = (char *)&optstr + optstr.map[type];
152         if (!ENABLE_NLS)
153                 goto no_translate;
154
155         nls_arg = argname ? pgettext_expr(opt->name, argname) : argname;
156         if (has_short_char(type)) {
157                 w = help_do_snprintf(optstring, sizeof optstring,
158                                      gettext(fmt), opt->val, opt->name,
159                                      nls_arg);
160         } else {
161                 w = help_do_snprintf(optstring, sizeof optstring,
162                                      gettext(fmt), opt->name,
163                                      nls_arg);
164         }
165
166         if (w < 0 || w >= sizeof optstring)
167                 goto no_translate;
168
169         w = mbsnwidth(optstring, w, 0);
170         printf("%s", optstring);
171         goto out;
172
173 no_translate:
174         if (has_short_char(type)) {
175                 w = printf(fmt, opt->val, opt->name, argname);
176         } else {
177                 w = printf(fmt, opt->name, argname);
178         }
179 out:
180         if (w < 0 || w > l) {
181                 putchar('\n');
182                 return 0;
183         }
184
185         return w;
186 }
187
188 void help_print_desc(const struct option *opt, const char *s, int i, int w)
189 {
190         if (opt)
191                 s = pgettext_expr(opt->name, s);
192
193         do {
194                 size_t n = strcspn(s, "\n");
195
196                 printf("%*s%.*s\n", n ? i-w : 0, "", (int)n, s);
197                 s += n + (s[n] != '\0');
198                 w = 0;
199         } while (*s);
200 }