Argument Parser for Modern C++ | Fix Xmake and Modules
Go to file
Pranav Srinivas Kumar fd884c340c
Update README.md
2019-03-31 17:53:40 -04:00
src Implemented Argument::operator== and Argument::operator!= with specializations for std::vector and std::list 2019-03-31 16:27:18 -04:00
tests Added test to square a number 2019-03-31 16:59:45 -04:00
.gitignore Added visual studio gitignore 2019-03-30 16:59:57 -04:00
LICENSE Added LICENSE 2019-03-29 18:41:37 -04:00
README.md Update README.md 2019-03-31 17:53:40 -04:00

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 arguments, optional arguments, toggle arguments 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({ "./main", "--verbose" });

if (program["--verbose'] == true) {
    std::cout << "Verbosity enabled" << std::endl;
}

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

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({"./main", "--input_files", "config.yml", "System.xml"});

auto files = program.get<std::vector<std::string>>("--input_files");  // {"config.yml", "System.xml"}

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

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 -abc and -bac. 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.
  • Using -cab will throw an error since argparse expects two inputs for the argument -c.
  • Notice how argparse is able to quietly and peacefully return an std::vector when asked for it.

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({ "./test.exe", "1", "-abc", "3.14", "2.718", "2", "--files",
  "a.txt", "b.txt", "c.txt", "3" });

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"}

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