aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavel Belikov <pavel.fuchs.belikov@gmail.com>2017-11-09 20:05:40 +0300
committerPavel Belikov <pavel.fuchs.belikov@gmail.com>2017-11-09 20:05:40 +0300
commitcf665adc56e9185553960132de1b4115a3216ded (patch)
treed494f62d73fa1b4d41532f2a8e366409469567b1
parentadd validation for commands (diff)
downloadargs.hxx-cf665adc56e9185553960132de1b4115a3216ded.tar.xz
add subparsers validation
-rw-r--r--args.hxx73
-rw-r--r--test.cxx86
2 files changed, 154 insertions, 5 deletions
diff --git a/args.hxx b/args.hxx
index c290d6b..00b5706 100644
--- a/args.hxx
+++ b/args.hxx
@@ -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)
diff --git a/test.cxx b/test.cxx
index 7f92f26..0bb58d2 100644
--- a/test.cxx
+++ b/test.cxx
@@ -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);
+}