Fix: Error out when passing an argument to long option that takes no argument
[argpar.git] / argpar / argpar.c
CommitLineData
903a5b8a
SM
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
7ac57709
SM
23#include <assert.h>
24#include <stdarg.h>
903a5b8a 25#include <stdbool.h>
7ac57709 26#include <stdio.h>
903a5b8a
SM
27#include <stdlib.h>
28#include <string.h>
903a5b8a
SM
29
30#include "argpar.h"
31
7ac57709
SM
32#define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
33#define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type)))
34#define argpar_zalloc(_type) argpar_calloc(_type, 1)
35
36#define ARGPAR_ASSERT(_cond) assert(_cond)
37
1ae22b5e
SM
38#ifdef __MINGW_PRINTF_FORMAT
39# define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
40#else
41# define ARGPAR_PRINTF_FORMAT printf
42#endif
43
44static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0)))
7ac57709
SM
45char *argpar_vasprintf(const char *fmt, va_list args)
46{
47 int len1, len2;
48 char *str;
49 va_list args2;
50
51 va_copy(args2, args);
52
53 len1 = vsnprintf(NULL, 0, fmt, args);
54 if (len1 < 0) {
55 str = NULL;
56 goto end;
57 }
58
59 str = malloc(len1 + 1);
60 if (!str) {
61 goto end;
62 }
63
64 len2 = vsnprintf(str, len1 + 1, fmt, args2);
65
66 ARGPAR_ASSERT(len1 == len2);
67
68end:
92ecd98e 69 va_end(args2);
7ac57709
SM
70 return str;
71}
72
73
1ae22b5e 74static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2)))
7ac57709
SM
75char *argpar_asprintf(const char *fmt, ...)
76{
77 va_list args;
78 char *str;
f46b5106 79
7ac57709
SM
80 va_start(args, fmt);
81 str = argpar_vasprintf(fmt, args);
82 va_end(args);
83
84 return str;
85}
86
1ae22b5e 87static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3)))
7ac57709
SM
88bool argpar_string_append_printf(char **str, const char *fmt, ...)
89{
90 char *new_str = NULL;
91 char *addendum;
92 bool success;
93 va_list args;
94
95 ARGPAR_ASSERT(str);
96
97 va_start(args, fmt);
98 addendum = argpar_vasprintf(fmt, args);
99 va_end(args);
100
101 if (!addendum) {
102 success = false;
103 goto end;
104 }
105
106 new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum);
107 if (!new_str) {
108 success = false;
109 goto end;
110 }
f46b5106 111
7ac57709
SM
112 free(*str);
113 *str = new_str;
114
115 success = true;
116
117end:
118 free(addendum);
119
120 return success;
121}
122
903a5b8a 123static
1c9a6bde 124void destroy_item(struct argpar_item * const item)
903a5b8a
SM
125{
126 if (!item) {
127 goto end;
128 }
129
1c9a6bde
SM
130 if (item->type == ARGPAR_ITEM_TYPE_OPT) {
131 struct argpar_item_opt * const opt_item = (void *) item;
903a5b8a 132
7ac57709 133 free((void *) opt_item->arg);
903a5b8a
SM
134 }
135
7ac57709 136 free(item);
903a5b8a
SM
137
138end:
139 return;
140}
141
7ac57709 142static
1c9a6bde
SM
143bool push_item(struct argpar_item_array * const array,
144 struct argpar_item * const item)
7ac57709
SM
145{
146 bool success;
147
148 ARGPAR_ASSERT(array);
149 ARGPAR_ASSERT(item);
150
151 if (array->n_items == array->n_alloc) {
152 unsigned int new_n_alloc = array->n_alloc * 2;
1c9a6bde 153 struct argpar_item **new_items;
7ac57709 154
f46b5106 155 new_items = argpar_realloc(array->items,
1c9a6bde 156 struct argpar_item *, new_n_alloc);
7ac57709
SM
157 if (!new_items) {
158 success = false;
159 goto end;
160 }
161
162 array->n_alloc = new_n_alloc;
163 array->items = new_items;
164 }
165
166 array->items[array->n_items] = item;
167 array->n_items++;
168
169 success = true;
170
171end:
172 return success;
173}
174
175static
1c9a6bde 176void destroy_item_array(struct argpar_item_array * const array)
7ac57709
SM
177{
178 if (array) {
179 unsigned int i;
180
181 for (i = 0; i < array->n_items; i++) {
182 destroy_item(array->items[i]);
183 }
184
185 free(array->items);
186 free(array);
187 }
188}
189
190static
1c9a6bde 191struct argpar_item_array *new_item_array(void)
7ac57709 192{
1c9a6bde 193 struct argpar_item_array *ret;
7ac57709
SM
194 const int initial_size = 10;
195
1c9a6bde 196 ret = argpar_zalloc(struct argpar_item_array);
7ac57709
SM
197 if (!ret) {
198 goto end;
199 }
200
1c9a6bde 201 ret->items = argpar_calloc(struct argpar_item *, initial_size);
7ac57709
SM
202 if (!ret->items) {
203 goto error;
204 }
205
206 ret->n_alloc = initial_size;
207
208 goto end;
209
210error:
211 destroy_item_array(ret);
212 ret = NULL;
213
214end:
215 return ret;
216}
217
903a5b8a 218static
1c9a6bde
SM
219struct argpar_item_opt *create_opt_item(
220 const struct argpar_opt_descr * const descr,
903a5b8a
SM
221 const char * const arg)
222{
1c9a6bde
SM
223 struct argpar_item_opt *opt_item =
224 argpar_zalloc(struct argpar_item_opt);
903a5b8a
SM
225
226 if (!opt_item) {
227 goto end;
228 }
229
1c9a6bde 230 opt_item->base.type = ARGPAR_ITEM_TYPE_OPT;
903a5b8a
SM
231 opt_item->descr = descr;
232
233 if (arg) {
7ac57709 234 opt_item->arg = strdup(arg);
903a5b8a
SM
235 if (!opt_item->arg) {
236 goto error;
237 }
238 }
239
240 goto end;
241
242error:
243 destroy_item(&opt_item->base);
244 opt_item = NULL;
245
246end:
247 return opt_item;
248}
249
250static
1c9a6bde 251struct argpar_item_non_opt *create_non_opt_item(const char * const arg,
903a5b8a
SM
252 const unsigned int orig_index,
253 const unsigned int non_opt_index)
254{
1c9a6bde
SM
255 struct argpar_item_non_opt * const non_opt_item =
256 argpar_zalloc(struct argpar_item_non_opt);
903a5b8a
SM
257
258 if (!non_opt_item) {
259 goto end;
260 }
261
1c9a6bde 262 non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT;
903a5b8a
SM
263 non_opt_item->arg = arg;
264 non_opt_item->orig_index = orig_index;
265 non_opt_item->non_opt_index = non_opt_index;
266
267end:
268 return non_opt_item;
269}
270
271static
1c9a6bde
SM
272const struct argpar_opt_descr *find_descr(
273 const struct argpar_opt_descr * const descrs,
903a5b8a
SM
274 const char short_name, const char * const long_name)
275{
1c9a6bde 276 const struct argpar_opt_descr *descr;
903a5b8a
SM
277
278 for (descr = descrs; descr->short_name || descr->long_name; descr++) {
279 if (short_name && descr->short_name &&
280 short_name == descr->short_name) {
281 goto end;
282 }
283
284 if (long_name && descr->long_name &&
285 strcmp(long_name, descr->long_name) == 0) {
286 goto end;
287 }
288 }
289
290end:
291 return !descr->short_name && !descr->long_name ? NULL : descr;
292}
293
294enum parse_orig_arg_opt_ret {
295 PARSE_ORIG_ARG_OPT_RET_OK,
296 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT = -2,
297 PARSE_ORIG_ARG_OPT_RET_ERROR = -1,
298};
299
300static
301enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts,
302 const char * const next_orig_arg,
1c9a6bde
SM
303 const struct argpar_opt_descr * const descrs,
304 struct argpar_parse_ret * const parse_ret,
903a5b8a
SM
305 bool * const used_next_orig_arg)
306{
307 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
308 const char *short_opt_ch = short_opts;
309
310 if (strlen(short_opts) == 0) {
7ac57709 311 argpar_string_append_printf(&parse_ret->error, "Invalid argument");
903a5b8a
SM
312 goto error;
313 }
314
315 while (*short_opt_ch) {
316 const char *opt_arg = NULL;
1c9a6bde
SM
317 const struct argpar_opt_descr *descr;
318 struct argpar_item_opt *opt_item;
903a5b8a
SM
319
320 /* Find corresponding option descriptor */
321 descr = find_descr(descrs, *short_opt_ch, NULL);
322 if (!descr) {
323 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
7ac57709 324 argpar_string_append_printf(&parse_ret->error,
903a5b8a
SM
325 "Unknown option `-%c`", *short_opt_ch);
326 goto error;
327 }
328
329 if (descr->with_arg) {
330 if (short_opt_ch[1]) {
331 /* `-oarg` form */
332 opt_arg = &short_opt_ch[1];
333 } else {
334 /* `-o arg` form */
335 opt_arg = next_orig_arg;
336 *used_next_orig_arg = true;
337 }
338
339 /*
340 * We accept `-o ''` (empty option's argument),
341 * but not `-o` alone if an option's argument is
342 * expected.
343 */
344 if (!opt_arg || (short_opt_ch[1] && strlen(opt_arg) == 0)) {
7ac57709 345 argpar_string_append_printf(&parse_ret->error,
903a5b8a
SM
346 "Missing required argument for option `-%c`",
347 *short_opt_ch);
348 *used_next_orig_arg = false;
349 goto error;
350 }
351 }
352
353 /* Create and append option argument */
354 opt_item = create_opt_item(descr, opt_arg);
355 if (!opt_item) {
356 goto error;
357 }
358
7ac57709
SM
359 if (!push_item(parse_ret->items, &opt_item->base)) {
360 goto error;
361 }
903a5b8a
SM
362
363 if (descr->with_arg) {
364 /* Option has an argument: no more options */
365 break;
366 }
367
368 /* Go to next short option */
369 short_opt_ch++;
370 }
371
372 goto end;
373
374error:
375 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
376 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
377 }
378
379end:
380 return ret;
381}
382
383static
384enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
385 const char * const next_orig_arg,
1c9a6bde
SM
386 const struct argpar_opt_descr * const descrs,
387 struct argpar_parse_ret * const parse_ret,
903a5b8a
SM
388 bool * const used_next_orig_arg)
389{
390 const size_t max_len = 127;
391 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
1c9a6bde
SM
392 const struct argpar_opt_descr *descr;
393 struct argpar_item_opt *opt_item;
903a5b8a
SM
394
395 /* Option's argument, if any */
396 const char *opt_arg = NULL;
397
398 /* Position of first `=`, if any */
399 const char *eq_pos;
400
401 /* Buffer holding option name when `long_opt_arg` contains `=` */
402 char buf[max_len + 1];
403
404 /* Option name */
405 const char *long_opt_name = long_opt_arg;
406
407 if (strlen(long_opt_arg) == 0) {
7ac57709
SM
408 argpar_string_append_printf(&parse_ret->error,
409 "Invalid argument");
903a5b8a
SM
410 goto error;
411 }
412
413 /* Find the first `=` in original argument */
414 eq_pos = strchr(long_opt_arg, '=');
415 if (eq_pos) {
416 const size_t long_opt_name_size = eq_pos - long_opt_arg;
417
418 /* Isolate the option name */
419 if (long_opt_name_size > max_len) {
7ac57709 420 argpar_string_append_printf(&parse_ret->error,
903a5b8a
SM
421 "Invalid argument `--%s`", long_opt_arg);
422 goto error;
423 }
424
425 memcpy(buf, long_opt_arg, long_opt_name_size);
426 buf[long_opt_name_size] = '\0';
427 long_opt_name = buf;
428 }
429
430 /* Find corresponding option descriptor */
431 descr = find_descr(descrs, '\0', long_opt_name);
432 if (!descr) {
7ac57709 433 argpar_string_append_printf(&parse_ret->error,
903a5b8a
SM
434 "Unknown option `--%s`", long_opt_name);
435 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
436 goto error;
437 }
438
439 /* Find option's argument if any */
440 if (descr->with_arg) {
441 if (eq_pos) {
442 /* `--long-opt=arg` style */
443 opt_arg = eq_pos + 1;
444 } else {
445 /* `--long-opt arg` style */
446 if (!next_orig_arg) {
7ac57709 447 argpar_string_append_printf(&parse_ret->error,
903a5b8a
SM
448 "Missing required argument for option `--%s`",
449 long_opt_name);
450 goto error;
451 }
452
453 opt_arg = next_orig_arg;
454 *used_next_orig_arg = true;
455 }
430fe886
SM
456 } else if (eq_pos) {
457 /*
458 * Unexpected `--opt=arg` style for a long option which
459 * doesn't accept an argument.
460 */
461 argpar_string_append_printf(&parse_ret->error,
462 "Unexpected argument for option `--%s`",
463 long_opt_name);
464 goto error;
903a5b8a
SM
465 }
466
467 /* Create and append option argument */
468 opt_item = create_opt_item(descr, opt_arg);
469 if (!opt_item) {
470 goto error;
471 }
472
7ac57709
SM
473 if (!push_item(parse_ret->items, &opt_item->base)) {
474 goto error;
475 }
476
903a5b8a
SM
477 goto end;
478
479error:
480 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
481 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
482 }
483
484end:
485 return ret;
486}
487
488static
489enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
490 const char * const next_orig_arg,
1c9a6bde
SM
491 const struct argpar_opt_descr * const descrs,
492 struct argpar_parse_ret * const parse_ret,
903a5b8a
SM
493 bool * const used_next_orig_arg)
494{
495 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
496
7ac57709 497 ARGPAR_ASSERT(orig_arg[0] == '-');
903a5b8a
SM
498
499 if (orig_arg[1] == '-') {
500 /* Long option */
501 ret = parse_long_opt(&orig_arg[2],
502 next_orig_arg, descrs, parse_ret,
503 used_next_orig_arg);
504 } else {
505 /* Short option */
506 ret = parse_short_opts(&orig_arg[1],
507 next_orig_arg, descrs, parse_ret,
508 used_next_orig_arg);
509 }
510
511 return ret;
512}
513
514static
7ac57709 515bool prepend_while_parsing_arg_to_error(char **error,
903a5b8a
SM
516 const unsigned int i, const char * const arg)
517{
7ac57709
SM
518 char *new_error;
519 bool success;
903a5b8a 520
7ac57709
SM
521 ARGPAR_ASSERT(error);
522 ARGPAR_ASSERT(*error);
903a5b8a 523
7ac57709
SM
524 new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s",
525 i + 1, arg, *error);
526 if (!new_error) {
527 success = false;
903a5b8a
SM
528 goto end;
529 }
530
7ac57709
SM
531 free(*error);
532 *error = new_error;
533 success = true;
903a5b8a
SM
534
535end:
7ac57709 536 return success;
903a5b8a
SM
537}
538
7ac57709 539ARGPAR_HIDDEN
1c9a6bde 540struct argpar_parse_ret argpar_parse(unsigned int argc,
903a5b8a 541 const char * const *argv,
1c9a6bde 542 const struct argpar_opt_descr * const descrs,
903a5b8a
SM
543 bool fail_on_unknown_opt)
544{
1c9a6bde 545 struct argpar_parse_ret parse_ret = { 0 };
903a5b8a
SM
546 unsigned int i;
547 unsigned int non_opt_index = 0;
548
7ac57709 549 parse_ret.items = new_item_array();
903a5b8a
SM
550 if (!parse_ret.items) {
551 goto error;
552 }
553
554 for (i = 0; i < argc; i++) {
555 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
556 bool used_next_orig_arg = false;
557 const char * const orig_arg = argv[i];
558 const char * const next_orig_arg =
559 i < argc - 1 ? argv[i + 1] : NULL;
560
561 if (orig_arg[0] != '-') {
562 /* Non-option argument */
1c9a6bde 563 struct argpar_item_non_opt *non_opt_item =
903a5b8a
SM
564 create_non_opt_item(orig_arg, i, non_opt_index);
565
566 if (!non_opt_item) {
567 goto error;
568 }
569
570 non_opt_index++;
7ac57709
SM
571
572 if (!push_item(parse_ret.items, &non_opt_item->base)) {
573 goto error;
574 }
575
903a5b8a
SM
576 continue;
577 }
578
579 /* Option argument */
580 parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
581 next_orig_arg, descrs, &parse_ret, &used_next_orig_arg);
582 switch (parse_orig_arg_opt_ret) {
583 case PARSE_ORIG_ARG_OPT_RET_OK:
584 break;
585 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
7ac57709 586 ARGPAR_ASSERT(!used_next_orig_arg);
903a5b8a
SM
587
588 if (fail_on_unknown_opt) {
589 prepend_while_parsing_arg_to_error(
7ac57709 590 &parse_ret.error, i, orig_arg);
903a5b8a
SM
591 goto error;
592 }
593
594 /*
595 * The current original argument is not
596 * considered ingested because it triggered an
597 * unknown option.
598 */
599 parse_ret.ingested_orig_args = i;
7ac57709 600 free(parse_ret.error);
903a5b8a
SM
601 parse_ret.error = NULL;
602 goto end;
603 case PARSE_ORIG_ARG_OPT_RET_ERROR:
604 prepend_while_parsing_arg_to_error(
7ac57709 605 &parse_ret.error, i, orig_arg);
903a5b8a
SM
606 goto error;
607 default:
7ac57709 608 abort();
903a5b8a
SM
609 }
610
611 if (used_next_orig_arg) {
612 i++;
613 }
614 }
615
616 parse_ret.ingested_orig_args = argc;
7ac57709 617 free(parse_ret.error);
903a5b8a
SM
618 parse_ret.error = NULL;
619 goto end;
620
621error:
7ac57709
SM
622 /* That's how we indicate that an error occured */
623 destroy_item_array(parse_ret.items);
624 parse_ret.items = NULL;
903a5b8a
SM
625
626end:
627 return parse_ret;
628}
629
7ac57709 630ARGPAR_HIDDEN
1c9a6bde 631void argpar_parse_ret_fini(struct argpar_parse_ret *ret)
903a5b8a 632{
7ac57709 633 ARGPAR_ASSERT(ret);
903a5b8a 634
7ac57709
SM
635 destroy_item_array(ret->items);
636 ret->items = NULL;
903a5b8a 637
7ac57709
SM
638 free(ret->error);
639 ret->error = NULL;
903a5b8a 640}
This page took 0.047463 seconds and 4 git commands to generate.