aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylor C. Richberger <taywee@gmx.com>2016-06-29 12:23:56 -0400
committerTaylor C. Richberger <taywee@gmx.com>2016-06-29 12:23:56 -0400
commit2c44b454a240859147bc327c7430a884b7a29080 (patch)
tree66db74498d4cd96df9f2f961640fe14067ef9211
parentfix weird typo (diff)
parentadd extra test (diff)
downloadargs.hxx-2c44b454a240859147bc327c7430a884b7a29080.tar.xz
Merge branch '20-allow-an-argument-to-be-a-kick-out' into 'master'
Resolve "Allow an argument to be a kick-out" Closes #20 See merge request !13
-rw-r--r--args.hxx45
-rw-r--r--test.cxx82
2 files changed, 119 insertions, 8 deletions
diff --git a/args.hxx b/args.hxx
index 9a5681a..509a5c3 100644
--- a/args.hxx
+++ b/args.hxx
@@ -380,9 +380,10 @@ namespace args
{
protected:
const std::string name;
+ bool kickout;
public:
- NamedBase(const std::string &name, const std::string &help) : Base(help), name(name) {}
+ NamedBase(const std::string &name, const std::string &help) : Base(help), name(name), kickout(false) {}
virtual ~NamedBase() {}
virtual std::tuple<std::string, std::string> GetDescription(const std::string &shortPrefix, const std::string &longPrefi, const std::string &shortSeparator, const std::string &longSeparator) const override
@@ -396,6 +397,16 @@ namespace args
{
return name;
}
+
+ void KickOut(bool kickout) noexcept
+ {
+ this->kickout = kickout;
+ }
+
+ bool KickOut() const noexcept
+ {
+ return kickout;
+ }
};
/** Base class for all flag options
@@ -1093,9 +1104,10 @@ namespace args
*
* \param begin an iterator to the beginning of the argument list
* \param end an iterator to the past-the-end element of the argument list
+ * \return the iterator after the last parsed value. Only useful for kick-out
*/
template <typename It>
- void ParseArgs(It begin, It end)
+ It ParseArgs(It begin, It end)
{
// Reset all Matched statuses to false, for validation. Don't reset values.
ResetMatched();
@@ -1160,6 +1172,11 @@ namespace args
problem << "Passed an argument into a non-argument flag: " << chunk;
throw ParseError(problem.str());
}
+
+ if (base->KickOut())
+ {
+ return ++it;
+ }
} else
{
std::ostringstream problem;
@@ -1174,7 +1191,7 @@ namespace args
{
const char arg = *argit;
- if (Base *base = Match(arg))
+ if (FlagBase *base = Match(arg))
{
if (ValueFlagBase *argbase = dynamic_cast<ValueFlagBase *>(base))
{
@@ -1213,6 +1230,11 @@ namespace args
// Because this argchunk is done regardless
break;
}
+
+ if (base->KickOut())
+ {
+ return ++it;
+ }
} else
{
std::ostringstream problem;
@@ -1226,6 +1248,11 @@ namespace args
if (pos)
{
pos->ParseValue(chunk);
+
+ if (pos->KickOut())
+ {
+ return ++it;
+ }
} else
{
std::ostringstream problem;
@@ -1240,30 +1267,34 @@ namespace args
problem << "Group validation failed somewhere!";
throw ValidationError(problem.str());
}
+ return end;
}
/** Parse all arguments.
*
* \param args an iterable of the arguments
+ * \return the iterator after the last parsed value. Only useful for kick-out
*/
template <typename T>
- void ParseArgs(const T &args)
+ auto ParseArgs(const T &args) -> decltype(std::begin(args))
{
- ParseArgs(std::begin(args), std::end(args));
+ return ParseArgs(std::begin(args), std::end(args));
}
/** Convenience function to parse the CLI from argc and argv
*
* Just assigns the program name and vectorizes arguments for passing into ParseArgs()
+ *
+ * \return whether or not all arguments were parsed. This works for detecting kick-out, but is generally useless as it can't do anything with it.
*/
- void ParseCLI(const int argc, const char * const * const argv)
+ bool ParseCLI(const int argc, const char * const * const argv)
{
if (prog.empty())
{
prog.assign(argv[0]);
}
const std::vector<std::string> args(argv + 1, argv + argc);
- ParseArgs(args);
+ return ParseArgs(args) == std::end(args);
}
};
diff --git a/test.cxx b/test.cxx
index c368bf9..8113943 100644
--- a/test.cxx
+++ b/test.cxx
@@ -423,7 +423,6 @@ TEST_CASE("An exception should be thrown when a single-argument flag is matched
{"yellow", MappingEnum::yellow},
{"green", MappingEnum::green}};
- std::ostream null(nullptr);
args::ArgumentParser parser("Test command");
args::Flag foo(parser, "Foo", "Foo", {'f', "foo"}, true);
args::ValueFlag<std::string> bar(parser, "Bar", "Bar", {'b', "bar"}, "", true);
@@ -442,3 +441,84 @@ TEST_CASE("An exception should be thrown when a single-argument flag is matched
REQUIRE(baz);
REQUIRE(args::get(baz) == MappingEnum::green);
}
+
+TEST_CASE("Sub-parsers should work through kick-out", "[args]")
+{
+ std::unordered_map<std::string, MappingEnum> map{
+ {"default", MappingEnum::def},
+ {"foo", MappingEnum::foo},
+ {"bar", MappingEnum::bar},
+ {"red", MappingEnum::red},
+ {"yellow", MappingEnum::yellow},
+ {"green", MappingEnum::green}};
+
+ const std::vector<std::string> 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<std::string, MappingEnum> 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<std::string> 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<std::string> 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);
+}