Add missing va_end in argpar_vasprintf
[argpar.git] / argpar / argpar.c
... / ...
CommitLineData
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 <assert.h>
24#include <stdarg.h>
25#include <stdbool.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29
30#include "argpar.h"
31
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:
63 va_end(args2);
64 return str;
65}
66
67
68static
69char *argpar_asprintf(const char *fmt, ...)
70{
71 va_list args;
72 char *str;
73
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 }
105
106 free(*str);
107 *str = new_str;
108
109 success = true;
110
111end:
112 free(addendum);
113
114 return success;
115}
116
117static
118void destroy_item(struct argpar_item * const item)
119{
120 if (!item) {
121 goto end;
122 }
123
124 if (item->type == ARGPAR_ITEM_TYPE_OPT) {
125 struct argpar_item_opt * const opt_item = (void *) item;
126
127 free((void *) opt_item->arg);
128 }
129
130 free(item);
131
132end:
133 return;
134}
135
136static
137bool push_item(struct argpar_item_array * const array,
138 struct argpar_item * const item)
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;
147 struct argpar_item **new_items;
148
149 new_items = argpar_realloc(array->items,
150 struct argpar_item *, new_n_alloc);
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
170void destroy_item_array(struct argpar_item_array * const array)
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
185struct argpar_item_array *new_item_array(void)
186{
187 struct argpar_item_array *ret;
188 const int initial_size = 10;
189
190 ret = argpar_zalloc(struct argpar_item_array);
191 if (!ret) {
192 goto end;
193 }
194
195 ret->items = argpar_calloc(struct argpar_item *, initial_size);
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
212static
213struct argpar_item_opt *create_opt_item(
214 const struct argpar_opt_descr * const descr,
215 const char * const arg)
216{
217 struct argpar_item_opt *opt_item =
218 argpar_zalloc(struct argpar_item_opt);
219
220 if (!opt_item) {
221 goto end;
222 }
223
224 opt_item->base.type = ARGPAR_ITEM_TYPE_OPT;
225 opt_item->descr = descr;
226
227 if (arg) {
228 opt_item->arg = strdup(arg);
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
245struct argpar_item_non_opt *create_non_opt_item(const char * const arg,
246 const unsigned int orig_index,
247 const unsigned int non_opt_index)
248{
249 struct argpar_item_non_opt * const non_opt_item =
250 argpar_zalloc(struct argpar_item_non_opt);
251
252 if (!non_opt_item) {
253 goto end;
254 }
255
256 non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT;
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
266const struct argpar_opt_descr *find_descr(
267 const struct argpar_opt_descr * const descrs,
268 const char short_name, const char * const long_name)
269{
270 const struct argpar_opt_descr *descr;
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,
297 const struct argpar_opt_descr * const descrs,
298 struct argpar_parse_ret * const parse_ret,
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) {
305 argpar_string_append_printf(&parse_ret->error, "Invalid argument");
306 goto error;
307 }
308
309 while (*short_opt_ch) {
310 const char *opt_arg = NULL;
311 const struct argpar_opt_descr *descr;
312 struct argpar_item_opt *opt_item;
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;
318 argpar_string_append_printf(&parse_ret->error,
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)) {
339 argpar_string_append_printf(&parse_ret->error,
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
353 if (!push_item(parse_ret->items, &opt_item->base)) {
354 goto error;
355 }
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,
380 const struct argpar_opt_descr * const descrs,
381 struct argpar_parse_ret * const parse_ret,
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;
386 const struct argpar_opt_descr *descr;
387 struct argpar_item_opt *opt_item;
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) {
402 argpar_string_append_printf(&parse_ret->error,
403 "Invalid argument");
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) {
414 argpar_string_append_printf(&parse_ret->error,
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) {
427 argpar_string_append_printf(&parse_ret->error,
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) {
441 argpar_string_append_printf(&parse_ret->error,
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
458 if (!push_item(parse_ret->items, &opt_item->base)) {
459 goto error;
460 }
461
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,
476 const struct argpar_opt_descr * const descrs,
477 struct argpar_parse_ret * const parse_ret,
478 bool * const used_next_orig_arg)
479{
480 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
481
482 ARGPAR_ASSERT(orig_arg[0] == '-');
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
500bool prepend_while_parsing_arg_to_error(char **error,
501 const unsigned int i, const char * const arg)
502{
503 char *new_error;
504 bool success;
505
506 ARGPAR_ASSERT(error);
507 ARGPAR_ASSERT(*error);
508
509 new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s",
510 i + 1, arg, *error);
511 if (!new_error) {
512 success = false;
513 goto end;
514 }
515
516 free(*error);
517 *error = new_error;
518 success = true;
519
520end:
521 return success;
522}
523
524ARGPAR_HIDDEN
525struct argpar_parse_ret argpar_parse(unsigned int argc,
526 const char * const *argv,
527 const struct argpar_opt_descr * const descrs,
528 bool fail_on_unknown_opt)
529{
530 struct argpar_parse_ret parse_ret = { 0 };
531 unsigned int i;
532 unsigned int non_opt_index = 0;
533
534 parse_ret.items = new_item_array();
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 */
548 struct argpar_item_non_opt *non_opt_item =
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++;
556
557 if (!push_item(parse_ret.items, &non_opt_item->base)) {
558 goto error;
559 }
560
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:
571 ARGPAR_ASSERT(!used_next_orig_arg);
572
573 if (fail_on_unknown_opt) {
574 prepend_while_parsing_arg_to_error(
575 &parse_ret.error, i, orig_arg);
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;
585 free(parse_ret.error);
586 parse_ret.error = NULL;
587 goto end;
588 case PARSE_ORIG_ARG_OPT_RET_ERROR:
589 prepend_while_parsing_arg_to_error(
590 &parse_ret.error, i, orig_arg);
591 goto error;
592 default:
593 abort();
594 }
595
596 if (used_next_orig_arg) {
597 i++;
598 }
599 }
600
601 parse_ret.ingested_orig_args = argc;
602 free(parse_ret.error);
603 parse_ret.error = NULL;
604 goto end;
605
606error:
607 /* That's how we indicate that an error occured */
608 destroy_item_array(parse_ret.items);
609 parse_ret.items = NULL;
610
611end:
612 return parse_ret;
613}
614
615ARGPAR_HIDDEN
616void argpar_parse_ret_fini(struct argpar_parse_ret *ret)
617{
618 ARGPAR_ASSERT(ret);
619
620 destroy_item_array(ret->items);
621 ret->items = NULL;
622
623 free(ret->error);
624 ret->error = NULL;
625}
This page took 0.02396 seconds and 4 git commands to generate.