#include "malunal/microtest.h" #include "malunal/argparse.h" #define PARSE(parser, result, ...) \ argparser_parse( \ (parser), \ (malunal_int32_t)( \ sizeof((malunal_cstr_t[]){ __VA_ARGS__ }) / sizeof(malunal_str_t) \ ), \ (malunal_cstr_t[]){ __VA_ARGS__ }, \ (result) \ ) static malunal_bool_t view_is( strview_t view, malunal_cstr_t text ) { strview_t other = strview_from_cstr(text); return strview_equals(&view, &other); } /* ------------------------------------------------------------------------- * Parser construction. * ---------------------------------------------------------------------- */ MICROTEST(argparser_add_option, accepts_flag_with_short_and_long) { argparser_t parser; argparser_init("prog", null, null, &parser); error_t err = argparser_add_option(&parser, &ARGOPTION_FLAG( .short_name = 'v', .long_name = "verbose" )); MICROTEST_EXPECT_NULL(err.domain); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_option_without_names) { argparser_t parser; argparser_init("prog", null, null, &parser); error_t err = argparser_add_option(&parser, &ARGOPTION_FLAG( .value_type = ARGOPTION_TYPE_STRING )); MICROTEST_EXPECT_NOT_NULL(err.domain); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_long_name_with_dashes) { argparser_t parser; argparser_init("prog", null, null, &parser); error_t err = argparser_add_option(&parser, &ARGOPTION_FLAG( .long_name = "--verbose" )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_duplicate_short_name) { argparser_t parser; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG( .short_name = 'v', .long_name = "verbose" )); error_t err = argparser_add_option(&parser, &ARGOPTION_FLAG( .short_name = 'v', .long_name = "version" )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_DUPLICATE_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_duplicate_long_name) { argparser_t parser; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG( .short_name = 'v', .long_name = "verbose" )); error_t err = argparser_add_option(&parser, &ARGOPTION_FLAG( .long_name = "verbose" )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_DUPLICATE_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_conflict_with_builtin_help) { argparser_t parser; argparser_init("prog", null, null, &parser); error_t err = argparser_add_option(&parser, &ARGOPTION_FLAG( .short_name = 'h', .long_name = "host" )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_DUPLICATE_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_positional_named_like_option) { argparser_t parser; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG( .long_name = "input", .value_type = ARGOPTION_TYPE_STRING )); error_t err = argparser_add_option(&parser, &ARGOPTION_POSITIONAL( .long_name = "input", .value_type = ARGOPTION_TYPE_STRING )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_DUPLICATE_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_required_flag) { argparser_t parser; argparser_init("prog", null, null, &parser); error_t err = argparser_add_option(&parser, &ARGOPTION_FLAG( .short_name = 'v', .required = true )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_flag_typed_positional) { argparser_t parser; argparser_init("prog", null, null, &parser); error_t err = argparser_add_option(&parser, &ARGOPTION_POSITIONAL( .long_name = "input", .value_type = ARGOPTION_TYPE_FLAG )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_positional_after_multiple) { argparser_t parser; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_POSITIONAL( .long_name = "files", .value_type = ARGOPTION_TYPE_STRING, .multiple = true )); error_t err = argparser_add_option(&parser, &ARGOPTION_POSITIONAL( .long_name = "extra", .value_type = ARGOPTION_TYPE_STRING )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_required_positional_after_optional) { argparser_t parser; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_POSITIONAL( .long_name = "first", .value_type = ARGOPTION_TYPE_STRING )); error_t err = argparser_add_option(&parser, &ARGOPTION_POSITIONAL( .long_name = "second", .value_type = ARGOPTION_TYPE_STRING, .required = true )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_unconvertible_default) { argparser_t parser; argparser_init("prog", null, null, &parser); error_t err = argparser_add_option(&parser, &ARGOPTION_FLAG( .long_name = "jobs", .value_type = ARGOPTION_TYPE_NUMBER, .default_value = "many" )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_option, rejects_positional_when_subcommands_exist) { argparser_t parser; argparser_mptr_t sub; argparser_init("git", null, null, &parser); argparser_add_subcommand(&parser, "commit", null, &sub); error_t err = argparser_add_option(&parser, &ARGOPTION_POSITIONAL( .long_name = "x", .value_type = ARGOPTION_TYPE_STRING )); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_subcommand, rejects_when_positionals_exist) { argparser_t parser; argparser_mptr_t sub; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_POSITIONAL( .long_name = "x", .value_type = ARGOPTION_TYPE_STRING )); error_t err = argparser_add_subcommand(&parser, "sub", null, &sub); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_DEFINITION); argparser_free(&parser); } MICROTEST(argparser_add_subcommand, rejects_duplicate_name) { argparser_t parser; argparser_mptr_t first; argparser_mptr_t second; argparser_init("git", null, null, &parser); argparser_add_subcommand(&parser, "build", null, &first); error_t err = argparser_add_subcommand(&parser, "build", null, &second); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_DUPLICATE_DEFINITION); argparser_free(&parser); } /* ------------------------------------------------------------------------- * Parsing: flags. * ---------------------------------------------------------------------- */ MICROTEST(argparser_parse, short_and_long_flags_are_recognized) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'a', .long_name = "all")); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'b', .long_name = "bold")); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'c', .long_name = "color")); error_t err = PARSE(&parser, &result, "prog", "-a", "--bold"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_TRUE(argresult_has(&result, "all")); MICROTEST_EXPECT_TRUE(argresult_has(&result, "-b")); MICROTEST_EXPECT_TRUE(argresult_has(&result, "--bold")); MICROTEST_EXPECT_FALSE(argresult_has(&result, "color")); malunal_bool_t color = true; MICROTEST_EXPECT_NULL(argresult_get_boolean(&result, "color", &color).domain); MICROTEST_EXPECT_FALSE(color); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, flags_count_occurrences_and_cluster) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'v', .long_name = "verbose")); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'q')); error_t err = PARSE(&parser, &result, "prog", "-vvq", "--verbose"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_EQ(argresult_count(&result, "verbose"), (malunal_size_t)3); MICROTEST_EXPECT_EQ(argresult_count(&result, "q"), (malunal_size_t)1); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, flag_with_inline_value_fails) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'v', .long_name = "verbose")); error_t err = PARSE(&parser, &result, "prog", "--verbose=yes"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_UNEXPECTED_VALUE); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, unknown_options_fail) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); error_t err = PARSE(&parser, &result, "prog", "--bogus"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_UNKNOWN_OPTION); argresult_free(&result); err = PARSE(&parser, &result, "prog", "-z"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_UNKNOWN_OPTION); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, help_flag_is_reported) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); error_t err = PARSE(&parser, &result, "prog", "-h"); MICROTEST_EXPECT_NOT_NULL(err.domain); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_HELP_REQUESTED); argresult_free(&result); err = PARSE(&parser, &result, "prog", "--help"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_HELP_REQUESTED); argresult_free(&result); argparser_free(&parser); } /* ------------------------------------------------------------------------- * Parsing: value-bearing options. * ---------------------------------------------------------------------- */ MICROTEST(argparser_parse, string_option_accepts_every_spelling) { argparser_t parser; argresult_t result; strview_t value; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG( .short_name = 'o', .long_name = "output", .value_type = ARGOPTION_TYPE_STRING )); malunal_cstr_t spellings[][3] = { { "-o", "out.txt", null }, { "-oout.txt", null, null }, { "-o=out.txt", null, null }, { "--output", "out.txt", null }, { "--output=out.txt", null, null }, }; malunal_size_t index; for (index = 0; index < sizeof(spellings) / sizeof(spellings[0]); index++) { malunal_cstr_t argv[3] = { "prog", spellings[index][0], spellings[index][1] }; malunal_int32_t argc = spellings[index][1] != null ? 3 : 2; error_t err = argparser_parse(&parser, argc, argv, &result); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_NULL(argresult_get_string(&result, "output", &value).domain); MICROTEST_EXPECT_TRUE(view_is(value, "out.txt")); argresult_free(&result); } argparser_free(&parser); } MICROTEST(argparser_parse, flag_cluster_ends_with_value_option) { argparser_t parser; argparser_mptr_t commit; argresult_t result; strview_t message; argparser_init("git", null, null, &parser); argparser_add_subcommand(&parser, "commit", null, &commit); argparser_add_option(commit, &ARGOPTION_FLAG(.short_name = 'a', .long_name = "all")); argparser_add_option(commit, &ARGOPTION_FLAG( .short_name = 'm', .long_name = "message", .value_type = ARGOPTION_TYPE_STRING, .required = true )); error_t err = PARSE(&parser, &result, "git", "commit", "-am", "fix bug"); MICROTEST_EXPECT_NULL(err.domain); argresult_iptr_t sub = argresult_subresult(&result); MICROTEST_EXPECT_TRUE(argresult_has(sub, "all")); MICROTEST_EXPECT_NULL(argresult_get_string(sub, "message", &message).domain); MICROTEST_EXPECT_TRUE(view_is(message, "fix bug")); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, single_option_keeps_last_value) { argparser_t parser; argresult_t result; strview_t value; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG( .short_name = 'o', .value_type = ARGOPTION_TYPE_STRING )); error_t err = PARSE(&parser, &result, "prog", "-o", "first", "-o", "second"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_EQ(argresult_count(&result, "o"), (malunal_size_t)1); argresult_get_string(&result, "o", &value); MICROTEST_EXPECT_TRUE(view_is(value, "second")); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, multiple_option_appends_values) { argparser_t parser; argresult_t result; strview_t value; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG( .short_name = 'I', .long_name = "include", .value_type = ARGOPTION_TYPE_STRING, .multiple = true )); error_t err = PARSE(&parser, &result, "prog", "-Ifoo", "--include", "bar", "--include=baz"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_EQ(argresult_count(&result, "include"), (malunal_size_t)3); argresult_get_string_at(&result, "include", 0, &value); MICROTEST_EXPECT_TRUE(view_is(value, "foo")); argresult_get_string_at(&result, "include", 1, &value); MICROTEST_EXPECT_TRUE(view_is(value, "bar")); argresult_get_string_at(&result, "include", 2, &value); MICROTEST_EXPECT_TRUE(view_is(value, "baz")); err = argresult_get_string_at(&result, "include", 3, &value); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_OUT_OF_RANGE); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, typed_options_convert_values) { argparser_t parser; argresult_t result; malunal_int64_t number = 0; malunal_double_t floating = 0.0; malunal_bool_t boolean = false; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.long_name = "jobs", .value_type = ARGOPTION_TYPE_NUMBER)); argparser_add_option(&parser, &ARGOPTION_FLAG(.long_name = "scale", .value_type = ARGOPTION_TYPE_FLOAT)); argparser_add_option(&parser, &ARGOPTION_FLAG(.long_name = "color", .value_type = ARGOPTION_TYPE_BOOLEAN)); error_t err = PARSE(&parser, &result, "prog", "--jobs", "-4", "--scale=2.5", "--color", "YES"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_NULL(argresult_get_number(&result, "jobs", &number).domain); MICROTEST_EXPECT_EQ(number, (malunal_int64_t)-4); MICROTEST_EXPECT_NULL(argresult_get_float(&result, "scale", &floating).domain); MICROTEST_EXPECT_TRUE(floating > 2.49 && floating < 2.51); MICROTEST_EXPECT_NULL(argresult_get_boolean(&result, "color", &boolean).domain); MICROTEST_EXPECT_TRUE(boolean); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, invalid_values_fail) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.long_name = "jobs", .value_type = ARGOPTION_TYPE_NUMBER)); argparser_add_option(&parser, &ARGOPTION_FLAG(.long_name = "color", .value_type = ARGOPTION_TYPE_BOOLEAN)); error_t err = PARSE(&parser, &result, "prog", "--jobs=abc"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_VALUE); argresult_free(&result); err = PARSE(&parser, &result, "prog", "--color=maybe"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_INVALID_VALUE); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, option_missing_value_fails) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'o', .value_type = ARGOPTION_TYPE_STRING)); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'v')); error_t err = PARSE(&parser, &result, "prog", "-o"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_MISSING_VALUE); argresult_free(&result); err = PARSE(&parser, &result, "prog", "-o", "-v"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_MISSING_VALUE); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, long_options_match_unique_prefixes) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.long_name = "verbose")); argparser_add_option(&parser, &ARGOPTION_FLAG(.long_name = "version")); argparser_add_option(&parser, &ARGOPTION_FLAG(.long_name = "quiet")); error_t err = PARSE(&parser, &result, "prog", "--q", "--verb"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_TRUE(argresult_has(&result, "quiet")); MICROTEST_EXPECT_TRUE(argresult_has(&result, "verbose")); argresult_free(&result); err = PARSE(&parser, &result, "prog", "--ver"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_AMBIGUOUS_OPTION); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, required_option_missing_fails) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG( .long_name = "jobs", .value_type = ARGOPTION_TYPE_NUMBER, .required = true )); error_t err = PARSE(&parser, &result, "prog"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_MISSING_REQUIRED_OPTION); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, defaults_apply_when_absent) { argparser_t parser; argresult_t result; malunal_int64_t jobs = 0; strview_t mode; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG( .long_name = "jobs", .value_type = ARGOPTION_TYPE_NUMBER, .default_value = "8" )); argparser_add_option(&parser, &ARGOPTION_POSITIONAL( .long_name = "mode", .value_type = ARGOPTION_TYPE_STRING, .default_value = "debug" )); error_t err = PARSE(&parser, &result, "prog"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_FALSE(argresult_has(&result, "jobs")); MICROTEST_EXPECT_EQ(argresult_count(&result, "jobs"), (malunal_size_t)1); MICROTEST_EXPECT_NULL(argresult_get_number(&result, "jobs", &jobs).domain); MICROTEST_EXPECT_EQ(jobs, (malunal_int64_t)8); MICROTEST_EXPECT_NULL(argresult_get_string(&result, "mode", &mode).domain); MICROTEST_EXPECT_TRUE(view_is(mode, "debug")); argresult_free(&result); err = PARSE(&parser, &result, "prog", "--jobs", "2", "release"); MICROTEST_EXPECT_NULL(err.domain); argresult_get_number(&result, "jobs", &jobs); MICROTEST_EXPECT_EQ(jobs, (malunal_int64_t)2); argresult_get_string(&result, "mode", &mode); MICROTEST_EXPECT_TRUE(view_is(mode, "release")); argresult_free(&result); argparser_free(&parser); } /* ------------------------------------------------------------------------- * Parsing: positionals. * ---------------------------------------------------------------------- */ MICROTEST(argparser_parse, positionals_fill_in_order_around_options) { argparser_t parser; argresult_t result; strview_t src; strview_t dst; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'v')); argparser_add_option(&parser, &ARGOPTION_POSITIONAL(.long_name = "src", .value_type = ARGOPTION_TYPE_STRING, .required = true)); argparser_add_option(&parser, &ARGOPTION_POSITIONAL(.long_name = "dst", .value_type = ARGOPTION_TYPE_STRING, .required = true)); error_t err = PARSE(&parser, &result, "prog", "a.txt", "-v", "b.txt"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_TRUE(argresult_has(&result, "v")); argresult_get_string(&result, "src", &src); argresult_get_string(&result, "dst", &dst); MICROTEST_EXPECT_TRUE(view_is(src, "a.txt")); MICROTEST_EXPECT_TRUE(view_is(dst, "b.txt")); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, multiple_positional_collects_remaining) { argparser_t parser; argresult_t result; strview_t value; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_POSITIONAL(.long_name = "cmd", .value_type = ARGOPTION_TYPE_STRING, .required = true)); argparser_add_option(&parser, &ARGOPTION_POSITIONAL(.long_name = "files", .value_type = ARGOPTION_TYPE_STRING, .multiple = true)); error_t err = PARSE(&parser, &result, "prog", "cat", "a", "b", "c"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_EQ(argresult_count(&result, "files"), (malunal_size_t)3); argresult_get_string_at(&result, "files", 2, &value); MICROTEST_EXPECT_TRUE(view_is(value, "c")); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, required_positional_missing_fails) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_POSITIONAL(.long_name = "files", .value_type = ARGOPTION_TYPE_STRING, .required = true, .multiple = true)); error_t err = PARSE(&parser, &result, "prog"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_MISSING_REQUIRED_POSITIONAL); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, extra_positional_fails) { argparser_t parser; argresult_t result; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_POSITIONAL(.long_name = "only", .value_type = ARGOPTION_TYPE_STRING)); error_t err = PARSE(&parser, &result, "prog", "one", "two"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_TOO_MANY_POSITIONALS); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, negative_numbers_are_values) { argparser_t parser; argresult_t result; malunal_double_t value = 0.0; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_POSITIONAL(.long_name = "n", .value_type = ARGOPTION_TYPE_FLOAT, .required = true)); error_t err = PARSE(&parser, &result, "prog", "-1.5"); MICROTEST_EXPECT_NULL(err.domain); argresult_get_float(&result, "n", &value); MICROTEST_EXPECT_TRUE(value < -1.49 && value > -1.51); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, double_dash_ends_options) { argparser_t parser; argresult_t result; strview_t value; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'v')); argparser_add_option(&parser, &ARGOPTION_POSITIONAL(.long_name = "file", .value_type = ARGOPTION_TYPE_STRING, .required = true)); error_t err = PARSE(&parser, &result, "prog", "-v", "--", "-v"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_EQ(argresult_count(&result, "v"), (malunal_size_t)1); argresult_get_string(&result, "file", &value); MICROTEST_EXPECT_TRUE(view_is(value, "-v")); argresult_free(&result); argparser_free(&parser); } /* ------------------------------------------------------------------------- * Parsing: subcommands. * ---------------------------------------------------------------------- */ MICROTEST(argparser_parse, subcommand_is_dispatched_after_parent_options) { argparser_t parser; argparser_mptr_t push; argparser_mptr_t commit; argresult_t result; strview_t remote; argparser_init("git", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'v')); argparser_add_subcommand(&parser, "commit", null, &commit); argparser_add_subcommand(&parser, "push", null, &push); argparser_add_option(push, &ARGOPTION_FLAG(.short_name = 'f', .long_name = "force")); argparser_add_option(push, &ARGOPTION_POSITIONAL(.long_name = "remote", .value_type = ARGOPTION_TYPE_STRING)); argparser_add_option(push, &ARGOPTION_POSITIONAL(.long_name = "refs", .value_type = ARGOPTION_TYPE_STRING, .multiple = true)); error_t err = PARSE(&parser, &result, "git", "-v", "push", "--force", "origin", "main", "dev"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_TRUE(argresult_has(&result, "v")); MICROTEST_EXPECT_TRUE(strcmp(argresult_subcommand(&result), "push") == 0); argresult_iptr_t sub = argresult_subresult(&result); MICROTEST_EXPECT_NOT_NULL(sub); MICROTEST_EXPECT_TRUE(argresult_has(sub, "force")); argresult_get_string(sub, "remote", &remote); MICROTEST_EXPECT_TRUE(view_is(remote, "origin")); MICROTEST_EXPECT_EQ(argresult_count(sub, "refs"), (malunal_size_t)2); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, no_subcommand_leaves_subresult_null) { argparser_t parser; argparser_mptr_t commit; argresult_t result; argparser_init("git", null, null, &parser); argparser_add_subcommand(&parser, "commit", null, &commit); error_t err = PARSE(&parser, &result, "git"); MICROTEST_EXPECT_NULL(err.domain); MICROTEST_EXPECT_NULL(argresult_subcommand(&result)); MICROTEST_EXPECT_NULL(argresult_subresult(&result)); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, unknown_subcommand_fails) { argparser_t parser; argparser_mptr_t commit; argresult_t result; argparser_init("git", null, null, &parser); argparser_add_subcommand(&parser, "commit", null, &commit); error_t err = PARSE(&parser, &result, "git", "bogus"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_UNKNOWN_SUBCOMMAND); argresult_free(&result); argparser_free(&parser); } MICROTEST(argparser_parse, sibling_subcommands_do_not_share_options) { argparser_t parser; argparser_mptr_t commit; argparser_mptr_t push; argresult_t result; argparser_init("git", null, null, &parser); argparser_add_subcommand(&parser, "commit", null, &commit); argparser_add_subcommand(&parser, "push", null, &push); argparser_add_option(commit, &ARGOPTION_FLAG(.short_name = 'm', .value_type = ARGOPTION_TYPE_STRING)); error_t err = PARSE(&parser, &result, "git", "push", "-m", "nope"); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_UNKNOWN_OPTION); argresult_free(&result); argparser_free(&parser); } /* ------------------------------------------------------------------------- * Reading results. * ---------------------------------------------------------------------- */ MICROTEST(argresult_get, reports_lookup_errors) { argparser_t parser; argresult_t result; malunal_int64_t number = 0; strview_t value; argparser_init("prog", null, null, &parser); argparser_add_option(&parser, &ARGOPTION_FLAG(.short_name = 'o', .value_type = ARGOPTION_TYPE_STRING)); error_t err = PARSE(&parser, &result, "prog"); MICROTEST_EXPECT_NULL(err.domain); err = argresult_get_string(&result, "missing", &value); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_UNKNOWN_NAME); err = argresult_get_number(&result, "o", &number); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_TYPE_MISMATCH); err = argresult_get_string(&result, "o", &value); MICROTEST_EXPECT_EQ(err.code, ARGPARSE_ERROR_NOT_PRESENT); argresult_free(&result); argparser_free(&parser); } MICROTEST_MAIN()