option=value
syntaxSimply include argparse.hpp and you're good to go.
#include <argparse/argparse.hpp>
To start parsing command-line arguments, create an ArgumentParser
.
argparse::ArgumentParser program("program_name");
NOTE: There is an optional second argument to the ArgumentParser
which is the program version. Example: argparse::ArgumentParser program("libfoo", "1.9.0");
NOTE: There are optional third and fourth arguments to the ArgumentParser
which control default arguments. Example: argparse::ArgumentParser program("libfoo", "1.9.0", default_arguments::help, false);
See Default Arguments, below.
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., -v
and --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:
#include <argparse/argparse.hpp>
int main(int argc, char *argv[]) {
argparse::ArgumentParser program("program_name");
program.add_argument("square")
.help("display the square of a given integer")
.scan<'i', int>();
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
}
auto input = program.get<int>("square");
std::cout << (input * input) << std::endl;
return 0;
}
And running the code:
foo@bar:/home/dev/$ ./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..scan
method to convert user input into an integer.parser.get<T>(key)
method.Now, let's look at optional arguments. Optional arguments start with -
or --
, e.g., --verbose
or -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::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
if (program["--verbose"] == true) {
std::cout << "Verbosity enabled" << std::endl;
}
foo@bar:/home/dev/$ ./main --verbose
Verbosity enabled
Here's what's happening:
--verbose
. Note that by using .default_value(false)
, if the optional argument isn’t used, it's value is automatically set to false..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.When defining flag arguments, you can use the shorthand flag()
which is the same as default_value(false).implicit_value(true)
.
argparse::ArgumentParser program("test");
program.add_argument("--verbose")
.help("increase output verbosity")
.flag();
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
if (program["--verbose"] == true) {
std::cout << "Verbosity enabled" << std::endl;
}
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 present
method also accepts a template argument. But rather than returning T
, parser.present<T>(key)
returns std::optional<T>
, so that when the user does not provide a value to this parameter, the return value compares equal to std::nullopt
.
If you want to know whether the user supplied a value for an argument that has a .default_value
, check whether the argument .is_used()
.
program.add_argument("--color")
.default_value(std::string{"orange"}) // might otherwise be type const char* leading to an error when trying program.get<std::string>
.help("specify the cat's fur color");
try {
program.parse_args(argc, argv); // Example: ./main --color orange
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
auto color = program.get<std::string>("--color"); // "orange"
auto explicit_color = program.is_used("--color"); // true, user provided orange
You may want to allow an optional argument to be repeated and gather all values in one place.
program.add_argument("--color")
.default_value<std::vector<std::string>>({ "orange" })
.append()
.help("specify the cat's fur color");
try {
program.parse_args(argc, argv); // Example: ./main --color red --color green --color blue
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
auto colors = program.get<std::vector<std::string>>("--color"); // {"red", "green", "blue"}
Notice that .default_value
is given an explicit template parameter to match the type you want to .get
.
A common pattern is to repeat an argument to indicate a greater value.
int verbosity = 0;
program.add_argument("-V", "--verbose")
.action([&](const auto &) { ++verbosity; })
.append()
.default_value(false)
.implicit_value(true)
.nargs(0);
program.parse_args(argc, argv); // Example: ./main -VVVV
std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4
Create a mutually exclusive group using program.add_mutually_exclusive_group(required = false)
. `argparse`` will make sure that only one of the arguments in the mutually exclusive group was present on the command line:
auto &group = program.add_mutually_exclusive_group();
group.add_argument("--first");
group.add_argument("--second");
with the following usage will yield an error:
foo@bar:/home/dev/$ ./main --first 1 --second 2
Argument '--second VAR' not allowed with '--first VAR'
The add_mutually_exclusive_group()
function also accepts a required
argument, to indicate that at least one of the mutually exclusive arguments is required:
auto &group = program.add_mutually_exclusive_group(true);
group.add_argument("--first");
group.add_argument("--second");
with the following usage will yield an error:
foo@bar:/home/dev/$ ./main
One of the arguments '--first VAR' or '--second VAR' is required
It is possible to bind arguments to a variable storing their value, as an
alternative to explicitly calling program.get<T>(arg_name)
or program[arg_name]
This is currently implementeted for variables of type bool
(this also
implicitly calls flag()
), int
, double
, std::string
,
std::vector<std::string>
and std::vector<int>
.
If the argument is not specified in the command
line, the default value (if set) is set into the variable.
bool flagvar = false;
program.add_argument("--flagvar").store_into(flagvar);
int intvar = 0;
program.add_argument("--intvar").store_into(intvar);
double doublevar = 0;
program.add_argument("--doublevar").store_into(doublevar);
std::string strvar;
program.add_argument("--strvar").store_into(strvar);
std::vector<std::string> strvar_repeated;
program.add_argument("--strvar-repeated").append().store_into(strvar_repeated);
std::vector<std::string> strvar_multi_valued;
program.add_argument("--strvar-multi-valued").nargs(2).store_into(strvar_multi_valued);
std::vector<int> intvar_repeated;
program.add_argument("--intvar-repeated").append().store_into(intvar_repeated);
std::vector<int> intvar_multi_valued;
program.add_argument("--intvar-multi-valued").nargs(2).store_into(intvar_multi_valued);
Optional arguments start with -
. Can argparse
handle negative numbers? The answer is yes!
argparse::ArgumentParser program;
program.add_argument("integer")
.help("Input number")
.scan<'i', int>();
program.add_argument("floats")
.help("Vector of floats")
.nargs(4)
.scan<'g', float>();
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
// Some code to print arguments
foo@bar:/home/dev/$ ./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, argparse
supports negative integers, negative floats and scientific notation.
argparse::ArgumentParser program("main");
program.add_argument("square")
.help("display the square of a given number")
.scan<'i', int>();
program.add_argument("--verbose")
.default_value(false)
.implicit_value(true);
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
int input = program.get<int>("square");
if (program["--verbose"] == true) {
std::cout << "The square of " << input << " is " << (input * input) << std::endl;
}
else {
std::cout << (input * input) << std::endl;
}
foo@bar:/home/dev/$ ./main 4
16
foo@bar:/home/dev/$ ./main 4 --verbose
The square of 4 is 16
foo@bar:/home/dev/$ ./main --verbose 4
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:
foo@bar:/home/dev/$ ./main --help
Usage: main [-h] [--verbose] square
Positional arguments:
square display the square of a given number
Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
--verbose
You may also get the help message in string via program.help().str()
.
ArgumentParser::add_description
will add text before the detailed argument
information. ArgumentParser::add_epilog
will add text after all other help output.
#include <argparse/argparse.hpp>
int main(int argc, char *argv[]) {
argparse::ArgumentParser program("main");
program.add_argument("thing").help("Thing to use.").metavar("THING");
program.add_argument("--member").help("The alias for the member to pass to.").metavar("ALIAS");
program.add_argument("--verbose").default_value(false).implicit_value(true);
program.add_description("Forward a thing to the next member.");
program.add_epilog("Possible things include betingalw, chiz, and res.");
program.parse_args(argc, argv);
std::cout << program << std::endl;
}
Usage: main [-h] [--member ALIAS] [--verbose] THING
Forward a thing to the next member.
Positional arguments:
THING Thing to use.
Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
--member ALIAS The alias for the member to pass to.
--verbose
Possible things include betingalw, chiz, and res.
ArgumentParser objects usually associate a single command-line argument with a single action to be taken. The .nargs
associates 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::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
auto files = program.get<std::vector<std::string>>("--input_files"); // {"config.yml", "System.xml"}
ArgumentParser.get<T>()
has specializations for std::vector
and std::list
. So, the following variant, .get<std::list>
, will also work.
auto files = program.get<std::list<std::string>>("--input_files"); // {"config.yml", "System.xml"}
Using .scan
, 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<double>{0.0, 0.0, 0.0})
.scan<'g', double>();
try {
program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
auto query_point = program.get<std::vector<double>>("--query_point"); // {3.5, 4.7, 9.2}
You can also make a variable length list of arguments with the .nargs
.
Below are some examples.
program.add_argument("--input_files")
.nargs(1, 3); // This accepts 1 to 3 arguments.
Some useful patterns are defined like "?", "*", "+" of argparse in Python.
program.add_argument("--input_files")
.nargs(argparse::nargs_pattern::any); // "*" in Python. This accepts any number of arguments including 0.
program.add_argument("--input_files")
.nargs(argparse::nargs_pattern::at_least_one); // "+" in Python. This accepts one or more number of arguments.
program.add_argument("--input_files")
.nargs(argparse::nargs_pattern::optional); // "?" in Python. This accepts an argument optionally.
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<float>{0.0f, 0.0f})
.scan<'g', float>();
try {
program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
auto a = program.get<bool>("-a"); // true
auto b = program.get<bool>("-b"); // true
auto c = program.get<std::vector<float>>("-c"); // {1.95, 2.47}
/// Some code that prints parsed arguments
foo@bar:/home/dev/$ ./main -ac 3.14 2.718
a = true
b = false
c = {3.14, 2.718}
foo@bar:/home/dev/$ ./main -cb
a = false
b = true
c = {0.0, 0.0}
Here's what's happening:
-a
, -b
and -c
.-a
and -b
are toggle arguments.-c
requires 2 floating point numbers from the command-line.-abc
or -bac
or -cab
. This only works with short single-character argument names.
-a
and -b
become true.-c
..default_value
For inputs, users can express a primitive type for the value.
The .scan<Shape, T>
method attempts to convert the incoming std::string
to T
following the Shape
conversion specifier. An std::invalid_argument
or std::range_error
exception is thrown for errors.
program.add_argument("-x")
.scan<'d', int>();
program.add_argument("scale")
.scan<'g', double>();
Shape
specifies what the input "looks like", and the type template argument specifies the return value of the predefined action. Acceptable types are floating point (i.e float, double, long double) and integral (i.e. signed char, short, int, long, long long).
The grammar follows std::from_chars
, but does not exactly duplicate it. For example, hexadecimal numbers may begin with 0x
or 0X
and numbers with a leading zero may be handled as octal values.
Shape | interpretation |
---|---|
'a' or 'A' | hexadecimal floating point |
'e' or 'E' | scientific notation (floating point) |
'f' or 'F' | fixed notation (floating point) |
'g' or 'G' | general form (either fixed or scientific) |
'd' | decimal |
'i' |
std::from_chars grammar with base == 10 |
'o' | octal (unsigned) |
'u' | decimal (unsigned) |
'x' or 'X' | hexadecimal (unsigned) |
argparse
provides predefined arguments and actions for -h
/--help
and -v
/--version
. By default, these actions will exit the program after displaying a help or version message, respectively. This exit does not call destructors, skipping clean-up of taken resources.
These default arguments can be disabled during ArgumentParser
creation so that you can handle these arguments in your own way. (Note that a program name and version must be included when choosing default arguments.)
argparse::ArgumentParser program("test", "1.0", default_arguments::none);
program.add_argument("-h", "--help")
.action([=](const std::string& s) {
std::cout << help().str();
})
.default_value(false)
.help("shows help message")
.implicit_value(true)
.nargs(0);
The above code snippet outputs a help message and continues to run. It does not support a --version
argument.
The default is default_arguments::all
for included arguments. No default arguments will be added with default_arguments::none
. default_arguments::help
and default_arguments::version
will individually add --help
and --version
.
The default arguments can be used while disabling the default exit with these arguments. This forth argument to ArgumentParser
(exit_on_default_arguments
) is a bool flag with a default true value. The following call will retain --help
and --version
, but will not exit when those arguments are used.
argparse::ArgumentParser program("test", "1.0", default_arguments::all, false)
argparse
supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
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.
argparse::ArgumentParser program("compiler");
program.add_argument("files")
.remaining();
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
try {
auto files = program.get<std::vector<std::string>>("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;
}
When no arguments are provided:
foo@bar:/home/dev/$ ./compiler
No files provided
and when multiple arguments are provided:
foo@bar:/home/dev/$ ./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: