argparse/README.md
Pranav Srinivas Kumar 3733ce170f
Update README.md
2019-04-01 19:54:48 -04:00

9.0 KiB
Raw Blame History

Argument Parser for Modern C++

Highlights

  • Header-only library
  • Requires C++17
  • MIT License

Quick Start

Simply include argparse.hpp and you're good to go.

#include <argparse.hpp>

To start parsing command-line arguments, create an ArgumentParser.

argparse::ArgumentParser program("program name");

Argparse supports a variety of argument types including positional, optional, and compound arguments.

Positional Arguments

Here's an example of a positional argument:

program.add_argument("square")
  .help("display the square of a given integer")
  .action([](const std::string& value) { auto integer = std::stoi(value); return integer * integer; });

program.parse_args(argc, argv);
std::cout << program.get<int>("square") << std::endl;

And running the code:

$ ./main 15
225

Here's what's happening:

  • The add_argument() method is used to specify which command-line options the program is willing to accept. In this case, Ive named it square so that its in line with its function.
  • Command-line arguments are strings. Inorder to square the argument and print the result, we need to convert this argument to a number. In order to do this, we use the .action method and provide a lambda function that takes the argument value (std::string) and returns the square of the number it represents. Actions are quite powerful as you will see in later examples.
  • Calling our program now requires us to specify an option.
  • The parse_args() method parses the arguments provided, converts our input into an integer and returns the square.
  • We can get the value stored by the parser for a given argument using parser.get<T>(key) method.

Optional Arguments

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);

program.parse_args(argc, argv);

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.
  • To show that the option is actually optional, there is no error when running the program without it. Note that by using .default_value(false), if the optional argument isnt 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.

Combining Positional and Optional Arguments

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);

program.parse_args(argc, argv);

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;
}
$ ./main 4
16

$ ./main 4 --verbose
The square of 4 is 16

$ ./main --verbose 4
The square of 4 is 16

List of Arguments

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);

program.parse_args(argc, argv);  // Example: ./main --input_files config.yml System.xml

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");  // Works! files = {"config.yml", "System.xml"}

Using .action, one can quickly build a list of custom objects from command line arguments. Here's an example:

argparse::ArgumentParser program("main");

program.add_argument("--query_point")
  .help("3D query point")
  .nargs(3)
  .action([](const std::string& value) { return std::stod(value); });

program.parse_args(argc, argv);  // Example: ./main --query_point 3.5 4.7 9.2

auto query_point = program.get<std::vector<double>>("--query_point");  // {3.5, 4.7, 9.2}

Compound Arguments

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})
  .action([](const std::string& value) { return std::stof(value); });

program.parse_args({ "./main", "-abc", "3.14", "2.718" });

auto a = program.get<bool>("-a");                // true
auto b = program.get<bool>("-b");                // true
auto c = program.get<std::vector<float>>("-c");  // {3.14f, 2.718f}

/// Some code that prints parsed arguments
$ ./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, -b and -c.
  • -a and -b are toggle arguments.
  • -c requires 2 floating point numbers from the command-line. You can specify how many inputs to expect using nargs.
  • argparse can handle compound arguments, e.g., -abc or -bac or -cab. This only works with short single-character argument names.
    • -a and -b become true.
    • argv is further parsed to identify the inputs mapped to -c.

Further Examples

Construct a JSON object from a filename argument

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;
  });

program.parse_args({"./test", "config.json"});

nlohmann::json config = program.get<nlohmann::json>("config");

Positional Arguments with Compound Toggle Arguments

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);

program.parse_args(argc, argv);

auto numbers = program.get<std::vector<int>>("numbers");        // {1, 2, 3}
auto a = program.get<bool>("-a");                               // true
auto b = program.get<bool>("-b");                               // true
auto c = program.get<std::vector<float>>("-c");                 // {3.14f, 2.718f}
auto files = program.get<std::vector<std::string>>("--files");  // {"a.txt", "b.txt", "c.txt"}

/// Some code that prints parsed arguments
./main 1 -abc 3.14 2.718 2 --files a.txt b.txt c.txt 3
numbers = {1, 2, 3}
a = true
b = true
c = {3.14, 2.718}
d = {"a.txt", "b.txt", "c.txt"}

Restricting the set of values for an argument

argparse::ArgumentParser program("test");

program.add_argument("input")
  .default_value("baz")
  .action([=](const std::string& value) {
    static const std::vector<std::string> choices = { "foo", "bar", "baz" };
    if (std::find(choices.begin(), choices.end(), value) != choices.end()) {
      return value;
    }
    return std::string{ "baz" };
  });

program.parse_args({ "./test", "fez" });

auto input = program.get("input"); // baz
std::cout << input << std::endl;
$ ./main fex
baz

Contributing

Contributions are welcomed, have a look at the CONTRIBUTING.md document for more information.

License

The project is available under the MIT license.