aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--args.hxx595
-rw-r--r--test.cxx65
2 files changed, 485 insertions, 175 deletions
diff --git a/args.hxx b/args.hxx
index 1af7491..9f934ec 100644
--- a/args.hxx
+++ b/args.hxx
@@ -88,7 +88,7 @@ namespace args
* different width for the first line
*
* \param width The width of the body
- * \param the widtho f the first line, defaults to the width of the body
+ * \param the width of the first line, defaults to the width of the body
* \return the vector of lines
*/
inline std::vector<std::string> Wrap(const std::string &in, const std::string::size_type width, std::string::size_type firstlinewidth = 0)
@@ -510,6 +510,26 @@ namespace args
}
};
+ struct Nargs
+ {
+ const size_t min;
+ const size_t max;
+
+ Nargs(size_t min_, size_t max_) : min(min_), max(max_)
+ {
+#ifndef ARGS_NOEXCEPT
+ if (max < min)
+ {
+ throw std::invalid_argument("Nargs: max > min");
+ }
+#endif
+ }
+
+ Nargs(size_t num_) : min(num_), max(num_)
+ {
+ }
+ };
+
/** Base class for all flag options
*/
class FlagBase : public NamedBase
@@ -595,6 +615,18 @@ namespace args
std::get<1>(description) = help;
return description;
}
+
+ /** Defines how many values can be consumed by this option.
+ *
+ * \return closed interval [min, max]
+ */
+ virtual Nargs NumberOfArguments() const noexcept = 0;
+
+ /** Parse values of this option.
+ *
+ * \param value Vector of values. It's size must be in NumberOfArguments() interval.
+ */
+ virtual void ParseValue(const std::vector<std::string> &value) = 0;
};
/** Base class for value-accepting flag options
@@ -605,7 +637,6 @@ namespace args
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;
virtual std::tuple<std::string, std::string> GetDescription(const std::string &shortPrefix, const std::string &longPrefix, const std::string &shortSeparator, const std::string &longSeparator) const override
{
@@ -624,6 +655,11 @@ namespace args
std::get<1>(description) = help;
return description;
}
+
+ virtual Nargs NumberOfArguments() const noexcept override
+ {
+ return 1;
+ }
};
/** Base class for positional options
@@ -964,6 +1000,197 @@ namespace args
bool allowSeparateShortValue;
bool allowSeparateLongValue;
+ protected:
+ bool RaiseParseError(const std::string &message)
+ {
+#ifdef ARGS_NOEXCEPT
+ (void)message;
+ error = Error::Parse;
+ return false;
+#else
+ throw ParseError(message);
+#endif
+ }
+
+ enum class OptionType
+ {
+ LongFlag,
+ ShortFlag,
+ Positional
+ };
+
+ OptionType ParseOption(const std::string &s)
+ {
+ if (s.find(longprefix) == 0 && s.length() > longprefix.length())
+ {
+ return OptionType::LongFlag;
+ }
+
+ if (s.find(shortprefix) == 0 && s.length() > shortprefix.length())
+ {
+ return OptionType::ShortFlag;
+ }
+
+ return OptionType::Positional;
+ }
+
+ /** (INTERNAL) Parse flag's values
+ *
+ * \param arg The string to display in error message as a flag name
+ * \param[in, out] it The iterator to first value. It will point to the last value
+ * \param end The end iterator
+ * \param joinedArg Joined value (e.g. bar in --foo=bar)
+ * \param canDiscardJoined If true joined value can be parsed as flag not as a value (as in -abcd)
+ * \param[out] values The vector to store parsed arg's values
+ */
+ template <typename It>
+ bool ParseArgsValues(FlagBase &flag, const std::string &arg, It &it, It end,
+ const bool allowSeparate, const bool allowJoined,
+ const bool hasJoined, const std::string &joinedArg,
+ const bool canDiscardJoined, std::vector<std::string> &values)
+ {
+ values.clear();
+
+ Nargs nargs = flag.NumberOfArguments();
+
+ if (hasJoined && !allowJoined && nargs.min != 0)
+ {
+ return RaiseParseError("Flag '" + arg + "' was passed a joined argument, but these are disallowed");
+ }
+
+ if (hasJoined)
+ {
+ if (!canDiscardJoined || nargs.max != 0)
+ {
+ values.push_back(joinedArg);
+ }
+ } else if (!allowSeparate)
+ {
+ if (nargs.min != 0)
+ {
+ return RaiseParseError("Flag '" + arg + "' was passed a separate argument, but these are disallowed");
+ }
+ } else
+ {
+ auto valueIt = it;
+ ++valueIt;
+
+ while (valueIt != end &&
+ values.size() < nargs.max &&
+ (nargs.min == nargs.max || ParseOption(*valueIt) == OptionType::Positional))
+ {
+
+ values.push_back(*valueIt);
+ ++it;
+ ++valueIt;
+ }
+ }
+
+ if (values.size() > nargs.max)
+ {
+ return RaiseParseError("Passed an argument into a non-argument flag: " + arg);
+ } else if (values.size() < nargs.min)
+ {
+ if (nargs.min == 1 && nargs.max == 1)
+ {
+ return RaiseParseError("Flag '" + arg + "' requires an argument but received none");
+ } else if (nargs.min == 1)
+ {
+ return RaiseParseError("Flag '" + arg + "' requires at least one argument but received none");
+ } else if (nargs.min != nargs.max)
+ {
+ return RaiseParseError("Flag '" + arg + "' requires at least " + std::to_string(nargs.min) +
+ " arguments but received " + std::to_string(values.size()));
+ } else
+ {
+ return RaiseParseError("Flag '" + arg + "' requires " + std::to_string(nargs.min) +
+ " arguments but received " + std::to_string(values.size()));
+ }
+ }
+
+ return true;
+ }
+
+ template <typename It>
+ bool ParseLong(It &it, It end)
+ {
+ const auto &chunk = *it;
+ const auto argchunk = chunk.substr(longprefix.size());
+ // Try to separate it, in case of a separator:
+ const auto separator = longseparator.empty() ? argchunk.npos : argchunk.find(longseparator);
+ // If the separator is in the argument, separate it.
+ const auto arg = (separator != argchunk.npos ?
+ std::string(argchunk, 0, separator)
+ : argchunk);
+ const auto joined = (separator != argchunk.npos ?
+ argchunk.substr(separator + longseparator.size())
+ : std::string());
+
+ if (auto flag = Match(arg))
+ {
+ std::vector<std::string> values;
+ if (!ParseArgsValues(*flag, arg, it, end, allowSeparateLongValue, allowJoinedLongValue,
+ separator != argchunk.npos, joined, false, values))
+ {
+ return false;
+ }
+
+ flag->ParseValue(values);
+
+ if (flag->KickOut())
+ {
+ ++it;
+ return false;
+ }
+ } else
+ {
+ return RaiseParseError("Flag could not be matched: " + arg);
+ }
+
+ return true;
+ }
+
+ template <typename It>
+ bool ParseShort(It &it, It end)
+ {
+ const auto &chunk = *it;
+ const auto argchunk = chunk.substr(shortprefix.size());
+ for (auto argit = std::begin(argchunk); argit != std::end(argchunk); ++argit)
+ {
+ const auto arg = *argit;
+
+ if (auto flag = Match(arg))
+ {
+ const std::string value(argit + 1, std::end(argchunk));
+ std::vector<std::string> values;
+ if (!ParseArgsValues(*flag, std::string(1, arg), it, end,
+ allowSeparateShortValue, allowJoinedShortValue,
+ !value.empty(), value, !value.empty(), values))
+ {
+ return false;
+ }
+
+ flag->ParseValue(values);
+
+ if (flag->KickOut())
+ {
+ ++it;
+ return false;
+ }
+
+ if (!values.empty())
+ {
+ break;
+ }
+ } else
+ {
+ return RaiseParseError("Flag could not be matched: '" + std::string(1, arg) + "'");
+ }
+ }
+
+ return true;
+ }
+
public:
/** A simple structure of parameters for easy user-modifyable help menus
*/
@@ -1270,172 +1497,17 @@ namespace args
if (!terminated && chunk == terminator)
{
terminated = true;
- // If a long arg was found
- } else if (!terminated && chunk.find(longprefix) == 0 && chunk.size() > longprefix.size())
+ } else if (!terminated && ParseOption(chunk) == OptionType::LongFlag)
{
- const auto argchunk = chunk.substr(longprefix.size());
- // Try to separate it, in case of a separator:
- const auto separator = longseparator.empty() ? argchunk.npos : argchunk.find(longseparator);
- // If the separator is in the argument, separate it.
- const auto arg = (separator != argchunk.npos ?
- std::string(argchunk, 0, separator)
- : argchunk);
-
- if (auto base = Match(arg))
+ if (!ParseLong(it, end))
{
- if (auto argbase = dynamic_cast<ValueFlagBase *>(base))
- {
- if (separator != argchunk.npos)
- {
- if (allowJoinedLongValue)
- {
- argbase->ParseValue(argchunk.substr(separator + longseparator.size()));
- } else
- {
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
- return it;
-#else
- std::ostringstream problem;
- problem << "Flag '" << arg << "' was passed a joined argument, but these are disallowed";
- throw ParseError(problem.str());
-#endif
- }
- } else
- {
- ++it;
- if (it == end)
- {
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
- return it;
-#else
- std::ostringstream problem;
- problem << "Flag '" << arg << "' requires an argument but received none";
- throw ParseError(problem.str());
-#endif
- }
-
- if (allowSeparateLongValue)
- {
- argbase->ParseValue(*it);
- } else
- {
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
- return it;
-#else
- std::ostringstream problem;
- problem << "Flag '" << arg << "' was passed a separate argument, but these are disallowed";
- throw ParseError(problem.str());
-#endif
- }
- }
- } else if (separator != argchunk.npos)
- {
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
- return it;
-#else
- std::ostringstream problem;
- problem << "Passed an argument into a non-argument flag: " << chunk;
- throw ParseError(problem.str());
-#endif
- }
-
- if (base->KickOut())
- {
- return ++it;
- }
- } else
- {
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
return it;
-#else
- std::ostringstream problem;
- problem << "Flag could not be matched: " << arg;
- throw ParseError(problem.str());
-#endif
}
- // Check short args
- } else if (!terminated && chunk.find(shortprefix) == 0 && chunk.size() > shortprefix.size())
+ } else if (!terminated && ParseOption(chunk) == OptionType::ShortFlag)
{
- const auto argchunk = chunk.substr(shortprefix.size());
- for (auto argit = std::begin(argchunk); argit != std::end(argchunk); ++argit)
+ if (!ParseShort(it, end))
{
- const auto arg = *argit;
-
- if (auto base = Match(arg))
- {
- if (auto argbase = dynamic_cast<ValueFlagBase *>(base))
- {
- const std::string value(++argit, std::end(argchunk));
- if (!value.empty())
- {
- if (allowJoinedShortValue)
- {
- argbase->ParseValue(value);
- } else
- {
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
- return it;
-#else
- std::ostringstream problem;
- problem << "Flag '" << arg << "' was passed a joined argument, but these are disallowed";
- throw ParseError(problem.str());
-#endif
- }
- } else
- {
- ++it;
- if (it == end)
- {
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
- return it;
-#else
- std::ostringstream problem;
- problem << "Flag '" << arg << "' requires an argument but received none";
- throw ParseError(problem.str());
-#endif
- }
-
- if (allowSeparateShortValue)
- {
- argbase->ParseValue(*it);
- } else
- {
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
- return it;
-#else
- std::ostringstream problem;
- problem << "Flag '" << arg << "' was passed a separate argument, but these are disallowed";
- throw ParseError(problem.str());
-#endif
- }
- }
- // Because this argchunk is done regardless
- break;
- }
-
- if (base->KickOut())
- {
- return ++it;
- }
- } else
- {
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
- return it;
-#else
- std::ostringstream problem;
- problem << "Flag could not be matched: '" << arg << "'";
- throw ParseError(problem.str());
-#endif
- }
+ return it;
}
} else
{
@@ -1450,14 +1522,8 @@ namespace args
}
} else
{
-#ifdef ARGS_NOEXCEPT
- error = Error::Parse;
+ RaiseParseError("Passed in argument, but no positional arguments were ready to receive it: " + chunk);
return it;
-#else
- std::ostringstream problem;
- problem << "Passed in argument, but no positional arguments were ready to receive it: " << chunk;
- throw ParseError(problem.str());
-#endif
}
}
}
@@ -1477,6 +1543,7 @@ namespace args
throw ValidationError(problem.str());
#endif
}
+
return end;
}
@@ -1536,6 +1603,15 @@ namespace args
{
return Matched();
}
+
+ virtual Nargs NumberOfArguments() const noexcept override
+ {
+ return 0;
+ }
+
+ virtual void ParseValue(const std::vector<std::string>&) override
+ {
+ }
};
/** Help flag class
@@ -1684,8 +1760,10 @@ namespace args
typename Reader = ValueReader<T>>
class ValueFlag : public ValueFlagBase
{
- private:
+ protected:
T value;
+
+ private:
Reader reader;
public:
@@ -1705,8 +1783,10 @@ namespace args
virtual ~ValueFlag() {}
- virtual void ParseValue(const std::string &value_) override
+ virtual void ParseValue(const std::vector<std::string> &values_) override
{
+ const std::string &value_ = values_.at(0);
+
#ifdef ARGS_NOEXCEPT
if (!reader(name, value_, this->value))
{
@@ -1725,6 +1805,165 @@ namespace args
}
};
+ /** An optional argument-accepting flag class
+ *
+ * \tparam T the type to extract the argument as
+ * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
+ */
+ template <
+ typename T,
+ typename Reader = ValueReader<T>>
+ class ImplicitValueFlag : public ValueFlag<T, Reader>
+ {
+ protected:
+
+ T implicitValue;
+ T defaultValue;
+
+ public:
+
+ ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &implicitValue_, const T &defaultValue_ = T(), Options options_ = {})
+ : ValueFlag<T, Reader>(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(implicitValue_), defaultValue(defaultValue_)
+ {
+ }
+
+ ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), Options options_ = {})
+ : ValueFlag<T, Reader>(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(defaultValue_), defaultValue(defaultValue_)
+ {
+ }
+
+ ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_)
+ : ValueFlag<T, Reader>(group_, name_, help_, std::move(matcher_), {}, options_), implicitValue(), defaultValue()
+ {
+ }
+
+ virtual ~ImplicitValueFlag() {}
+
+ virtual Nargs NumberOfArguments() const noexcept override
+ {
+ return {0, 1};
+ }
+
+ virtual void ParseValue(const std::vector<std::string> &value_) override
+ {
+ if (value_.empty())
+ {
+ this->value = implicitValue;
+ } else
+ {
+ ValueFlag<T, Reader>::ParseValue(value_);
+ }
+ }
+
+ virtual void Reset() noexcept override
+ {
+ this->value = defaultValue;
+ ValueFlag<T, Reader>::Reset();
+ }
+ };
+
+ /** A variadic arguments accepting flag class
+ *
+ * \tparam T the type to extract the argument as
+ * \tparam List the list type that houses the values
+ * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
+ */
+ template <
+ typename T,
+ template <typename...> class List = std::vector,
+ typename Reader = ValueReader<T>>
+ class NargsValueFlag : public FlagBase
+ {
+ protected:
+
+ List<T> values;
+ Nargs nargs;
+ Reader reader;
+
+ public:
+
+ typedef List<T> Container;
+ typedef T value_type;
+ typedef typename Container::allocator_type allocator_type;
+ typedef typename Container::pointer pointer;
+ typedef typename Container::const_pointer const_pointer;
+ typedef T& reference;
+ typedef const T& const_reference;
+ typedef typename Container::size_type size_type;
+ typedef typename Container::difference_type difference_type;
+ typedef typename Container::iterator iterator;
+ typedef typename Container::const_iterator const_iterator;
+ typedef std::reverse_iterator<iterator> reverse_iterator;
+ typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+
+ NargsValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Nargs nargs_, const List<T> &defaultValues_ = {}, Options options_ = {})
+ : FlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_), nargs(nargs_)
+ {
+ group_.Add(*this);
+ }
+
+ virtual ~NargsValueFlag() {}
+
+ virtual Nargs NumberOfArguments() const noexcept override
+ {
+ return nargs;
+ }
+
+ virtual void ParseValue(const std::vector<std::string> &values_) override
+ {
+ values.clear();
+
+ for (const std::string &value : values_)
+ {
+ T v;
+#ifdef ARGS_NOEXCEPT
+ if (!reader(name, value, v))
+ {
+ error = Error::Parse;
+ }
+#else
+ reader(name, value, v);
+#endif
+ values.insert(std::end(values), v);
+ }
+ }
+
+ List<T> &Get() noexcept
+ {
+ return values;
+ }
+
+ iterator begin() noexcept
+ {
+ return values.begin();
+ }
+
+ const_iterator begin() const noexcept
+ {
+ return values.begin();
+ }
+
+ const_iterator cbegin() const noexcept
+ {
+ return values.cbegin();
+ }
+
+ iterator end() noexcept
+ {
+ return values.end();
+ }
+
+ const_iterator end() const noexcept
+ {
+ return values.end();
+ }
+
+ const_iterator cend() const noexcept
+ {
+ return values.cend();
+ }
+ };
+
/** An argument-accepting flag class that pushes the found values into a list
*
* \tparam T the type to extract the argument as
@@ -1741,7 +1980,7 @@ namespace args
using Container = List<T>;
Container values;
Reader reader;
-
+
public:
typedef T value_type;
@@ -1764,8 +2003,10 @@ namespace args
virtual ~ValueFlagList() {}
- virtual void ParseValue(const std::string &value_) override
+ virtual void ParseValue(const std::vector<std::string> &values_) override
{
+ const std::string &value_ = values_.at(0);
+
T v;
#ifdef ARGS_NOEXCEPT
if (!reader(name, value_, v))
@@ -1863,8 +2104,10 @@ namespace args
virtual ~MapFlag() {}
- virtual void ParseValue(const std::string &value_) override
+ virtual void ParseValue(const std::vector<std::string> &values_) override
{
+ const std::string &value_ = values_.at(0);
+
K key;
#ifdef ARGS_NOEXCEPT
if (!reader(name, value_, key))
@@ -1941,8 +2184,10 @@ namespace args
virtual ~MapFlagList() {}
- virtual void ParseValue(const std::string &value) override
+ virtual void ParseValue(const std::vector<std::string> &values_) override
{
+ const std::string &value = values_.at(0);
+
K key;
#ifdef ARGS_NOEXCEPT
if (!reader(name, value, key))
diff --git a/test.cxx b/test.cxx
index 67172b4..0d471a5 100644
--- a/test.cxx
+++ b/test.cxx
@@ -606,6 +606,71 @@ TEST_CASE("Hidden options are excluded from help", "[args]")
REQUIRE(std::get<0>(desc[2]) == "b[bar]");
}
+TEST_CASE("Implicit values work as expected", "[args]")
+{
+ args::ArgumentParser parser("Test command");
+ args::ImplicitValueFlag<int> j(parser, "parallel", "parallel", {'j', "parallel"}, 0, 1);
+ args::Flag foo(parser, "FOO", "test flag", {'f', "foo"});
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-j"}));
+ REQUIRE(args::get(j) == 0);
+
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-j4"}));
+ REQUIRE(args::get(j) == 4);
+
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-j", "4"}));
+ REQUIRE(args::get(j) == 4);
+
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-j", "-f"}));
+ REQUIRE(args::get(j) == 0);
+}
+
+TEST_CASE("Nargs work as expected", "[args]")
+{
+ args::ArgumentParser parser("Test command");
+ 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::Flag f(parser, "", "", {'f'});
+
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-a", "1", "2"}));
+ REQUIRE((args::get(a) == std::vector<int>{1, 2}));
+
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-a", "1", "2", "-f"}));
+ REQUIRE((args::get(a) == std::vector<int>{1, 2}));
+ REQUIRE(args::get(f) == true);
+
+ REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"-a", "1"}), args::ParseError);
+ REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"-a1"}), args::ParseError);
+ REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"-a1", "2"}), args::ParseError);
+
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-b", "1", "2", "-f"}));
+ REQUIRE((args::get(b) == std::vector<int>{1, 2}));
+ REQUIRE(args::get(f) == true);
+
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-b", "1", "2", "3"}));
+ REQUIRE((args::get(b) == std::vector<int>{1, 2, 3}));
+ REQUIRE(args::get(f) == false);
+
+ std::vector<int> vec;
+ for (int c : b)
+ {
+ vec.push_back(c);
+ }
+
+ REQUIRE((vec == std::vector<int>{1, 2, 3}));
+
+ parser.SetArgumentSeparations(true, true, false, false);
+ REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"-a", "1", "2"}), args::ParseError);
+
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-c", "-f"}));
+ REQUIRE(args::get(c).empty());
+ REQUIRE(args::get(f) == true);
+
+ REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{"-cf"}));
+ REQUIRE((args::get(c) == std::vector<std::string>{"f"}));
+ REQUIRE(args::get(f) == false);
+}
+
#undef ARGS_HXX
#define ARGS_TESTNAMESPACE
#define ARGS_NOEXCEPT