mirror of
https://github.com/KeqingMoe/argparse.git
synced 2025-07-04 15:14:39 +00:00
Merge pull request #57 from zhihaoy/remaining
Support capturing remaining() arguments
This commit is contained in:
commit
0cabe9569a
@ -36,6 +36,7 @@ SOFTWARE.
|
|||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -159,11 +160,18 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Argument &nargs(size_t aNumArgs) {
|
Argument &nargs(int aNumArgs) {
|
||||||
|
if (aNumArgs < 0)
|
||||||
|
throw std::logic_error("Number of arguments must be non-negative");
|
||||||
mNumArgs = aNumArgs;
|
mNumArgs = aNumArgs;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Argument &remaining() {
|
||||||
|
mNumArgs = -1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
Iterator consume(Iterator start, Iterator end, std::string usedName = {}) {
|
Iterator consume(Iterator start, Iterator end, std::string usedName = {}) {
|
||||||
if (mIsUsed) {
|
if (mIsUsed) {
|
||||||
@ -174,11 +182,14 @@ public:
|
|||||||
if (mNumArgs == 0) {
|
if (mNumArgs == 0) {
|
||||||
mValues.emplace_back(mImplicitValue);
|
mValues.emplace_back(mImplicitValue);
|
||||||
return start;
|
return start;
|
||||||
} else if (mNumArgs <= static_cast<size_t>(std::distance(start, end))) {
|
} else if (mNumArgs <= std::distance(start, end)) {
|
||||||
end = std::next(start, mNumArgs);
|
if (auto expected = maybe_nargs()) {
|
||||||
if (std::any_of(start, end, Argument::is_optional)) {
|
end = std::next(start, *expected);
|
||||||
throw std::runtime_error("optional argument in parameter sequence");
|
if (std::any_of(start, end, Argument::is_optional)) {
|
||||||
|
throw std::runtime_error("optional argument in parameter sequence");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct action_apply {
|
struct action_apply {
|
||||||
void operator()(valued_action &f) {
|
void operator()(valued_action &f) {
|
||||||
std::transform(start, end, std::back_inserter(self.mValues), f);
|
std::transform(start, end, std::back_inserter(self.mValues), f);
|
||||||
@ -186,8 +197,10 @@ public:
|
|||||||
|
|
||||||
void operator()(void_action &f) {
|
void operator()(void_action &f) {
|
||||||
std::for_each(start, end, f);
|
std::for_each(start, end, f);
|
||||||
if (!self.mDefaultValue.has_value())
|
if (!self.mDefaultValue.has_value()) {
|
||||||
self.mValues.resize(self.mNumArgs);
|
if (auto expected = self.maybe_nargs())
|
||||||
|
self.mValues.resize(*expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator start, end;
|
Iterator start, end;
|
||||||
@ -206,35 +219,45 @@ public:
|
|||||||
* @throws std::runtime_error if argument values are not valid
|
* @throws std::runtime_error if argument values are not valid
|
||||||
*/
|
*/
|
||||||
void validate() const {
|
void validate() const {
|
||||||
if (mIsOptional) {
|
if (auto expected = maybe_nargs()) {
|
||||||
if (mIsUsed && mValues.size() != mNumArgs && !mDefaultValue.has_value()) {
|
if (mIsOptional) {
|
||||||
std::stringstream stream;
|
if (mIsUsed && mValues.size() != *expected &&
|
||||||
stream << mUsedName << ": expected " << mNumArgs << " argument(s). "
|
!mDefaultValue.has_value()) {
|
||||||
<< mValues.size() << " provided.";
|
std::stringstream stream;
|
||||||
throw std::runtime_error(stream.str());
|
stream << mUsedName << ": expected " << *expected << " argument(s). "
|
||||||
|
<< mValues.size() << " provided.";
|
||||||
|
throw std::runtime_error(stream.str());
|
||||||
|
} 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: check if an implicit value was programmed for this argument
|
if (mValues.size() != expected && !mDefaultValue.has_value()) {
|
||||||
if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) {
|
|
||||||
std::stringstream stream;
|
std::stringstream stream;
|
||||||
stream << mNames[0] << ": required.";
|
stream << mUsedName << ": expected " << *expected << " argument(s). "
|
||||||
|
<< mValues.size() << " provided.";
|
||||||
throw std::runtime_error(stream.str());
|
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (mValues.size() != mNumArgs && !mDefaultValue.has_value()) {
|
|
||||||
std::stringstream stream;
|
|
||||||
stream << mUsedName << ": expected " << mNumArgs << " argument(s). "
|
|
||||||
<< mValues.size() << " provided.";
|
|
||||||
throw std::runtime_error(stream.str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto maybe_nargs() const -> std::optional<size_t> {
|
||||||
|
if (mNumArgs < 0)
|
||||||
|
return std::nullopt;
|
||||||
|
else
|
||||||
|
return static_cast<size_t>(mNumArgs);
|
||||||
|
}
|
||||||
|
|
||||||
size_t get_arguments_length() const {
|
size_t get_arguments_length() const {
|
||||||
return std::accumulate(std::begin(mNames), std::end(mNames), size_t(0),
|
return std::accumulate(std::begin(mNames), std::end(mNames), size_t(0),
|
||||||
[](const auto &sum, const auto &s) {
|
[](const auto &sum, const auto &s) {
|
||||||
@ -345,7 +368,7 @@ private:
|
|||||||
std::in_place_type<valued_action>,
|
std::in_place_type<valued_action>,
|
||||||
[](const std::string &aValue) { return aValue; }};
|
[](const std::string &aValue) { return aValue; }};
|
||||||
std::vector<std::any> mValues;
|
std::vector<std::any> mValues;
|
||||||
size_t mNumArgs = 1;
|
int mNumArgs = 1;
|
||||||
bool mIsOptional : 1;
|
bool mIsOptional : 1;
|
||||||
bool mIsRequired : 1;
|
bool mIsRequired : 1;
|
||||||
bool mIsUsed : 1; // True if the optional argument is used by user
|
bool mIsUsed : 1; // True if the optional argument is used by user
|
||||||
|
@ -119,3 +119,15 @@ TEST_CASE("Users can bind arguments to actions", "[actions]") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Users can use actions on remaining arguments", "[actions]") {
|
||||||
|
argparse::ArgumentParser program("sum");
|
||||||
|
|
||||||
|
int result = 0;
|
||||||
|
program.add_argument("all").remaining().action(
|
||||||
|
[](int &sum, std::string const &value) { sum += std::stoi(value); },
|
||||||
|
std::ref(result));
|
||||||
|
|
||||||
|
program.parse_args({"sum", "42", "100", "-3", "-20"});
|
||||||
|
REQUIRE(result == 119);
|
||||||
|
}
|
||||||
|
@ -45,6 +45,35 @@ TEST_CASE("Parse multiple toggle arguments with implicit values", "[optional_arg
|
|||||||
REQUIRE(program.get<bool>("-x") == true);
|
REQUIRE(program.get<bool>("-x") == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse optional arguments of many values", "[optional_arguments]") {
|
||||||
|
GIVEN("a program that accepts an optional argument of many values") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("-i").remaining().action(
|
||||||
|
[](const std::string &value) { return std::stoi(value); });
|
||||||
|
|
||||||
|
WHEN("provided no argument") {
|
||||||
|
THEN("the program accepts it but gets nothing") {
|
||||||
|
REQUIRE_NOTHROW(program.parse_args({"test"}));
|
||||||
|
REQUIRE_THROWS_AS(program.get<std::vector<int>>("-i"),
|
||||||
|
std::logic_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("provided remaining arguments follow the option") {
|
||||||
|
program.parse_args({"test", "-i", "-42", "8", "100", "300"});
|
||||||
|
|
||||||
|
THEN("the optional parameter consumes all of them") {
|
||||||
|
auto inputs = program.get<std::vector<int>>("-i");
|
||||||
|
REQUIRE(inputs.size() == 4);
|
||||||
|
REQUIRE(inputs[0] == -42);
|
||||||
|
REQUIRE(inputs[1] == 8);
|
||||||
|
REQUIRE(inputs[2] == 100);
|
||||||
|
REQUIRE(inputs[3] == 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Parse arguments of different types", "[optional_arguments]") {
|
TEST_CASE("Parse arguments of different types", "[optional_arguments]") {
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
@ -47,6 +47,59 @@ TEST_CASE("Parse positional arguments with optional arguments in the middle", "[
|
|||||||
REQUIRE_THROWS(program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv", "--num_iterations", "15", "output.mesh" }));
|
REQUIRE_THROWS(program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv", "--num_iterations", "15", "output.mesh" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse remaining arguments deemed positional",
|
||||||
|
"[positional_arguments]") {
|
||||||
|
GIVEN("a program that accepts an optional argument and remaining arguments") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("-o");
|
||||||
|
program.add_argument("input").remaining();
|
||||||
|
|
||||||
|
WHEN("provided no argument") {
|
||||||
|
THEN("the program accepts it but gets nothing") {
|
||||||
|
REQUIRE_NOTHROW(program.parse_args({"test"}));
|
||||||
|
REQUIRE_THROWS_AS(program.get<std::vector<std::string>>("input"),
|
||||||
|
std::logic_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("provided an optional followed by remaining arguments") {
|
||||||
|
program.parse_args({"test", "-o", "a.out", "a.c", "b.c", "main.c"});
|
||||||
|
|
||||||
|
THEN("the optional parameter consumes an argument") {
|
||||||
|
using namespace std::literals;
|
||||||
|
REQUIRE(program["-o"] == "a.out"s);
|
||||||
|
|
||||||
|
auto inputs = program.get<std::vector<std::string>>("input");
|
||||||
|
REQUIRE(inputs.size() == 3);
|
||||||
|
REQUIRE(inputs[0] == "a.c");
|
||||||
|
REQUIRE(inputs[1] == "b.c");
|
||||||
|
REQUIRE(inputs[2] == "main.c");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("provided remaining arguments including optional arguments") {
|
||||||
|
program.parse_args({"test", "a.c", "b.c", "main.c", "-o", "a.out"});
|
||||||
|
|
||||||
|
THEN("the optional argument is deemed remaining") {
|
||||||
|
REQUIRE_THROWS_AS(program.get("-o"), std::logic_error);
|
||||||
|
|
||||||
|
auto inputs = program.get<std::vector<std::string>>("input");
|
||||||
|
REQUIRE(inputs.size() == 5);
|
||||||
|
REQUIRE(inputs[0] == "a.c");
|
||||||
|
REQUIRE(inputs[1] == "b.c");
|
||||||
|
REQUIRE(inputs[2] == "main.c");
|
||||||
|
REQUIRE(inputs[3] == "-o");
|
||||||
|
REQUIRE(inputs[4] == "a.out");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Negative nargs is not allowed", "[positional_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
REQUIRE_THROWS_AS(program.add_argument("output").nargs(-1), std::logic_error);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Square a number", "[positional_arguments]") {
|
TEST_CASE("Square a number", "[positional_arguments]") {
|
||||||
argparse::ArgumentParser program;
|
argparse::ArgumentParser program;
|
||||||
program.add_argument("--verbose", "-v")
|
program.add_argument("--verbose", "-v")
|
||||||
|
Loading…
Reference in New Issue
Block a user