diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index cb427cd7..d855ee3a 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -37,6 +37,7 @@ SOFTWARE. #include #include #include +#include #include #include #include @@ -312,6 +313,45 @@ template struct parse_number { } // namespace details +class SizeRange { + std::size_t mMin; + std::size_t mMax; + +public: + SizeRange(std::size_t aMin, std::size_t aMax) { + if (aMin > aMax) + throw std::logic_error("Range of number of arguments is invalid"); + mMin = aMin; + mMax = aMax; + } + + bool contains(std::size_t value) const { + return value >= mMin && value <= mMax; + } + + bool is_exact() const { + return mMin == mMax; + } + + bool is_right_bounded() const { + return mMax < std::numeric_limits::max(); + } + + std::size_t get_min() const { + return mMin; + } + + std::size_t get_max() const { + return mMax; + } +}; + +enum class NArgsPattern { + ZERO_OR_ONE, + ANY, + AT_LEAST_ONE +}; + class ArgumentParser; class Argument { @@ -353,7 +393,7 @@ class Argument { Argument &implicit_value(std::any aImplicitValue) { mImplicitValue = std::move(aImplicitValue); - mNumArgs = 0; + mNumArgsRange = SizeRange{0, 0}; return *this; } @@ -420,15 +460,33 @@ class Argument { return *this; } - Argument &nargs(int aNumArgs) { - if (aNumArgs < 0) - throw std::logic_error("Number of arguments must be non-negative"); - mNumArgs = aNumArgs; + Argument &nargs(std::size_t aNumArgs) { + mNumArgsRange = SizeRange{aNumArgs, aNumArgs}; + return *this; + } + + Argument &nargs(std::size_t aNumArgsMin, std::size_t aNumArgsMax) { + mNumArgsRange = SizeRange{aNumArgsMin, aNumArgsMax}; + return *this; + } + + Argument &nargs(SizeRange aNumArgsRange) { + mNumArgsRange = aNumArgsRange; return *this; } - Argument &remaining() { - mNumArgs = -1; + Argument &nargs(NArgsPattern aNargs) { + switch (aNargs) { + case NArgsPattern::ZERO_OR_ONE: + mNumArgsRange = SizeRange{0, 1}; + break; + case NArgsPattern::ANY: + mNumArgsRange = SizeRange{0, std::numeric_limits::max()}; + break; + case NArgsPattern::AT_LEAST_ONE: + mNumArgsRange = SizeRange{1, std::numeric_limits::max()}; + break; + } return *this; } @@ -440,16 +498,28 @@ class Argument { } mIsUsed = true; mUsedName = usedName; - if (mNumArgs == 0) { + + const auto numArgsMax = mNumArgsRange.get_max(); + const auto numArgsMin = mNumArgsRange.get_min(); + if (numArgsMax == 0) { mValues.emplace_back(mImplicitValue); return start; - } else if (mNumArgs <= std::distance(start, end)) { - if (auto expected = maybe_nargs()) { - end = std::next(start, *expected); - if (std::any_of(start, end, Argument::is_optional)) { - throw std::runtime_error("optional argument in parameter sequence"); + } else if (static_cast(std::distance(start, end)) >= numArgsMin) { + + auto it = start; + for (std::size_t i = 0; it != end; ++it, ++i) { + if (Argument::is_optional(*it)) { + break; } + if (i >= numArgsMax) { + break; + } + } + auto dist = static_cast(std::distance(start, it)); + if (dist < numArgsMin) { + throw std::runtime_error("Too few arguments"); } + end = it; struct action_apply { void operator()(valued_action &f) { @@ -459,8 +529,7 @@ class Argument { void operator()(void_action &f) { std::for_each(start, end, f); if (!self.mDefaultValue.has_value()) { - if (auto expected = self.maybe_nargs()) - self.mValues.resize(*expected); + self.mValues.resize(std::distance(start, end)); } } @@ -481,47 +550,53 @@ class Argument { * @throws std::runtime_error if argument values are not valid */ void validate() const { - if (auto expected = maybe_nargs()) { - if (mIsOptional) { - if (mIsUsed && mValues.size() != *expected && !mIsRepeatable && - !mDefaultValue.has_value()) { - std::stringstream stream; - stream << mUsedName << ": expected " << *expected << " argument(s). " - << mValues.size() << " provided."; - throw std::runtime_error(stream.str()); + if (mIsOptional) { + if (mIsUsed && !mNumArgsRange.contains(mValues.size()) && !mIsRepeatable && + !mDefaultValue.has_value()) { + std::stringstream stream; + stream << mUsedName << ": expected "; + if (mNumArgsRange.is_exact()) { + stream << mNumArgsRange.get_min(); + } else if (mNumArgsRange.is_right_bounded()) { + stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max(); } else { - // TODO: check if an implicit value was programmed for this argument - if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { - std::stringstream stream; - stream << mNames[0] << ": required."; - throw std::runtime_error(stream.str()); - } - if (mIsUsed && mIsRequired && mValues.size() == 0) { - std::stringstream stream; - stream << mUsedName << ": no value provided."; - throw std::runtime_error(stream.str()); - } + stream << mNumArgsRange.get_min() << " or more"; } + stream << " argument(s). " + << mValues.size() << " provided."; + throw std::runtime_error(stream.str()); } else { - if (mValues.size() != expected && !mDefaultValue.has_value()) { + // TODO: check if an implicit value was programmed for this argument + if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { + std::stringstream stream; + stream << mNames[0] << ": required."; + throw std::runtime_error(stream.str()); + } + if (mIsUsed && mIsRequired && mValues.size() == 0) { std::stringstream stream; - if (!mUsedName.empty()) - stream << mUsedName << ": "; - stream << *expected << " argument(s) expected. " << mValues.size() - << " provided."; + stream << mUsedName << ": no value provided."; throw std::runtime_error(stream.str()); } } + } else { + if (!mNumArgsRange.contains(mValues.size()) && !mDefaultValue.has_value()) { + std::stringstream stream; + if (!mUsedName.empty()) + stream << mUsedName << ": "; + if (mNumArgsRange.is_exact()) { + stream << mNumArgsRange.get_min(); + } else if (mNumArgsRange.is_right_bounded()) { + stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max(); + } else { + stream << mNumArgsRange.get_min() << " or more"; + } + stream << " argument(s) expected. " << mValues.size() + << " provided."; + throw std::runtime_error(stream.str()); + } } } - auto maybe_nargs() const -> std::optional { - if (mNumArgs < 0) - return std::nullopt; - else - return static_cast(mNumArgs); - } - std::size_t get_arguments_length() const { return std::accumulate(std::begin(mNames), std::end(mNames), std::size_t(0), [](const auto &sum, const auto &s) { @@ -759,6 +834,9 @@ class Argument { } if (mDefaultValue.has_value()) { return std::any_cast(mDefaultValue); + } else { + if constexpr (details::is_container_v) + return any_cast_container(mValues); } throw std::logic_error("No value provided for '" + mNames.back() + "'."); } @@ -803,7 +881,7 @@ class Argument { std::in_place_type, [](const std::string &aValue) { return aValue; }}; std::vector mValues; - int mNumArgs = 1; + SizeRange mNumArgsRange {1, 1}; bool mIsOptional : true; bool mIsRequired : true; bool mIsRepeatable : true;