Cleanup: add `#include <stdbool.h>` whenever `bool` type is used
[babeltrace.git] / src / argpar / argpar.c
1 /*
2 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <glib.h>
27
28 #include "common/assert.h"
29
30 #include "argpar.h"
31
32 static
33 void destroy_item(struct bt_argpar_item * const item)
34 {
35 if (!item) {
36 goto end;
37 }
38
39 if (item->type == BT_ARGPAR_ITEM_TYPE_OPT) {
40 struct bt_argpar_item_opt * const opt_item = (void *) item;
41
42 g_free((void *) opt_item->arg);
43 }
44
45 g_free(item);
46
47 end:
48 return;
49 }
50
51 static
52 struct bt_argpar_item_opt *create_opt_item(
53 const struct bt_argpar_opt_descr * const descr,
54 const char * const arg)
55 {
56 struct bt_argpar_item_opt *opt_item =
57 g_new0(struct bt_argpar_item_opt, 1);
58
59 if (!opt_item) {
60 goto end;
61 }
62
63 opt_item->base.type = BT_ARGPAR_ITEM_TYPE_OPT;
64 opt_item->descr = descr;
65
66 if (arg) {
67 opt_item->arg = g_strdup(arg);
68 if (!opt_item->arg) {
69 goto error;
70 }
71 }
72
73 goto end;
74
75 error:
76 destroy_item(&opt_item->base);
77 opt_item = NULL;
78
79 end:
80 return opt_item;
81 }
82
83 static
84 struct bt_argpar_item_non_opt *create_non_opt_item(const char * const arg,
85 const unsigned int orig_index,
86 const unsigned int non_opt_index)
87 {
88 struct bt_argpar_item_non_opt * const non_opt_item =
89 g_new0(struct bt_argpar_item_non_opt, 1);
90
91 if (!non_opt_item) {
92 goto end;
93 }
94
95 non_opt_item->base.type = BT_ARGPAR_ITEM_TYPE_NON_OPT;
96 non_opt_item->arg = arg;
97 non_opt_item->orig_index = orig_index;
98 non_opt_item->non_opt_index = non_opt_index;
99
100 end:
101 return non_opt_item;
102 }
103
104 static
105 const struct bt_argpar_opt_descr *find_descr(
106 const struct bt_argpar_opt_descr * const descrs,
107 const char short_name, const char * const long_name)
108 {
109 const struct bt_argpar_opt_descr *descr;
110
111 for (descr = descrs; descr->short_name || descr->long_name; descr++) {
112 if (short_name && descr->short_name &&
113 short_name == descr->short_name) {
114 goto end;
115 }
116
117 if (long_name && descr->long_name &&
118 strcmp(long_name, descr->long_name) == 0) {
119 goto end;
120 }
121 }
122
123 end:
124 return !descr->short_name && !descr->long_name ? NULL : descr;
125 }
126
127 enum parse_orig_arg_opt_ret {
128 PARSE_ORIG_ARG_OPT_RET_OK,
129 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT = -2,
130 PARSE_ORIG_ARG_OPT_RET_ERROR = -1,
131 };
132
133 static
134 enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts,
135 const char * const next_orig_arg,
136 const struct bt_argpar_opt_descr * const descrs,
137 struct bt_argpar_parse_ret * const parse_ret,
138 bool * const used_next_orig_arg)
139 {
140 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
141 const char *short_opt_ch = short_opts;
142
143 if (strlen(short_opts) == 0) {
144 g_string_append(parse_ret->error, "Invalid argument");
145 goto error;
146 }
147
148 while (*short_opt_ch) {
149 const char *opt_arg = NULL;
150 const struct bt_argpar_opt_descr *descr;
151 struct bt_argpar_item_opt *opt_item;
152
153 /* Find corresponding option descriptor */
154 descr = find_descr(descrs, *short_opt_ch, NULL);
155 if (!descr) {
156 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
157 g_string_append_printf(parse_ret->error,
158 "Unknown option `-%c`", *short_opt_ch);
159 goto error;
160 }
161
162 if (descr->with_arg) {
163 if (short_opt_ch[1]) {
164 /* `-oarg` form */
165 opt_arg = &short_opt_ch[1];
166 } else {
167 /* `-o arg` form */
168 opt_arg = next_orig_arg;
169 *used_next_orig_arg = true;
170 }
171
172 /*
173 * We accept `-o ''` (empty option's argument),
174 * but not `-o` alone if an option's argument is
175 * expected.
176 */
177 if (!opt_arg || (short_opt_ch[1] && strlen(opt_arg) == 0)) {
178 g_string_append_printf(parse_ret->error,
179 "Missing required argument for option `-%c`",
180 *short_opt_ch);
181 *used_next_orig_arg = false;
182 goto error;
183 }
184 }
185
186 /* Create and append option argument */
187 opt_item = create_opt_item(descr, opt_arg);
188 if (!opt_item) {
189 goto error;
190 }
191
192 g_ptr_array_add(parse_ret->items, opt_item);
193
194 if (descr->with_arg) {
195 /* Option has an argument: no more options */
196 break;
197 }
198
199 /* Go to next short option */
200 short_opt_ch++;
201 }
202
203 goto end;
204
205 error:
206 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
207 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
208 }
209
210 end:
211 return ret;
212 }
213
214 static
215 enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
216 const char * const next_orig_arg,
217 const struct bt_argpar_opt_descr * const descrs,
218 struct bt_argpar_parse_ret * const parse_ret,
219 bool * const used_next_orig_arg)
220 {
221 const size_t max_len = 127;
222 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
223 const struct bt_argpar_opt_descr *descr;
224 struct bt_argpar_item_opt *opt_item;
225
226 /* Option's argument, if any */
227 const char *opt_arg = NULL;
228
229 /* Position of first `=`, if any */
230 const char *eq_pos;
231
232 /* Buffer holding option name when `long_opt_arg` contains `=` */
233 char buf[max_len + 1];
234
235 /* Option name */
236 const char *long_opt_name = long_opt_arg;
237
238 if (strlen(long_opt_arg) == 0) {
239 g_string_append(parse_ret->error, "Invalid argument");
240 goto error;
241 }
242
243 /* Find the first `=` in original argument */
244 eq_pos = strchr(long_opt_arg, '=');
245 if (eq_pos) {
246 const size_t long_opt_name_size = eq_pos - long_opt_arg;
247
248 /* Isolate the option name */
249 if (long_opt_name_size > max_len) {
250 g_string_append_printf(parse_ret->error,
251 "Invalid argument `--%s`", long_opt_arg);
252 goto error;
253 }
254
255 memcpy(buf, long_opt_arg, long_opt_name_size);
256 buf[long_opt_name_size] = '\0';
257 long_opt_name = buf;
258 }
259
260 /* Find corresponding option descriptor */
261 descr = find_descr(descrs, '\0', long_opt_name);
262 if (!descr) {
263 g_string_append_printf(parse_ret->error,
264 "Unknown option `--%s`", long_opt_name);
265 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
266 goto error;
267 }
268
269 /* Find option's argument if any */
270 if (descr->with_arg) {
271 if (eq_pos) {
272 /* `--long-opt=arg` style */
273 opt_arg = eq_pos + 1;
274 } else {
275 /* `--long-opt arg` style */
276 if (!next_orig_arg) {
277 g_string_append_printf(parse_ret->error,
278 "Missing required argument for option `--%s`",
279 long_opt_name);
280 goto error;
281 }
282
283 opt_arg = next_orig_arg;
284 *used_next_orig_arg = true;
285 }
286 }
287
288 /* Create and append option argument */
289 opt_item = create_opt_item(descr, opt_arg);
290 if (!opt_item) {
291 goto error;
292 }
293
294 g_ptr_array_add(parse_ret->items, opt_item);
295 goto end;
296
297 error:
298 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
299 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
300 }
301
302 end:
303 return ret;
304 }
305
306 static
307 enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
308 const char * const next_orig_arg,
309 const struct bt_argpar_opt_descr * const descrs,
310 struct bt_argpar_parse_ret * const parse_ret,
311 bool * const used_next_orig_arg)
312 {
313 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
314
315 BT_ASSERT(orig_arg[0] == '-');
316
317 if (orig_arg[1] == '-') {
318 /* Long option */
319 ret = parse_long_opt(&orig_arg[2],
320 next_orig_arg, descrs, parse_ret,
321 used_next_orig_arg);
322 } else {
323 /* Short option */
324 ret = parse_short_opts(&orig_arg[1],
325 next_orig_arg, descrs, parse_ret,
326 used_next_orig_arg);
327 }
328
329 return ret;
330 }
331
332 static
333 void prepend_while_parsing_arg_to_error(GString * const error,
334 const unsigned int i, const char * const arg)
335 {
336 /* 🙁 There's no g_string_prepend_printf()! */
337 GString * const tmp_str = g_string_new(NULL);
338
339 BT_ASSERT(error);
340 BT_ASSERT(arg);
341
342 if (!tmp_str) {
343 goto end;
344 }
345
346 g_string_append_printf(tmp_str, "While parsing argument #%u (`%s`): %s",
347 i + 1, arg, error->str);
348 g_string_assign(error, tmp_str->str);
349 g_string_free(tmp_str, TRUE);
350
351 end:
352 return;
353 }
354
355 BT_HIDDEN
356 struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc,
357 const char * const *argv,
358 const struct bt_argpar_opt_descr * const descrs,
359 bool fail_on_unknown_opt)
360 {
361 struct bt_argpar_parse_ret parse_ret = { 0 };
362 unsigned int i;
363 unsigned int non_opt_index = 0;
364
365 parse_ret.error = g_string_new(NULL);
366 if (!parse_ret.error) {
367 goto error;
368 }
369
370 parse_ret.items = g_ptr_array_new_with_free_func(
371 (GDestroyNotify) destroy_item);
372 if (!parse_ret.items) {
373 goto error;
374 }
375
376 for (i = 0; i < argc; i++) {
377 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
378 bool used_next_orig_arg = false;
379 const char * const orig_arg = argv[i];
380 const char * const next_orig_arg =
381 i < argc - 1 ? argv[i + 1] : NULL;
382
383 if (orig_arg[0] != '-') {
384 /* Non-option argument */
385 struct bt_argpar_item_non_opt *non_opt_item =
386 create_non_opt_item(orig_arg, i, non_opt_index);
387
388 if (!non_opt_item) {
389 goto error;
390 }
391
392 non_opt_index++;
393 g_ptr_array_add(parse_ret.items, non_opt_item);
394 continue;
395 }
396
397 /* Option argument */
398 parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
399 next_orig_arg, descrs, &parse_ret, &used_next_orig_arg);
400 switch (parse_orig_arg_opt_ret) {
401 case PARSE_ORIG_ARG_OPT_RET_OK:
402 break;
403 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
404 BT_ASSERT(!used_next_orig_arg);
405
406 if (fail_on_unknown_opt) {
407 prepend_while_parsing_arg_to_error(
408 parse_ret.error, i, orig_arg);
409 goto error;
410 }
411
412 /*
413 * The current original argument is not
414 * considered ingested because it triggered an
415 * unknown option.
416 */
417 parse_ret.ingested_orig_args = i;
418 g_string_free(parse_ret.error, TRUE);
419 parse_ret.error = NULL;
420 goto end;
421 case PARSE_ORIG_ARG_OPT_RET_ERROR:
422 prepend_while_parsing_arg_to_error(
423 parse_ret.error, i, orig_arg);
424 goto error;
425 default:
426 abort();
427 }
428
429 if (used_next_orig_arg) {
430 i++;
431 }
432 }
433
434 parse_ret.ingested_orig_args = argc;
435 g_string_free(parse_ret.error, TRUE);
436 parse_ret.error = NULL;
437 goto end;
438
439 error:
440 if (parse_ret.items) {
441 /* That's how we indicate that an error occured */
442 g_ptr_array_free(parse_ret.items, TRUE);
443 parse_ret.items = NULL;
444 }
445
446 end:
447 return parse_ret;
448 }
449
450 BT_HIDDEN
451 void bt_argpar_parse_ret_fini(struct bt_argpar_parse_ret *ret)
452 {
453 BT_ASSERT(ret);
454
455 if (ret->items) {
456 g_ptr_array_free(ret->items, TRUE);
457 ret->items = NULL;
458 }
459
460 if (ret->error) {
461 g_string_free(ret->error, TRUE);
462 ret->error = NULL;
463 }
464 }
This page took 0.052378 seconds and 4 git commands to generate.