mirror of
https://github.com/KeqingMoe/argparse.git
synced 2025-07-04 07:04:39 +00:00
Merge pull request #201 from p-ranav/feature/parse_known_args
parse_known_args
This commit is contained in:
commit
4dbc910a49
23
README.md
23
README.md
@ -37,6 +37,7 @@
|
||||
* [Gathering Remaining Arguments](#gathering-remaining-arguments)
|
||||
* [Parent Parsers](#parent-parsers)
|
||||
* [Subcommands](#subcommands)
|
||||
* [Parse Known Args](#parse-known-args)
|
||||
* [Further Examples](#further-examples)
|
||||
* [Construct a JSON object from a filename argument](#construct-a-json-object-from-a-filename-argument)
|
||||
* [Positional Arguments with Compound Toggle Arguments](#positional-arguments-with-compound-toggle-arguments)
|
||||
@ -799,6 +800,28 @@ When a help message is requested from a subparser, only the help for that partic
|
||||
|
||||
Additionally, every parser has a `.is_subcommand_used("<command_name>")` member function to check if a subcommand was used.
|
||||
|
||||
### Parse Known Args
|
||||
|
||||
Sometimes a program may only parse a few of the command-line arguments, passing the remaining arguments on to another script or program. In these cases, the `parse_known_args()` function can be useful. It works much like `parse_args()` except that it does not produce an error when extra arguments are present. Instead, it returns a list of remaining argument strings.
|
||||
|
||||
```cpp
|
||||
#include <argparse/argparse.hpp>
|
||||
#include <cassert>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
argparse::ArgumentParser program("test");
|
||||
program.add_argument("--foo").implicit_value(true).default_value(false);
|
||||
program.add_argument("bar");
|
||||
|
||||
auto unknown_args =
|
||||
program.parse_known_args({"test", "--foo", "--badger", "BAR", "spam"});
|
||||
|
||||
assert(program.get<bool>("--foo") == true);
|
||||
assert(program.get<std::string>("bar") == std::string{"BAR"});
|
||||
assert((unknown_args == std::vector<std::string>{"--badger", "spam"}));
|
||||
}
|
||||
```
|
||||
|
||||
## Further Examples
|
||||
|
||||
### Construct a JSON object from a filename argument
|
||||
|
@ -1041,6 +1041,21 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/* Call parse_known_args_internal - which does all the work
|
||||
* Then, validate the parsed arguments
|
||||
* This variant is used mainly for testing
|
||||
* @throws std::runtime_error in case of any invalid argument
|
||||
*/
|
||||
std::vector<std::string>
|
||||
parse_known_args(const std::vector<std::string> &arguments) {
|
||||
auto unknown_arguments = parse_known_args_internal(arguments);
|
||||
// Check if all arguments are parsed
|
||||
for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
|
||||
argument->validate();
|
||||
}
|
||||
return unknown_arguments;
|
||||
}
|
||||
|
||||
/* Main entry point for parsing command-line arguments using this
|
||||
* ArgumentParser
|
||||
* @throws std::runtime_error in case of any invalid argument
|
||||
@ -1050,6 +1065,15 @@ public:
|
||||
parse_args({argv, argv + argc});
|
||||
}
|
||||
|
||||
/* Main entry point for parsing command-line arguments using this
|
||||
* ArgumentParser
|
||||
* @throws std::runtime_error in case of any invalid argument
|
||||
*/
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
|
||||
void parse_known_args(int argc, const char *const argv[]) {
|
||||
parse_known_args({argv, argv + argc});
|
||||
}
|
||||
|
||||
/* Getter for options with default values.
|
||||
* @throws std::logic_error if parse_args() has not been previously called
|
||||
* @throws std::logic_error if there is no such option
|
||||
@ -1262,6 +1286,83 @@ private:
|
||||
m_is_parsed = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Like parse_args_internal but collects unused args into a vector<string>
|
||||
*/
|
||||
std::vector<std::string>
|
||||
parse_known_args_internal(const std::vector<std::string> &arguments) {
|
||||
|
||||
std::vector<std::string> unknown_arguments{};
|
||||
|
||||
if (m_program_name.empty() && !arguments.empty()) {
|
||||
m_program_name = arguments.front();
|
||||
}
|
||||
auto end = std::end(arguments);
|
||||
auto positional_argument_it = std::begin(m_positional_arguments);
|
||||
for (auto it = std::next(std::begin(arguments)); it != end;) {
|
||||
const auto ¤t_argument = *it;
|
||||
if (Argument::is_positional(current_argument)) {
|
||||
if (positional_argument_it == std::end(m_positional_arguments)) {
|
||||
|
||||
std::string_view maybe_command = current_argument;
|
||||
|
||||
// Check sub-parsers
|
||||
auto subparser_it = m_subparser_map.find(maybe_command);
|
||||
if (subparser_it != m_subparser_map.end()) {
|
||||
|
||||
// build list of remaining args
|
||||
const auto unprocessed_arguments =
|
||||
std::vector<std::string>(it, end);
|
||||
|
||||
// invoke subparser
|
||||
m_is_parsed = true;
|
||||
m_subparser_used[maybe_command] = true;
|
||||
return subparser_it->second->get().parse_known_args_internal(
|
||||
unprocessed_arguments);
|
||||
}
|
||||
|
||||
// save current argument as unknown and go to next argument
|
||||
unknown_arguments.push_back(current_argument);
|
||||
++it;
|
||||
} else {
|
||||
// current argument is the value of a positional argument
|
||||
// consume it
|
||||
auto argument = positional_argument_it++;
|
||||
it = argument->consume(it, end);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto arg_map_it = m_argument_map.find(current_argument);
|
||||
if (arg_map_it != m_argument_map.end()) {
|
||||
auto argument = arg_map_it->second;
|
||||
it = argument->consume(std::next(it), end, arg_map_it->first);
|
||||
} else if (const auto &compound_arg = current_argument;
|
||||
compound_arg.size() > 1 && compound_arg[0] == '-' &&
|
||||
compound_arg[1] != '-') {
|
||||
++it;
|
||||
for (std::size_t j = 1; j < compound_arg.size(); j++) {
|
||||
auto hypothetical_arg = std::string{'-', compound_arg[j]};
|
||||
auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
|
||||
if (arg_map_it2 != m_argument_map.end()) {
|
||||
auto argument = arg_map_it2->second;
|
||||
it = argument->consume(it, end, arg_map_it2->first);
|
||||
} else {
|
||||
unknown_arguments.push_back(current_argument);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// current argument is an optional-like argument that is unknown
|
||||
// save it and move to next argument
|
||||
unknown_arguments.push_back(current_argument);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
m_is_parsed = true;
|
||||
return unknown_arguments;
|
||||
}
|
||||
|
||||
// Used by print_help.
|
||||
std::size_t get_length_of_longest_argument() const {
|
||||
if (m_argument_map.empty()) {
|
||||
|
@ -47,6 +47,7 @@ file(GLOB ARGPARSE_TEST_SOURCES
|
||||
test_value_semantics.cpp
|
||||
test_version.cpp
|
||||
test_subparsers.cpp
|
||||
test_parse_known_args.cpp
|
||||
)
|
||||
set_source_files_properties(main.cpp
|
||||
PROPERTIES
|
||||
|
82
test/test_parse_known_args.cpp
Normal file
82
test/test_parse_known_args.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include <argparse/argparse.hpp>
|
||||
#include <doctest.hpp>
|
||||
|
||||
using doctest::test_suite;
|
||||
|
||||
TEST_CASE("Parse unknown optional and positional arguments without exceptions" *
|
||||
test_suite("parse_known_args")) {
|
||||
argparse::ArgumentParser program("test");
|
||||
program.add_argument("--foo").implicit_value(true).default_value(false);
|
||||
program.add_argument("bar");
|
||||
|
||||
SUBCASE("Parse unknown optional and positional arguments") {
|
||||
auto unknown_args =
|
||||
program.parse_known_args({"test", "--foo", "--badger", "BAR", "spam"});
|
||||
REQUIRE((unknown_args == std::vector<std::string>{"--badger", "spam"}));
|
||||
REQUIRE(program.get<bool>("--foo") == true);
|
||||
REQUIRE(program.get<std::string>("bar") == std::string{"BAR"});
|
||||
}
|
||||
|
||||
SUBCASE("Parse unknown compound arguments") {
|
||||
auto unknown_args = program.parse_known_args({"test", "-jc", "BAR"});
|
||||
REQUIRE((unknown_args == std::vector<std::string>{"-jc"}));
|
||||
REQUIRE(program.get<bool>("--foo") == false);
|
||||
REQUIRE(program.get<std::string>("bar") == std::string{"BAR"});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Parse unknown optional and positional arguments in subparsers "
|
||||
"without exceptions" *
|
||||
test_suite("parse_known_args")) {
|
||||
argparse::ArgumentParser program("test");
|
||||
program.add_argument("--output");
|
||||
|
||||
argparse::ArgumentParser command_1("add");
|
||||
command_1.add_argument("file").nargs(2);
|
||||
|
||||
argparse::ArgumentParser command_2("clean");
|
||||
command_2.add_argument("--fullclean")
|
||||
.default_value(false)
|
||||
.implicit_value(true);
|
||||
|
||||
program.add_subparser(command_1);
|
||||
program.add_subparser(command_2);
|
||||
|
||||
SUBCASE("Parse unknown optional argument") {
|
||||
auto unknown_args =
|
||||
program.parse_known_args({"test", "add", "--badger", "BAR", "spam"});
|
||||
REQUIRE(program.is_subcommand_used("add") == true);
|
||||
REQUIRE((command_1.get<std::vector<std::string>>("file") ==
|
||||
std::vector<std::string>{"BAR", "spam"}));
|
||||
REQUIRE((unknown_args == std::vector<std::string>{"--badger"}));
|
||||
}
|
||||
|
||||
SUBCASE("Parse unknown positional argument") {
|
||||
auto unknown_args =
|
||||
program.parse_known_args({"test", "add", "FOO", "BAR", "spam"});
|
||||
REQUIRE(program.is_subcommand_used("add") == true);
|
||||
REQUIRE((command_1.get<std::vector<std::string>>("file") ==
|
||||
std::vector<std::string>{"FOO", "BAR"}));
|
||||
REQUIRE((unknown_args == std::vector<std::string>{"spam"}));
|
||||
}
|
||||
|
||||
SUBCASE("Parse unknown positional and optional arguments") {
|
||||
auto unknown_args = program.parse_known_args(
|
||||
{"test", "add", "--verbose", "FOO", "5", "BAR", "-jn", "spam"});
|
||||
REQUIRE(program.is_subcommand_used("add") == true);
|
||||
REQUIRE((command_1.get<std::vector<std::string>>("file") ==
|
||||
std::vector<std::string>{"FOO", "5"}));
|
||||
REQUIRE((unknown_args ==
|
||||
std::vector<std::string>{"--verbose", "BAR", "-jn", "spam"}));
|
||||
}
|
||||
|
||||
SUBCASE("Parse unknown positional and optional arguments 2") {
|
||||
auto unknown_args =
|
||||
program.parse_known_args({"test", "clean", "--verbose", "FOO", "5",
|
||||
"BAR", "--fullclean", "-jn", "spam"});
|
||||
REQUIRE(program.is_subcommand_used("clean") == true);
|
||||
REQUIRE(command_2.get<bool>("--fullclean") == true);
|
||||
REQUIRE((unknown_args == std::vector<std::string>{"--verbose", "FOO", "5",
|
||||
"BAR", "-jn", "spam"}));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user