Fix: Error out when passing an argument to long option that takes no argument
[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 #ifdef __MINGW_PRINTF_FORMAT
39 # define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
40 #else
41 # define ARGPAR_PRINTF_FORMAT printf
42 #endif
43
44 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0)))
45 char *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
68 end:
69 va_end(args2);
70 return str;
71 }
72
73
74 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2)))
75 char *argpar_asprintf(const char *fmt, ...)
76 {
77 va_list args;
78 char *str;
79
80 va_start(args, fmt);
81 str = argpar_vasprintf(fmt, args);
82 va_end(args);
83
84 return str;
85 }
86
87 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3)))
88 bool 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 }
111
112 free(*str);
113 *str = new_str;
114
115 success = true;
116
117 end:
118 free(addendum);
119
120 return success;
121 }
122
123 static
124 void destroy_item(struct argpar_item * const item)
125 {
126 if (!item) {
127 goto end;
128 }
129
130 if (item->type == ARGPAR_ITEM_TYPE_OPT) {
131 struct argpar_item_opt * const opt_item = (void *) item;
132
133 free((void *) opt_item->arg);
134 }
135
136 free(item);
137
138 end:
139 return;
140 }
141
142 static
143 bool push_item(struct argpar_item_array * const array,
144 struct argpar_item * const item)
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;
153 struct argpar_item **new_items;
154
155 new_items = argpar_realloc(array->items,
156 struct argpar_item *, new_n_alloc);
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
171 end:
172 return success;
173 }
174
175 static
176 void destroy_item_array(struct argpar_item_array * const array)
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
190 static
191 struct argpar_item_array *new_item_array(void)
192 {
193 struct argpar_item_array *ret;
194 const int initial_size = 10;
195
196 ret = argpar_zalloc(struct argpar_item_array);
197 if (!ret) {
198 goto end;
199 }
200
201 ret->items = argpar_calloc(struct argpar_item *, initial_size);
202 if (!ret->items) {
203 goto error;
204 }
205
206 ret->n_alloc = initial_size;
207
208 goto end;
209
210 error:
211 destroy_item_array(ret);
212 ret = NULL;
213
214 end:
215 return ret;
216 }
217
218 static
219 struct argpar_item_opt *create_opt_item(
220 const struct argpar_opt_descr * const descr,
221 const char * const arg)
222 {
223 struct argpar_item_opt *opt_item =
224 argpar_zalloc(struct argpar_item_opt);
225
226 if (!opt_item) {
227 goto end;
228 }
229
230 opt_item->base.type = ARGPAR_ITEM_TYPE_OPT;
231 opt_item->descr = descr;
232
233 if (arg) {
234 opt_item->arg = strdup(arg);
235 if (!opt_item->arg) {
236 goto error;
237 }
238 }
239
240 goto end;
241
242 error:
243 destroy_item(&opt_item->base);
244 opt_item = NULL;
245
246 end:
247 return opt_item;
248 }
249
250 static
251 struct argpar_item_non_opt *create_non_opt_item(const char * const arg,
252 const unsigned int orig_index,
253 const unsigned int non_opt_index)
254 {
255 struct argpar_item_non_opt * const non_opt_item =
256 argpar_zalloc(struct argpar_item_non_opt);
257
258 if (!non_opt_item) {
259 goto end;
260 }
261
262 non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT;
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
267 end:
268 return non_opt_item;
269 }
270
271 static
272 const struct argpar_opt_descr *find_descr(
273 const struct argpar_opt_descr * const descrs,
274 const char short_name, const char * const long_name)
275 {
276 const struct argpar_opt_descr *descr;
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
290 end:
291 return !descr->short_name && !descr->long_name ? NULL : descr;
292 }
293
294 enum 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
300 static
301 enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts,
302 const char * const next_orig_arg,
303 const struct argpar_opt_descr * const descrs,
304 struct argpar_parse_ret * const parse_ret,
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) {
311 argpar_string_append_printf(&parse_ret->error, "Invalid argument");
312 goto error;
313 }
314
315 while (*short_opt_ch) {
316 const char *opt_arg = NULL;
317 const struct argpar_opt_descr *descr;
318 struct argpar_item_opt *opt_item;
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;
324 argpar_string_append_printf(&parse_ret->error,
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)) {
345 argpar_string_append_printf(&parse_ret->error,
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
359 if (!push_item(parse_ret->items, &opt_item->base)) {
360 goto error;
361 }
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
374 error:
375 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
376 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
377 }
378
379 end:
380 return ret;
381 }
382
383 static
384 enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
385 const char * const next_orig_arg,
386 const struct argpar_opt_descr * const descrs,
387 struct argpar_parse_ret * const parse_ret,
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;
392 const struct argpar_opt_descr *descr;
393 struct argpar_item_opt *opt_item;
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) {
408 argpar_string_append_printf(&parse_ret->error,
409 "Invalid argument");
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) {
420 argpar_string_append_printf(&parse_ret->error,
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) {
433 argpar_string_append_printf(&parse_ret->error,
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) {
447 argpar_string_append_printf(&parse_ret->error,
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 } else if (eq_pos) {
457 /*
458 * Unexpected `--opt=arg` style for a long option which
459 * doesn't accept an argument.
460 */
461 argpar_string_append_printf(&parse_ret->error,
462 "Unexpected argument for option `--%s`",
463 long_opt_name);
464 goto error;
465 }
466
467 /* Create and append option argument */
468 opt_item = create_opt_item(descr, opt_arg);
469 if (!opt_item) {
470 goto error;
471 }
472
473 if (!push_item(parse_ret->items, &opt_item->base)) {
474 goto error;
475 }
476
477 goto end;
478
479 error:
480 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
481 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
482 }
483
484 end:
485 return ret;
486 }
487
488 static
489 enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
490 const char * const next_orig_arg,
491 const struct argpar_opt_descr * const descrs,
492 struct argpar_parse_ret * const parse_ret,
493 bool * const used_next_orig_arg)
494 {
495 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
496
497 ARGPAR_ASSERT(orig_arg[0] == '-');
498
499 if (orig_arg[1] == '-') {
500 /* Long option */
501 ret = parse_long_opt(&orig_arg[2],
502 next_orig_arg, descrs, parse_ret,
503 used_next_orig_arg);
504 } else {
505 /* Short option */
506 ret = parse_short_opts(&orig_arg[1],
507 next_orig_arg, descrs, parse_ret,
508 used_next_orig_arg);
509 }
510
511 return ret;
512 }
513
514 static
515 bool prepend_while_parsing_arg_to_error(char **error,
516 const unsigned int i, const char * const arg)
517 {
518 char *new_error;
519 bool success;
520
521 ARGPAR_ASSERT(error);
522 ARGPAR_ASSERT(*error);
523
524 new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s",
525 i + 1, arg, *error);
526 if (!new_error) {
527 success = false;
528 goto end;
529 }
530
531 free(*error);
532 *error = new_error;
533 success = true;
534
535 end:
536 return success;
537 }
538
539 ARGPAR_HIDDEN
540 struct argpar_parse_ret argpar_parse(unsigned int argc,
541 const char * const *argv,
542 const struct argpar_opt_descr * const descrs,
543 bool fail_on_unknown_opt)
544 {
545 struct argpar_parse_ret parse_ret = { 0 };
546 unsigned int i;
547 unsigned int non_opt_index = 0;
548
549 parse_ret.items = new_item_array();
550 if (!parse_ret.items) {
551 goto error;
552 }
553
554 for (i = 0; i < argc; i++) {
555 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
556 bool used_next_orig_arg = false;
557 const char * const orig_arg = argv[i];
558 const char * const next_orig_arg =
559 i < argc - 1 ? argv[i + 1] : NULL;
560
561 if (orig_arg[0] != '-') {
562 /* Non-option argument */
563 struct argpar_item_non_opt *non_opt_item =
564 create_non_opt_item(orig_arg, i, non_opt_index);
565
566 if (!non_opt_item) {
567 goto error;
568 }
569
570 non_opt_index++;
571
572 if (!push_item(parse_ret.items, &non_opt_item->base)) {
573 goto error;
574 }
575
576 continue;
577 }
578
579 /* Option argument */
580 parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
581 next_orig_arg, descrs, &parse_ret, &used_next_orig_arg);
582 switch (parse_orig_arg_opt_ret) {
583 case PARSE_ORIG_ARG_OPT_RET_OK:
584 break;
585 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
586 ARGPAR_ASSERT(!used_next_orig_arg);
587
588 if (fail_on_unknown_opt) {
589 prepend_while_parsing_arg_to_error(
590 &parse_ret.error, i, orig_arg);
591 goto error;
592 }
593
594 /*
595 * The current original argument is not
596 * considered ingested because it triggered an
597 * unknown option.
598 */
599 parse_ret.ingested_orig_args = i;
600 free(parse_ret.error);
601 parse_ret.error = NULL;
602 goto end;
603 case PARSE_ORIG_ARG_OPT_RET_ERROR:
604 prepend_while_parsing_arg_to_error(
605 &parse_ret.error, i, orig_arg);
606 goto error;
607 default:
608 abort();
609 }
610
611 if (used_next_orig_arg) {
612 i++;
613 }
614 }
615
616 parse_ret.ingested_orig_args = argc;
617 free(parse_ret.error);
618 parse_ret.error = NULL;
619 goto end;
620
621 error:
622 /* That's how we indicate that an error occured */
623 destroy_item_array(parse_ret.items);
624 parse_ret.items = NULL;
625
626 end:
627 return parse_ret;
628 }
629
630 ARGPAR_HIDDEN
631 void argpar_parse_ret_fini(struct argpar_parse_ret *ret)
632 {
633 ARGPAR_ASSERT(ret);
634
635 destroy_item_array(ret->items);
636 ret->items = NULL;
637
638 free(ret->error);
639 ret->error = NULL;
640 }
This page took 0.066795 seconds and 5 git commands to generate.