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: And running the code:
```bash ```console
$ ./main 15 foo@bar:/home/dev/$ ./main 15
225 225
``` ```
@ -141,8 +141,8 @@ if (program["--verbose"] == true) {
} }
``` ```
```bash ```console
$ ./main --verbose foo@bar:/home/dev/$ ./main --verbose
Verbosity enabled Verbosity enabled
``` ```
@ -276,8 +276,8 @@ catch (const std::runtime_error& err) {
// Some code to print arguments // Some code to print arguments
``` ```
```bash ```console
$ ./main -5 -1.1 -3.1415 -3.1e2 -4.51329E3 foo@bar:/home/dev/$ ./main -5 -1.1 -3.1415 -3.1e2 -4.51329E3
integer : -5 integer : -5
floats : -1.1 -3.1415 -310 -4513.29 floats : -1.1 -3.1415 -310 -4513.29
``` ```
@ -316,14 +316,14 @@ else {
} }
``` ```
```bash ```console
$ ./main 4 foo@bar:/home/dev/$ ./main 4
16 16
$ ./main 4 --verbose foo@bar:/home/dev/$ ./main 4 --verbose
The square of 4 is 16 The square of 4 is 16
$ ./main --verbose 4 foo@bar:/home/dev/$ ./main --verbose 4
The square of 4 is 16 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: `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 Usage: main [options] square
Positional arguments: Positional arguments:
@ -364,8 +364,8 @@ program.parse_args(argc, argv);
std::cout << program << std::endl; std::cout << program << std::endl;
``` ```
```bash ```console
$ ./main --help foo@bar:/home/dev/$ ./main --help
Usage: main thing Usage: main thing
Forward a thing to the next member. 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 /// Some code that prints parsed arguments
``` ```
```bash ```console
$ ./main -ac 3.14 2.718 foo@bar:/home/dev/$ ./main -ac 3.14 2.718
a = true a = true
b = false b = false
c = {3.14, 2.718} c = {3.14, 2.718}
$ ./main -cb foo@bar:/home/dev/$ ./main -cb
a = false a = false
b = true b = true
c = {0.0, 0.0} 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: `argparse` supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
```bash ```console
$ compiler file1 file2 file3 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. 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: When no arguments are provided:
```bash ```console
$ ./compiler foo@bar:/home/dev/$ ./compiler
No files provided No files provided
``` ```
and when multiple arguments are provided: and when multiple arguments are provided:
```bash ```console
$ ./compiler foo.txt bar.txt baz.txt foo@bar:/home/dev/$ ./compiler foo.txt bar.txt baz.txt
3 files provided 3 files provided
foo.txt foo.txt
bar.txt bar.txt
@ -650,8 +650,8 @@ try {
``` ```
```bash ```console
$ ./compiler -o main foo.cpp bar.cpp baz.cpp foo@bar:/home/dev/$ ./compiler -o main foo.cpp bar.cpp baz.cpp
Output filename: main Output filename: main
3 files provided 3 files provided
foo.cpp 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: ***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 ```console
$ ./compiler foo.cpp bar.cpp baz.cpp -o main foo@bar:/home/dev/$ ./compiler foo.cpp bar.cpp baz.cpp -o main
5 arguments provided 5 arguments provided
foo.cpp foo.cpp
bar.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[]) { int main(int argc, char *argv[]) {
argparse::ArgumentParser program("git"); argparse::ArgumentParser program("git");
// git add subparser
argparse::ArgumentParser add_command("add"); argparse::ArgumentParser add_command("add");
add_command.add_argument("files") add_command.add_argument("files")
.help("Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files.") .help("Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files.")
.remaining(); .remaining();
// git commit subparser
argparse::ArgumentParser commit_command("commit"); argparse::ArgumentParser commit_command("commit");
commit_command.add_argument("-a", "--all") commit_command.add_argument("-a", "--all")
.help("Tell the command to automatically stage files that have been modified and deleted.") .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") commit_command.add_argument("-m", "--message")
.help("Use the given <msg> as the commit message."); .help("Use the given <msg> as the commit message.");
// git cat-file subparser
argparse::ArgumentParser catfile_command("cat-file"); argparse::ArgumentParser catfile_command("cat-file");
catfile_command.add_argument("-t") catfile_command.add_argument("-t")
.help("Instead of the content, show the object type identified by <object>."); .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") catfile_command.add_argument("-p")
.help("Pretty-print the contents of <object> based on its type."); .help("Pretty-print the contents of <object> based on its type.");
// git submodule subparser
argparse::ArgumentParser submodule_command("submodule"); argparse::ArgumentParser submodule_command("submodule");
argparse::ArgumentParser submodule_update_command("update"); argparse::ArgumentParser submodule_update_command("update");
submodule_update_command.add_argument("--init") submodule_update_command.add_argument("--init")
@ -752,8 +756,8 @@ int main(int argc, char *argv[]) {
} }
``` ```
```bash ```console
$ ./git --help foo@bar:/home/dev/$ ./git --help
Usage: git [options] <command> [<args>] Usage: git [options] <command> [<args>]
Optional arguments: Optional arguments:
@ -761,11 +765,16 @@ Optional arguments:
-v --version prints version information and exits [default: false] -v --version prints version information and exits [default: false]
Subcommands: 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 Usage: git add [options] files
Add file contents to the index
Positional arguments: Positional arguments:
files Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files. 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] -h --help shows help message and exits [default: false]
-v --version prints version information and exits [default: false] -v --version prints version information and exits [default: false]
$ ./git submodule update --help foo@bar:/home/dev/$ ./git submodule --help
Usage: submodule update [options] Usage: git submodule [options] <command> [<args>]
Initialize, update or inspect submodules
Optional arguments: Optional arguments:
-h --help shows help message and exits [default: false] -h --help shows help message and exits [default: false]
-v --version prints version information 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 ## Further Examples
### Construct a JSON object from a filename argument ### 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 /// Some code that prints parsed arguments
``` ```
```bash ```console
$ ./main 1 2 3 -abc 3.14 2.718 --files a.txt b.txt c.txt foo@bar:/home/dev/$ ./main 1 2 3 -abc 3.14 2.718 --files a.txt b.txt c.txt
numbers = {1, 2, 3} numbers = {1, 2, 3}
a = true a = true
b = true b = true
@ -891,8 +906,8 @@ auto input = program.get("input");
std::cout << input << std::endl; std::cout << input << std::endl;
``` ```
```bash ```console
$ ./main fex foo@bar:/home/dev/$ ./main fex
baz baz
``` ```

View File

@ -959,8 +959,7 @@ public:
m_is_parsed(other.m_is_parsed), m_is_parsed(other.m_is_parsed),
m_positional_arguments(other.m_positional_arguments), m_positional_arguments(other.m_positional_arguments),
m_optional_arguments(other.m_optional_arguments), m_optional_arguments(other.m_optional_arguments),
m_parser_path(other.m_parser_path), m_parser_path(other.m_parser_path), m_subparsers(other.m_subparsers) {
m_subparsers(other.m_subparsers) {
for (auto it = std::begin(m_positional_arguments); for (auto it = std::begin(m_positional_arguments);
it != std::end(m_positional_arguments); ++it) { it != std::end(m_positional_arguments); ++it) {
index_argument(it); index_argument(it);
@ -969,9 +968,10 @@ public:
it != std::end(m_optional_arguments); ++it) { it != std::end(m_optional_arguments); ++it) {
index_argument(it); index_argument(it);
} }
for (auto it = std::begin(m_subparsers); for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers);
it != std::end(m_subparsers); ++it) { ++it) {
m_subparser_map.insert_or_assign(it->get().m_program_name, 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; 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 /* Indexing operator. Return a reference to an Argument object
* Used in conjuction with Argument.operator== e.g., parser["foo"] == true * Used in conjuction with Argument.operator== e.g., parser["foo"] == true
* @throws std::logic_error in case of an invalid argument name * @throws std::logic_error in case of an invalid argument name
@ -1153,17 +1160,10 @@ public:
? (parser.m_optional_arguments.empty() ? "" : "\n") ? (parser.m_optional_arguments.empty() ? "" : "\n")
: "\n") : "\n")
<< "Subcommands:\n"; << "Subcommands:\n";
stream << "{"; for (const auto &[command, subparser] : parser.m_subparser_map) {
std::size_t i = 0; stream.width(longest_arg_length);
for (const auto &[argument, unused] : parser.m_subparser_map) { stream << command << "\t" << subparser->get().m_description << "\n";
if (i == 0) {
stream << argument;
} else {
stream << ", " << argument;
} }
++i;
}
stream << "}\n";
} }
if (!parser.m_epilog.empty()) { if (!parser.m_epilog.empty()) {
@ -1194,6 +1194,7 @@ public:
parser.m_parser_path = m_program_name + " " + parser.m_program_name; parser.m_parser_path = m_program_name + " " + parser.m_program_name;
auto it = m_subparsers.emplace(std::cend(m_subparsers), parser); auto it = m_subparsers.emplace(std::cend(m_subparsers), parser);
m_subparser_map.insert_or_assign(parser.m_program_name, it); m_subparser_map.insert_or_assign(parser.m_program_name, it);
m_subparser_used.insert_or_assign(parser.m_program_name, false);
} }
private: private:
@ -1223,6 +1224,7 @@ private:
// invoke subparser // invoke subparser
m_is_parsed = true; m_is_parsed = true;
m_subparser_used[maybe_command] = true;
return subparser_it->second->get().parse_args( return subparser_it->second->get().parse_args(
unprocessed_arguments); unprocessed_arguments);
} }
@ -1296,6 +1298,7 @@ private:
std::string m_parser_path; std::string m_parser_path;
std::list<std::reference_wrapper<ArgumentParser>> m_subparsers; 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, argument_parser_it, std::less<>> m_subparser_map;
std::map<std::string_view, bool, std::less<>> m_subparser_used;
}; };
} // namespace argparse } // namespace argparse

View File

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