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