diff options
-rw-r--r-- | args.hxx | 73 | ||||
-rw-r--r-- | test.cxx | 86 |
2 files changed, 154 insertions, 5 deletions
@@ -160,7 +160,8 @@ namespace args Required, Map, Extra, - Help + Help, + Subparser, }; #else /** Base error class @@ -234,6 +235,15 @@ namespace args Help(const std::string &flag) : Error(flag) {} virtual ~Help() {}; }; + + /** (INTERNAL) An exception that emulates coroutine-like control flow for subparsers. + */ + class SubparserError : public Error + { + public: + SubparserError() : Error("") {} + virtual ~SubparserError() {}; + }; #endif /** A simple unified option type for unified initializer lists for the Matcher class. @@ -1222,6 +1232,9 @@ namespace args mutable bool subparserHasFlag = false; mutable bool subparserHasPositional = false; mutable bool subparserHasCommand = false; +#ifdef ARGS_NOEXCEPT + mutable Error subparserError = Error::None; +#endif mutable Subparser *subparser = nullptr; protected: @@ -1508,7 +1521,7 @@ namespace args { parserCoroutine(coro.Parser()); } - catch (args::Help) + catch (args::SubparserError) { } #else @@ -1641,7 +1654,33 @@ namespace args subparserHasFlag = false; subparserHasPositional = false; subparserHasCommand = false; +#ifdef ARGS_NOEXCEPT + subparserError = Error::None; +#endif + } + +#ifdef ARGS_NOEXCEPT + /// Only for ARGS_NOEXCEPT + virtual Error GetError() const override + { + if (!Matched()) + { + return Error::None; + } + + if (error != Error::None) + { + return error; + } + + if (subparserError != Error::None) + { + return subparserError; + } + + return Group::GetError(); } +#endif }; /** The main user facing command line argument parser class @@ -1914,10 +1953,22 @@ namespace args RaiiSubparser coro(*this, std::vector<std::string>(it, end)); coroutine(coro.Parser()); #ifdef ARGS_NOEXCEPT - if (GetError() != Error::None) + error = GetError(); + if (error != Error::None) + { + return end; + } + + if (!coro.Parser().IsParsed()) { + error = Error::Usage; return end; } +#else + if (!coro.Parser().IsParsed()) + { + throw UsageError("Subparser::Parse was not called"); + } #endif break; @@ -2196,6 +2247,13 @@ namespace args { // Reset all Matched statuses and errors Reset(); +#ifdef ARGS_NOEXCEPT + error = GetError(); + if (error != Error::None) + { + return end; + } +#endif return Parse(begin, end); } @@ -2241,6 +2299,7 @@ namespace args void Subparser::Parse() { isParsed = true; + Reset(); command.subparserDescription = GetDescription(helpParams, 0); command.subparserHasFlag = HasFlag(); command.subparserHasPositional = HasPositional(); @@ -2249,9 +2308,9 @@ namespace args if (parser == nullptr) { #ifndef ARGS_NOEXCEPT - throw args::Help(""); + throw args::SubparserError(); #else - error = Error::Help; + error = Error::Subparser; return; #endif } @@ -2259,6 +2318,10 @@ namespace args auto it = parser->Parse(args.begin(), args.end()); command.Validate(parser->ShortPrefix(), parser->LongPrefix()); kicked.assign(it, args.end()); + +#ifdef ARGS_NOEXCEPT + command.subparserError = GetError(); +#endif } inline std::ostream &operator<<(std::ostream &os, const ArgumentParser &parser) @@ -635,8 +635,11 @@ TEST_CASE("Nargs work as expected", "[args]") args::NargsValueFlag<int> a(parser, "", "", {'a'}, 2); args::NargsValueFlag<int> b(parser, "", "", {'b'}, {2, 3}); args::NargsValueFlag<std::string> c(parser, "", "", {'c'}, {0, 2}); + args::NargsValueFlag<int> 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<std::string>{"-a", "1", "2"})); REQUIRE((args::get(a) == std::vector<int>{1, 2})); @@ -676,6 +679,9 @@ TEST_CASE("Nargs work as expected", "[args]") REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-cf"})); REQUIRE((args::get(c) == std::vector<std::string>{"f"})); REQUIRE(args::get(f) == false); + + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"-d"}), args::ParseError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"-b"}), args::ParseError); } TEST_CASE("Simple commands work as expected", "[args]") @@ -869,6 +875,8 @@ TEST_CASE("Subparser validation works as expected", "[args]") args::Command b(p, "b", "command b"); args::ValueFlag<std::string> f(b, "", "", {'f'}, args::Options::Required); + args::Command c(p, "c", "command c", [](args::Subparser&){}); + REQUIRE_NOTHROW(p.ParseArgs(std::vector<std::string>{})); REQUIRE_THROWS_AS(p.ParseArgs(std::vector<std::string>{"a"}), args::RequiredError); REQUIRE_NOTHROW(p.ParseArgs(std::vector<std::string>{"a", "-f", "F"})); @@ -880,6 +888,10 @@ TEST_CASE("Subparser validation works as expected", "[args]") p.RequireCommand(false); REQUIRE_NOTHROW(p.ParseArgs(std::vector<std::string>{})); + + REQUIRE_THROWS_AS(p.ParseArgs(std::vector<std::string>{"c"}), args::UsageError); + + REQUIRE_THROWS_AS(p.ParseArgs(std::vector<std::string>{"unknown-command"}), args::ParseError); } TEST_CASE("Global options work as expected", "[args]") @@ -960,3 +972,77 @@ TEST_CASE("Noexcept mode works as expected", "[args]") parser.ParseArgs(std::vector<std::string>{"--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<int> foo(parser1, "foo", "foo", {'f', "foo"}, argstest::Options::Required); + argstest::ValueFlag<int> bar(parser1, "bar", "bar", {'b', "bar"}); + + parser1.ParseArgs(std::vector<std::string>{"-f", "42"}); + REQUIRE(foo.Get() == 42); + REQUIRE(parser1.GetError() == argstest::Error::None); + + parser1.ParseArgs(std::vector<std::string>{"-b4"}); + REQUIRE(parser1.GetError() == argstest::Error::Required); + + argstest::ArgumentParser parser2("Test command"); + argstest::Positional<int> pos1(parser2, "a", "a"); + parser2.ParseArgs(std::vector<std::string>{}); + REQUIRE(parser2.GetError() == argstest::Error::None); + + argstest::ArgumentParser parser3("Test command"); + argstest::Positional<int> pos2(parser3, "a", "a", argstest::Options::Required); + parser3.ParseArgs(std::vector<std::string>{}); + 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<std::string> f(s, "", "", {'f'}, argstest::Options::Required); + s.Parse(); + }); + + argstest::Command b(p, "b", "command b"); + argstest::ValueFlag<std::string> f(b, "", "", {'f'}, argstest::Options::Required); + + argstest::Command c(p, "c", "command c", [](argstest::Subparser&){}); + + p.ParseArgs(std::vector<std::string>{}); + REQUIRE(p.GetError() == argstest::Error::None); + + p.ParseArgs(std::vector<std::string>{"a"}); + REQUIRE((size_t)p.GetError() == (size_t)argstest::Error::Required); + + p.ParseArgs(std::vector<std::string>{"a", "-f", "F"}); + REQUIRE(p.GetError() == argstest::Error::None); + + p.ParseArgs(std::vector<std::string>{"b"}); + REQUIRE(p.GetError() == argstest::Error::Required); + + p.ParseArgs(std::vector<std::string>{"b", "-f", "F"}); + REQUIRE(p.GetError() == argstest::Error::None); + + p.RequireCommand(true); + p.ParseArgs(std::vector<std::string>{}); + REQUIRE(p.GetError() == argstest::Error::Validation); + + p.RequireCommand(false); + p.ParseArgs(std::vector<std::string>{}); + REQUIRE(p.GetError() == argstest::Error::None); + + p.ParseArgs(std::vector<std::string>{"c"}); + REQUIRE(p.GetError() == argstest::Error::Usage); +} + +TEST_CASE("Nargs work as expected in noexcept mode", "[args]") +{ + argstest::ArgumentParser parser("Test command"); + argstest::NargsValueFlag<int> a(parser, "", "", {'a'}, {3, 2}); + + parser.ParseArgs(std::vector<std::string>{"-a", "1", "2"}); + REQUIRE(parser.GetError() == argstest::Error::Usage); +} |