Added is_subcommand_used helper function

This commit is contained in:
Pranav Srinivas Kumar 2022-09-20 21:15:58 -07:00
parent e35ce54028
commit 793fbcde16
3 changed files with 88 additions and 53 deletions

View File

@ -103,8 +103,8 @@ int main(int argc, char *argv[]) {
And running the code:
```bash
$ ./main 15
```console
foo@bar:/home/dev/$ ./main 15
225
```
@ -141,8 +141,8 @@ if (program["--verbose"] == true) {
}
```
```bash
$ ./main --verbose
```console
foo@bar:/home/dev/$ ./main --verbose
Verbosity enabled
```
@ -276,8 +276,8 @@ catch (const std::runtime_error& err) {
// Some code to print arguments
```
```bash
$ ./main -5 -1.1 -3.1415 -3.1e2 -4.51329E3
```console
foo@bar:/home/dev/$ ./main -5 -1.1 -3.1415 -3.1e2 -4.51329E3
integer : -5
floats : -1.1 -3.1415 -310 -4513.29
```
@ -316,14 +316,14 @@ else {
}
```
```bash
$ ./main 4
```console
foo@bar:/home/dev/$ ./main 4
16
$ ./main 4 --verbose
foo@bar:/home/dev/$ ./main 4 --verbose
The square of 4 is 16
$ ./main --verbose 4
foo@bar:/home/dev/$ ./main --verbose 4
The square of 4 is 16
```
@ -332,7 +332,7 @@ The square of 4 is 16
`std::cout << program` prints a help message, including the program usage and information about the arguments registered with the `ArgumentParser`. For the previous example, here's the default help message:
```
$ ./main --help
foo@bar:/home/dev/$ ./main --help
Usage: main [options] square
Positional arguments:
@ -364,8 +364,8 @@ program.parse_args(argc, argv);
std::cout << program << std::endl;
```
```bash
$ ./main --help
```console
foo@bar:/home/dev/$ ./main --help
Usage: main thing
Forward a thing to the next member.
@ -491,13 +491,13 @@ auto c = program.get<std::vector<float>>("-c"); // {1.95, 2.47}
/// Some code that prints parsed arguments
```
```bash
$ ./main -ac 3.14 2.718
```console
foo@bar:/home/dev/$ ./main -ac 3.14 2.718
a = true
b = false
c = {3.14, 2.718}
$ ./main -cb
foo@bar:/home/dev/$ ./main -cb
a = false
b = true
c = {0.0, 0.0}
@ -568,8 +568,8 @@ The default is `default_arguments::all` for included arguments. No default argum
`argparse` supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
```bash
$ compiler file1 file2 file3
```console
foo@bar:/home/dev/$ compiler file1 file2 file3
```
To enable this, simply create an argument and mark it as `remaining`. All remaining arguments passed to argparse are gathered here.
@ -601,15 +601,15 @@ try {
When no arguments are provided:
```bash
$ ./compiler
```console
foo@bar:/home/dev/$ ./compiler
No files provided
```
and when multiple arguments are provided:
```bash
$ ./compiler foo.txt bar.txt baz.txt
```console
foo@bar:/home/dev/$ ./compiler foo.txt bar.txt baz.txt
3 files provided
foo.txt
bar.txt
@ -650,8 +650,8 @@ try {
```
```bash
$ ./compiler -o main foo.cpp bar.cpp baz.cpp
```console
foo@bar:/home/dev/$ ./compiler -o main foo.cpp bar.cpp baz.cpp
Output filename: main
3 files provided
foo.cpp
@ -661,8 +661,8 @@ baz.cpp
***NOTE***: Remember to place all optional arguments BEFORE the remaining argument. If the optional argument is placed after the remaining arguments, it too will be deemed remaining:
```bash
$ ./compiler foo.cpp bar.cpp baz.cpp -o main
```console
foo@bar:/home/dev/$ ./compiler foo.cpp bar.cpp baz.cpp -o main
5 arguments provided
foo.cpp
bar.cpp
@ -703,11 +703,13 @@ Many programs split up their functionality into a number of sub-commands, for ex
int main(int argc, char *argv[]) {
argparse::ArgumentParser program("git");
// git add subparser
argparse::ArgumentParser add_command("add");
add_command.add_argument("files")
.help("Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files.")
.remaining();
// git commit subparser
argparse::ArgumentParser commit_command("commit");
commit_command.add_argument("-a", "--all")
.help("Tell the command to automatically stage files that have been modified and deleted.")
@ -717,6 +719,7 @@ int main(int argc, char *argv[]) {
commit_command.add_argument("-m", "--message")
.help("Use the given <msg> as the commit message.");
// git cat-file subparser
argparse::ArgumentParser catfile_command("cat-file");
catfile_command.add_argument("-t")
.help("Instead of the content, show the object type identified by <object>.");
@ -724,6 +727,7 @@ int main(int argc, char *argv[]) {
catfile_command.add_argument("-p")
.help("Pretty-print the contents of <object> based on its type.");
// git submodule subparser
argparse::ArgumentParser submodule_command("submodule");
argparse::ArgumentParser submodule_update_command("update");
submodule_update_command.add_argument("--init")
@ -752,8 +756,8 @@ int main(int argc, char *argv[]) {
}
```
```bash
$ ./git --help
```console
foo@bar:/home/dev/$ ./git --help
Usage: git [options] <command> [<args>]
Optional arguments:
@ -761,11 +765,16 @@ Optional arguments:
-v --version prints version information and exits [default: false]
Subcommands:
{add, cat-file, commit, submodule}
add Add file contents to the index
cat-file Provide content or type and size information for repository objects
commit Record changes to the repository
submodule Initialize, update or inspect submodules
$ ./git add --help
foo@bar:/home/dev/$ ./git add --help
Usage: git add [options] files
Add file contents to the index
Positional arguments:
files Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files.
@ -773,17 +782,23 @@ Optional arguments:
-h --help shows help message and exits [default: false]
-v --version prints version information and exits [default: false]
$ ./git submodule update --help
Usage: submodule update [options]
foo@bar:/home/dev/$ ./git submodule --help
Usage: git submodule [options] <command> [<args>]
Initialize, update or inspect submodules
Optional arguments:
-h --help shows help message and exits [default: false]
-v --version prints version information and exits [default: false]
--init [default: false]
--recursive [default: false]
Subcommands:
update Update the registered submodules to match what the superproject expects
```
When a help message is requested from a subparser, only the help for that particular parser will be printed. The help message will not include parent parser or sibling parser messages.
Additionally, every parser has a `.is_subcommand_used("<command_name>")` member function to check if a subcommand was used.
## Further Examples
### Construct a JSON object from a filename argument
@ -854,8 +869,8 @@ auto files = program.get<std::vector<std::string>>("--files"); // {"a.txt", "b.
/// Some code that prints parsed arguments
```
```bash
$ ./main 1 2 3 -abc 3.14 2.718 --files a.txt b.txt c.txt
```console
foo@bar:/home/dev/$ ./main 1 2 3 -abc 3.14 2.718 --files a.txt b.txt c.txt
numbers = {1, 2, 3}
a = true
b = true
@ -891,8 +906,8 @@ auto input = program.get("input");
std::cout << input << std::endl;
```
```bash
$ ./main fex
```console
foo@bar:/home/dev/$ ./main fex
baz
```

View File

@ -925,7 +925,7 @@ public:
std::string version = "1.0",
default_arguments add_args = default_arguments::all)
: m_program_name(std::move(program_name)), m_version(std::move(version)),
m_parser_path(m_program_name) {
m_parser_path(m_program_name) {
if ((add_args & default_arguments::help) == default_arguments::help) {
add_argument("-h", "--help")
.action([&](const auto & /*unused*/) {
@ -959,8 +959,7 @@ public:
m_is_parsed(other.m_is_parsed),
m_positional_arguments(other.m_positional_arguments),
m_optional_arguments(other.m_optional_arguments),
m_parser_path(other.m_parser_path),
m_subparsers(other.m_subparsers) {
m_parser_path(other.m_parser_path), m_subparsers(other.m_subparsers) {
for (auto it = std::begin(m_positional_arguments);
it != std::end(m_positional_arguments); ++it) {
index_argument(it);
@ -969,9 +968,10 @@ public:
it != std::end(m_optional_arguments); ++it) {
index_argument(it);
}
for (auto it = std::begin(m_subparsers);
it != std::end(m_subparsers); ++it) {
for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers);
++it) {
m_subparser_map.insert_or_assign(it->get().m_program_name, it);
m_subparser_used.insert_or_assign(it->get().m_program_name, false);
}
}
@ -1082,6 +1082,13 @@ public:
return (*this)[arg_name].m_is_used;
}
/* Getter that returns true for user-supplied options. Returns false if not
* user-supplied, even with a default value.
*/
auto is_subcommand_used(std::string_view subcommand_name) const {
return m_subparser_used.at(subcommand_name);
}
/* Indexing operator. Return a reference to an Argument object
* Used in conjuction with Argument.operator== e.g., parser["foo"] == true
* @throws std::logic_error in case of an invalid argument name
@ -1153,17 +1160,10 @@ public:
? (parser.m_optional_arguments.empty() ? "" : "\n")
: "\n")
<< "Subcommands:\n";
stream << "{";
std::size_t i = 0;
for (const auto &[argument, unused] : parser.m_subparser_map) {
if (i == 0) {
stream << argument;
} else {
stream << ", " << argument;
}
++i;
for (const auto &[command, subparser] : parser.m_subparser_map) {
stream.width(longest_arg_length);
stream << command << "\t" << subparser->get().m_description << "\n";
}
stream << "}\n";
}
if (!parser.m_epilog.empty()) {
@ -1194,6 +1194,7 @@ public:
parser.m_parser_path = m_program_name + " " + parser.m_program_name;
auto it = m_subparsers.emplace(std::cend(m_subparsers), parser);
m_subparser_map.insert_or_assign(parser.m_program_name, it);
m_subparser_used.insert_or_assign(parser.m_program_name, false);
}
private:
@ -1223,6 +1224,7 @@ private:
// invoke subparser
m_is_parsed = true;
m_subparser_used[maybe_command] = true;
return subparser_it->second->get().parse_args(
unprocessed_arguments);
}
@ -1296,6 +1298,7 @@ private:
std::string m_parser_path;
std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
std::map<std::string_view, argument_parser_it, std::less<>> m_subparser_map;
std::map<std::string_view, bool, std::less<>> m_subparser_used;
};
} // namespace argparse

View File

@ -17,6 +17,7 @@ TEST_CASE("Add subparsers" * test_suite("subparsers")) {
program.add_subparser(command_2);
program.parse_args({"test", "--output", "thrust_profile.csv"});
REQUIRE(program.is_subcommand_used("add") == false);
REQUIRE(program.get("--output") == "thrust_profile.csv");
}
@ -37,6 +38,7 @@ TEST_CASE("Parse subparser command" * test_suite("subparsers")) {
SUBCASE("command 1") {
program.parse_args({"test", "add", "file1.txt", "file2.txt"});
REQUIRE(program.is_subcommand_used("add") == true);
REQUIRE(command_1.is_used("file"));
REQUIRE((command_1.get<std::vector<std::string>>("file") ==
std::vector<std::string>{"file1.txt", "file2.txt"}));
@ -44,6 +46,7 @@ TEST_CASE("Parse subparser command" * test_suite("subparsers")) {
SUBCASE("command 2") {
program.parse_args({"test", "clean", "--fullclean"});
REQUIRE(program.is_subcommand_used("clean") == true);
REQUIRE(command_2.get<bool>("--fullclean") == true);
}
}
@ -66,6 +69,7 @@ TEST_CASE("Parse subparser command with optional argument" *
SUBCASE("Optional argument BEFORE subcommand") {
program.parse_args({"test", "--verbose", "clean", "--fullclean"});
REQUIRE(program.is_subcommand_used("clean") == true);
REQUIRE(program.get<bool>("--verbose") == true);
REQUIRE(command_2.get<bool>("--fullclean") == true);
}
@ -98,6 +102,7 @@ TEST_CASE("Parse subparser command with parent parser" *
SUBCASE("Optional argument BEFORE subcommand") {
program.parse_args({"test", "--verbose", "clean", "--fullclean"});
REQUIRE(program.is_subcommand_used("clean") == true);
REQUIRE(program.get<bool>("--verbose") == true);
REQUIRE(command_2.get<bool>("--fullclean") == true);
}
@ -141,12 +146,14 @@ TEST_CASE("Parse git commands" * test_suite("subparsers")) {
SUBCASE("git add") {
program.parse_args({"git", "add", "main.cpp", "foo.hpp", "foo.cpp"});
REQUIRE(program.is_subcommand_used("add") == true);
REQUIRE((add_command.get<std::vector<std::string>>("files") ==
std::vector<std::string>{"main.cpp", "foo.hpp", "foo.cpp"}));
}
SUBCASE("git commit") {
program.parse_args({"git", "commit", "-am", "Initial commit"});
REQUIRE(program.is_subcommand_used("commit") == true);
REQUIRE(commit_command.get<bool>("-a") == true);
REQUIRE(commit_command.get<std::string>("-m") ==
std::string{"Initial commit"});
@ -154,31 +161,41 @@ TEST_CASE("Parse git commands" * test_suite("subparsers")) {
SUBCASE("git cat-file -t") {
program.parse_args({"git", "cat-file", "-t", "3739f5"});
REQUIRE(program.is_subcommand_used("cat-file") == true);
REQUIRE(catfile_command.get<std::string>("-t") == std::string{"3739f5"});
}
SUBCASE("git cat-file -p") {
program.parse_args({"git", "cat-file", "-p", "3739f5"});
REQUIRE(program.is_subcommand_used("cat-file") == true);
REQUIRE(catfile_command.get<std::string>("-p") == std::string{"3739f5"});
}
SUBCASE("git submodule update") {
program.parse_args({"git", "submodule", "update"});
REQUIRE(program.is_subcommand_used("submodule") == true);
REQUIRE(submodule_command.is_subcommand_used("update") == true);
}
SUBCASE("git submodule update --init") {
program.parse_args({"git", "submodule", "update", "--init"});
REQUIRE(program.is_subcommand_used("submodule") == true);
REQUIRE(submodule_command.is_subcommand_used("update") == true);
REQUIRE(submodule_update_command.get<bool>("--init") == true);
REQUIRE(submodule_update_command.get<bool>("--recursive") == false);
}
SUBCASE("git submodule update --recursive") {
program.parse_args({"git", "submodule", "update", "--recursive"});
REQUIRE(program.is_subcommand_used("submodule") == true);
REQUIRE(submodule_command.is_subcommand_used("update") == true);
REQUIRE(submodule_update_command.get<bool>("--recursive") == true);
}
SUBCASE("git submodule update --init --recursive") {
program.parse_args({"git", "submodule", "update", "--init", "--recursive"});
REQUIRE(program.is_subcommand_used("submodule") == true);
REQUIRE(submodule_command.is_subcommand_used("update") == true);
REQUIRE(submodule_update_command.get<bool>("--init") == true);
REQUIRE(submodule_update_command.get<bool>("--recursive") == true);
}