-
Notifications
You must be signed in to change notification settings - Fork 256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve nargs #125
Improve nargs #125
Conversation
I looked into this more precisely and revived remaining() partially. The only difference is below. |
The But, there's many changes here and I may have more questions. |
I included "limits" and added some test cases. Thank you. |
include/argparse/argparse.hpp
Outdated
enum class NArgsPattern { | ||
ZERO_OR_ONE, | ||
ANY, | ||
AT_LEAST_ONE | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's follow the C++ core guidelines and avoid use ALL_CAPS for enumerators.
Please rebase on current master. While you're rebasing, I'd like to see a reduced number of commits and expanded messages telling why the commit was added. Trying to do per commit reviews was counter-productive due to later commits undoing earlier changes. Should SizeRange be renamed (e.g. NArgsRange) to indicate its specific purpose? I'm undecided on this and would like to hear from others. Can the two std::distance checks be shortened? I ask because this looks like duplication to be refactored, but I don't see a simple answer. Maybe it is best as the checks currently work. I see an opportunity to refactor your updated void validate() const {
std::stringstream stream;
if (mIsOptional) {
if ((mIsUsed && !mNumArgsRange.contains(mValues.size()) &&
!mIsRepeatable && !mDefaultValue.has_value()) ||
(!mNumArgsRange.contains(mValues.size()) && !mDefaultValue.has_value())) {
if (!mUsedName.empty())
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 {
stream << mNumArgsRange.get_min() << " or more";
}
stream << " argument(s). " << mValues.size() << " provided.";
} else {
// TODO: check if an implicit value was programmed for this argument
if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) {
stream << mNames[0] << ": required.";
}
if (mIsUsed && mIsRequired && mValues.size() == 0) {
stream << mUsedName << ": no value provided.";
}
}
}
if (stream.str().size() > 0)
throw std::runtime_error(stream.str());
} The project's preferred format for constructors appears to be (see SizeRange(std::size_t aMin, std::size_t aMax)
: mMin(aMin), mMax(aMax) {
if (aMin > aMax)
throw std::logic_error("Range of number of arguments is invalid");
} The added I've learned by reading your code. I'll see something that strikes me as odd. But research shows me why you made that decision and I generally agree with your choice. |
d5685bb
to
13f0939
Compare
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.
I reimplemented remaining() method for backward compatibility. It consumes "all" remaining args. So, I added mAcceptsOptionalLikeValue flag and handle it by using this flag. Currently, remaining() behavior is slightly different from the original when no args are provided and get<Container<T>>() method is called. Originally it raises an exception. But current implementation returns an empty container instead of exception. It is possible to implement complete backward compatibility by referencing mAcceptsOptionalLikeValue flag and raises an exception in get() method, but I did not do this. I think that is too much.
2cc2ccf
to
14abaa4
Compare
Thank you for your feedback. I rebased this on master. I added some explanation below. hokacci@c6c3be0 And I dealt with ALL_CAPS issue and member initializer list preference. hokacci@bec93ac Later, I will look at other issues you pointed out. Thank you. |
Thank you for rebasing and consolidating. And your extended commit comments helped me confirm what you intended to do matched the actual changes. Why do you think it's "too much" to raise an exception in .get()? I'm not disagreeing, I just don't understand why you prefer the new behavior. In
Is there a reason for No rush, but I'd like to see Thank you for the many test cases, they helped me better understand your changes. |
Hello. |
@EchoPouet
It seemed natural for me. But now I think compatibility counts. So I fixed it.
No. Seemingly this project uses pre-const style. Now I also took this style.
I wrote some helper functions and make validate() clearer. Also, I merged current master branch and fixed many conflicts. Thank you. |
Thank you @hokacci. I'm looking forward to the merge and the release. |
include/argparse/argparse.hpp
Outdated
}; | ||
|
||
enum class NArgsPattern { | ||
ZeroOrOne, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency, let's use lower_case underscore-separated field names for enum class
es.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in b869b5a
I'd also love for the README to be updated as part of the PR, to describe the new version of |
For commit 37a1f3b, I retrieved it in 5d6544a For commit 748bc95, it seems the change is already retrieved. Thank you. |
I also did some small fixes in the recent three commits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. Thank you, @hokacci.
Hi.
Thank you for this nice library.
I see some issues regarding "remaining" behavior.
#118
#81
I also wanted optional argument preceded by variable length positional arguments (including zero length) like Python argparse nargs "*", "+", "?"
So, I implemented it just for experiment.
For now, this worked for me.
I know this change drops "remaining" feature, which would impact existing projects.
Also, this seems a complicated problem and I think more study may be needed.
What do you think about this change?
Thank you in advance.