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