2 * Copyright (c) 2019-2021 Philippe Proulx <pproulx@efficios.com>
3 * Copyright (c) 2020-2021 Simon Marchi <simon.marchi@efficios.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; under version 2 of the License.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 #include "argpar/argpar.h"
30 * Formats `item` and appends the resulting string to `res_str` to
31 * incrementally build an expected command line string.
35 * * Prefers the `--long-opt=arg` style over the `-s arg` style.
37 * * Uses the `arg<A,B>` form for non-option arguments, where `A` is the
38 * original argument index and `B` is the non-option argument index.
41 void append_to_res_str(GString
* const res_str
,
42 const struct argpar_item
* const item
)
44 if (res_str
->len
> 0) {
45 g_string_append_c(res_str
, ' ');
48 switch (argpar_item_type(item
)) {
49 case ARGPAR_ITEM_TYPE_OPT
:
51 const struct argpar_opt_descr
* const descr
=
52 argpar_item_opt_descr(item
);
53 const char * const arg
= argpar_item_opt_arg(item
);
55 if (descr
->long_name
) {
56 g_string_append_printf(res_str
, "--%s",
60 g_string_append_printf(res_str
, "=%s", arg
);
62 } else if (descr
->short_name
) {
63 g_string_append_printf(res_str
, "-%c",
67 g_string_append_printf(res_str
, " %s", arg
);
73 case ARGPAR_ITEM_TYPE_NON_OPT
:
75 const char * const arg
= argpar_item_non_opt_arg(item
);
76 const unsigned int orig_index
=
77 argpar_item_non_opt_orig_index(item
);
78 const unsigned int non_opt_index
=
79 argpar_item_non_opt_non_opt_index(item
);
81 g_string_append_printf(res_str
, "%s<%u,%u>", arg
, orig_index
,
91 * Parses `cmdline` with the argpar API using the option descriptors
92 * `descrs`, and ensures that the resulting effective command line is
93 * `expected_cmd_line` and that the number of ingested original
94 * arguments is `expected_ingested_orig_args`.
96 * This function splits `cmdline` on spaces to create an original
99 * This function builds the resulting command line from parsing items
100 * by space-separating each formatted item (see append_to_res_str()).
103 void test_succeed(const char * const cmdline
,
104 const char * const expected_cmd_line
,
105 const struct argpar_opt_descr
* const descrs
,
106 const unsigned int expected_ingested_orig_args
)
108 struct argpar_iter
*iter
= NULL
;
109 const struct argpar_item
*item
= NULL
;
110 const struct argpar_error
*error
= NULL
;
111 GString
* const res_str
= g_string_new(NULL
);
112 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
113 unsigned int i
, actual_ingested_orig_args
;
117 iter
= argpar_iter_create(g_strv_length(argv
),
118 (const char * const *) argv
, descrs
);
122 enum argpar_iter_next_status status
;
124 ARGPAR_ITEM_DESTROY_AND_RESET(item
);
125 status
= argpar_iter_next(iter
, &item
, &error
);
127 ok(status
== ARGPAR_ITER_NEXT_STATUS_OK
||
128 status
== ARGPAR_ITER_NEXT_STATUS_END
,
129 "argpar_iter_next() returns the expected status "
130 "(%d) for command line `%s` (call %u)",
131 status
, cmdline
, i
+ 1);
133 "argpar_iter_next() doesn't set an error for "
134 "command line `%s` (call %u)",
137 if (status
== ARGPAR_ITER_NEXT_STATUS_END
) {
139 "argpar_iter_next() doesn't set an item "
140 "for status `ARGPAR_ITER_NEXT_STATUS_END` "
141 "and command line `%s` (call %u)",
146 append_to_res_str(res_str
, item
);
149 actual_ingested_orig_args
= argpar_iter_ingested_orig_args(iter
);
150 ok(actual_ingested_orig_args
== expected_ingested_orig_args
,
151 "argpar_iter_ingested_orig_args() returns the expected "
152 "number of ingested original arguments for command line `%s`",
155 if (actual_ingested_orig_args
!= expected_ingested_orig_args
) {
156 diag("Expected: %u Got: %u", expected_ingested_orig_args
,
157 actual_ingested_orig_args
);
160 ok(strcmp(expected_cmd_line
, res_str
->str
) == 0,
161 "argpar_iter_next() returns the expected parsing items "
162 "for command line `%s`", cmdline
);
164 if (strcmp(expected_cmd_line
, res_str
->str
) != 0) {
165 diag("Expected: `%s`", expected_cmd_line
);
166 diag("Got: `%s`", res_str
->str
);
169 argpar_item_destroy(item
);
170 argpar_iter_destroy(iter
);
172 g_string_free(res_str
, TRUE
);
177 void succeed_tests(void)
181 const struct argpar_opt_descr descrs
[] = {
182 ARGPAR_OPT_DESCR_SENTINEL
191 /* Single long option */
193 const struct argpar_opt_descr descrs
[] = {
194 { 0, '\0', "salut", false },
195 ARGPAR_OPT_DESCR_SENTINEL
204 /* Single short option */
206 const struct argpar_opt_descr descrs
[] = {
207 { 0, 'f', NULL
, false },
208 ARGPAR_OPT_DESCR_SENTINEL
217 /* Short and long option (aliases) */
219 const struct argpar_opt_descr descrs
[] = {
220 { 0, 'f', "flaw", false },
221 ARGPAR_OPT_DESCR_SENTINEL
230 /* Long option with argument (space form) */
232 const struct argpar_opt_descr descrs
[] = {
233 { 0, '\0', "tooth", true },
234 ARGPAR_OPT_DESCR_SENTINEL
243 /* Long option with argument (equal form) */
245 const struct argpar_opt_descr descrs
[] = {
246 { 0, '\0', "polish", true },
247 ARGPAR_OPT_DESCR_SENTINEL
256 /* Short option with argument (space form) */
258 const struct argpar_opt_descr descrs
[] = {
259 { 0, 'c', NULL
, true },
260 ARGPAR_OPT_DESCR_SENTINEL
269 /* Short option with argument (glued form) */
271 const struct argpar_opt_descr descrs
[] = {
272 { 0, 'c', NULL
, true },
273 ARGPAR_OPT_DESCR_SENTINEL
282 /* Short and long option (aliases) with argument (all forms) */
284 const struct argpar_opt_descr descrs
[] = {
285 { 0, 'd', "dry", true },
286 ARGPAR_OPT_DESCR_SENTINEL
290 "--dry=rate -dthing --dry street --dry=shape",
291 "--dry=rate --dry=thing --dry=street --dry=shape",
295 /* Many short options, last one with argument (glued form) */
297 const struct argpar_opt_descr descrs
[] = {
298 { 0, 'd', NULL
, false },
299 { 0, 'e', NULL
, false },
300 { 0, 'f', NULL
, true },
301 ARGPAR_OPT_DESCR_SENTINEL
312 const struct argpar_opt_descr descrs
[] = {
313 { 0, 'd', NULL
, false },
314 { 0, 'e', "east", true },
315 { 0, '\0', "mind", false },
316 ARGPAR_OPT_DESCR_SENTINEL
320 "-d --mind -destart --mind --east cough -d --east=itch",
321 "-d --mind -d --east=start --mind --east=cough -d --east=itch",
325 /* Single non-option argument */
327 const struct argpar_opt_descr descrs
[] = {
328 ARGPAR_OPT_DESCR_SENTINEL
337 /* Two non-option arguments */
339 const struct argpar_opt_descr descrs
[] = {
340 ARGPAR_OPT_DESCR_SENTINEL
345 "kilojoule<0,0> mitaine<1,1>",
349 /* Single non-option argument mixed with options */
351 const struct argpar_opt_descr descrs
[] = {
352 { 0, 'd', NULL
, false },
353 { 0, '\0', "squeeze", true },
354 ARGPAR_OPT_DESCR_SENTINEL
358 "-d sprout yes --squeeze little bag -d",
359 "-d sprout<1,0> yes<2,1> --squeeze=little bag<5,2> -d",
365 const struct argpar_opt_descr descrs
[] = {
366 { 0, '\0', "-fuel", true },
367 ARGPAR_OPT_DESCR_SENTINEL
376 /* Long option containing `=` in argument (equal form) */
378 const struct argpar_opt_descr descrs
[] = {
379 { 0, '\0', "zebra", true },
380 ARGPAR_OPT_DESCR_SENTINEL
389 /* Short option's argument starting with `-` (glued form) */
391 const struct argpar_opt_descr descrs
[] = {
392 { 0, 'z', NULL
, true },
393 ARGPAR_OPT_DESCR_SENTINEL
402 /* Short option's argument starting with `-` (space form) */
404 const struct argpar_opt_descr descrs
[] = {
405 { 0, 'z', NULL
, true },
406 ARGPAR_OPT_DESCR_SENTINEL
415 /* Long option's argument starting with `-` (space form) */
417 const struct argpar_opt_descr descrs
[] = {
418 { 0, '\0', "janine", true },
419 ARGPAR_OPT_DESCR_SENTINEL
428 /* Long option's argument starting with `-` (equal form) */
430 const struct argpar_opt_descr descrs
[] = {
431 { 0, '\0', "janine", true },
432 ARGPAR_OPT_DESCR_SENTINEL
441 /* Long option's empty argument (equal form) */
443 const struct argpar_opt_descr descrs
[] = {
444 { 0, 'f', NULL
, false },
445 { 0, '\0', "yeah", true },
446 ARGPAR_OPT_DESCR_SENTINEL
455 /* `-` non-option argument */
457 const struct argpar_opt_descr descrs
[] = {
458 { 0, 'f', NULL
, false },
459 ARGPAR_OPT_DESCR_SENTINEL
468 /* `--` non-option argument */
470 const struct argpar_opt_descr descrs
[] = {
471 { 0, 'f', NULL
, false },
472 ARGPAR_OPT_DESCR_SENTINEL
481 /* Very long name of long option */
483 const char opt_name
[] =
484 "kale-chips-waistcoat-yr-bicycle-rights-gochujang-"
485 "woke-tumeric-flexitarian-biodiesel-chillwave-cliche-"
486 "ethical-cardigan-listicle-pok-pok-sustainable-live-"
487 "edge-jianbing-gochujang-butcher-disrupt-tattooed-"
488 "tumeric-prism-photo-booth-vape-kogi-jean-shorts-"
489 "blog-williamsburg-fingerstache-palo-santo-artisan-"
490 "affogato-occupy-skateboard-adaptogen-neutra-celiac-"
491 "put-a-bird-on-it-kombucha-everyday-carry-hot-chicken-"
492 "craft-beer-subway-tile-tote-bag-disrupt-selvage-"
493 "raclette-art-party-readymade-paleo-heirloom-trust-"
494 "fund-small-batch-kinfolk-woke-cardigan-prism-"
495 "chambray-la-croix-hashtag-unicorn-edison-bulb-tbh-"
496 "cornhole-cliche-tattooed-green-juice-adaptogen-"
497 "kitsch-lo-fi-vexillologist-migas-gentrify-"
499 const struct argpar_opt_descr descrs
[] = {
500 { 0, '\0', opt_name
, true },
501 ARGPAR_OPT_DESCR_SENTINEL
505 sprintf(cmdline
, "--%s=23", opt_name
);
506 test_succeed(cmdline
, cmdline
, descrs
, 1);
511 * Parses `cmdline` with the argpar API using the option descriptors
512 * `descrs`, and ensures that argpar_iter_next() fails with status
513 * `expected_status` and that it sets an error having:
515 * * The original argument index `expected_orig_index`.
519 * * The unknown option name `expected_unknown_opt_name`.
521 * * The option descriptor at index `expected_opt_descr_index` of
524 * * The option type `expected_is_short`.
526 * This function splits `cmdline` on spaces to create an original
530 void test_fail(const char * const cmdline
,
531 const enum argpar_error_type expected_error_type
,
532 const unsigned int expected_orig_index
,
533 const char * const expected_unknown_opt_name
,
534 const unsigned int expected_opt_descr_index
,
535 const bool expected_is_short
,
536 const struct argpar_opt_descr
* const descrs
)
538 struct argpar_iter
*iter
= NULL
;
539 const struct argpar_item
*item
= NULL
;
540 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
542 const struct argpar_error
*error
= NULL
;
544 iter
= argpar_iter_create(g_strv_length(argv
),
545 (const char * const *) argv
, descrs
);
549 enum argpar_iter_next_status status
;
551 ARGPAR_ITEM_DESTROY_AND_RESET(item
);
552 status
= argpar_iter_next(iter
, &item
, &error
);
553 ok(status
== ARGPAR_ITER_NEXT_STATUS_OK
||
554 (status
== ARGPAR_ITER_NEXT_STATUS_ERROR
&&
555 argpar_error_type(error
) == expected_error_type
),
556 "argpar_iter_next() returns the expected status "
557 "and error type (%d) for command line `%s` (call %u)",
558 expected_error_type
, cmdline
, i
+ 1);
560 if (status
!= ARGPAR_ITER_NEXT_STATUS_OK
) {
562 "argpar_iter_next() doesn't set an item "
563 "for other status than "
564 "`ARGPAR_ITER_NEXT_STATUS_OK` "
565 "and command line `%s` (call %u)",
568 "argpar_iter_next() sets an error for "
570 " `ARGPAR_ITER_NEXT_STATUS_OK` "
571 "and command line `%s` (call %u)",
573 ok(argpar_error_orig_index(error
) ==
575 "argpar_iter_next() sets an error with "
576 "the expected original argument index "
577 "for command line `%s` (call %u)",
580 if (argpar_error_type(error
) == ARGPAR_ERROR_TYPE_UNKNOWN_OPT
) {
581 ok(strcmp(argpar_error_unknown_opt_name(error
),
582 expected_unknown_opt_name
) == 0,
583 "argpar_iter_next() sets an error with "
584 "the expected unknown option name "
585 "for command line `%s` (call %u)",
590 ok(argpar_error_opt_descr(error
, &is_short
) ==
591 &descrs
[expected_opt_descr_index
],
592 "argpar_iter_next() sets an error with "
593 "the expected option descriptor "
594 "for command line `%s` (call %u)",
596 ok(is_short
== expected_is_short
,
597 "argpar_iter_next() sets an error with "
598 "the expected option type "
599 "for command line `%s` (call %u)",
606 "argpar_iter_next() sets an item for status "
607 "`ARGPAR_ITER_NEXT_STATUS_OK` "
608 "and command line `%s` (call %u)",
611 "argpar_iter_next() doesn't set an error for status "
612 "`ARGPAR_ITER_NEXT_STATUS_OK` "
613 "and command line `%s` (call %u)",
618 ok(strcmp(expected_error, error) == 0,
619 "argpar_iter_next() sets the expected error string "
620 "for command line `%s`", cmdline);
622 if (strcmp(expected_error, error) != 0) {
623 diag("Expected: `%s`", expected_error);
624 diag("Got: `%s`", error);
628 argpar_item_destroy(item
);
629 argpar_iter_destroy(iter
);
630 argpar_error_destroy(error
);
635 void fail_tests(void)
638 /* Unknown short option (space form) */
640 const struct argpar_opt_descr descrs
[] = {
641 { 0, 'd', NULL
, true },
642 ARGPAR_OPT_DESCR_SENTINEL
646 "-d salut -e -d meow",
647 ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
652 /* Unknown short option (glued form) */
654 const struct argpar_opt_descr descrs
[] = {
656 ARGPAR_OPT_DESCR_SENTINEL
660 "-dsalut -e -d meow",
661 ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
666 /* Unknown long option (space form) */
668 const struct argpar_opt_descr descrs
[] = {
669 { 0, '\0', "sink", true },
670 ARGPAR_OPT_DESCR_SENTINEL
674 "--sink party --food --sink impulse",
675 ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
676 2, "--food", 0, false,
680 /* Unknown long option (equal form) */
682 const struct argpar_opt_descr descrs
[] = {
683 { 0, '\0', "sink", true },
684 ARGPAR_OPT_DESCR_SENTINEL
688 "--sink=party --food --sink=impulse",
689 ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
690 1, "--food", 0, false,
694 /* Unknown option before non-option argument */
696 const struct argpar_opt_descr descrs
[] = {
697 { 0, '\0', "thumb", true },
698 ARGPAR_OPT_DESCR_SENTINEL
702 "--thumb=party --food=18 bateau --thumb waves",
703 ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
704 1, "--food", 0, false,
708 /* Unknown option after non-option argument */
710 const struct argpar_opt_descr descrs
[] = {
711 { 0, '\0', "thumb", true },
712 ARGPAR_OPT_DESCR_SENTINEL
716 "--thumb=party wound --food --thumb waves",
717 ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
718 2, "--food", 0, false,
722 /* Missing long option argument */
724 const struct argpar_opt_descr descrs
[] = {
725 { 0, '\0', "thumb", true },
726 ARGPAR_OPT_DESCR_SENTINEL
731 ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
736 /* Missing short option argument */
738 const struct argpar_opt_descr descrs
[] = {
739 { 0, 'k', NULL
, true },
740 ARGPAR_OPT_DESCR_SENTINEL
745 ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
750 /* Missing short option argument (multiple glued) */
752 const struct argpar_opt_descr descrs
[] = {
753 { 0, 'a', NULL
, false },
754 { 0, 'b', NULL
, false },
755 { 0, 'c', NULL
, true },
756 ARGPAR_OPT_DESCR_SENTINEL
761 ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
766 /* Unexpected long option argument */
768 const struct argpar_opt_descr descrs
[] = {
769 { 0, 'c', "chevre", false },
770 ARGPAR_OPT_DESCR_SENTINEL
774 "ambulance --chevre=fromage tar -cjv",
775 ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
,
786 return exit_status();