1/* argmatch.h -- definitions and prototypes for argmatch.c
2
3 Copyright (C) 1990, 1998-1999, 2001-2002, 2004-2005, 2009-2019 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
36extern "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
52ptrdiff_t argmatch (char const *arg, char const *const *arglist,
53 void const *vallist, size_t valsize) _GL_ATTRIBUTE_PURE;
54
55# define ARGMATCH(Arg, Arglist, Vallist) \
56 argmatch (Arg, Arglist, (void const *) (Vallist), sizeof *(Vallist))
57
58/* xargmatch calls this function when it fails. This function should not
59 return. By default, this is a function that calls ARGMATCH_DIE which
60 in turn defaults to 'exit (exit_failure)'. */
61typedef void (*argmatch_exit_fn) (void);
62extern argmatch_exit_fn argmatch_die;
63
64/* Report on stderr why argmatch failed. Report correct values. */
65
66void argmatch_invalid (char const *context, char const *value,
67 ptrdiff_t problem);
68
69/* Left for compatibility with the old name invalid_arg */
70
71# define invalid_arg(Context, Value, Problem) \
72 argmatch_invalid (Context, Value, Problem)
73
74
75
76/* Report on stderr the list of possible arguments. */
77
78void argmatch_valid (char const *const *arglist,
79 void const *vallist, size_t valsize);
80
81# define ARGMATCH_VALID(Arglist, Vallist) \
82 argmatch_valid (Arglist, (void const *) (Vallist), sizeof *(Vallist))
83
84
85
86/* Same as argmatch, but upon failure, report an explanation of the
87 failure, and exit using the function EXIT_FN. */
88
89ptrdiff_t __xargmatch_internal (char const *context,
90 char const *arg, char const *const *arglist,
91 void const *vallist, size_t valsize,
92 argmatch_exit_fn exit_fn);
93
94/* Programmer friendly interface to __xargmatch_internal. */
95
96# define XARGMATCH(Context, Arg, Arglist, Vallist) \
97 ((Vallist) [__xargmatch_internal (Context, Arg, Arglist, \
98 (void const *) (Vallist), \
99 sizeof *(Vallist), \
100 argmatch_die)])
101
102/* Convert a value into a corresponding argument. */
103
104char const *argmatch_to_argument (void const *value,
105 char const *const *arglist,
106 void const *vallist, size_t valsize)
107 _GL_ATTRIBUTE_PURE;
108
109# define ARGMATCH_TO_ARGUMENT(Value, Arglist, Vallist) \
110 argmatch_to_argument (Value, Arglist, \
111 (void const *) (Vallist), sizeof *(Vallist))
112
113# define ARGMATCH_DEFINE_GROUP(Name, Type) \
114 /* The type of the values of this group. */ \
115 typedef Type argmatch_##Name##_type; \
116 \
117 /* The size of the type of the values of this group. */ \
118 enum argmatch_##Name##_size_enum \
119 { \
120 argmatch_##Name##_size = sizeof (argmatch_##Name##_type) \
121 }; \
122 \
123 /* Argument mapping of this group. */ \
124 typedef struct \
125 { \
126 /* Argument (e.g., "simple"). */ \
127 const char *arg; \
128 /* Value (e.g., simple_backups). */ \
129 const argmatch_##Name##_type val; \
130 } argmatch_##Name##_arg; \
131 \
132 /* Documentation of this group. */ \
133 typedef struct \
134 { \
135 /* Argument (e.g., "simple"). */ \
136 const char *arg; \
137 /* Documentation (e.g., N_("always make simple backups")). */ \
138 const char *doc; \
139 } argmatch_##Name##_doc; \
140 \
141 /* All the features of an argmatch group. */ \
142 typedef struct \
143 { \
144 const argmatch_##Name##_arg* args; \
145 const argmatch_##Name##_doc* docs; \
146 \
147 /* Printed before the usage message. */ \
148 const char *doc_pre; \
149 /* Printed after the usage message. */ \
150 const char *doc_post; \
151 } argmatch_##Name##_group_type; \
152 \
153 /* The structure the user must build. */ \
154 extern const argmatch_##Name##_group_type argmatch_##Name##_group; \
155 \
156 /* Print the documentation of this group. */ \
157 void argmatch_##Name##_usage (FILE *out); \
158 \
159 /* If nonnegative, the index I of ARG in ARGS, i.e, \
160 ARGS[I] == ARG. \
161 Return -1 for invalid argument, -2 for ambiguous argument. */ \
162 ptrdiff_t argmatch_##Name##_choice (const char *arg); \
163 \
164 /* A pointer to the corresponding value if it exists, or \
165 report an error and exit with failure if the argument was \
166 not recognized. */ \
167 const argmatch_##Name##_type* \
168 argmatch_##Name##_value (const char *context, const char *arg); \
169 \
170 /* The first argument in ARGS that matches this value, or NULL. */ \
171 const char * \
172 argmatch_##Name##_argument (const argmatch_##Name##_type *val); \
173 \
174 ptrdiff_t \
175 argmatch_##Name##_choice (const char *arg) \
176 { \
177 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
178 size_t size = argmatch_##Name##_size; \
179 ptrdiff_t res = -1; /* Index of first nonexact match. */ \
180 bool ambiguous = false; /* Whether multiple nonexact match(es). */ \
181 size_t arglen = strlen (arg); \
182 \
183 /* Test all elements for either exact match or abbreviated \
184 matches. */ \
185 for (size_t i = 0; g->args[i].arg; i++) \
186 if (!strncmp (g->args[i].arg, arg, arglen)) \
187 { \
188 if (strlen (g->args[i].arg) == arglen) \
189 /* Exact match found. */ \
190 return i; \
191 else if (res == -1) \
192 /* First nonexact match found. */ \
193 res = i; \
194 else if (memcmp (&g->args[res].val, &g->args[i].val, size)) \
195 /* Second nonexact match found. */ \
196 /* There is a real ambiguity, or we could not \
197 disambiguate. */ \
198 ambiguous = true; \
199 } \
200 return ambiguous ? -2 : res; \
201 } \
202 \
203 const char * \
204 argmatch_##Name##_argument (const argmatch_##Name##_type *val) \
205 { \
206 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
207 size_t size = argmatch_##Name##_size; \
208 for (size_t i = 0; g->args[i].arg; i++) \
209 if (!memcmp (val, &g->args[i].val, size)) \
210 return g->args[i].arg; \
211 return NULL; \
212 } \
213 \
214 /* List the valid values of this group. */ \
215 static void \
216 argmatch_##Name##_valid (FILE *out) \
217 { \
218 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
219 size_t size = argmatch_##Name##_size; \
220 \
221 /* Try to put synonyms on the same line. Synonyms are expected \
222 to follow each other. */ \
223 fputs (gettext ("Valid arguments are:"), out); \
224 for (int i = 0; g->args[i].arg; i++) \
225 if (i == 0 \
226 || memcmp (&g->args[i-1].val, &g->args[i].val, size)) \
227 fprintf (out, "\n - %s", quote (g->args[i].arg)); \
228 else \
229 fprintf (out, ", %s", quote (g->args[i].arg)); \
230 putc ('\n', out); \
231 } \
232 \
233 const argmatch_##Name##_type* \
234 argmatch_##Name##_value (const char *context, const char *arg) \
235 { \
236 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
237 ptrdiff_t res = argmatch_##Name##_choice (arg); \
238 if (res < 0) \
239 { \
240 argmatch_invalid (context, arg, res); \
241 argmatch_##Name##_valid (stderr); \
242 argmatch_die (); \
243 } \
244 return &g->args[res].val; \
245 } \
246 \
247 /* The column in which the documentation is displayed. \
248 The leftmost possible, but no more than 20. */ \
249 static int \
250 argmatch_##Name##_doc_col (void) \
251 { \
252 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
253 size_t size = argmatch_##Name##_size; \
254 int res = 0; \
255 for (int i = 0; g->docs[i].arg; ++i) \
256 { \
257 int col = 4; \
258 int ival = argmatch_##Name##_choice (g->docs[i].arg); \
259 if (ival < 0) \
260 /* Pseudo argument, display it. */ \
261 col += strlen (g->docs[i].arg); \
262 else \
263 /* Genuine argument, display it with its synonyms. */ \
264 for (int j = 0; g->args[j].arg; ++j) \
265 if (! memcmp (&g->args[ival].val, &g->args[j].val, size)) \
266 col += (col == 4 ? 0 : 2) + strlen (g->args[j].arg); \
267 if (res <= col) \
268 res = col <= 20 ? col : 20; \
269 } \
270 return res ? res : 20; \
271 } \
272 \
273 void \
274 argmatch_##Name##_usage (FILE *out) \
275 { \
276 const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
277 size_t size = argmatch_##Name##_size; \
278 /* Width of the screen. Help2man does not seem to support \
279 arguments on several lines, so in that case pretend a very \
280 large width. */ \
281 const int screen_width = getenv ("HELP2MAN") ? INT_MAX : 80; \
282 if (g->doc_pre) \
283 fprintf (out, "%s\n", gettext (g->doc_pre)); \
284 int doc_col = argmatch_##Name##_doc_col (); \
285 for (int i = 0; g->docs[i].arg; ++i) \
286 { \
287 int col = 0; \
288 bool first = true; \
289 int ival = argmatch_##Name##_choice (g->docs[i].arg); \
290 if (ival < 0) \
291 /* Pseudo argument, display it. */ \
292 col += fprintf (out, " %s", g->docs[i].arg); \
293 else \
294 /* Genuine argument, display it with its synonyms. */ \
295 for (int j = 0; g->args[j].arg; ++j) \
296 if (! memcmp (&g->args[ival].val, &g->args[j].val, size)) \
297 { \
298 if (!first \
299 && screen_width < col + 2 + strlen (g->args[j].arg)) \
300 { \
301 fprintf (out, ",\n"); \
302 col = 0; \
303 first = true; \
304 } \
305 if (first) \
306 { \
307 col += fprintf (out, " "); \
308 first = false; \
309 } \
310 else \
311 col += fprintf (out, ","); \
312 col += fprintf (out, " %s", g->args[j].arg); \
313 } \
314 /* The doc. Separated by at least two spaces. */ \
315 if (doc_col < col + 2) \
316 { \
317 fprintf (out, "\n"); \
318 col = 0; \
319 } \
320 fprintf (out, "%*s%s\n", \
321 doc_col - col, "", gettext (g->docs[i].doc)); \
322 } \
323 if (g->doc_post) \
324 fprintf (out, "%s\n", gettext (g->doc_post)); \
325 }
326
327# ifdef __cplusplus
328}
329# endif
330
331#endif /* ARGMATCH_H_ */
332