Skip to content

Commit

Permalink
Add support for collecing remaining arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
Zitrax committed Oct 13, 2023
1 parent bb82f85 commit 3c0a96b
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 7 deletions.
27 changes: 25 additions & 2 deletions src/arg_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ ArgParser::ArgIterator ArgParser::find(unsigned position) {
m_options, [&](const auto& a) { return a->position() == position; });
}

ArgParser::ArgIterator ArgParser::find_collecting() {
return std::ranges::find_if(
m_options, [&](const auto& a) { return a->is_collecting(); });
}

bool ArgParser::has_option(const std::string& option) const {
return find(option) != m_options.end();
}
Expand All @@ -54,13 +59,27 @@ void ArgParser::verify_no_duplicate_positionals() const {
}
}

void ArgParser::verify_no_duplicate_collecting() const {
int count{0};
for (const auto& option : m_options) {
if (option->is_collecting()) {
count++;
}
if (count >= 2) {
throw std::runtime_error(
"There cannot be multiple options marked as collecting");
}
}
}

// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
void ArgParser::parse(const std::vector<std::string>& argv) {
if (m_parsed) {
throw runtime_error("Options already parsed");
}

verify_no_duplicate_positionals();
verify_no_duplicate_collecting();

m_parsed = true;
unsigned positional{0};
Expand All @@ -72,7 +91,11 @@ void ArgParser::parse(const std::vector<std::string>& argv) {
// No named argument - check if we have positional
m = find(positional);
if (m == m_options.end()) {
throw runtime_error(fmt::format("Unknown argument: {}", name));
// No positional either - check if we have a collecting remaining arg
m = find_collecting();
if (m == m_options.end()) {
throw runtime_error(fmt::format("Unknown argument: {}", name));
}
}
positional++;
}
Expand All @@ -83,7 +106,7 @@ void ArgParser::parse(const std::vector<std::string>& argv) {
} else {
std::string val;
// Read next arg
if (arg->position()) {
if (arg->position() || arg->is_collecting()) {
val = name;
} else {
if (i == argv.size() - 1) {
Expand Down
19 changes: 14 additions & 5 deletions src/arg_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class ArgParser {
[[nodiscard]] auto is_required() const { return m_required; }
[[nodiscard]] auto is_help_arg() const { return m_help_arg; }
[[nodiscard]] auto is_multi() const { return m_is_multi; }
[[nodiscard]] auto is_collecting() const { return m_is_collecting; }
[[nodiscard]] auto position() const { return m_position; }

void set_help_arg(bool help_arg) { m_help_arg = help_arg; }
Expand All @@ -51,6 +52,7 @@ class ArgParser {
bool m_required = false;
bool m_help_arg = false;
bool m_is_multi = false;
bool m_is_collecting = false;
std::optional<unsigned> m_position{};
};

Expand Down Expand Up @@ -154,6 +156,13 @@ class ArgParser {
return *this;
}

/** Collects all non-named non-positional arguments */
auto& collecting() {
m_is_multi = true;
m_is_collecting = true;
return *this;
}

private:
std::vector<T> m_dst{};
std::vector<T> m_default{};
Expand All @@ -167,9 +176,12 @@ class ArgParser {
[[nodiscard]] ConstArgIterator find(const std::string& option) const;
[[nodiscard]] ArgIterator find(const std::string& option);
[[nodiscard]] ArgIterator find(unsigned position);
[[nodiscard]] ArgIterator find_collecting();

[[nodiscard]] bool has_option(const std::string& option) const;

void verify_no_duplicate_positionals() const;
void verify_no_duplicate_collecting() const;

std::string m_desc;
std::vector<std::unique_ptr<BaseArg>> m_options{};
Expand Down Expand Up @@ -223,12 +235,9 @@ class ArgParser {
auto [dst, multi] = get_internal<T>(option);
if (!multi) {
throw std::runtime_error(
"get?multi() called on single value option, use get");
}
if (!dst.empty()) {
return dst;
"get_multi() called on single value option, use get");
}
throw std::runtime_error("No value provided for option: " + option);
return dst;
}

[[nodiscard]] bool is_provided(const std::string& option) const {
Expand Down
65 changes: 65 additions & 0 deletions tests/test_arg_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,71 @@ TEST(arg_parser, positional) {
}
}

TEST(arg_parser, collecting) {
{ // Single collecting
ArgParser parser("desc");
auto& option = parser.add_option<int>("--rem").collecting();
EXPECT_EQ(option.get_type(), ArgParser::Type::INT);
EXPECT_TRUE(option.is_multi());
EXPECT_TRUE(option.is_collecting());
EXPECT_FALSE(parser.is_provided("--rem"));
parser.parse({"cmd", "5"});
EXPECT_TRUE(parser.is_provided("--rem"));
EXPECT_THROW(std::ignore = parser.get<int>("--rem"), std::runtime_error);
EXPECT_THAT(parser.get_multi<int>("--rem"), ElementsAreArray({5}));
}

{ // Dual collecting
ArgParser parser("desc");
auto& option = parser.add_option<int>("--rem").collecting();
EXPECT_EQ(option.get_type(), ArgParser::Type::INT);
EXPECT_TRUE(option.is_multi());
EXPECT_TRUE(option.is_collecting());
EXPECT_FALSE(parser.is_provided("--rem"));
parser.parse({"cmd", "5", "6"});
EXPECT_TRUE(parser.is_provided("--rem"));
EXPECT_THROW(std::ignore = parser.get<int>("--rem"), std::runtime_error);
EXPECT_THAT(parser.get_multi<int>("--rem"), ElementsAreArray({5, 6}));
}

{ // Mixed
ArgParser parser("desc");
parser.add_option<float>("--rem").collecting();
parser.add_option<float>("--named");
parser.add_option<float>("--multi").multi();
parser.parse({"cmd", "5.0", "--named", "-1.1", "--multi", "2.0", "--multi",
"2.1", "5.1"});
EXPECT_THAT(parser.get_multi<float>("--rem"), ElementsAreArray({5.0, 5.1}));
}

{ // Required collecting
ArgParser parser("desc");
parser.add_option<float>("--rem").collecting().required();
EXPECT_THROW(parser.parse({"cmd"}), std::runtime_error);
}

{ // Zero collecting
ArgParser parser("desc");
parser.add_option<float>("--rem").collecting();
parser.parse({"cmd"});
EXPECT_TRUE(parser.get_multi<float>("--rem").empty());
}

{ // Two different collecting
ArgParser parser("desc");
parser.add_option<float>("--rem").collecting();
parser.add_option<float>("--rem2").collecting();
EXPECT_THROW(parser.parse({"cmd"}), std::runtime_error);
}

{ // Collecting with default
ArgParser parser("desc");
parser.add_option<unsigned>("--rem").collecting().default_value(3U);
parser.parse({"cmd"});
EXPECT_THAT(parser.get_multi<unsigned>("--rem"), ElementsAreArray({3U}));
}
}

TEST(arg_parser, int_multi) {
{
ArgParser parser("desc");
Expand Down

0 comments on commit 3c0a96b

Please sign in to comment.