Add format attributes to functions with format strings
[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 }
456 }
457
458 /* Create and append option argument */
459 opt_item = create_opt_item(descr, opt_arg);
460 if (!opt_item) {
461 goto error;
462 }
463
7ac57709
SM
464 if (!push_item(parse_ret->items, &opt_item->base)) {
465 goto error;
466 }
467
903a5b8a
SM
468 goto end;
469
470error:
471 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
472 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
473 }
474
475end:
476 return ret;
477}
478
479static
480enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
481 const char * const next_orig_arg,
1c9a6bde
SM
482 const struct argpar_opt_descr * const descrs,
483 struct argpar_parse_ret * const parse_ret,
903a5b8a
SM
484 bool * const used_next_orig_arg)
485{
486 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
487
7ac57709 488 ARGPAR_ASSERT(orig_arg[0] == '-');
903a5b8a
SM
489
490 if (orig_arg[1] == '-') {
491 /* Long option */
492 ret = parse_long_opt(&orig_arg[2],
493 next_orig_arg, descrs, parse_ret,
494 used_next_orig_arg);
495 } else {
496 /* Short option */
497 ret = parse_short_opts(&orig_arg[1],
498 next_orig_arg, descrs, parse_ret,
499 used_next_orig_arg);
500 }
501
502 return ret;
503}
504
505static
7ac57709 506bool prepend_while_parsing_arg_to_error(char **error,
903a5b8a
SM
507 const unsigned int i, const char * const arg)
508{
7ac57709
SM
509 char *new_error;
510 bool success;
903a5b8a 511
7ac57709
SM
512 ARGPAR_ASSERT(error);
513 ARGPAR_ASSERT(*error);
903a5b8a 514
7ac57709
SM
515 new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s",
516 i + 1, arg, *error);
517 if (!new_error) {
518 success = false;
903a5b8a
SM
519 goto end;
520 }
521
7ac57709
SM
522 free(*error);
523 *error = new_error;
524 success = true;
903a5b8a
SM
525
526end:
7ac57709 527 return success;
903a5b8a
SM
528}
529
7ac57709 530ARGPAR_HIDDEN
1c9a6bde 531struct argpar_parse_ret argpar_parse(unsigned int argc,
903a5b8a 532 const char * const *argv,
1c9a6bde 533 const struct argpar_opt_descr * const descrs,
903a5b8a
SM
534 bool fail_on_unknown_opt)
535{
1c9a6bde 536 struct argpar_parse_ret parse_ret = { 0 };
903a5b8a
SM
537 unsigned int i;
538 unsigned int non_opt_index = 0;
539
7ac57709 540 parse_ret.items = new_item_array();
903a5b8a
SM
541 if (!parse_ret.items) {
542 goto error;
543 }
544
545 for (i = 0; i < argc; i++) {
546 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
547 bool used_next_orig_arg = false;
548 const char * const orig_arg = argv[i];
549 const char * const next_orig_arg =
550 i < argc - 1 ? argv[i + 1] : NULL;
551
552 if (orig_arg[0] != '-') {
553 /* Non-option argument */
1c9a6bde 554 struct argpar_item_non_opt *non_opt_item =
903a5b8a
SM
555 create_non_opt_item(orig_arg, i, non_opt_index);
556
557 if (!non_opt_item) {
558 goto error;
559 }
560
561 non_opt_index++;
7ac57709
SM
562
563 if (!push_item(parse_ret.items, &non_opt_item->base)) {
564 goto error;
565 }
566
903a5b8a
SM
567 continue;
568 }
569
570 /* Option argument */
571 parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
572 next_orig_arg, descrs, &parse_ret, &used_next_orig_arg);
573 switch (parse_orig_arg_opt_ret) {
574 case PARSE_ORIG_ARG_OPT_RET_OK:
575 break;
576 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
7ac57709 577 ARGPAR_ASSERT(!used_next_orig_arg);
903a5b8a
SM
578
579 if (fail_on_unknown_opt) {
580 prepend_while_parsing_arg_to_error(
7ac57709 581 &parse_ret.error, i, orig_arg);
903a5b8a
SM
582 goto error;
583 }
584
585 /*
586 * The current original argument is not
587 * considered ingested because it triggered an
588 * unknown option.
589 */
590 parse_ret.ingested_orig_args = i;
7ac57709 591 free(parse_ret.error);
903a5b8a
SM
592 parse_ret.error = NULL;
593 goto end;
594 case PARSE_ORIG_ARG_OPT_RET_ERROR:
595 prepend_while_parsing_arg_to_error(
7ac57709 596 &parse_ret.error, i, orig_arg);
903a5b8a
SM
597 goto error;
598 default:
7ac57709 599 abort();
903a5b8a
SM
600 }
601
602 if (used_next_orig_arg) {
603 i++;
604 }
605 }
606
607 parse_ret.ingested_orig_args = argc;
7ac57709 608 free(parse_ret.error);
903a5b8a
SM
609 parse_ret.error = NULL;
610 goto end;
611
612error:
7ac57709
SM
613 /* That's how we indicate that an error occured */
614 destroy_item_array(parse_ret.items);
615 parse_ret.items = NULL;
903a5b8a
SM
616
617end:
618 return parse_ret;
619}
620
7ac57709 621ARGPAR_HIDDEN
1c9a6bde 622void argpar_parse_ret_fini(struct argpar_parse_ret *ret)
903a5b8a 623{
7ac57709 624 ARGPAR_ASSERT(ret);
903a5b8a 625
7ac57709
SM
626 destroy_item_array(ret->items);
627 ret->items = NULL;
903a5b8a 628
7ac57709
SM
629 free(ret->error);
630 ret->error = NULL;
903a5b8a 631}
This page took 0.078739 seconds and 4 git commands to generate.