1 /* argmatch.h -- definitions and prototypes for argmatch.c
2
3 Copyright (C) 1990, 1998-1999, 2001-2002, 2004-2005, 2009-2022 Free Software
4 Foundation, Inc.
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18
19 /* Written by David MacKenzie <djm@ai.mit.edu>
20 Modified by Akim Demaille <demaille@inf.enst.fr> */
21
22 #ifndef ARGMATCH_H_
23 # define ARGMATCH_H_ 1
24
25 # include <limits.h>
26 # include <stdbool.h>
27 # include <stddef.h>
28 # include <stdio.h>
29 # include <string.h> /* memcmp */
30
31 # include "gettext.h"
32 # include "quote.h"
33 # include "verify.h"
34
35 # ifdef __cplusplus
36 extern "C" {
37 # endif
38
39 # define ARRAY_CARDINALITY(Array) (sizeof (Array) / sizeof *(Array))
40
41 /* Assert there are as many real arguments as there are values
42 (argument list ends with a NULL guard). */
43
44 # define ARGMATCH_VERIFY(Arglist, Vallist) \
45 verify (ARRAY_CARDINALITY (Arglist) == ARRAY_CARDINALITY (Vallist) + 1)
46
47 /* Return the index of the element of ARGLIST (NULL terminated) that
48 matches with ARG. If VALLIST is not NULL, then use it to resolve
49 false ambiguities (i.e., different matches of ARG but corresponding
50 to the same values in VALLIST). */
51
52 ptrdiff_t argmatch (char const *arg, char const *const *arglist,
53 void const *vallist, size_t valsize) _GL_ATTRIBUTE_PURE;
54
55 ptrdiff_t argmatch_exact (char const *arg, char const *const *arglist)
56 _GL_ATTRIBUTE_PURE;
57
58 # define ARGMATCH(Arg, Arglist, Vallist) \
59 argmatch (Arg, Arglist, (void const *) (Vallist), sizeof *(Vallist))
60
61 # define ARGMATCH_EXACT(Arg, Arglist) \
62 argmatch_exact (Arg, Arglist)
63
64 /* xargmatch calls this function when it fails. This function should not
65 return. By default, this is a function that calls ARGMATCH_DIE which
66 in turn defaults to 'exit (exit_failure)'. */
67 typedef void (*argmatch_exit_fn) (void);
68 extern argmatch_exit_fn argmatch_die;
69
70 /* Report on stderr why argmatch failed. Report correct values. */
71
72 void argmatch_invalid (char const *context, char const *value,
73 ptrdiff_t problem);
74
75 /* Left for compatibility with the old name invalid_arg */
76
77 # define invalid_arg(Context, Value, Problem) \
78 argmatch_invalid (Context, Value, Problem)
79
80
81
82 /* Report on stderr the list of possible arguments. */
83
84 void argmatch_valid (char const *const *arglist,
85 void const *vallist, size_t valsize);
86
87 # define ARGMATCH_VALID(Arglist, Vallist) \
88 argmatch_valid (Arglist, (void const *) (Vallist), sizeof *(Vallist))
89
90
91
92 /* Like argmatch/argmatch_exact, but upon failure, report an explanation
93 of the failure, and exit using the function EXIT_FN. */
94
95 ptrdiff_t __xargmatch_internal (char const *context,
96 char const *arg, char const *const *arglist,
97 void const *vallist, size_t valsize,
98 argmatch_exit_fn exit_fn,
99 bool allow_abbreviation);
100
101 /* Programmer friendly interface to __xargmatch_internal. */
102
103 # define XARGMATCH(Context, Arg, Arglist, Vallist) \
104 ((Vallist) [__xargmatch_internal (Context, Arg, Arglist, \
105 (void const *) (Vallist), \
106 sizeof *(Vallist), \
107 argmatch_die, \
108 true)])
109
110 # define XARGMATCH_EXACT(Context, Arg, Arglist, Vallist) \
111 ((Vallist) [__xargmatch_internal (Context, Arg, Arglist, \
112 (void const *) (Vallist), \
113 sizeof *(Vallist), \
114 argmatch_die, \
115 false)])
116
117 /* Convert a value into a corresponding argument. */
118
119 char const *argmatch_to_argument (void const *value,
120 char const *const *arglist,
121 void const *vallist, size_t valsize)
122 _GL_ATTRIBUTE_PURE;
123
124 # define ARGMATCH_TO_ARGUMENT(Value, Arglist, Vallist) \
125 argmatch_to_argument (Value, Arglist, \
126 (void const *) (Vallist), sizeof *(Vallist))
127
128 # define ARGMATCH_DEFINE_GROUP(Name, Type) \
129 /* The type of the values of this group. */ \
130 typedef Type argmatch_##Name##_type; \
131 \
132 /* The size of the type of the values of this group. */ \
133 enum argmatch_##Name##_size_enum \
134 { \
135 argmatch_##Name##_size = sizeof (argmatch_##Name##_type) \
136 }; \
137 \
138 /* Argument mapping of this group. */ \
139 typedef struct \
140 { \
141 /* Argument (e.g., "simple"). */ \
142 const char *arg; \
143 /* Value (e.g., simple_backups). */ \
144 const argmatch_##Name##_type val; \
145 } argmatch_##Name##_arg; \
146 \
147 /* Documentation of this group. */ \
148 typedef struct \
149 { \
150 /* Argument (e.g., "simple"). */ \
151 const char *arg; \
152 /* Documentation (e.g., N_("always make simple backups")). */ \
153 const char *doc; \
154 } argmatch_##Name##_doc; \
155 \
156 /* All the features of an argmatch group. */ \
157 typedef struct \
158 { \
159 const argmatch_##Name##_arg* args; \
160 const argmatch_##Name##_doc* docs; \
161 \
162 /* Printed before the usage message. */ \
163 const char *doc_pre; \
164 /* Printed after the usage message. */ \
165 const char *doc_post; \
166 } argmatch_##Name##_group_type; \
167 \
168 /* The structure the user must build. */ \
169 extern const argmatch_##Name##_group_type argmatch_##Name##_group; \
170 \
171 /* Print the documentation of this group. */ \
172 void argmatch_##Name##_usage (FILE *out); \
173 \
174 /* If nonnegative, the index I of ARG in ARGS, i.e, \
175 ARGS[I] == ARG. \
176 Return -1 for invalid argument, -2 for ambiguous argument. */ \
177 ptrdiff_t argmatch_##Name##_choice (const char *arg); \
178 \
179 /* A pointer to the corresponding value if it exists, or \
180 report an error and exit with failure if the argument was \
181 not recognized. */ \
182 const argmatch_##Name##_type* \
183 argmatch_##Name##_value (const char *context, const char *arg); \
184 \
185 /* The first argument in ARGS that matches this value, or NULL. */ \
186 const char * \
187 argmatch_##Name##_argument (const argmatch_##Name##_type *val); \
188 \
189 ptrdiff_t \
190 argmatch_##Name##_choice (const char *arg) \
191 { \
192 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
193 size_t size = argmatch_##Name##_size; \
194 ptrdiff_t res = -1; /* Index of first nonexact match. */ \
195 bool ambiguous = false; /* Whether multiple nonexact match(es). */ \
196 size_t arglen = strlen (arg); \
197 \
198 /* Test all elements for either exact match or abbreviated \
199 matches. */ \
200 for (size_t i = 0; g->args[i].arg; i++) \
201 if (!strncmp (g->args[i].arg, arg, arglen)) \
202 { \
203 if (strlen (g->args[i].arg) == arglen) \
204 /* Exact match found. */ \
205 return i; \
206 else if (res == -1) \
207 /* First nonexact match found. */ \
208 res = i; \
209 else if (memcmp (&g->args[res].val, &g->args[i].val, size)) \
210 /* Second nonexact match found. */ \
211 /* There is a real ambiguity, or we could not \
212 disambiguate. */ \
213 ambiguous = true; \
214 } \
215 return ambiguous ? -2 : res; \
216 } \
217 \
218 const char * \
219 argmatch_##Name##_argument (const argmatch_##Name##_type *val) \
220 { \
221 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
222 size_t size = argmatch_##Name##_size; \
223 for (size_t i = 0; g->args[i].arg; i++) \
224 if (!memcmp (val, &g->args[i].val, size)) \
225 return g->args[i].arg; \
226 return NULL; \
227 } \
228 \
229 /* List the valid values of this group. */ \
230 static void \
231 argmatch_##Name##_valid (FILE *out) \
232 { \
233 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
234 size_t size = argmatch_##Name##_size; \
235 \
236 /* Try to put synonyms on the same line. Synonyms are expected \
237 to follow each other. */ \
238 fputs (gettext ("Valid arguments are:"), out); \
239 for (int i = 0; g->args[i].arg; i++) \
240 if (i == 0 \
241 || memcmp (&g->args[i-1].val, &g->args[i].val, size)) \
242 fprintf (out, "\n - %s", quote (g->args[i].arg)); \
243 else \
244 fprintf (out, ", %s", quote (g->args[i].arg)); \
245 putc ('\n', out); \
246 } \
247 \
248 const argmatch_##Name##_type* \
249 argmatch_##Name##_value (const char *context, const char *arg) \
250 { \
251 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
252 ptrdiff_t res = argmatch_##Name##_choice (arg); \
253 if (res < 0) \
254 { \
255 argmatch_invalid (context, arg, res); \
256 argmatch_##Name##_valid (stderr); \
257 argmatch_die (); \
258 } \
259 return &g->args[res].val; \
260 } \
261 \
262 /* The column in which the documentation is displayed. \
263 The leftmost possible, but no more than 20. */ \
264 static int \
265 argmatch_##Name##_doc_col (void) \
266 { \
267 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
268 size_t size = argmatch_##Name##_size; \
269 int res = 0; \
270 for (int i = 0; g->docs[i].arg; ++i) \
271 { \
272 int col = 4; \
273 int ival = argmatch_##Name##_choice (g->docs[i].arg); \
274 if (ival < 0) \
275 /* Pseudo argument, display it. */ \
276 col += strlen (g->docs[i].arg); \
277 else \
278 /* Genuine argument, display it with its synonyms. */ \
279 for (int j = 0; g->args[j].arg; ++j) \
280 if (! memcmp (&g->args[ival].val, &g->args[j].val, size)) \
281 col += (col == 4 ? 0 : 2) + strlen (g->args[j].arg); \
282 if (res <= col) \
283 res = col <= 20 ? col : 20; \
284 } \
285 return res ? res : 20; \
286 } \
287 \
288 void \
289 argmatch_##Name##_usage (FILE *out) \
290 { \
291 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
292 size_t size = argmatch_##Name##_size; \
293 /* Width of the screen. Help2man does not seem to support \
294 arguments on several lines, so in that case pretend a very \
295 large width. */ \
296 const int screen_width = getenv ("HELP2MAN") ? INT_MAX : 80; \
297 if (g->doc_pre) \
298 fprintf (out, "%s\n", gettext (g->doc_pre)); \
299 int doc_col = argmatch_##Name##_doc_col (); \
300 for (int i = 0; g->docs[i].arg; ++i) \
301 { \
302 int col = 0; \
303 bool first = true; \
304 int ival = argmatch_##Name##_choice (g->docs[i].arg); \
305 if (ival < 0) \
306 /* Pseudo argument, display it. */ \
307 col += fprintf (out, " %s", g->docs[i].arg); \
308 else \
309 /* Genuine argument, display it with its synonyms. */ \
310 for (int j = 0; g->args[j].arg; ++j) \
311 if (! memcmp (&g->args[ival].val, &g->args[j].val, size)) \
312 { \
313 if (!first \
314 && screen_width < col + 2 + strlen (g->args[j].arg)) \
315 { \
316 fprintf (out, ",\n"); \
317 col = 0; \
318 first = true; \
319 } \
320 if (first) \
321 { \
322 col += fprintf (out, " "); \
323 first = false; \
324 } \
325 else \
326 col += fprintf (out, ","); \
327 col += fprintf (out, " %s", g->args[j].arg); \
328 } \
329 /* The doc. Separated by at least two spaces. */ \
330 if (doc_col < col + 2) \
331 { \
332 fprintf (out, "\n"); \
333 col = 0; \
334 } \
335 fprintf (out, "%*s%s\n", \
336 doc_col - col, "", gettext (g->docs[i].doc)); \
337 } \
338 if (g->doc_post) \
339 fprintf (out, "%s\n", gettext (g->doc_post)); \
340 }
341
342 # ifdef __cplusplus
343 }
344 # endif
345
346 #endif /* ARGMATCH_H_ */