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