aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylor C. Richberger <taywee@gmx.com>2017-10-15 09:58:44 -0600
committerGitHub <noreply@github.com>2017-10-15 09:58:44 -0600
commit69547d215c0a58f42f0ba97a281d56d8a001240d (patch)
tree101c4aa5d90165e1b2d44bb7dd4cb05004e5e4cb
parentadd readme note (diff)
parentadd Options::Required flag (diff)
downloadargs.hxx-69547d215c0a58f42f0ba97a281d56d8a001240d.tar.xz
Merge pull request #31 from pavel-belikov/required-options
Required flags
-rw-r--r--args.hxx140
-rw-r--r--test.cxx20
2 files changed, 151 insertions, 9 deletions
diff --git a/args.hxx b/args.hxx
index 69f5e4d..ff0e0dd 100644
--- a/args.hxx
+++ b/args.hxx
@@ -156,6 +156,7 @@ namespace args
Usage,
Parse,
Validation,
+ Required,
Map,
Extra,
Help
@@ -197,6 +198,15 @@ namespace args
virtual ~ValidationError() {};
};
+ /** Errors that when a required flag is omitted
+ */
+ class RequiredError : public ValidationError
+ {
+ public:
+ RequiredError(const std::string &problem) : ValidationError(problem) {}
+ virtual ~RequiredError() {};
+ };
+
/** Errors in map lookups
*/
class MapError : public ParseError
@@ -392,6 +402,10 @@ namespace args
return matched;
}
+ virtual void Validate(const std::string &, const std::string &)
+ {
+ }
+
operator bool() const noexcept
{
return Matched();
@@ -458,26 +472,59 @@ namespace args
}
};
+
+ enum class Options
+ {
+ /** Default options.
+ */
+ None = 0x0,
+
+ /** Flag can't be passed multiple times.
+ */
+ Single = 0x01,
+
+ /** Flag can't be omitted.
+ */
+ Required = 0x02,
+ };
+
+ inline Options operator | (Options lhs, Options rhs)
+ {
+ return static_cast<Options>(static_cast<int>(lhs) | static_cast<int>(rhs));
+ }
+
+ inline Options operator & (Options lhs, Options rhs)
+ {
+ return static_cast<Options>(static_cast<int>(lhs) & static_cast<int>(rhs));
+ }
+
/** Base class for all flag options
*/
class FlagBase : public NamedBase
{
private:
- const bool extraError;
+ const Options options;
protected:
const Matcher matcher;
public:
- FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : NamedBase(name_, help_), extraError(extraError_), matcher(std::move(matcher_)) {}
+ FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : NamedBase(name_, help_), options(extraError_ ? Options::Single : Options()), matcher(std::move(matcher_)) {}
+
+ FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : NamedBase(name_, help_), options(options_), matcher(std::move(matcher_)) {}
virtual ~FlagBase() {}
+ Options GetOptions() const
+ {
+ return options;
+ }
+
virtual FlagBase *Match(const std::string &flag)
{
if (matcher.Match(flag))
{
- if (extraError && matched)
+ if ((options & Options::Single) != Options::None && matched)
{
#ifdef ARGS_NOEXCEPT
error = Error::Extra;
@@ -493,11 +540,25 @@ namespace args
return nullptr;
}
+ virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) override
+ {
+ if (!Matched() && (options & Options::Required) != Options::None)
+ {
+#ifdef ARGS_NOEXCEPT
+ error = Error::Required;
+#else
+ std::ostringstream problem;
+ problem << "Flag '" << matcher.GetFlagStrings(shortPrefix, longPrefix).at(0) << "' is required";
+ throw RequiredError(problem.str());
+#endif
+ }
+ }
+
virtual FlagBase *Match(const char flag)
{
if (matcher.Match(flag))
{
- if (extraError && matched)
+ if ((options & Options::Single) != Options::None && matched)
{
#ifdef ARGS_NOEXCEPT
error = Error::Extra;
@@ -538,6 +599,7 @@ namespace args
{
public:
ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : FlagBase(name_, help_, std::move(matcher_), extraError_) {}
+ ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : FlagBase(name_, help_, std::move(matcher_), options_) {}
virtual ~ValueFlagBase() {}
virtual void ParseValue(const std::string &value) = 0;
@@ -564,11 +626,14 @@ namespace args
*/
class PositionalBase : public NamedBase
{
+ private:
+ const Options options;
+
protected:
bool ready;
public:
- PositionalBase(const std::string &name_, const std::string &help_) : NamedBase(name_, help_), ready(true) {}
+ PositionalBase(const std::string &name_, const std::string &help_, Options options_ = Options::None) : NamedBase(name_, help_), options(options_), ready(true) {}
virtual ~PositionalBase() {}
bool Ready()
@@ -576,6 +641,11 @@ namespace args
return ready;
}
+ Options GetOptions() const
+ {
+ return options;
+ }
+
virtual void ParseValue(const std::string &value_) = 0;
virtual void Reset() noexcept override
@@ -586,6 +656,20 @@ namespace args
error = Error::None;
#endif
}
+
+ virtual void Validate(const std::string &, const std::string &) override
+ {
+ if ((options & Options::Required) != Options::None && !Matched())
+ {
+#ifdef ARGS_NOEXCEPT
+ error = Error::Required;
+#else
+ std::ostringstream problem;
+ problem << "Option '" << Name() << "' is required";
+ throw RequiredError(problem.str());
+#endif
+ }
+ }
};
/** Class for all kinds of validating groups, including ArgumentParser
@@ -684,6 +768,14 @@ namespace args
return nullptr;
}
+ virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) override
+ {
+ for (Base *child: children)
+ {
+ child->Validate(shortPrefix, longPrefix);
+ }
+ }
+
/** Get the next ready positional, or nullptr if there is none
*
* \return the first ready PositionalBase pointer, or nullptr if there is no match
@@ -1368,6 +1460,12 @@ namespace args
}
}
}
+
+ for (Base *child: Children())
+ {
+ child->Validate(shortprefix, longprefix);
+ }
+
if (!Matched())
{
#ifdef ARGS_NOEXCEPT
@@ -1420,11 +1518,15 @@ namespace args
class Flag : public FlagBase
{
public:
- Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false): FlagBase(name_, help_, std::move(matcher_), extraError_)
+ Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): FlagBase(name_, help_, std::move(matcher_), options_)
{
group_.Add(*this);
}
+ Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false): Flag(group_, name_, help_, std::move(matcher_), extraError_ ? Options::Single : Options::None)
+ {
+ }
+
virtual ~Flag() {}
/** Get whether this was matched
@@ -1587,11 +1689,19 @@ namespace args
public:
- ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), const bool extraError_ = false): ValueFlagBase(name_, help_, std::move(matcher_), extraError_), value(defaultValue_)
+ ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), value(defaultValue_)
{
group_.Add(*this);
}
+ ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), const bool extraError_ = false): ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, extraError_ ? Options::Single : Options::None)
+ {
+ }
+
+ ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): ValueFlag(group_, name_, help_, std::move(matcher_), T(), options_)
+ {
+ }
+
virtual ~ValueFlag() {}
virtual void ParseValue(const std::string &value_) override
@@ -1737,11 +1847,19 @@ namespace args
public:
- MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map<K, T> &map_, const T &defaultValue_ = T(), const bool extraError_ = false): ValueFlagBase(name_, help_, std::move(matcher_), extraError_), map(map_), value(defaultValue_)
+ MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map<K, T> &map_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), map(map_), value(defaultValue_)
{
group_.Add(*this);
}
+ MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map<K, T> &map_, const T &defaultValue_ = T(), const bool extraError_ = false): MapFlag(group_, name_, help_, std::move(matcher_), map_, defaultValue_, extraError_ ? Options::Single : Options::None)
+ {
+ }
+
+ MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map<K, T> &map_, Options options_): MapFlag(group_, name_, help_, std::move(matcher_), map_, T(), options_)
+ {
+ }
+
virtual ~MapFlag() {}
virtual void ParseValue(const std::string &value_) override
@@ -1912,11 +2030,15 @@ namespace args
T value;
Reader reader;
public:
- Positional(Group &group_, const std::string &name_, const std::string &help_, const T &defaultValue_ = T()): PositionalBase(name_, help_), value(defaultValue_)
+ Positional(Group &group_, const std::string &name_, const std::string &help_, const T &defaultValue_ = T(), Options options_ = Options::None): PositionalBase(name_, help_, options_), value(defaultValue_)
{
group_.Add(*this);
}
+ Positional(Group &group_, const std::string &name_, const std::string &help_, Options options_): Positional(group_, name_, help_, T(), options_)
+ {
+ }
+
virtual ~Positional() {}
virtual void ParseValue(const std::string &value_) override
diff --git a/test.cxx b/test.cxx
index c7dbb8b..e8a7d81 100644
--- a/test.cxx
+++ b/test.cxx
@@ -570,6 +570,26 @@ TEST_CASE("Kick-out should work via all flags and value flags", "[args]")
REQUIRE(d3);
}
+TEST_CASE("Required flags work as expected", "[args]")
+{
+ args::ArgumentParser parser1("Test command");
+ args::ValueFlag<int> foo(parser1, "foo", "foo", {'f', "foo"}, args::Options::Required);
+ args::ValueFlag<int> bar(parser1, "bar", "bar", {'b', "bar"});
+
+ parser1.ParseArgs(std::vector<std::string>{"-f", "42"});
+ REQUIRE(foo.Get() == 42);
+
+ REQUIRE_THROWS_AS(parser1.ParseArgs(std::vector<std::string>{"-b4"}), args::RequiredError);
+
+ args::ArgumentParser parser2("Test command");
+ args::Positional<int> pos1(parser2, "a", "a");
+ REQUIRE_NOTHROW(parser2.ParseArgs(std::vector<std::string>{}));
+
+ args::ArgumentParser parser3("Test command");
+ args::Positional<int> pos2(parser3, "a", "a", args::Options::Required);
+ REQUIRE_THROWS_AS(parser3.ParseArgs(std::vector<std::string>{}), args::RequiredError);
+}
+
#undef ARGS_HXX
#define ARGS_TESTNAMESPACE
#define ARGS_NOEXCEPT