Argument Parser for Modern C++
Simply include argparse.hpp and you're good to go.
#include
To start parsing command-line arguments, create an
ArgumentParser.
argparse::ArgumentParser program("program name");
NOTE: There is an optional second argument to the
ArgumentParserwhich is the program version. Example:
argparse::ArgumentParser program("libfoo", "1.9.0");
To add a new argument, simply call
.add_argument(...). You can provide a variadic list of argument names that you want to group together, e.g.,
-vand
--verbose
program.add_argument("foo"); program.add_argument("-v", "--verbose"); // parameter packing
Argparse supports a variety of argument types including positional, optional, and compound arguments. Below you can see how to configure each of these types:
Here's an example of a positional argument:
#includeint main(int argc, char *argv[]) { argparse::ArgumentParser program("program name");
program.add_argument("square") .help("display the square of a given integer") .action([](const std::string& value) { return std::stoi(value); });
try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
auto input = program.get("square"); std::cout << (input * input) << std::endl;
return 0; }
And running the code:
$ ./main 15 225
Here's what's happening:
add_argument()method is used to specify which command-line options the program is willing to accept. In this case, I’ve named it square so that it’s in line with its function.
.actionmethod and provide a lambda function that tries to convert user input into an integer.
parser.get(key)method.
Now, let's look at optional arguments. Optional arguments start with
-or
--, e.g.,
--verboseor
-a. Optional arguments can be placed anywhere in the input sequence.
argparse::ArgumentParser program("test");program.add_argument("--verbose") .help("increase output verbosity") .default_value(false) .implicit_value(true);
try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
if (program["--verbose"] == true) { std::cout << "Verbosity enabled" << std::endl; }
$ ./main --verbose Verbosity enabled
Here's what's happening: * The program is written so as to display something when --verbose is specified and display nothing when not. * Since the argument is actually optional, no error is thrown when running the program without
--verbose. Note that by using
.default_value(false), if the optional argument isn’t used, it's value is automatically set to false. * By using
.implicit_value(true), the user specifies that this option is more of a flag than something that requires a value. When the user provides the --verbose option, it's value is set to true.
There are scenarios where you would like to make an optional argument required. As discussed above, optional arguments either begin with
-or
--. You can make these types of arguments required like so:
program.add_argument("-o", "--output") .required() .help("specify the output file.");
If the user does not provide a value for this parameter, an exception is thrown.
Alternatively, you could provide a default value like so:
program.add_argument("-o", "--output") .default_value(std::string("-")) .required() .help("specify the output file.");
If you require an optional argument to be present but have no good default value for it, you can combine testing and accessing the argument as following:
if (auto fn = program.present("-o")) { do_something_with(*fn); }
Similar to
get, the
presentmethod also accepts a template argument. But rather than returning
T,
parser.present(key)returns
std::optional, so that when the user does not provide a value to this parameter, the return value compares equal to
std::nullopt.
Optional arguments start with
-. Can
argparsehandle negative numbers? The answer is yes!
argparse::ArgumentParser program;program.add_argument("integer") .help("Input number") .action([](const std::string& value) { return std::stoi(value); });
program.add_argument("floats") .help("Vector of floats") .nargs(4) .action([](const std::string& value) { return std::stof(value); });
try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
// Some code to print arguments
$ ./main -5 -1.1 -3.1415 -3.1e2 -4.51329E3 integer : -5 floats : -1.1 -3.1415 -310 -4513.29
As you can see here,
argparsesupports negative integers, negative floats and scientific notation.
argparse::ArgumentParser program("test");program.add_argument("square") .help("display the square of a given number") .action([](const std::string& value) { return std::stoi(value); });
program.add_argument("--verbose") .default_value(false) .implicit_value(true);
try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
int input = program.get("square");
if (program["--verbose"] == true) { std::cout << "The square of " << input << " is " << (input * input) << std::endl; } else { std::cout << (input * input) << std::endl; }
$ ./main 4 16$ ./main 4 --verbose The square of 4 is 16
$ ./main --verbose 4 The square of 4 is 16
std::cout << programprints 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 Usage: ./main [options] squarePositional arguments: square display a square of a given number
Optional arguments: -h, --help show this help message and exit -v, --verbose enable verbose logging
You may also get the help message in string via
program.help().str().
ArgumentParser objects usually associate a single command-line argument with a single action to be taken. The
.nargsassociates a different number of command-line arguments with a single action. When using
nargs(N), N arguments from the command line will be gathered together into a list.
argparse::ArgumentParser program("main");program.add_argument("--input_files") .help("The list of input files") .nargs(2);
try { program.parse_args(argc, argv); // Example: ./main --input_files config.yml System.xml } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
auto files = program.get<:vector>>("--input_files"); // {"config.yml", "System.xml"} </:vector>
ArgumentParser.get()has specializations for
std::vectorand
std::list. So, the following variant,
.get<:list>, will also work.
auto files = program.get<:list>>("--input_files"); // {"config.yml", "System.xml"}
Using
.action, one can quickly build a list of desired value types from command line arguments. Here's an example:
argparse::ArgumentParser program("main");program.add_argument("--query_point") .help("3D query point") .nargs(3) .default_value(std::vector{0.0, 0.0, 0.0}) .action([](const std::string& value) { return std::stod(value); });
try { program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2 } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
auto query_point = program.get<:vector>>("--query_point"); // {3.5, 4.7, 9.2} </:vector>
Compound arguments are optional arguments that are combined and provided as a single argument. Example:
ps -aux
argparse::ArgumentParser program("test");program.add_argument("-a") .default_value(false) .implicit_value(true);
program.add_argument("-b") .default_value(false) .implicit_value(true);
program.add_argument("-c") .nargs(2) .default_value(std::vector{0.0f, 0.0f}) .action([](const std::string& value) { return std::stof(value); });
try { program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47 } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
auto a = program.get("-a"); // true auto b = program.get("-b"); // true auto c = program.get<:vector>>("-c"); // {1.95, 2.47}
/// Some code that prints parsed arguments </:vector>
$ ./main -ac 3.14 2.718 a = true b = false c = {3.14, 2.718}$ ./main -cb a = false b = true c = {0.0, 0.0}
Here's what's happening: * We have three optional arguments
-a,
-band
-c. *
-aand
-bare toggle arguments. *
-crequires 2 floating point numbers from the command-line. * argparse can handle compound arguments, e.g.,
-abcor
-bacor
-cab. This only works with short single-character argument names. -
-aand
-bbecome true. - argv is further parsed to identify the inputs mapped to
-c. - If argparse cannot find any arguments to map to c, then c defaults to {0.0, 0.0} as defined by
.default_value
argparsesupports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
$ 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.
argparse::ArgumentParser program("compiler");program.add_argument("files")
.remaining();try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
std::cout << err.what() << std::endl;
std::cout << program;
exit(0);
}try { auto files = program.get<:vector>>("files"); std::cout << files.size() << " files provided" << std::endl;
for (auto& file : files)
std::cout << file << std::endl; } catch (std::logic_error& e) { std::cout << "No files provided" << std::endl; }
</:vector>
When no arguments are provided:
$ ./compiler No files provided
and when multiple arguments are provided:
$ ./compiler foo.txt bar.txt baz.txt 3 files provided foo.txt bar.txt baz.txt
The process of gathering remaining arguments plays nicely with optional arguments too:
argparse::ArgumentParser program("compiler");program.add_arguments("-o") .default_value(std::string("a.out"));
program.add_argument("files")
.remaining();try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
std::cout << err.what() << std::endl;
std::cout << program;
exit(0);
}auto output_filename = program.get<:string>("-o"); std::cout << "Output filename: " << output_filename << std::endl;
try { auto files = program.get<:vector>>("files"); std::cout << files.size() << " files provided" << std::endl;
for (auto& file : files)
std::cout << file << std::endl; } catch (std::logic_error& e) { std::cout << "No files provided" << std::endl; }</:vector></:string>
$ ./compiler -o main foo.cpp bar.cpp baz.cpp Output filename: main 3 files provided foo.cpp bar.cpp 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:
$ ./compiler foo.cpp bar.cpp baz.cpp -o main 5 arguments provided foo.cpp bar.cpp baz.cpp -o main
Sometimes, several parsers share a common set of arguments. Rather than repeating the definitions of these arguments, a single parser with all the common arguments can be added as a parent to another ArgumentParser instance. The
.add_parentsmethod takes a list of ArgumentParser objects, collects all the positional and optional actions from them, and adds these actions to the ArgumentParser object being constructed:
argparse::ArgumentParser parent_parser("main"); parent_parser.add_argument("--parent") .default_value(0) .action([](const std::string& value) { return std::stoi(value); });argparse::ArgumentParser foo_parser("foo"); foo_parser.add_argument("foo"); foo_parser.add_parents(parent_parser); foo_parser.parse_args({ "./main", "--parent", "2", "XXX" }); // parent = 2, foo = XXX
argparse::ArgumentParser bar_parser("bar"); bar_parser.add_argument("--bar"); bar_parser.parse_args({ "./main", "--bar", "YYY" }); // bar = YYY
Note You must fully initialize the parsers before passing them via
.add_parents. If you change the parent parsers after the child parser, those changes will not be reflected in the child.
argparse::ArgumentParser program("json_test");program.add_argument("config") .action([](const std::string& value) { // read a JSON file std::ifstream stream(value); nlohmann::json config_json; stream >> config_json; return config_json; });
try { program.parse_args({"./test", "config.json"}); } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
nlohmann::json config = program.get<:json>("config"); </:json>
argparse::ArgumentParser program("test");program.add_argument("numbers") .nargs(3) .action([](const std::string& value) { return std::stoi(value); });
program.add_argument("-a") .default_value(false) .implicit_value(true);
program.add_argument("-b") .default_value(false) .implicit_value(true);
program.add_argument("-c") .nargs(2) .action([](const std::string& value) { return std::stof(value); });
program.add_argument("--files") .nargs(3);
try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
auto numbers = program.get<:vector>>("numbers"); // {1, 2, 3} auto a = program.get("-a"); // true auto b = program.get("-b"); // true auto c = program.get<:vector>>("-c"); // {3.14f, 2.718f} auto files = program.get<:vector>>("--files"); // {"a.txt", "b.txt", "c.txt"}
/// Some code that prints parsed arguments </:vector></:vector></:vector>
$ ./main 1 2 3 -abc 3.14 2.718 --files a.txt b.txt c.txt numbers = {1, 2, 3} a = true b = true c = {3.14, 2.718} files = {"a.txt", "b.txt", "c.txt"}
argparse::ArgumentParser program("test");program.add_argument("input") .default_value("baz") .action([](const std::string& value) { static const std::vector<:string> choices = { "foo", "bar", "baz" }; if (std::find(choices.begin(), choices.end(), value) != choices.end()) { return value; } return std::string{ "baz" }; });
try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; exit(0); }
auto input = program.get("input"); std::cout << input << std::endl; </:string>
$ ./main fex baz
| Compiler | Standard Library | Test Environment | | :------------------- | :--------------- | :----------------- | | GCC >= 8.3.0 | libstdc++ | Ubuntu 18.04 | | Clang >= 7.0.0 | libc++ | Xcode 10.2 | | MSVC >= 14.16 | Microsoft STL | Visual Studio 2017 |
Contributions are welcome, have a look at the CONTRIBUTING.md document for more information.
Thanks goes to these wonderful people:
svanveen |
Zhihao Yuan |
Mio |
zhihaoy |
Jack Clarke |
Daniel Marshall |
mupp |
Ethan Slattery |
The project is available under the MIT license.