/* Copyright © 2016-2017 Taylor C. Richberger and Pavel Belikov * This code is released under the license described in the LICENSE file */ #include #include std::istream& operator>>(std::istream& is, std::tuple& ints) { is >> std::get<0>(ints); is.get(); is >> std::get<1>(ints); return is; } #include #define CATCH_CONFIG_MAIN #include "catch.hpp" TEST_CASE("Help flag throws Help exception", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--help"}), args::Help); } TEST_CASE("Unknown flags throw exceptions", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--Help"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-H"}), args::ParseError); } TEST_CASE("Boolean flags work as expected, with clustering", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::Flag foo(parser, "FOO", "test flag", {'f', "foo"}); args::Flag bar(parser, "BAR", "test flag", {'b', "bar"}); args::Flag baz(parser, "BAZ", "test flag", {'a', "baz"}); args::Flag bix(parser, "BAZ", "test flag", {'x', "bix"}); parser.ParseArgs(std::vector{"--baz", "-fb"}); REQUIRE(foo); REQUIRE(bar); REQUIRE(baz); REQUIRE_FALSE(bix); } TEST_CASE("Count flag works as expected", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::CounterFlag foo(parser, "FOO", "test flag", {'f', "foo"}); args::CounterFlag bar(parser, "BAR", "test flag", {'b', "bar"}, 7); args::CounterFlag baz(parser, "BAZ", "test flag", {'z', "baz"}, 7); parser.ParseArgs(std::vector{"--foo", "-fb", "--bar", "-b", "-f", "--foo"}); REQUIRE(foo); REQUIRE(bar); REQUIRE_FALSE(baz); REQUIRE(args::get(foo) == 4); REQUIRE(args::get(bar) == 10); REQUIRE(args::get(baz) == 7); } TEST_CASE("Argument flags work as expected, with clustering", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::ValueFlag foo(parser, "FOO", "test flag", {'f', "foo"}); args::Flag bar(parser, "BAR", "test flag", {'b', "bar"}); args::ValueFlag baz(parser, "BAZ", "test flag", {'a', "baz"}); args::ValueFlag bim(parser, "BAZ", "test flag", {'B', "bim"}); args::Flag bix(parser, "BAZ", "test flag", {'x', "bix"}); parser.ParseArgs(std::vector{"-bftest", "--baz=7.555e2", "--bim", "c"}); REQUIRE(foo); REQUIRE(args::get(foo) == "test"); REQUIRE(bar); REQUIRE(baz); REQUIRE((args::get(baz) > 755.49 && args::get(baz) < 755.51)); REQUIRE(bim); REQUIRE(args::get(bim) == 'c'); REQUIRE_FALSE(bix); } TEST_CASE("Passing an argument to a non-argument flag throws an error", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::Flag bar(parser, "BAR", "test flag", {'b', "bar"}); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--bar=test"}), args::ParseError); } TEST_CASE("Unified argument lists for match work", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::ValueFlag foo(parser, "FOO", "test flag", {'f', "foo"}); args::Flag bar(parser, "BAR", "test flag", {"bar", 'b'}); args::ValueFlag baz(parser, "BAZ", "test flag", {'a', "baz"}); args::ValueFlag bim(parser, "BAZ", "test flag", {'B', "bim"}); args::Flag bix(parser, "BAZ", "test flag", {"bix"}); parser.ParseArgs(std::vector{"-bftest", "--baz=7.555e2", "--bim", "c"}); REQUIRE(foo); REQUIRE(args::get(foo) == "test"); REQUIRE(bar); REQUIRE(baz); REQUIRE((args::get(baz) > 755.49 && args::get(baz) < 755.51)); REQUIRE(bim); REQUIRE(args::get(bim) == 'c'); REQUIRE_FALSE(bix); } TEST_CASE("Get can be assigned to for non-reference types", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::ValueFlag foo(parser, "FOO", "test flag", {'f', "foo"}); parser.ParseArgs(std::vector{"--foo=test"}); REQUIRE(foo); REQUIRE(args::get(foo) == "test"); args::get(foo) = "bar"; REQUIRE(args::get(foo) == "bar"); } TEST_CASE("Invalid argument parsing throws parsing exceptions", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::ValueFlag foo(parser, "FOO", "test flag", {'f', "foo"}); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo=7.5"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo", "7a"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo", "7e4"}), args::ParseError); } TEST_CASE("Argument flag lists work as expected", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::ValueFlagList foo(parser, "FOO", "test flag", {'f', "foo"}); parser.ParseArgs(std::vector{"--foo=7", "-f2", "-f", "9", "--foo", "42"}); REQUIRE((args::get(foo) == std::vector{7, 2, 9, 42})); } TEST_CASE("Argument flag lists use default values", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::ValueFlagList foo(parser, "FOO", "test flag", {'f', "foo"}, {9, 7, 5}); parser.ParseArgs(std::vector()); REQUIRE((args::get(foo) == std::vector{9, 7, 5})); } TEST_CASE("Argument flag lists replace default values", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::ValueFlagList foo(parser, "FOO", "test flag", {'f', "foo"}, {9, 7, 5}); parser.ParseArgs(std::vector{"--foo=7", "-f2", "-f", "9", "--foo", "42"}); REQUIRE((args::get(foo) == std::vector{7, 2, 9, 42})); } TEST_CASE("Positional lists work as expected", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::PositionalList foo(parser, "FOO", "test flag"); parser.ParseArgs(std::vector{"7", "2", "9", "42"}); REQUIRE((args::get(foo) == std::vector{7, 2, 9, 42})); } TEST_CASE("Positional lists use default values", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::PositionalList foo(parser, "FOO", "test flag", {9, 7, 5}); parser.ParseArgs(std::vector()); REQUIRE((args::get(foo) == std::vector{9, 7, 5})); } TEST_CASE("Positional lists replace default values", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::PositionalList foo(parser, "FOO", "test flag", {9, 7, 5}); parser.ParseArgs(std::vector{"7", "2", "9", "42"}); REQUIRE((args::get(foo) == std::vector{7, 2, 9, 42})); } #include TEST_CASE("Argument flag lists work with sets", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::ValueFlagList foo(parser, "FOO", "test flag", {'f', "foo"}); parser.ParseArgs(std::vector{"--foo=7", "-fblah", "-f", "9", "--foo", "blah"}); REQUIRE((args::get(foo) == std::unordered_set{"7", "9", "blah"})); } TEST_CASE("Positional arguments and positional argument lists work as expected", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::Positional foo(parser, "FOO", "test flag"); args::Positional bar(parser, "BAR", "test flag"); args::PositionalList baz(parser, "BAZ", "test flag"); parser.ParseArgs(std::vector{"this is a test flag", "0", "a", "b", "c", "x", "y", "z"}); REQUIRE(foo); REQUIRE((args::get(foo) == "this is a test flag")); REQUIRE(bar); REQUIRE(!args::get(bar)); REQUIRE(baz); REQUIRE((args::get(baz) == std::vector{'a', 'b', 'c', 'x', 'y', 'z'})); } TEST_CASE("The option terminator works as expected", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::Positional foo(parser, "FOO", "test flag"); args::Positional bar(parser, "BAR", "test flag"); args::PositionalList baz(parser, "BAZ", "test flag"); args::Flag ofoo(parser, "FOO", "test flag", {'f', "foo"}); args::Flag obar(parser, "BAR", "test flag", {"bar", 'b'}); args::ValueFlag obaz(parser, "BAZ", "test flag", {'a', "baz"}); parser.ParseArgs(std::vector{"--foo", "this is a test flag", "0", "a", "b", "--baz", "7.0", "c", "x", "y", "z"}); REQUIRE(foo); REQUIRE((args::get(foo) == "this is a test flag")); REQUIRE(bar); REQUIRE(!args::get(bar)); REQUIRE(baz); REQUIRE((args::get(baz) == std::vector{"a", "b", "c", "x", "y", "z"})); REQUIRE(ofoo); REQUIRE(!obar); REQUIRE(obaz); parser.ParseArgs(std::vector{"--foo", "this is a test flag", "0", "a", "--", "b", "--baz", "7.0", "c", "x", "y", "z"}); REQUIRE(foo); REQUIRE((args::get(foo) == "this is a test flag")); REQUIRE(bar); REQUIRE(!args::get(bar)); REQUIRE(baz); REQUIRE((args::get(baz) == std::vector{"a", "b", "--baz", "7.0", "c", "x", "y", "z"})); REQUIRE(ofoo); REQUIRE(!obar); REQUIRE(!obaz); parser.ParseArgs(std::vector{"--foo", "--", "this is a test flag", "0", "a", "b", "--baz", "7.0", "c", "x", "y", "z"}); REQUIRE(foo); REQUIRE((args::get(foo) == "this is a test flag")); REQUIRE(bar); REQUIRE(!args::get(bar)); REQUIRE(baz); REQUIRE((args::get(baz) == std::vector{"a", "b", "--baz", "7.0", "c", "x", "y", "z"})); REQUIRE(ofoo); REQUIRE(!obar); REQUIRE(!obaz); } TEST_CASE("Positional lists work with sets", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::PositionalList foo(parser, "FOO", "test positional"); parser.ParseArgs(std::vector{"foo", "FoO", "bar", "baz", "foo", "9", "baz"}); REQUIRE((args::get(foo) == std::unordered_set{"foo", "FoO", "bar", "baz", "9"})); } TEST_CASE("Positionals that are unspecified evaluate false", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::Positional foo(parser, "FOO", "test flag"); args::Positional bar(parser, "BAR", "test flag"); args::PositionalList baz(parser, "BAZ", "test flag"); parser.ParseArgs(std::vector{"this is a test flag again"}); REQUIRE(foo); REQUIRE((args::get(foo) == "this is a test flag again")); REQUIRE_FALSE(bar); REQUIRE_FALSE(baz); } TEST_CASE("Additional positionals throw an exception", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::Positional foo(parser, "FOO", "test flag"); args::Positional bar(parser, "BAR", "test flag"); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"this is a test flag again", "1", "this has no positional available"}), args::ParseError); } TEST_CASE("Argument groups should throw when validation fails", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::Group xorgroup(parser, "this group provides xor validation", args::Group::Validators::Xor); args::Flag a(xorgroup, "a", "test flag", {'a'}); args::Flag b(xorgroup, "b", "test flag", {'b'}); args::Flag c(xorgroup, "c", "test flag", {'c'}); args::Group nxor(parser, "this group provides all-or-none (nxor) validation", args::Group::Validators::AllOrNone); args::Flag d(nxor, "d", "test flag", {'d'}); args::Flag e(nxor, "e", "test flag", {'e'}); args::Flag f(nxor, "f", "test flag", {'f'}); args::Group atleastone(parser, "this group provides at-least-one validation", args::Group::Validators::AtLeastOne); args::Flag g(atleastone, "g", "test flag", {'g'}); args::Flag h(atleastone, "h", "test flag", {'h'}); // Needs g or h REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a"}), args::ValidationError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-g", "-a"})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-h", "-a"})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-gh", "-a"})); // Xor stuff REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g"}), args::ValidationError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-h", "-b"})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-ab"}), args::ValidationError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-ac"}), args::ValidationError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-abc"}), args::ValidationError); // Nxor stuff REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-h", "-a"})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-h", "-adef"})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-ad"}), args::ValidationError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-adf"}), args::ValidationError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-aef"}), args::ValidationError); } TEST_CASE("Argument groups should nest", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::Group xorgroup(parser, "this group provides xor validation", args::Group::Validators::Xor); args::Flag a(xorgroup, "a", "test flag", {'a'}); args::Flag b(xorgroup, "b", "test flag", {'b'}); args::Flag c(xorgroup, "c", "test flag", {'c'}); args::Group nxor(xorgroup, "this group provides all-or-none (nxor) validation", args::Group::Validators::AllOrNone); args::Flag d(nxor, "d", "test flag", {'d'}); args::Flag e(nxor, "e", "test flag", {'e'}); args::Flag f(nxor, "f", "test flag", {'f'}); args::Group atleastone(xorgroup, "this group provides at-least-one validation", args::Group::Validators::AtLeastOne); args::Flag g(atleastone, "g", "test flag", {'g'}); args::Flag h(atleastone, "h", "test flag", {'h'}); // Nothing actually matches, because nxor validates properly when it's empty, REQUIRE_NOTHROW(parser.ParseArgs(std::vector{})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-a", "-d"})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-c", "-f"})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-de", "-f"})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-gh", "-f"})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g"}), args::ValidationError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a"}), args::ValidationError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-b"}), args::ValidationError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a", "-dg"}), args::ValidationError); } struct DoublesReader { void operator()(const std::string &, const std::string &value, std::tuple &destination) { size_t commapos = 0; std::get<0>(destination) = std::stod(value, &commapos); std::get<1>(destination) = std::stod(std::string(value, commapos + 1)); } }; TEST_CASE("Custom types work", "[args]") { { args::ArgumentParser parser("This is a test program."); args::Positional> ints(parser, "INTS", "This takes a pair of integers."); args::Positional, DoublesReader> doubles(parser, "DOUBLES", "This takes a pair of doubles."); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"1.2,2", "3.8,4"}), args::ParseError); } args::ArgumentParser parser("This is a test program."); args::Positional> ints(parser, "INTS", "This takes a pair of integers."); args::Positional, DoublesReader> doubles(parser, "DOUBLES", "This takes a pair of doubles."); parser.ParseArgs(std::vector{"1,2", "3.8,4"}); REQUIRE(std::get<0>(args::get(ints)) == 1); REQUIRE(std::get<1>(args::get(ints)) == 2); REQUIRE((std::get<0>(args::get(doubles)) > 3.79 && std::get<0>(args::get(doubles)) < 3.81)); REQUIRE((std::get<1>(args::get(doubles)) > 3.99 && std::get<1>(args::get(doubles)) < 4.01)); } TEST_CASE("Custom parser prefixes (dd-style)", "[args]") { args::ArgumentParser parser("This command likes to break your disks"); parser.LongPrefix(""); parser.LongSeparator("="); args::HelpFlag help(parser, "HELP", "Show this help menu.", {"help"}); args::ValueFlag bs(parser, "BYTES", "Block size", {"bs"}, 512); args::ValueFlag skip(parser, "BYTES", "Bytes to skip", {"skip"}, 0); args::ValueFlag input(parser, "BLOCK SIZE", "Block size", {"if"}); args::ValueFlag output(parser, "BLOCK SIZE", "Block size", {"of"}); parser.ParseArgs(std::vector{"skip=8", "if=/dev/null"}); REQUIRE_FALSE(bs); REQUIRE(args::get(bs) == 512); REQUIRE(skip); REQUIRE(args::get(skip) == 8); REQUIRE(input); REQUIRE(args::get(input) == "/dev/null"); REQUIRE_FALSE(output); } TEST_CASE("Custom parser prefixes (Some Windows styles)", "[args]") { args::ArgumentParser parser("This command likes to break your disks"); parser.LongPrefix("/"); parser.LongSeparator(":"); args::HelpFlag help(parser, "HELP", "Show this help menu.", {"help"}); args::ValueFlag bs(parser, "BYTES", "Block size", {"bs"}, 512); args::ValueFlag skip(parser, "BYTES", "Bytes to skip", {"skip"}, 0); args::ValueFlag input(parser, "BLOCK SIZE", "Block size", {"if"}); args::ValueFlag output(parser, "BLOCK SIZE", "Block size", {"of"}); parser.ParseArgs(std::vector{"/skip:8", "/if:/dev/null"}); REQUIRE_FALSE(bs); REQUIRE(args::get(bs) == 512); REQUIRE(skip); REQUIRE(args::get(skip) == 8); REQUIRE(input); REQUIRE(args::get(input) == "/dev/null"); REQUIRE_FALSE(output); } TEST_CASE("Help menu can be grabbed as a string, passed into a stream, or by using the overloaded stream operator", "[args]") { std::ostream null(nullptr); args::ArgumentParser parser("This command likes to break your disks"); args::HelpFlag help(parser, "HELP", "Show this help menu.", {"help"}); args::ValueFlag bs(parser, "BYTES", "Block size", {"bs"}, 512); args::ValueFlag skip(parser, "BYTES", "Bytes to skip", {"skip"}, 0); args::ValueFlag input(parser, "BLOCK SIZE", "Block size", {"if"}); args::ValueFlag output(parser, "BLOCK SIZE", "Block size", {"of"}); parser.ParseArgs(std::vector{"--skip=8", "--if=/dev/null"}); null << parser.Help(); parser.Help(null); null << parser; } TEST_CASE("Required argument separation being violated throws an error", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::ValueFlag bar(parser, "BAR", "test flag", {'b', "bar"}); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-btest"})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"--bar=test"})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-b", "test"})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"--bar", "test"})); parser.SetArgumentSeparations(true, false, false, false); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-btest"})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--bar=test"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-b", "test"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--bar", "test"}), args::ParseError); parser.SetArgumentSeparations(false, true, false, false); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-btest"}), args::ParseError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"--bar=test"})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-b", "test"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--bar", "test"}), args::ParseError); parser.SetArgumentSeparations(false, false, true, false); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-btest"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--bar=test"}), args::ParseError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-b", "test"})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--bar", "test"}), args::ParseError); parser.SetArgumentSeparations(false, false, false, true); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-btest"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--bar=test"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-b", "test"}), args::ParseError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"--bar", "test"})); } enum class MappingEnum { def, foo, bar, red, yellow, green }; #include #include #include struct ToLowerReader { void operator()(const std::string &, const std::string &value, std::string &destination) { destination = value; std::transform(destination.begin(), destination.end(), destination.begin(), [](char c) -> char { return static_cast(tolower(c)); }); } }; TEST_CASE("Mapping types work as needed", "[args]") { std::unordered_map map{ {"default", MappingEnum::def}, {"foo", MappingEnum::foo}, {"bar", MappingEnum::bar}, {"red", MappingEnum::red}, {"yellow", MappingEnum::yellow}, {"green", MappingEnum::green}}; args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::MapFlag dmf(parser, "DMF", "Maps string to an enum", {"dmf"}, map); args::MapFlag mf(parser, "MF", "Maps string to an enum", {"mf"}, map); args::MapFlag cimf(parser, "CIMF", "Maps string to an enum case-insensitively", {"cimf"}, map); args::MapFlagList mfl(parser, "MFL", "Maps string to an enum list", {"mfl"}, map); args::MapPositional mp(parser, "MP", "Maps string to an enum", map); args::MapPositionalList mpl(parser, "MPL", "Maps string to an enum list", map); parser.ParseArgs(std::vector{"--mf=red", "--cimf=YeLLoW", "--mfl=bar", "foo", "--mfl=green", "red", "--mfl", "bar", "default"}); REQUIRE_FALSE(dmf); REQUIRE(args::get(dmf) == MappingEnum::def); REQUIRE(mf); REQUIRE(args::get(mf) == MappingEnum::red); REQUIRE(cimf); REQUIRE(args::get(cimf) == MappingEnum::yellow); REQUIRE(mfl); REQUIRE((args::get(mfl) == std::vector{MappingEnum::bar, MappingEnum::green, MappingEnum::bar})); REQUIRE(mp); REQUIRE((args::get(mp) == MappingEnum::foo)); REQUIRE(mpl); REQUIRE((args::get(mpl) == std::vector{MappingEnum::red, MappingEnum::def})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--mf=YeLLoW"}), args::MapError); } TEST_CASE("An exception should be thrown when a single-argument flag is matched multiple times and the constructor option is specified", "[args]") { std::unordered_map map{ {"default", MappingEnum::def}, {"foo", MappingEnum::foo}, {"bar", MappingEnum::bar}, {"red", MappingEnum::red}, {"yellow", MappingEnum::yellow}, {"green", MappingEnum::green}}; args::ArgumentParser parser("Test command"); args::Flag foo(parser, "Foo", "Foo", {'f', "foo"}, true); args::ValueFlag bar(parser, "Bar", "Bar", {'b', "bar"}, "", true); args::Flag bix(parser, "Bix", "Bix", {'x', "bix"}); args::MapFlag baz(parser, "Baz", "Baz", {'B', "baz"}, map, MappingEnum::def, true); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo", "-f", "-bblah"}), args::ExtraError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"--foo", "-xxx", "--bix", "-bblah", "--bix"})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo", "-bblah", "-blah"}), args::ExtraError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo", "-bblah", "--bar", "blah"}), args::ExtraError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--baz=red", "-B", "yellow"}), args::ExtraError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--baz", "red", "-Byellow"}), args::ExtraError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"--foo", "-Bgreen"})); REQUIRE(foo); REQUIRE_FALSE(bar); REQUIRE_FALSE(bix); REQUIRE(baz); REQUIRE(args::get(baz) == MappingEnum::green); } TEST_CASE("Sub-parsers should work through kick-out", "[args]") { std::unordered_map map{ {"default", MappingEnum::def}, {"foo", MappingEnum::foo}, {"bar", MappingEnum::bar}, {"red", MappingEnum::red}, {"yellow", MappingEnum::yellow}, {"green", MappingEnum::green}}; const std::vector args{"--foo", "green", "--bar"}; args::ArgumentParser parser1("Test command"); args::Flag foo1(parser1, "Foo", "Foo", {'f', "foo"}); args::Flag bar1(parser1, "Bar", "Bar", {'b', "bar"}); args::MapPositional sub(parser1, "sub", "sub", map); sub.KickOut(true); auto next = parser1.ParseArgs(args); args::ArgumentParser parser2("Test command"); args::Flag foo2(parser2, "Foo", "Foo", {'f', "foo"}); args::Flag bar2(parser2, "Bar", "Bar", {'b', "bar"}); parser2.ParseArgs(next, std::end(args)); REQUIRE(foo1); REQUIRE_FALSE(bar1); REQUIRE(sub); REQUIRE(args::get(sub) == MappingEnum::green); REQUIRE_FALSE(foo2); REQUIRE(bar2); } TEST_CASE("Kick-out should work via all flags and value flags", "[args]") { const std::vector args{"-a", "-b", "--foo", "-ca", "--bar", "barvalue", "-db"}; args::ArgumentParser parser1("Test command"); args::Flag a1(parser1, "a", "a", {'a'}); args::Flag b1(parser1, "b", "b", {'b'}); args::Flag c1(parser1, "c", "c", {'c'}); args::Flag d1(parser1, "d", "d", {'d'}); args::Flag foo(parser1, "foo", "foo", {'f', "foo"}); foo.KickOut(true); args::ArgumentParser parser2("Test command"); args::Flag a2(parser2, "a", "a", {'a'}); args::Flag b2(parser2, "b", "b", {'b'}); args::Flag c2(parser2, "c", "c", {'c'}); args::Flag d2(parser2, "d", "d", {'d'}); args::ValueFlag bar(parser2, "bar", "bar", {'B', "bar"}); bar.KickOut(true); args::ArgumentParser parser3("Test command"); args::Flag a3(parser3, "a", "a", {'a'}); args::Flag b3(parser3, "b", "b", {'b'}); args::Flag c3(parser3, "c", "c", {'c'}); args::Flag d3(parser3, "d", "d", {'d'}); auto next = parser1.ParseArgs(args); next = parser2.ParseArgs(next, std::end(args)); next = parser3.ParseArgs(next, std::end(args)); REQUIRE(next == std::end(args)); REQUIRE(a1); REQUIRE(b1); REQUIRE_FALSE(c1); REQUIRE_FALSE(d1); REQUIRE(foo); REQUIRE(a2); REQUIRE_FALSE(b2); REQUIRE(c2); REQUIRE_FALSE(d2); REQUIRE(bar); REQUIRE(args::get(bar) == "barvalue"); REQUIRE_FALSE(a3); REQUIRE(b3); REQUIRE_FALSE(c3); REQUIRE(d3); } TEST_CASE("Required flags work as expected", "[args]") { args::ArgumentParser parser1("Test command"); args::ValueFlag foo(parser1, "foo", "foo", {'f', "foo"}, args::Options::Required); args::ValueFlag bar(parser1, "bar", "bar", {'b', "bar"}); parser1.ParseArgs(std::vector{"-f", "42"}); REQUIRE(foo.Get() == 42); REQUIRE_THROWS_AS(parser1.ParseArgs(std::vector{"-b4"}), args::RequiredError); args::ArgumentParser parser2("Test command"); args::Positional pos1(parser2, "a", "a"); REQUIRE_NOTHROW(parser2.ParseArgs(std::vector{})); args::ArgumentParser parser3("Test command"); args::Positional pos2(parser3, "a", "a", args::Options::Required); REQUIRE_THROWS_AS(parser3.ParseArgs(std::vector{}), args::RequiredError); } TEST_CASE("Hidden options are excluded from help", "[args]") { args::ArgumentParser parser1(""); args::ValueFlag foo(parser1, "foo", "foo", {'f', "foo"}, args::Options::HiddenFromDescription); args::ValueFlag bar(parser1, "bar", "bar", {'b'}, args::Options::HiddenFromUsage); args::Group group(parser1, "group"); args::ValueFlag foo1(group, "foo", "foo", {'f', "foo"}, args::Options::Hidden); args::ValueFlag bar2(group, "bar", "bar", {'b'}); auto desc = parser1.GetDescription(parser1.helpParams, 0); REQUIRE(desc.size() == 3); REQUIRE(std::get<0>(desc[0]) == "-b[bar]"); REQUIRE(std::get<0>(desc[1]) == "group"); REQUIRE(std::get<0>(desc[2]) == "-b[bar]"); parser1.helpParams.proglineShowFlags = true; parser1.helpParams.proglinePreferShortFlags = true; REQUIRE((parser1.GetProgramLine(parser1.helpParams) == std::vector{"[-f ]", "[-b ]"})); } TEST_CASE("Implicit values work as expected", "[args]") { args::ArgumentParser parser("Test command"); args::ImplicitValueFlag j(parser, "parallel", "parallel", {'j', "parallel"}, 0, 1); args::Flag foo(parser, "FOO", "test flag", {'f', "foo"}); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-j"})); REQUIRE(args::get(j) == 0); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-j4"})); REQUIRE(args::get(j) == 4); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-j", "4"})); REQUIRE(args::get(j) == 4); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-j", "-f"})); REQUIRE(args::get(j) == 0); REQUIRE(foo); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-f"})); REQUIRE(args::get(j) == 1); REQUIRE_FALSE(j); } TEST_CASE("Nargs work as expected", "[args]") { args::ArgumentParser parser("Test command"); args::NargsValueFlag a(parser, "", "", {'a'}, 2); args::NargsValueFlag b(parser, "", "", {'b'}, {2, 3}); args::NargsValueFlag c(parser, "", "", {'c'}, {0, 2}); args::NargsValueFlag d(parser, "", "", {'d'}, {1, 3}); args::Flag f(parser, "", "", {'f'}); REQUIRE_THROWS_AS(args::Nargs(3, 2), args::UsageError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-a", "1", "2"})); REQUIRE((args::get(a) == std::vector{1, 2})); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-a", "1", "2", "-f"})); REQUIRE((args::get(a) == std::vector{1, 2})); REQUIRE(args::get(f) == true); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a", "1"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a1"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a1", "2"}), args::ParseError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-b", "1", "2", "-f"})); REQUIRE((args::get(b) == std::vector{1, 2})); REQUIRE(args::get(f) == true); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-b", "1", "2", "3"})); REQUIRE((args::get(b) == std::vector{1, 2, 3})); REQUIRE(args::get(f) == false); std::vector vec; for (int be : b) { vec.push_back(be); } REQUIRE((vec == std::vector{1, 2, 3})); vec.assign(std::begin(b), std::end(b)); REQUIRE((vec == std::vector{1, 2, 3})); parser.SetArgumentSeparations(true, true, false, false); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a", "1", "2"}), args::ParseError); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-c", "-f"})); REQUIRE(args::get(c).empty()); REQUIRE(args::get(f) == true); REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-cf"})); REQUIRE((args::get(c) == std::vector{"f"})); REQUIRE(args::get(f) == false); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-d"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-b"}), args::ParseError); } TEST_CASE("Simple commands work as expected", "[args]") { args::ArgumentParser p("git-like parser"); args::ValueFlag gitdir(p, "path", "", {"git-dir"}, args::Options::Global); args::HelpFlag h(p, "help", "help", {"help"}, args::Options::Global); args::PositionalList pathsList(p, "paths", "files to commit", args::Options::Global); args::Command add(p, "add", "Add file contents to the index"); args::Command commit(p, "commit", "record changes to the repository"); p.RequireCommand(true); p.ParseArgs(std::vector{"add", "--git-dir", "A", "B", "C", "D"}); REQUIRE(add); REQUIRE(!commit); REQUIRE((args::get(pathsList) == std::vector{"B", "C", "D"})); REQUIRE(args::get(gitdir) == "A"); } TEST_CASE("Subparser commands work as expected", "[args]") { args::Group globals; args::ValueFlag gitdir(globals, "path", "", {"git-dir"}); args::HelpFlag h(globals, "help", "help", {"help"}); args::ArgumentParser p("git-like parser"); args::GlobalOptions g(p, globals); std::vector paths; args::Command add(p, "add", "Add file contents to the index", [&](args::Subparser &c) { args::PositionalList pathsList(c, "paths", "files to add"); c.Parse(); paths.assign(std::begin(pathsList), std::end(pathsList)); }); args::Command commit(p, "commit", "record changes to the repository", [&](args::Subparser &c) { args::PositionalList pathsList(c, "paths", "files to commit"); c.Parse(); paths.assign(std::begin(pathsList), std::end(pathsList)); }); p.RequireCommand(true); p.ParseArgs(std::vector{"add", "--git-dir", "A", "B", "C", "D"}); REQUIRE(add); REQUIRE(!commit); REQUIRE((paths == std::vector{"B", "C", "D"})); REQUIRE(args::get(gitdir) == "A"); } TEST_CASE("Subparser commands with kick-out flags work as expected", "[args]") { args::ArgumentParser p("git-like parser"); std::vector kickedOut; args::Command add(p, "add", "Add file contents to the index", [&](args::Subparser &c) { args::Flag kickoutFlag(c, "kick-out", "kick-out flag", {'k'}, args::Options::KickOut); c.Parse(); REQUIRE(kickoutFlag); kickedOut = c.KickedOut(); }); p.ParseArgs(std::vector{"add", "-k", "A", "B", "C", "D"}); REQUIRE(add); REQUIRE((kickedOut == std::vector{"A", "B", "C", "D"})); } TEST_CASE("Subparser help works as expected", "[args]") { args::ArgumentParser p("git-like parser"); args::Flag g(p, "GLOBAL", "global flag", {'g'}, args::Options::Global); args::Command add(p, "add", "add file contents to the index", [&](args::Subparser &c) { args::Flag flag(c, "FLAG", "flag", {'f'}); c.Parse(); }); args::Command commit(p, "commit", "record changes to the repository", [&](args::Subparser &c) { args::Flag flag(c, "FLAG", "flag", {'f'}); c.Parse(); }); p.Prog("git"); p.RequireCommand(false); std::ostringstream s; auto d = p.GetDescription(p.helpParams, 0); s << p; REQUIRE(s.str() == R"( git [COMMAND] {OPTIONS} git-like parser OPTIONS: -g global flag add add file contents to the index commit record changes to the repository )"); p.ParseArgs(std::vector{"add"}); s.str(""); s << p; REQUIRE(s.str() == R"( git add {OPTIONS} add file contents to the index OPTIONS: -f flag )"); p.ParseArgs(std::vector{}); s.str(""); s << p; REQUIRE(s.str() == R"( git [COMMAND] {OPTIONS} git-like parser OPTIONS: -g global flag add add file contents to the index commit record changes to the repository )"); p.helpParams.showCommandChildren = true; p.ParseArgs(std::vector{}); s.str(""); s << p; REQUIRE(s.str() == R"( git [COMMAND] {OPTIONS} git-like parser OPTIONS: -g global flag add add file contents to the index -f flag commit record changes to the repository -f flag )"); commit.Epilog("epilog"); p.helpParams.showCommandFullHelp = true; p.ParseArgs(std::vector{}); s.str(""); s << p; REQUIRE(s.str() == R"( git [COMMAND] {OPTIONS} git-like parser OPTIONS: -g global flag add {OPTIONS} add file contents to the index -f flag commit {OPTIONS} record changes to the repository -f flag epilog )"); } TEST_CASE("Subparser validation works as expected", "[args]") { args::ArgumentParser p("parser"); args::Command a(p, "a", "command a", [](args::Subparser &s) { args::ValueFlag f(s, "", "", {'f'}, args::Options::Required); s.Parse(); }); args::Command b(p, "b", "command b"); args::ValueFlag f(b, "", "", {'f'}, args::Options::Required); args::Command c(p, "c", "command c", [](args::Subparser&){}); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{}), args::ValidationError); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{"a"}), args::RequiredError); REQUIRE_NOTHROW(p.ParseArgs(std::vector{"a", "-f", "F"})); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{"b"}), args::RequiredError); REQUIRE_NOTHROW(p.ParseArgs(std::vector{"b", "-f", "F"})); p.RequireCommand(false); REQUIRE_NOTHROW(p.ParseArgs(std::vector{})); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{"c"}), args::UsageError); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{"unknown-command"}), args::ParseError); } TEST_CASE("Global options work as expected", "[args]") { args::Group globals; args::Flag f(globals, "f", "f", {'f'}); args::ArgumentParser p("parser"); args::GlobalOptions g(p, globals); args::Command a(p, "a", "command a"); args::Command b(p, "b", "command b"); p.RequireCommand(false); REQUIRE_NOTHROW(p.ParseArgs(std::vector{"-f"})); REQUIRE_NOTHROW(p.ParseArgs(std::vector{"a", "-f"})); REQUIRE_NOTHROW(p.ParseArgs(std::vector{"b", "-f"})); } TEST_CASE("GetProgramLine works as expected", "[args]") { args::ArgumentParser p("parser"); args::Flag g(p, "g", "g", {'g'}, args::Options::Global); args::Flag hidden(p, "hidden", "hidden flag", {'h'}, args::Options::Hidden); args::Command a(p, "a", "command a", [](args::Subparser &s) { args::ValueFlag f(s, "STRING", "my f flag", {'f', "f-long"}, args::Options::Required); args::Positional pos(s, "positional", "positional", args::Options::Required); s.Parse(); }); args::Command b(p, "b", "command b"); args::ValueFlag f(b, "STRING", "my f flag", {'f'}, args::Options::Required); args::Positional pos(b, "positional", "positional"); auto line = [&](args::Command &element) { p.Reset(); auto strings = element.GetCommandProgramLine(p.helpParams); std::string res; for (const std::string &s: strings) { if (!res.empty()) { res += ' '; } res += s; } return res; }; REQUIRE(line(p) == "COMMAND {OPTIONS}"); REQUIRE(line(a) == "a positional {OPTIONS}"); REQUIRE(line(b) == "b [positional] {OPTIONS}"); p.helpParams.proglineShowFlags = true; REQUIRE(line(p) == "COMMAND [-g]"); REQUIRE(line(a) == "a --f-long positional"); REQUIRE(line(b) == "b -f [positional]"); p.helpParams.proglinePreferShortFlags = true; REQUIRE(line(p) == "COMMAND [-g]"); REQUIRE(line(a) == "a -f positional"); REQUIRE(line(b) == "b -f [positional]"); } TEST_CASE("Program line wrapping works as expected", "[args]") { args::ArgumentParser p("parser"); args::ValueFlag f(p, "foo_name", "f", {"foo"}); args::ValueFlag g(p, "bar_name", "b", {"bar"}); args::ValueFlag z(p, "baz_name", "z", {"baz"}); p.helpParams.proglineShowFlags = true; p.helpParams.width = 42; p.Prog("parser"); p.ProglinePostfix("\na\nliiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiine line2 line2tail"); REQUIRE((p.GetProgramLine(p.helpParams) == std::vector{ "[--foo ]", "[--bar ]", "[--baz ]", "\n", "a", "\n", "liiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiine", "line2", "line2tail", })); std::ostringstream s; s << p; REQUIRE(s.str() == R"( parser [--foo ] [--bar ] [--baz ] a liiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiine line2 line2tail parser OPTIONS: --foo=[foo_name] f --bar=[bar_name] b --baz=[baz_name] z )"); } TEST_CASE("Matcher validation works as expected", "[args]") { args::ArgumentParser parser("Test command"); REQUIRE_THROWS_AS(args::ValueFlag(parser, "", "", {}), args::UsageError); } TEST_CASE("HelpParams work as expected", "[args]") { args::ArgumentParser p("parser"); args::ValueFlag f(p, "name", "description", {'f', "foo"}); args::ValueFlag g(p, "name", "description\n d1\n d2", {'g'}); p.Prog("prog"); REQUIRE(p.Help() == R"( prog {OPTIONS} parser OPTIONS: -f[name], --foo=[name] description -g[name] description d1 d2 )"); p.helpParams.usageString = "usage:"; p.helpParams.optionsString = "Options"; p.helpParams.useValueNameOnce = true; REQUIRE(p.Help() == R"( usage: prog {OPTIONS} parser Options -f, --foo=[name] description -g[name] description d1 d2 )"); p.helpParams.showValueName = false; p.helpParams.optionsString = {}; REQUIRE(p.Help() == R"( usage: prog {OPTIONS} parser -f, --foo description -g description d1 d2 )"); p.helpParams.helpindent = 12; p.helpParams.optionsString = "Options"; REQUIRE(p.Help() == R"( usage: prog {OPTIONS} parser Options -f, --foo description -g description d1 d2 )"); p.helpParams.addNewlineBeforeDescription = true; REQUIRE(p.Help() == R"( usage: prog {OPTIONS} parser Options -f, --foo description -g description d1 d2 )"); args::ValueFlag e(p, "name", "some reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaally loooooooooooooooooooooooooooong description", {'e'}); REQUIRE(p.Help() == R"( usage: prog {OPTIONS} parser Options -f, --foo description -g description d1 d2 -e some reaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaally loooooooooooooooooooooooooooong description )"); } struct StringAssignable { public: StringAssignable() = default; StringAssignable(const std::string &p) : path(p) {} std::string path; friend std::istream &operator >> (std::istream &s, StringAssignable &a) { return s >> a.path; } }; TEST_CASE("ValueParser works as expected", "[args]") { static_assert(std::is_assignable::value, "StringAssignable must be assignable to std::string"); args::ArgumentParser p("parser"); args::ValueFlag f(p, "name", "description", {'f'}); args::ValueFlag b(p, "name", "description", {'b'}); args::ValueFlag i(p, "name", "description", {'i'}); args::ValueFlag d(p, "name", "description", {'d'}); args::PositionalList ds(p, "name", "description"); REQUIRE_NOTHROW(p.ParseArgs(std::vector{"-f", "a b"})); REQUIRE(args::get(f) == "a b"); REQUIRE_NOTHROW(p.ParseArgs(std::vector{"-b", "a b"})); REQUIRE(args::get(b).path == "a b"); REQUIRE_NOTHROW(p.ParseArgs(std::vector{"-i", "42 "})); REQUIRE(args::get(i) == 42); REQUIRE_NOTHROW(p.ParseArgs(std::vector{"-i", " 12"})); REQUIRE(args::get(i) == 12); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{"-i", "a"}), args::ParseError); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{"-d", "b"}), args::ParseError); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{"c"}), args::ParseError); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{"s"}), args::ParseError); } TEST_CASE("ActionFlag works as expected", "[args]") { args::ArgumentParser p("parser"); std::string s; args::ActionFlag action0(p, "name", "description", {'x'}, [&]() { s = "flag"; }); args::ActionFlag action1(p, "name", "description", {'y'}, [&](const std::string &arg) { s = arg; }); args::ActionFlag actionN(p, "name", "description", {'z'}, 2, [&](const std::vector &arg) { s = arg[0] + arg[1]; }); args::ActionFlag actionThrow(p, "name", "description", {'v'}, [&]() { throw std::runtime_error(""); }); p.ParseArgs(std::vector{"-x"}); REQUIRE(s == "flag"); p.ParseArgs(std::vector{"-y", "a"}); REQUIRE(s == "a"); p.ParseArgs(std::vector{"-z", "a", "b"}); REQUIRE(s == "ab"); REQUIRE_THROWS_AS(p.ParseArgs(std::vector{"-v"}), std::runtime_error); } TEST_CASE("Default values work as expected", "[args]") { args::ArgumentParser p("parser"); args::ValueFlag f(p, "name", "description", {'f', "foo"}, "abc"); args::MapFlag b(p, "name", "description", {'b', "bar"}, {{"a", 1}, {"b", 2}, {"c", 3}}); p.Prog("prog"); REQUIRE(p.Help() == R"( prog {OPTIONS} parser OPTIONS: -f[name], --foo=[name] description -b[name], --bar=[name] description )"); p.helpParams.addDefault = true; p.helpParams.addChoices = true; REQUIRE(p.Help() == R"( prog {OPTIONS} parser OPTIONS: -f[name], --foo=[name] description Default: abc -b[name], --bar=[name] description One of: a, b, c )"); f.HelpDefault("123"); b.HelpChoices({"1", "2", "3"}); REQUIRE(p.Help() == R"( prog {OPTIONS} parser OPTIONS: -f[name], --foo=[name] description Default: 123 -b[name], --bar=[name] description One of: 1, 2, 3 )"); f.HelpDefault({}); b.HelpChoices({}); REQUIRE(p.Help() == R"( prog {OPTIONS} parser OPTIONS: -f[name], --foo=[name] description -b[name], --bar=[name] description )"); } TEST_CASE("Choices description works as expected", "[args]") { args::ArgumentParser p("parser"); args::MapFlag map(p, "map", "map", {"map"}, {{1,1}, {2, 2}}); args::MapFlagList maplist(p, "maplist", "maplist", {"maplist"}, {{'1',1}, {'2', 2}}); args::MapPositional mappos(p, "mappos", "mappos", {{"1",1}, {"2", 2}}); args::MapPositionalList mapposlist(p, "mapposlist", "mapposlist", {{'1',1}, {'2', 2}}); REQUIRE(map.HelpChoices(p.helpParams) == std::vector{"1", "2"}); REQUIRE(maplist.HelpChoices(p.helpParams) == std::vector{"1", "2"}); REQUIRE(mappos.HelpChoices(p.helpParams) == std::vector{"1", "2"}); REQUIRE(mapposlist.HelpChoices(p.helpParams) == std::vector{"1", "2"}); } TEST_CASE("Completion works as expected", "[args]") { using namespace Catch::Matchers; args::ArgumentParser p("parser"); args::CompletionFlag c(p, {"completion"}); args::Group g(p); args::ValueFlag f(g, "name", "description", {'f', "foo"}, "abc"); args::ValueFlag b(g, "name", "description", {'b', "bar"}, "abc"); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}), Equals("-f\n-b")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-f"}), Equals("-f")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--"}), Equals("--foo\n--bar")); args::MapFlag m(p, "mappos", "mappos", {'m', "map"}, {{"1",1}, {"2", 2}}); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "2", "test", "-m", ""}), Equals("1\n2")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--map="}), Equals("1\n2")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "2", "test", "--map", "="}), Equals("1\n2")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-m1"}), Equals("-m1")); args::Positional pos(p, "name", "desc"); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", ""}), Equals("")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}), Equals("-f\n-b\n-m")); REQUIRE_THROWS_WITH(p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "--"}), Equals("--foo\n--bar\n--map")); args::ArgumentParser p2("parser"); args::CompletionFlag complete2(p2, {"completion"}); args::Command c1(p2, "command1", "desc", [](args::Subparser &sp) { args::ValueFlag f1(sp, "name", "description", {'f', "foo"}, "abc"); f1.KickOut(); sp.Parse(); }); args::Command c2(p2, "command2", "desc", [](args::Subparser &sp) { args::ValueFlag f1(sp, "name", "description", {'b', "bar"}, "abc"); sp.Parse(); }); REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}), Equals("")); REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "1", "test", ""}), Equals("command1\ncommand2")); REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "2", "test", "command1", ""}), Equals("-f")); REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "2", "test", "command2", ""}), Equals("-b")); REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "2", "test", "command3", ""}), Equals("")); REQUIRE_THROWS_WITH(p2.ParseArgs(std::vector{"--completion", "bash", "3", "test", "command1", "-f", "-"}), Equals("")); } #undef ARGS_HXX #define ARGS_TESTNAMESPACE #define ARGS_NOEXCEPT #include TEST_CASE("Noexcept mode works as expected", "[args]") { std::unordered_map map{ {"default", MappingEnum::def}, {"foo", MappingEnum::foo}, {"bar", MappingEnum::bar}, {"red", MappingEnum::red}, {"yellow", MappingEnum::yellow}, {"green", MappingEnum::green}}; argstest::ArgumentParser parser("This is a test program.", "This goes after the options."); argstest::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); argstest::Flag bar(parser, "BAR", "test flag", {'b', "bar"}, true); argstest::ValueFlag foo(parser, "FOO", "test flag", {'f', "foo"}); argstest::Group nandgroup(parser, "this group provides nand validation", argstest::Group::Validators::AtMostOne); argstest::Flag x(nandgroup, "x", "test flag", {'x'}); argstest::Flag y(nandgroup, "y", "test flag", {'y'}); argstest::Flag z(nandgroup, "z", "test flag", {'z'}); argstest::MapFlag mf(parser, "MF", "Maps string to an enum", {"mf"}, map); parser.ParseArgs(std::vector{"-h"}); REQUIRE(parser.GetError() == argstest::Error::Help); parser.ParseArgs(std::vector{"--Help"}); REQUIRE(parser.GetError() == argstest::Error::Parse); parser.ParseArgs(std::vector{"--bar=test"}); REQUIRE(parser.GetError() == argstest::Error::Parse); parser.ParseArgs(std::vector{"--bar"}); REQUIRE(parser.GetError() == argstest::Error::None); parser.ParseArgs(std::vector{"--bar", "-b"}); REQUIRE(parser.GetError() == argstest::Error::Extra); parser.ParseArgs(std::vector{"--foo=7.5"}); REQUIRE(parser.GetError() == argstest::Error::Parse); parser.ParseArgs(std::vector{"--foo", "7a"}); REQUIRE(parser.GetError() == argstest::Error::Parse); parser.ParseArgs(std::vector{"--foo", "7e4"}); REQUIRE(parser.GetError() == argstest::Error::Parse); parser.ParseArgs(std::vector{"--foo"}); REQUIRE(parser.GetError() == argstest::Error::Parse); parser.ParseArgs(std::vector{"--foo=85"}); REQUIRE(parser.GetError() == argstest::Error::None); parser.ParseArgs(std::vector{"this is a test flag again", "1", "this has no positional available"}); REQUIRE(parser.GetError() == argstest::Error::Parse); parser.ParseArgs(std::vector{"-x"}); REQUIRE(parser.GetError() == argstest::Error::None); parser.ParseArgs(std::vector{"-xz"}); REQUIRE(parser.GetError() == argstest::Error::Validation); parser.ParseArgs(std::vector{"-y"}); REQUIRE(parser.GetError() == argstest::Error::None); parser.ParseArgs(std::vector{"-y", "-xz"}); REQUIRE(parser.GetError() == argstest::Error::Validation); parser.ParseArgs(std::vector{"--mf", "YeLLoW"}); REQUIRE(parser.GetError() == argstest::Error::Map); parser.ParseArgs(std::vector{"--mf", "yellow"}); REQUIRE(parser.GetError() == argstest::Error::None); } TEST_CASE("Required flags work as expected in noexcept mode", "[args]") { argstest::ArgumentParser parser1("Test command"); argstest::ValueFlag foo(parser1, "foo", "foo", {'f', "foo"}, argstest::Options::Required); argstest::ValueFlag bar(parser1, "bar", "bar", {'b', "bar"}); parser1.ParseArgs(std::vector{"-f", "42"}); REQUIRE(foo.Get() == 42); REQUIRE(parser1.GetError() == argstest::Error::None); parser1.ParseArgs(std::vector{"-b4"}); REQUIRE(parser1.GetError() == argstest::Error::Required); argstest::ArgumentParser parser2("Test command"); argstest::Positional pos1(parser2, "a", "a"); parser2.ParseArgs(std::vector{}); REQUIRE(parser2.GetError() == argstest::Error::None); argstest::ArgumentParser parser3("Test command"); argstest::Positional pos2(parser3, "a", "a", argstest::Options::Required); parser3.ParseArgs(std::vector{}); REQUIRE(parser3.GetError() == argstest::Error::Required); } TEST_CASE("Subparser validation works as expected in noexcept mode", "[args]") { argstest::ArgumentParser p("parser"); argstest::Command a(p, "a", "command a", [](argstest::Subparser &s) { argstest::ValueFlag f(s, "", "", {'f'}, argstest::Options::Required); s.Parse(); }); argstest::Command b(p, "b", "command b"); argstest::ValueFlag f(b, "", "", {'f'}, argstest::Options::Required); argstest::Command c(p, "c", "command c", [](argstest::Subparser&){}); p.ParseArgs(std::vector{}); REQUIRE(p.GetError() == argstest::Error::Validation); p.ParseArgs(std::vector{"a"}); REQUIRE((size_t)p.GetError() == (size_t)argstest::Error::Required); p.ParseArgs(std::vector{"a", "-f", "F"}); REQUIRE(p.GetError() == argstest::Error::None); p.ParseArgs(std::vector{"b"}); REQUIRE(p.GetError() == argstest::Error::Required); p.ParseArgs(std::vector{"b", "-f", "F"}); REQUIRE(p.GetError() == argstest::Error::None); p.RequireCommand(false); p.ParseArgs(std::vector{}); REQUIRE(p.GetError() == argstest::Error::None); p.ParseArgs(std::vector{"c"}); REQUIRE(p.GetError() == argstest::Error::Usage); } TEST_CASE("Nargs work as expected in noexcept mode", "[args]") { argstest::ArgumentParser parser("Test command"); argstest::NargsValueFlag a(parser, "", "", {'a'}, {3, 2}); REQUIRE(parser.GetError() == argstest::Error::Usage); parser.ParseArgs(std::vector{"-a", "1", "2"}); REQUIRE(parser.GetError() == argstest::Error::Usage); } TEST_CASE("Matcher validation works as expected in noexcept mode", "[args]") { argstest::ArgumentParser parser("Test command"); argstest::ValueFlag a(parser, "", "", {}); REQUIRE(parser.GetError() == argstest::Error::Usage); parser.ParseArgs(std::vector{"-a", "1", "2"}); REQUIRE(parser.GetError() == argstest::Error::Usage); } TEST_CASE("Completion works as expected in noexcept mode", "[args]") { using namespace Catch::Matchers; argstest::ArgumentParser p("parser"); argstest::CompletionFlag c(p, {"completion"}); argstest::Group g(p); argstest::ValueFlag f(g, "name", "description", {'f', "foo"}, "abc"); argstest::ValueFlag b(g, "name", "description", {'b', "bar"}, "abc"); p.ParseArgs(std::vector{"--completion", "bash", "1", "test", "-"}); REQUIRE(p.GetError() == argstest::Error::Completion); REQUIRE(argstest::get(c) == "-f\n-b"); }