Skip to content

Commit

Permalink
1st version of handling variable length nargs
Browse files Browse the repository at this point in the history
To handle variable length nargs, I replaced mNumArgs with mNumArgsRange.
I defined SizeRange class for mNumArgsRange, which has simply min and
max std::size_t member.

To concentrate on this big change, I tentatively deleted remaining
feature, which was originally implemented in the way that mNumArgs = -1
internally and maybe_args() -> Optional wrap method.

Library users may make use of 4 types of interface to set
mNumArgsRange.

1. nargs(std::size_t)
2. nargs(std::size_t, std::size_t)
3. nargs(SizeRange)
4. nargs(NArgsPattern)

1. is expected to behave same as original. This mthod sets min=max
SizeRange to mNumArgsRange, which is actually, not a range, but an
"exact" number.

2. sets min and max.

3. uses SizeRange class. This interface may be unnecessary. It is also
an option to delete this method and make SizeRange class internal.

4. is provided to set common patterns. In Python, they are "?", "*" and
"+". NArgsPattern is an enum class for type safety. std::string
interface is also an option to mimic Python argparse. char interface
would be ambiguous with 1.

Changes on consume method is important.
The parser tries to consume args until the count reaches mNumArgsRanges::max or
it meets another optional like string.
If consumed args count is under mNumArgsRanges::min, the parser fails.

Now, when the required number of arguments are not provided, the parser
will fail.
So, we have to take care of get() method as well.
get() failed when argument count is 0 and default value not provided.
But now there are 0..1 or 0..* nargs are OK.
So this behaviour has to be fixed.
When T is container_v, it returns empty container.

I implemented validate method so that it shows kind message.
  • Loading branch information
hokacci committed Sep 15, 2021
1 parent ab0a28c commit c6c3be0
Showing 1 changed file with 125 additions and 47 deletions.
172 changes: 125 additions & 47 deletions include/argparse/argparse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ SOFTWARE.
#include <functional>
#include <iostream>
#include <iterator>
#include <limits>
#include <list>
#include <map>
#include <numeric>
Expand Down Expand Up @@ -312,6 +313,45 @@ template <class T> struct parse_number<T, chars_format::fixed> {

} // 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<std::size_t>::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 {
Expand Down Expand Up @@ -353,7 +393,7 @@ class Argument {

Argument &implicit_value(std::any aImplicitValue) {
mImplicitValue = std::move(aImplicitValue);
mNumArgs = 0;
mNumArgsRange = SizeRange{0, 0};
return *this;
}

Expand Down Expand Up @@ -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<std::size_t>::max()};
break;
case NArgsPattern::AT_LEAST_ONE:
mNumArgsRange = SizeRange{1, std::numeric_limits<std::size_t>::max()};
break;
}
return *this;
}

Expand All @@ -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::size_t>(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::size_t>(std::distance(start, it));
if (dist < numArgsMin) {
throw std::runtime_error("Too few arguments");
}
end = it;

struct action_apply {
void operator()(valued_action &f) {
Expand All @@ -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));
}
}

Expand All @@ -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<std::size_t> {
if (mNumArgs < 0)
return std::nullopt;
else
return static_cast<std::size_t>(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) {
Expand Down Expand Up @@ -759,6 +834,9 @@ class Argument {
}
if (mDefaultValue.has_value()) {
return std::any_cast<T>(mDefaultValue);
} else {
if constexpr (details::is_container_v<T>)
return any_cast_container<T>(mValues);
}
throw std::logic_error("No value provided for '" + mNames.back() + "'.");
}
Expand Down Expand Up @@ -803,7 +881,7 @@ class Argument {
std::in_place_type<valued_action>,
[](const std::string &aValue) { return aValue; }};
std::vector<std::any> mValues;
int mNumArgs = 1;
SizeRange mNumArgsRange {1, 1};
bool mIsOptional : true;
bool mIsRequired : true;
bool mIsRepeatable : true;
Expand Down

0 comments on commit c6c3be0

Please sign in to comment.