]> git.draconx.ca Git - dxcommon.git/blob - src/help.c
4b77b2ce2e78587df19e0548ee2dea49bb4b9d1b
[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 <string.h>
20 #include <assert.h>
21 #include <limits.h>
22 #include <getopt.h>
23
24 #include "help.h"
25
26 #ifndef ENABLE_NLS
27 #       define ENABLE_NLS 0
28 #endif
29
30 #if ENABLE_NLS
31 #       include <locale.h>
32 #       include <gettext.h>
33 #       include <mbswidth.h>
34 #else
35 #       define gettext(s) (s)
36 #       define pgettext_expr(c, s) (s)
37 #       define mbsnwidth(a, b, c) (assert(0), 0)
38 #endif
39
40 #define _(s) gettext(s)
41
42 /* Returns a single numeric value depending on the type of option:
43  *
44  *   6 - if the option has a short option character and an optional argument
45  *   5 - if the option has a short option character and a mandatory argument
46  *   4 - if the option has a short option charater and no argument
47  *   3 - N/A
48  *   2 - if the option has no short option character and an optional argument
49  *   1 - if the option has no short option character and a mandatory argument
50  *   0 - if the option has no short option character and no argument
51  */
52 static int option_type(const struct option *opt)
53 {
54         int ret = opt->has_arg & 3;
55
56         if (!opt->flag)
57                 ret |= (opt->val <= CHAR_MAX) << 2;
58
59         return ret;
60 }
61
62 enum {
63         OPT_SHORT_WITH_OPTIONAL_ARG  = 6,
64         OPT_SHORT_WITH_MANDATORY_ARG = 5,
65         OPT_SHORT_WITHOUT_ARG        = 4,
66         OPT_LONG_WITH_OPTIONAL_ARG   = 2,
67         OPT_LONG_WITH_MANDATORY_ARG  = 1,
68         OPT_LONG_WITHOUT_ARG         = 0,
69 };
70
71 /*
72  * When NLS is enabled, we assume snprintf is available.  The GNU libintl
73  * library should provide a suitable fallback if necessary.
74  */
75 #if ENABLE_NLS && !defined(help_do_snprintf)
76 #define help_do_snprintf snprintf
77 #elif !ENABLE_NLS
78 #define help_do_snprintf fake_snprintf
79 static inline int fake_snprintf(char *buf, size_t n, const char *fmt, ...)
80 {
81         return -1;
82 }
83 #endif
84
85 int help_print_optstring(const struct option *opt, const char *argname, int l)
86 {
87         char optstring[256];
88         int w;
89
90         if (!ENABLE_NLS)
91                 goto no_translate;
92
93         switch (option_type(opt)) {
94 #if HELP_GETOPT_LONG_ONLY
95         case OPT_SHORT_WITH_OPTIONAL_ARG:
96         case OPT_LONG_WITH_OPTIONAL_ARG:
97                 w = help_do_snprintf(optstring, sizeof optstring,
98                                      _("  -%s [%s]"), opt->name,
99                                      pgettext_expr(opt->name, argname));
100                 break;
101         case OPT_SHORT_WITH_MANDATORY_ARG:
102         case OPT_LONG_WITH_MANDATORY_ARG:
103                 w = help_do_snprintf(optstring, sizeof optstring,
104                                      _("  -%s %s"), opt->name,
105                                      pgettext_expr(opt->name, argname));
106                 break;
107         case OPT_SHORT_WITHOUT_ARG:
108         case OPT_LONG_WITHOUT_ARG:
109                 w = help_do_snprintf(optstring, sizeof optstring,
110                                      _("  -%s"), opt->name);
111                 break;
112 #else
113         case OPT_SHORT_WITH_OPTIONAL_ARG:
114                 w = help_do_snprintf(optstring, sizeof optstring,
115                                      _("  -%c, --%s[=%s]"), opt->val, opt->name,
116                                      pgettext_expr(opt->name, argname));
117                 break;
118         case OPT_LONG_WITH_OPTIONAL_ARG:
119                 w = help_do_snprintf(optstring, sizeof optstring,
120                                      _("  --%s[=%s]"), opt->name,
121                                      pgettext_expr(opt->name, argname));
122                 break;
123         case OPT_SHORT_WITH_MANDATORY_ARG:
124                 w = help_do_snprintf(optstring, sizeof optstring,
125                                      _("  -%c, --%s=%s"), opt->val, opt->name,
126                                      pgettext_expr(opt->name, argname));
127                 break;
128         case OPT_LONG_WITH_MANDATORY_ARG:
129                 w = help_do_snprintf(optstring, sizeof optstring,
130                                      _("  --%s=%s"), opt->name,
131                                      pgettext_expr(opt->name, argname));
132                 break;
133         case OPT_SHORT_WITHOUT_ARG:
134                 w = help_do_snprintf(optstring, sizeof optstring,
135                                      _("  -%c, --%s"), opt->val, opt->name);
136                 break;
137         case OPT_LONG_WITHOUT_ARG:
138                 w = help_do_snprintf(optstring, sizeof optstring,
139                                      _("  --%s"), opt->name);
140                 break;
141 #endif
142         default:
143                 assert(0);
144         }
145
146         if (w < 0 || w >= sizeof optstring)
147                 goto no_translate;
148
149         w = mbsnwidth(optstring, w, 0);
150         printf("%s", optstring);
151         goto out;
152
153 no_translate:
154         switch (option_type(opt)) {
155 #if HELP_GETOPT_LONG_ONLY
156         case OPT_SHORT_WITH_OPTIONAL_ARG:
157         case OPT_LONG_WITH_OPTIONAL_ARG:
158                 w = printf("  -%s [%s]", opt->name, argname);
159                 break;
160         case OPT_SHORT_WITH_MANDATORY_ARG:
161         case OPT_LONG_WITH_MANDATORY_ARG:
162                 w = printf("  -%s %s", opt->name, argname);
163                 break;
164         case OPT_SHORT_WITHOUT_ARG:
165         case OPT_LONG_WITHOUT_ARG:
166                 w = printf("  -%s", opt->name);
167                 break;
168 #else
169         case OPT_SHORT_WITH_OPTIONAL_ARG:
170                 w = printf("  -%c, --%s[=%s]", opt->val, opt->name, argname);
171                 break;
172         case OPT_LONG_WITH_OPTIONAL_ARG:
173                 w = printf("  --%s[=%s]", opt->name, argname);
174                 break;
175         case OPT_SHORT_WITH_MANDATORY_ARG:
176                 w = printf("  -%c, --%s=%s", opt->val, opt->name, argname);
177                 break;
178         case OPT_LONG_WITH_MANDATORY_ARG:
179                 w = printf("  --%s=%s", opt->name, argname);
180                 break;
181         case OPT_SHORT_WITHOUT_ARG:
182                 w = printf("  -%c, --%s", opt->val, opt->name);
183                 break;
184         case OPT_LONG_WITHOUT_ARG:
185                 w = printf("  --%s", opt->name);
186                 break;
187 #endif
188         default:
189                 assert(0);
190         }
191 out:
192         if (w < 0 || w > l) {
193                 putchar('\n');
194                 return 0;
195         }
196
197         return w;
198 }
199
200 void help_print_desc(const struct option *opt, const char *s, int i, int w)
201 {
202         if (opt)
203                 s = pgettext_expr(opt->name, s);
204
205         if (i && s[0] == '\0')
206                 putchar('\n');
207
208         for (; *s; w = 0) {
209                 const char *nl = strchr(s, '\n');
210                 int n = (nl ? nl-s : -1);
211
212                 printf("%*s%.*s\n", i-w, "", n, s);
213                 if (!nl)
214                         break;
215
216                 s = nl+1;
217         }
218 }