Coder Social home page Coder Social logo

p-ranav / argparse Goto Github PK

View Code? Open in Web Editor NEW
2.6K 31.0 242.0 873 KB

Argument Parser for Modern C++

License: MIT License

C++ 98.35% CMake 1.02% Python 0.04% Batchfile 0.04% Shell 0.03% Lua 0.42% Starlark 0.10%
cpp17 header-only library argument-parser cross-platform mit-license

argparse's Introduction

argparse

license version

Highlights

  • Single header file
  • Requires C++17
  • MIT License

Table of Contents

Quick Start

Simply 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:

Positional Arguments

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:

  • The 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.
  • Command-line arguments are strings. 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 .scan method to convert user input into an integer.
  • 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);

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:

  • 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.

Flag

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

Requiring optional arguments

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

Accessing optional arguments without default values

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.

Deciding if the value was given by the user

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

Joining values of repeated optional arguments

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.

Repeating an argument to increase a value

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

Mutually Exclusive Group

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

Storing values into variables

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

Negative Numbers

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.

Combining Positional and Optional Arguments

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

Printing Help

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

Adding a description and an epilog to help

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.

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

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

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:

  • 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.
  • 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.
    • If argparse cannot find any arguments to map to c, then c defaults to {0.0, 0.0} as defined by .default_value

Converting to Numeric Types

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)

Default Arguments

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)

Gathering Remaining Arguments

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:

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::exception& err) {
  std::cerr << err.what() << std::endl;
  std::cerr << program;
  std::exit(1);
}

auto output_filename = program.get<std::string>("-o");
std::cout << "Output filename: " << output_filename << std::endl;

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;
}
foo@bar:/home/dev/$ ./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:

foo@bar:/home/dev/$ ./compiler foo.cpp bar.cpp baz.cpp -o main
5 arguments provided
foo.cpp
bar.cpp
baz.cpp
-o
main

Parent Parsers

A parser may use arguments that could be used by other parsers.

These shared arguments can be added to a parser which is then used as a "parent" for parsers which also need those arguments. One or more parent parsers may be added to a parser with .add_parents. The positional and optional arguments in each parent is added to the child parser.

argparse::ArgumentParser surface_parser("surface", "1.0", argparse::default_arguments::none);
surface_parser.add_argument("--area")
  .default_value(0)
  .scan<'i', int>();

argparse::ArgumentParser floor_parser("floor");
floor_parser.add_argument("tile_size").scan<'i', int>();
floor_parser.add_parents(surface_parser);
floor_parser.parse_args({ "./main", "--area", "200", "12" });  // --area = 200, tile_size = 12

argparse::ArgumentParser ceiling_parser("ceiling");
ceiling_parser.add_argument("--color");
ceiling_parser.add_parents(surface_parser);
ceiling_parser.parse_args({ "./main", "--color", "gray" });  // --area = 0, --color = "gray"

Changes made to parents after they are added to a parser are not reflected in any child parsers. Completely initialize parent parsers before adding them to a parser.

Each parser will have the standard set of default arguments. Disable the default arguments in parent parsers to avoid duplicate help output.

Subcommands

Many programs split up their functionality into a number of sub-commands, for example, the git program can invoke sub-commands like git checkout, git add, and git commit. Splitting up functionality this way can be a particularly good idea when a program performs several different functions which require different kinds of command-line arguments. ArgumentParser supports the creation of such sub-commands with the add_subparser() member function.

#include <argparse/argparse.hpp>

int main(int argc, char *argv[]) {
  argparse::ArgumentParser program("git");

  // git add subparser
  argparse::ArgumentParser add_command("add");
  add_command.add_description("Add file contents to the index");
  add_command.add_argument("files")
    .help("Files to add content from. Fileglobs (e.g.  *.c) can be given to add all matching files.")
    .remaining();

  // git commit subparser
  argparse::ArgumentParser commit_command("commit");
  commit_command.add_description("Record changes to the repository");
  commit_command.add_argument("-a", "--all")
    .help("Tell the command to automatically stage files that have been modified and deleted.")
    .default_value(false)
    .implicit_value(true);

  commit_command.add_argument("-m", "--message")
    .help("Use the given <msg> as the commit message.");

  // git cat-file subparser
  argparse::ArgumentParser catfile_command("cat-file");
  catfile_command.add_description("Provide content or type and size information for repository objects");
  catfile_command.add_argument("-t")
    .help("Instead of the content, show the object type identified by <object>.");

  catfile_command.add_argument("-p")
    .help("Pretty-print the contents of <object> based on its type.");

  // git submodule subparser
  argparse::ArgumentParser submodule_command("submodule");
  submodule_command.add_description("Initialize, update or inspect submodules");
  argparse::ArgumentParser submodule_update_command("update");
  submodule_update_command.add_description("Update the registered submodules to match what the superproject expects");
  submodule_update_command.add_argument("--init")
    .default_value(false)
    .implicit_value(true);
  submodule_update_command.add_argument("--recursive")
    .default_value(false)
    .implicit_value(true);
  submodule_command.add_subparser(submodule_update_command);

  program.add_subparser(add_command);
  program.add_subparser(commit_command);
  program.add_subparser(catfile_command);
  program.add_subparser(submodule_command);

  try {
    program.parse_args(argc, argv);
  }
  catch (const std::exception& err) {
    std::cerr << err.what() << std::endl;
    std::cerr << program;
    return 1;
  }

  // Use arguments
}
foo@bar:/home/dev/$ ./git --help
Usage: git [-h] {add,cat-file,commit,submodule}

Optional arguments:
  -h, --help   	shows help message and exits
  -v, --version	prints version information and exits

Subcommands:
  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

foo@bar:/home/dev/$ ./git add --help
Usage: add [-h] files

Add file contents to the index

Positional arguments:
  files        	Files to add content from. Fileglobs (e.g.  *.c) can be given to add all matching files.

Optional arguments:
  -h, --help   	shows help message and exits
  -v, --version	prints version information and exits

foo@bar:/home/dev/$ ./git commit --help
Usage: commit [-h] [--all] [--message VAR]

Record changes to the repository

Optional arguments:
  -h, --help   	shows help message and exits
  -v, --version	prints version information and exits
  -a, --all    	Tell the command to automatically stage files that have been modified and deleted.
  -m, --message	Use the given <msg> as the commit message.

foo@bar:/home/dev/$ ./git submodule --help
Usage: submodule [-h] {update}

Initialize, update or inspect submodules

Optional arguments:
  -h, --help   	shows help message and exits
  -v, --version	prints version information and exits

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 the .is_subcommand_used("<command_name>") and .is_subcommand_used(subparser) member functions to check if a subcommand was used.

Sometimes there may be a need to hide part of the subcommands from the user by suppressing information about them in an help message. To do this, ArgumentParser contains the method .set_suppress(bool suppress):

argparse::ArgumentParser program("test");

argparse::ArgumentParser hidden_cmd("hidden");
hidden_cmd.add_argument("files").remaining();
hidden_cmd.set_suppress(true);

program.add_subparser(hidden_cmd);
foo@bar:/home/dev/$ ./main -h
Usage: test [--help] [--version] {}

Optional arguments:
  -h, --help    shows help message and exits
  -v, --version prints version information and exits

foo@bar:/home/dev/$ ./main hidden -h
Usage: hidden [--help] [--version] files

Positional arguments:
  files         [nargs: 0 or more]

Optional arguments:
  -h, --help    shows help message and exits
  -v, --version prints version information and exits

Getting Argument and Subparser Instances

Argument and ArgumentParser instances added to an ArgumentParser can be retrieved with .at<T>(). The default return type is Argument.

argparse::ArgumentParser program("test");

program.add_argument("--dir");
program.at("--dir").default_value(std::string("/home/user"));

program.add_subparser(argparse::ArgumentParser{"walk"});
program.at<argparse::ArgumentParser>("walk").add_argument("depth");

Parse Known Args

Sometimes a program may only parse a few of the command-line arguments, passing the remaining arguments on to another script or program. In these cases, the parse_known_args() function can be useful. It works much like parse_args() except that it does not produce an error when extra arguments are present. Instead, it returns a list of remaining argument strings.

#include <argparse/argparse.hpp>
#include <cassert>

int main(int argc, char *argv[]) {
  argparse::ArgumentParser program("test");
  program.add_argument("--foo").implicit_value(true).default_value(false);
  program.add_argument("bar");

  auto unknown_args =
    program.parse_known_args({"test", "--foo", "--badger", "BAR", "spam"});

  assert(program.get<bool>("--foo") == true);
  assert(program.get<std::string>("bar") == std::string{"BAR"});
  assert((unknown_args == std::vector<std::string>{"--badger", "spam"}));
}

Hidden argument and alias

It is sometimes desirable to offer an alias for an argument, but without it appearing it in the usage. For example, to phase out a deprecated wording of an argument while not breaking backwards compatible. This can be done with the ``ArgumentParser::add_hidden_alias_for()` method.

argparse::ArgumentParser program("test");

auto &arg = program.add_argument("--suppress").flag();
program.add_hidden_alias_for(arg, "--supress"); // old misspelled alias

The Argument::hidden() method can also be used to prevent a (generally optional) argument from appearing in the usage or help.

argparse::ArgumentParser program("test");

program.add_argument("--non-documented").flag().hidden();

This can also be used on positional arguments, but in that later case it only makes sense in practice for the last ones.

ArgumentParser in bool Context

An ArgumentParser is false until it (or one of its subparsers) have extracted known value(s) with .parse_args or .parse_known_args. When using .parse_known_args, unknown arguments will not make a parser true.

Custom Prefix Characters

Most command-line options will use - as the prefix, e.g. -f/--foo. Parsers that need to support different or additional prefix characters, e.g. for options like +f or /foo, may specify them using the set_prefix_chars().

The default prefix character is -.

#include <argparse/argparse.hpp>
#include <cassert>

int main(int argc, char *argv[]) {
  argparse::ArgumentParser program("test");
  program.set_prefix_chars("-+/");

  program.add_argument("+f");
  program.add_argument("--bar");
  program.add_argument("/foo");

  try {
    program.parse_args(argc, argv);
  }
  catch (const std::exception& err) {
    std::cerr << err.what() << std::endl;
    std::cerr << program;
    return 1;
  }

  if (program.is_used("+f")) {
    std::cout << "+f    : " << program.get("+f") << "\n";
  }

  if (program.is_used("--bar")) {
    std::cout << "--bar : " << program.get("--bar") << "\n";
  }

  if (program.is_used("/foo")) {
    std::cout << "/foo  : " << program.get("/foo") << "\n";
  }  
}
foo@bar:/home/dev/$ ./main +f 5 --bar 3.14f /foo "Hello"
+f    : 5
--bar : 3.14f
/foo  : Hello

Custom Assignment Characters

In addition to prefix characters, custom 'assign' characters can be set. This setting is used to allow invocations like ./test --foo=Foo /B:Bar.

The default assign character is =.

#include <argparse/argparse.hpp>
#include <cassert>

int main(int argc, char *argv[]) {
  argparse::ArgumentParser program("test");
  program.set_prefix_chars("-+/");
  program.set_assign_chars("=:");

  program.add_argument("--foo");
  program.add_argument("/B");

  try {
    program.parse_args(argc, argv);
  }
  catch (const std::exception& err) {
    std::cerr << err.what() << std::endl;
    std::cerr << program;
    return 1;
  }

  if (program.is_used("--foo")) {
    std::cout << "--foo : " << program.get("--foo") << "\n";
  }

  if (program.is_used("/B")) {
    std::cout << "/B    : " << program.get("/B") << "\n";
  }
}
foo@bar:/home/dev/$ ./main --foo=Foo /B:Bar
--foo : Foo
/B    : Bar

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

try {
  program.parse_args({"./test", "config.json"});
}
catch (const std::exception& err) {
  std::cerr << err.what() << std::endl;
  std::cerr << program;
  std::exit(1);
}

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

Positional Arguments with Compound Toggle Arguments

argparse::ArgumentParser program("test");

program.add_argument("numbers")
  .nargs(3)
  .scan<'i', int>();

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)
  .scan<'g', float>();

program.add_argument("--files")
  .nargs(3);

try {
  program.parse_args(argc, argv);
}
catch (const std::exception& err) {
  std::cerr << err.what() << std::endl;
  std::cerr << program;
  std::exit(1);
}

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
foo@bar:/home/dev/$ ./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"}

Restricting the set of values for an argument

argparse::ArgumentParser program("test");

program.add_argument("input")
  .default_value(std::string{"baz"})
  .choices("foo", "bar", "baz");

try {
  program.parse_args(argc, argv);
}
catch (const std::exception& err) {
  std::cerr << err.what() << std::endl;
  std::cerr << program;
  std::exit(1);
}

auto input = program.get("input");
std::cout << input << std::endl;
foo@bar:/home/dev/$ ./main fex
Invalid argument "fex" - allowed options: {foo, bar, baz}

Using choices also works with integer types, e.g.,

argparse::ArgumentParser program("test");

program.add_argument("input")
  .default_value(0)
  .choices(0, 1, 2, 3, 4, 5);

try {
  program.parse_args(argc, argv);
}
catch (const std::exception& err) {
  std::cerr << err.what() << std::endl;
  std::cerr << program;
  std::exit(1);
}

auto input = program.get("input");
std::cout << input << std::endl;
foo@bar:/home/dev/$ ./main 6
Invalid argument "6" - allowed options: {0, 1, 2, 3, 4, 5}

Using option=value syntax

#include "argparse.hpp"
#include <cassert>

int main(int argc, char *argv[]) {
  argparse::ArgumentParser program("test");
  program.add_argument("--foo").implicit_value(true).default_value(false);
  program.add_argument("--bar");

  try {
    program.parse_args(argc, argv);
  }
  catch (const std::exception& err) {
    std::cerr << err.what() << std::endl;
    std::cerr << program;
    return 1;
  }

  if (program.is_used("--foo")) {
    std::cout << "--foo: " << std::boolalpha << program.get<bool>("--foo") << "\n";
  }

  if (program.is_used("--bar")) {
    std::cout << "--bar: " << program.get("--bar") << "\n";
  }  
}
foo@bar:/home/dev/$ ./test --bar=BAR --foo
--foo: true
--bar: BAR

Advanced usage formatting

By default usage is reported on a single line.

The ArgumentParser::set_usage_max_line_width(width) method can be used to display the usage() on multiple lines, by defining the maximum line width.

It can be combined with a call to ArgumentParser::set_usage_break_on_mutex() to ask grouped mutually exclusive arguments to be displayed on a separate line.

ArgumentParser::add_usage_newline() can also be used to force the next argument to be displayed on a new line in the usage output.

The following snippet

    argparse::ArgumentParser program("program");
    program.set_usage_max_line_width(80);
    program.set_usage_break_on_mutex();
    program.add_argument("--quite-long-option-name").flag();
    auto &group = program.add_mutually_exclusive_group();
    group.add_argument("-a").flag();
    group.add_argument("-b").flag();
    program.add_argument("-c").flag();
    program.add_argument("--another-one").flag();
    program.add_argument("-d").flag();
    program.add_argument("--yet-another-long-one").flag();
    program.add_argument("--will-go-on-new-line").flag();
    program.add_usage_newline();
    program.add_argument("--new-line").flag();
    std::cout << program.usage() << std::endl;

will display:

Usage: program [--help] [--version] [--quite-long-option-name]
               [[-a]|[-b]]
               [-c] [--another-one] [-d] [--yet-another-long-one]
               [--will-go-on-new-line]
               [--new-line]

Furthermore arguments can be separated into several groups by calling ArgumentParser::add_group(group_name). Only optional arguments should be specified after the first call to add_group().

    argparse::ArgumentParser program("program");
    program.set_usage_max_line_width(80);
    program.add_argument("-a").flag().help("help_a");
    program.add_group("Advanced options");
    program.add_argument("-b").flag().help("help_b");

will display:

Usage: program [--help] [--version] [-a]

Advanced options:
               [-b]

Developer Notes

Copying and Moving

argparse::ArgumentParser is intended to be used in a single function - setup everything and parse arguments in one place. Attempting to move or copy invalidates internal references (issue #260). Thus, starting with v3.0, argparse::ArgumentParser copy and move constructors are marked as delete.

CMake Integration

Use the latest argparse in your CMake project without copying any content.

cmake_minimum_required(VERSION 3.14)

PROJECT(myproject)

# fetch latest argparse
include(FetchContent)
FetchContent_Declare(
    argparse
    GIT_REPOSITORY https://github.com/p-ranav/argparse.git
)
FetchContent_MakeAvailable(argparse)

add_executable(myproject main.cpp)
target_link_libraries(myproject argparse)

Bazel Integration

Add an http_archive in WORKSPACE.bazel, for example

http_archive(
    name = "argparse",
    sha256 = "674e724c2702f0bfef1619161815257a407e1babce30d908327729fba6ce4124",
    strip_prefix = "argparse-3.1",
    url = "https://github.com/p-ranav/argparse/archive/refs/tags/v3.1.zip",
)

Building, Installing, and Testing

# Clone the repository
git clone https://github.com/p-ranav/argparse
cd argparse

# Build the tests
mkdir build
cd build
cmake -DARGPARSE_BUILD_SAMPLES=on -DARGPARSE_BUILD_TESTS=on ..
make

# Run tests
./test/tests

# Install the library
sudo make install

Supported Toolchains

Compiler Standard Library Test Environment
GCC >= 8.3.0 libstdc++ Ubuntu 18.04
Clang >= 7.0.0 libc++ Xcode 10.2
MSVC >= 16.8 Microsoft STL Visual Studio 2019

Contributing

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

License

The project is available under the MIT license.

argparse's People

Contributors

aashwinr avatar aayush749 avatar abellgithub avatar arthapz avatar bedzior avatar bryanflynt avatar chuvi-w avatar cobyj33 avatar crustyauklet avatar ericonr avatar hokacci avatar ismagilli avatar jackojc avatar jun-sheaf avatar kfsone avatar kolanich avatar lichray avatar maciejpatro avatar marzer avatar ntr2024 avatar p-ranav avatar richardbrown384 avatar rouault avatar rysson avatar sam4uk avatar skrobinson avatar stripe2933 avatar svanveen avatar wtdcode avatar zhihaoy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

argparse's Issues

Reify values of optional arguments into std::optional

Semantically, an optional argument is "optional" to present. But currently, if default_value isn't set, an optional argument's "not presented" state is not representable. Attempting to access the state (value) will get a logic_error. Representing the "not presented" state is often necessary because the option's type may leave no value for a good default_value. For example, an empty string may be an valid argument to an option.

Python argparse has no such problem because any option may be None. Universally Nullable is a problem, but allowing None for specific types is adopted by many languages. In C++ it's std::optional<T>.

I suggest to add a program.present<T>("--option") API in addition to the existing program.get<T>("--option") API. The new API returns std::optional<T> by reifying T's possible values and the "not presented" state. Its usage looks like the following:

if (auto v = program.present<int>("-n")) {
  do_something_with(*v);
}

The name "present" is taken from Java's Optional.ifPresent. Bike-shedding is mildly welcome.

Negative integers and floats.

Is it possible to input negative integers or floats? From what I can see -1 or -1.2e3 are being treated as optional arguments. Maybe std::is_integral and std::is_floating_point could be used to first test for integrals or floating points before testing for an optional argument.

Switch to doctest and use parallel build

Unit tests compile too slow. We should compile different test translation units in parallel. Including and compile catch2 header 20 times is still, but to doctest it isn't an issue. By doing so, when changing a test file, you don't have to recompile all the tests.

Issues with implicit values

I am currently running into some issues with implicit values (both happening and not happening). Here is my code:

m_bfm.add_argument("-l", "--load")
    .help("load a VMM into the kernel");

m_bfm.add_argument("-u", "--unload")
    .default_value(false)
    .implicit_value(true)
    .help("unload a previously loaded VMM");

m_bfm.add_argument("-x", "--start")
    .default_value(false)
    .implicit_value(true)
    .help("start a previously loaded VMM");

m_bfm.add_argument("-s", "--stop")
    .default_value(false)
    .implicit_value(true)
    .help("stop a previously started VMM");

m_bfm.add_argument("-d", "--dump")
    .default_value(false)
    .implicit_value(true)
    .help("output the contents of the VMM's debug buffer");

m_bfm.add_argument("-m", "--mem")
    .default_value(default_heap_size)
    .action([](const std::string &val) { return std::stoull(val); })
    .help("memory in MB to give the VMM when loading");

m_bfm.parse_args(argc, argv);

I am currently seeing two different issues:

  • The long versions seem to work fine, but the short versions do not. That is, if I use the short versions I get a "No value provided" error. The long versions do not have this issue.
  • The "--mem" option doesn't have an implicit value, just a default value. If I do something like "--mem" it knows that a value is missing, if I do "--load blah --mem" it doesn't know that a value is missing. The correct input should be "--mem 64" or "--load blah --mem 64".

A way to require "optional" arguments to present

Currently it's very inconvenient to make an option in the form of -o output required. It is not checked if it does not present, if you get("-o") you get a logic error, which requires lots of post processing. There should be a way to declare that this argument must present. If not presents, exception; if only -o presents, status quo (a different exception).

Also, the current situation makes -o equivalent to not presenting if there is a default_value. I think it's not a very good interface. I tried to simulate a "all or nothing" default value with .implicit_value(x).nargs(1), but no luck.

Output default value with help text

Hi @p-ranav,

I think it would be nice, if you could print default values specified by "default_value" method when outputting the help text:

    program
        .add_argument("n")
        .help("Number of iterations to be performed")
        .default_value("10");

->

...
Positional arguments:
n               Number of messages to be sent (default: 10)

this would be really useful

Best regards
Philipp

no viable overloaded operator[] for type

Hello, compiler returns an error when trying to build the code. The problem seems related to the inclusion of the header in the cpp file.

#include <argparse.hpp>

Here's the error:

.../argparse/include/argparse.hpp:845:19: error:
no viable overloaded operator[] for type
'const argparse::ArgumentParser'
return (*this)[aArgumentName].get();
~~~~~~~^~~~~~~~~~~~~~

Allow argument alias

I am wondering whether we could add alias for arguments. For example, having --verbose as an argument, we can also allow -v as an alias.

So in the code, maybe we can do:

program.add_argument("--verbose")
  .default_value(false)
  .implicit_value(true)
  .alias("-v");

Any advice?

Add an example how to use it

I created an example by copy & pasting things from the README:

#include "include/argparse.hpp"

int main(int argc, char *argv[]) {
	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;
	}

	return 0;
}

And it seems to fail:

$ ./a.out 4
16
$ ./a.out --help
terminate called after throwing an instance of 'std::runtime_error'
  what():  help called
Aborted (core dumped)

Compiler diagnostic with clang from the LLVM 9 download package

clang generates the following diagnostic fault when compiling using this library:

C:\Src\NetworkedGraphics\out\packages\argparse-src\package\include\argparse\argparse.hpp(196,28): error G3F63BFAE: constexpr variable 'generic_strtod' must be initialized by a constant expression [clang-diagnostic-error]
template <> constexpr auto generic_strtod = strtof;
^
C:\Src\NetworkedGraphics\out\packages\argparse-src\package\include\argparse\argparse.hpp(197,28): error G3F63BFAE: constexpr variable 'generic_strtod' must be initialized by a constant expression [clang-diagnostic-error]
template <> constexpr auto generic_strtod = strtod;
^
C:\Src\NetworkedGraphics\out\packages\argparse-src\package\include\argparse\argparse.hpp(198,28): error G3F63BFAE: constexpr variable 'generic_strtod' must be initialized by a constant expression [clang-diagnostic-error]
template <> constexpr auto generic_strtod = strtold;
^

The quick fix appears to be to remove the indicated constexpr qualifier.

This was picked up running an msvc compile (vs 16.6.0) followed by a clang-tidy run (llvm 9.0). The diagnostic is produced by clang within clang-tidy. clang-tidy is being run with msvc build commands from the configured cmake build database (standard usage).

Consider parsing more option value syntax

First we need to define what are short options and long options.
Long options: options with names longer than a single character
Short options: options only one character long
Option name is anything after - or -- (or / for Windows style as a future extension).

  1. Allow short options to have their values concatenated to their names: -g3, -lzlib (if in the future we allow unified option prefix like a single / or -, we may want to disable this parsing mode)
  2. Allow long options to separate their names and values with = (or : for Windows style as a future extension): --foo=a.txt. This does not work for nargs>1.

See also https://docs.python.org/3/library/argparse.html#option-value-syntax

core dump on optional arg

if I add an optional arg with a single "-" something like "-o" but don't define how to handle it, I get a seg fault. If I handle it, it's fine. The issue is if the user ads a single - argument that is not supported it will cause a seg fault and there is nothing I can do on my end

Question: parent parser and value semantics

The mParentParsers member is currently unused. add_parents's semantics is only merging options. But consider the following: if there is only one parent parser, copy construct from an existing parser has the same effect. We only need a way to change the new instance's mProgramName.

Does ArgumentParser has value semantics? ArgumentParser is copy cunstructible, but a copy of ArgumentParser is not a distinct copy because shared_ptr are used without special handling. Parsing in the new parser will affect the old parser. But parent parser also has the "problem," unless this is by design.

If this is not by design, then we'd better to support value semantics in full in both copy-constructor and add_parents.

print_help should either print or return help string

But not both.

We may consider enabling

std::cerr << program;

So that user can use stringstream if they want to. To make it easier, we can even add a function help that returns a stringstream, so that cout << program is calling cout << program.help().rdbuf() and program.help().str() is the previous print_help()'s return value.

Consider deprecating print_help.

Mark getters const

void run_app(const argparse::ArgumentParser &program)
{
   // it's not possible to use get() or present() from here because of const
}

action that returns nothing

Currently this code isn't allowed in argparse:

program.add_argument(...)
    .action([](std::string const& filename)
    {
        load_cfg(filename);
    });

Because argparse internal expects action to return some value, but this one returns void.
My workaround for that is to define using unit = std::tuple<>; and returns unit(), but this isn't nice to C++'s taste.
But it's possible to bake this workaround into argparse (even though there is no any<void>). Let's say we define a type similar to the unit above, call is none_internal. In action, detects the the input's return type:

  template<class F>
  Argument &action(F&& f) {
    if constexpr (std::is_void_v<std::invoke_result_t<F, std::string>>)
      mAction = [f](std::string const& arg) { f(arg); return none_internal(); };
    else
      mAction = std::move(aAction);
    return *this;
  }

bind_front in action

action currently accept only a callable, but since pending member function call obj.fn isn't a callable object in C++, user can't even use a member function as action.
Note that mem_fn doesn't solve the problem. mem_fn is to wrap a callable object into a function object, but std::function ctor already does that. So user ends up with three choices:

  1. lambda
  2. the old and convoluted std::bind
  3. the new bind_front in C++20
    However, the standard library facilities have been solving this issue quite well for a long time. For example, you can use member call in std::thread ctor like this
thread(&Class::fn, obj)  // running obj.fn()

This is done as if forming a bind_front(&Class::fn, obj) function object and call it with empty argument list. We can do similar thing for action:

.action(&Class::mem, obj)  // calling obj.mem(arg_str)

by implicitly using bind_front for multi-argument calls to action.

Note: to get this working for C++17, we can include a homegrown version. Take a look at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0356r5.html#implementability and https://reviews.llvm.org/D60368 . It doesn't have to be perfect.

Infinite recursion on invalid argument

Code:

#include <iostream>

#include <argparse.hpp>

static bool load_xss_libs = true;

int main(int argc, char** argv) {
  argparse::ArgumentParser parser("awcsclient");
  
  parser.add_argument("--bootstrap")
    .help("Do not autoload XS# libraries")
    .default_value(false)
    .implicit_value(true);

  parser.parse_args(argc, argv);

  if (parser["--bootstrap"] == true) {
    load_xss_libs = false;
  }

  std::cout << load_xss_libs << std::endl;
}

A piece of stack trace from GDB:

#2963 0x000055555555a653 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, argc=2, argv=0x5555559640d0) at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:575
#2964 0x0000555555559c10 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, aArguments=std::vector of length 2, capacity 2 = {...})
    at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:507
#2965 0x000055555555a653 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, argc=2, argv=0x555555964050) at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:575
#2966 0x0000555555559c10 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, aArguments=std::vector of length 2, capacity 2 = {...})
    at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:507
#2967 0x000055555555a653 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, argc=2, argv=0x555555963fd0) at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:575
#2968 0x0000555555559c10 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, aArguments=std::vector of length 2, capacity 2 = {...})
    at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:507
#2969 0x000055555555a653 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, argc=2, argv=0x555555963f50) at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:575
#2970 0x0000555555559c10 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, aArguments=std::vector of length 2, capacity 2 = {...})
    at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:507

How to behave like ls?

The GNU ls utility allows its flags to be provided anywhere in the commandline, e.g.:

ls -l file1 file2 file3
ls file1 file2 file3 -l

If I understand correctly, .remaining(); does not allow that. Is there a workaround for this or another way to do it?

Linking errors when used in multiple places

I have encountered a linker error that I do not fully understand, and a workaround which I do not fully understand either.

I am using a pattern where I parse a global ArgumentParser in main() (ish) and then pass around my options (by non-const reference) to multiple translation units where the different parts of the program know what their option keys are and can retrieve their specialized values. This pattern requires including argparse.hpp in multiple source files so the definition of ArgumentParser is available in each place it is used. I am compiling with clang 9.0.1.

Everything compiles great this way, but fails to link. The problem is that all the template specializations, such as

 template <> constexpr bool standard_signed_integer<signed char> = true;

are multiply defined. Clang only tells me where they are initially defined (exactly one time) but also errors out because they are multiply defined. OK, then:

/usr/bin/ld: src/tickertape/CMakeFiles/tickertape.dir/timescaledb.cpp.o:(.rodata+0x8): multiple definition of `argparse::details::standard_unsigned_integer<unsigned long>'; src/tickertape/CMakeFiles/tickertape.dir/tickertape.cpp.o:(.rodata+0x8): first defined here

I find this confusing because I would have thought that constexpr would be, well, const, and perhaps not instantiated multiple times in a way that could not be resolved at link. I am no constexpr expert though, and clearly something else is happening.

I tried using inline:

inline template <> constexpr bool standard_signed_integer<signed char> = true;

which, to my constexpr-standard-naive mind looks silly, and indeed this had no effect.
I also tried reducing this to a declaration and defining the specialization exactly once (in a '.cpp' file):

template <> constexpr bool standard_signed_integer<signed char>;

which did not compile. Finally, I found that this guy is onto something: https://stackoverflow.com/a/47982008
who suggested to put these template specializations in an "unnamed namespace", which I think means that each object file gets their own copy and these have distinct names at link (avoiding the duplicate symbols). This worked.

So this is what I think:

  1. I don't know enough C++ generally, especially with regard to constexpr.
  2. argparse would ideally support being used in multiple object files linked together without too much hassle. I would actually prefer have to link argparse and lose the header-only benefits than hack around this linkage problem, but I suspect that we can have both features if we just get this constexpr thing right.

So thanks for the great software, but where am I messing this up?

Positional Arguments with Compound Toggle Arguments

I tested this example with the command line:

$ ./main 1 -abc 3.14 2.718 2 --files a.txt b.txt c.txt 3

and it fails to parse the options.

Keeping the positional arguments together as in:

$ ./main 1 2 3 -abc 3.14 2.71 --files a.txt b.txt c.txt

works.
This is where I noticed that negative numbers fail to parse. See: Issue 24

--help should print help and exit rather than throwing exceptions

From https://www.reddit.com/r/cpp/comments/dzm7lg/argparse_argument_parser_for_modern_c_v20_released/f8b1601?utm_source=share&utm_medium=web2x

Separately, I've noted about exceptions lightly [...] What is unacceptable is using exceptions for control flow. [...]

On a custom compiler, they may have a way to change all exception handling to calls to abort(). Even then, they ask for help, the program just ends. Nothing printed, even if the developer correctly handled it.

Versioning

Really neat library. Will you add tags for releases in the future?

Simplify parsing numeric arguments with predefined actions

Currently, we have no predefined actions at all (other than identity), and this is an obvious use case to improve. Asking users to write lambdas to parse raises the bar for use, and difficult to upgrade to a <charconv> future. For the most common inputs, users should be able to express what do they expect rather than how do they handle.

Python's argparse provides type=int and type=float, which do not take hexadecimal inputs and are lack of support for types of different ranges (int, long, etc.) We need to able to express both for C++ applications.

I propose to add a .scan method to the fluent interface, inspired by scanf. I took the name from scnlib. Usage looks like the following:

program.add_argument("-x")
       .scan<'d', int>();
program.add_argument("scale")
       .scan<'g', double>();

The non-type template argument specifies how the input "looks like" (where I call it shape), and the type template argument specifies what the return value of the predefined action is. The acceptable types are:

  • floating point: float, double, long double
  • integral (+ make_unsigned): signed char, short, int, long, long long

and the acceptable shapes are:

  • floating point:
    • 'a': hexadecimal floating-point
    • 'f': fixed form only
    • 'e': scientific form only
    • 'g': general form (either fixed or scientific)
  • integral:
    • 'd': decimal
    • 'u': decimal, requires an unsigned type
    • 'o': octal, requires an unsigned type
    • 'x': hexadecimal, requires an unsigned type
    • 'i': anything that from_chars's grammar accepts in base == 0

The exact grammar should follow from_chars's requirement. But our first implementation may still use strtol and strtod. When encounters errors, throw exceptions similar to what stoi and stod do.

FAQ

  1. Can I omit the type? No.
  2. Can I omit the shape? Not for these. from_chars and strto? default to parse anything, but Python's type=int and type=float only parse decimal. I'm not sure whether we can agree on a default. But when extending this for other types in the future, we may.
  3. How to extend this to other common types? Let's keep an eye on how the Text parsing proposal evolves.
  4. Why using non-type template parameters? So that we can select predefined actions at compile-time and go straight assigning one to mAction in each call to scan.
  5. Can auto non-type template parameter help? Sadly no. .scan<int('d')>() is okay but .scan<(unsigned long)'x'> is terrible.
  6. Can we support uppercase shapes like 'X'? What do you expect them to do? I guess letting them behave as same as the lowercase counterparts is the only reasonable answer. If we agree on that, I'm okay with it.

argparse usage question: can the Action class take different methods in it

Hi,
As of Python 3.8.2 argparse can have a custom action class defined and called to execute the default call method where we customize the type of actions that need to be done. Can we also include custom methods in the class ? so that I can call my add_argument as

parser.add_argument('-r', action=Action1.mymethod)

Regards
Ranga

Can you create releases?

I'm trying to use this library through hunter package management system. Would you mind creating some releases once in a while to make it easier to track?

thanks!
Marco

Question: is -- supported as an argument delimiter?

From https://www.reddit.com/r/cpp/comments/dzm7lg/argparse_argument_parser_for_modern_c_v20_released/f8c7erq?utm_source=share&utm_medium=web2x

It was not clear from the documentation, so I'd like to know if -- is supported as an argument delimiter?

-- is a convention for separating the list of arguments to the program from the list of arguments to be forwarded, an example:

./invoke sed --trace X=1 Y=2 Z=3 -- 's/foo/bar/g' fiddling.txt

Where invoke would:

  • Recognize sed as an optional argument.
  • Recognize --trace as an optional argument.
  • Recognize X=1, Y=2, and Z=3 as remaining arguments.
  • Recognize s/foo/bar/g and fiddling.txt as argument to pass to sed.

Header install path

The readme states that you just have to #include <argparse.hpp> to use the library. However, CMake installs the header into /usr/include/argparse and the target include directory list only specifies /usr/include, so you actually need to use #include <argparse/argparse.hpp>.

Is this a documentation problem in the readme, or should the CMakeLists.txt be changed from

target_include_directories(argparse INTERFACE
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)

to

target_include_directories(argparse INTERFACE
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/argparse>
  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)

?

Consistent tags format

Hi,

Could you please be more consistent in tag naming?
Either vMajor.Minor or vMajor.Minor.Patch always?
All tags were v2.0 and now we have v2.0.1.

This brokes some scripts and other automation.

What if actions want to return std::vector?

If an option wants to parse a list of values --foo 1,4,2,6, then it cannot return std::vector<int> because this type is considered a container. We probably will never want to parse a list like that (because there are so many good parsing libraries for structural data), so we cannot return the existing containers functionality to "make get<std::vector<T>>() work" for this demand.

We should consider changing the multivalue argument support. Some observations:

  • Blessing types takes away possible return types
  • std::vector<std::string> is verbose to spell
  • std::array is not supported (and is tricky to support, because it's not a container that can grow)
  • std::list is probably never used for lots of reasons

Inventing a new set of API is an option, but the present API (#66) will double the number of names. I think the situation can be improved by blessing a type that is both impossible to be stored in std::any, and is terse to write -- array type.

Here is how the new get may look like:

  • get<T[]>: returns std::vector<T>
  • get<T[N]>: returns std::array<T, N> if N == nargs, throws otherwise
  • get<T>: returns T

The old spelling get<std::vector<std::string>> becomes get<std::string[]> after the change. To not to break everything immediately, we can make the old spelling [[deprecated]].

FAQ

  1. Why T[] and T[N] return entirely different types? I hope users can interpret [] as "returning a type with operator[]."
  2. Why not other kinds of containers? If users want they can append to anything with void actions. We are providing a good default here.
  3. Why not std::span (of static extent or dynamic extent)? Technical issue, we only have storage for std::vector<std::any> rather than std::vector<T>.

Merged too fast.

I made a mistake.
$&lt;BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/include)

Should be CMAKE_CURRENT_LIST_DIR not SOURCE_DIR. Literally just fixed it as you merged lol.

Conan package

Hello.
At first thank you for the library - it's really easy-to-use and feature-enough complete library.

But I have a suggestion - can you create Conan package for the library? Even if library is single header-only using system-independent package manager has a lof of advantages.

One note - wiil be fine if you create release of the library. With it will be easier to create a package.

If you have any questions - just ask here.

Arguments which start with a dash could be positional arguments

Consider a common pattern: use a dash - to represent stdin or stdout, however, argparse simply considers all arguments which start with a dash as optional arguments. For more information, see this line.

Therefore, I think we can restrict optional arguments to those which

  1. start with a dash.
  2. and have a character following that dash.

For instance, - is a positional argument while -m is an optional argument.

Almost [Required] (help text)

Argument with .required() flag and with default value is not so required (can be omitted in command-line).
I think that [Required] is not necessary in this case.

A propose skip this text if argument has default value

    if (argument.mIsRequired && !argument.mDefaultValue.has_value()) 
      stream << "[Required]";

wmain support

A boss

https://docs.microsoft.com/en-us/cpp/c-language/using-wmain?view=vs-2019

That all C++ cmdline parsing libraries face.

A strong demand is to construct std::path from command-line arguments, where std::wstring is native string type for std::path on Windows.

In past experience, Boost.Program_options (Sphinx docs) is the only library that allowed me to program wmain in real code. But Boost's choice is to template everything on character type while still supporting some codecvt conversions internally. Using only wvalue may work for some applications, but in general wcommand_line_parser is needed.

Given argparse' type erasure design, we have an opportunity to look at the problem differently.

A simple rule for determining negative numeric values

Currently, specification-wise, the rule isn't clear. Implementation-wise, there is no need to actually interpret the values. I assume that you only want to parse (possibility negative) decimal numbers, without considering locale and thousand separators. In that case, see if the following rule works:

Let x be a variable of type T where std::is_arithmetic_v<T> is true. A pattern S is positional if std::from_chars(&*begin(S), &*end(S), x) points the ptr member of the result to &*end(S).

Note that this rule deems -inf, -infinity, and any form of -nan to be positional. I think it's desired and it's the status quo in code.

If we are agreed on a rule, we can derive a syntax and only parse that syntax.

See also https://en.cppreference.com/w/cpp/utility/from_chars the syntax is not fully documented here, you will have to lookup strtol and strtod as well.

Printing program version

Python's argparse has a special action that prints the program version and exits.

Currently printing the program version with this library can be done, but the presence of required arguments will also cause an error.

Support for printing the program version would be nice to have as it's an expected part of any CLI, akin to the --help option.

Failing tests

These tests fail:

#include <test_parse_args.hpp>
#include <test_positional_arguments.hpp>
#include <test_compound_arguments.hpp>
#include <test_container_arguments.hpp>

Here are the errors and warnings. I'm using Visual Studio 2019 and I get similar errors in Ubuntu 19.04, GCC 8.3

argparse.hpp(444,1): warning C4456:  declaration of 'tCurrentArgument' hides previous local declaration
argparse.hpp(423,21): message :  see declaration of 'tCurrentArgument'
argparse.hpp(445,1): warning C4456:  declaration of 'tIterator' hides previous local declaration
argparse.hpp(434,23): message :  see declaration of 'tIterator'
argparse.hpp(132,23): warning C4018:  '<=': signed/unsigned mismatch
argparse.hpp(432): message :  see reference to function template instantiation 'Iterator argparse::Argument::consume<_InIt>(Iterator,Iterator,std::string)' being compiled
argparse.hpp(432): message :         with
argparse.hpp(432): message :         [
argparse.hpp(432): message :             Iterator=std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::string>>>,
argparse.hpp(432): message :             _InIt=std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::string>>>
argparse.hpp(432): message :         ]
argparse.hpp(255,14): error C2672:  'std::transform': no matching overloaded function found
argparse.hpp(361): message :  see reference to function template instantiation 'std::vector<int,std::allocator<int>> argparse::Argument::get<T>(void) const' being compiled
argparse.hpp(361): message :         with
argparse.hpp(361): message :         [
argparse.hpp(361): message :             T=std::vector<int,std::allocator<int>>
argparse.hpp(361): message :         ]
argparse\test\test_parse_args.hpp(77): message :  see reference to function template instantiation 'T argparse::ArgumentParser::get<std::vector<int,std::allocator<int>>>(const std::string &)' being compiled
argparse\test\test_parse_args.hpp(77): message :         with
argparse\test\test_parse_args.hpp(77): message :         [
argparse\test\test_parse_args.hpp(77): message :             T=std::vector<int,std::allocator<int>>
argparse\test\test_parse_args.hpp(77): message :         ]
argparse.hpp(256,1): error C2783:  '_OutIt std::transform(const _InIt,const _InIt,_OutIt,_Fn)': could not deduce template argument for '_Fn'
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.21.27702\include\algorithm(1314): message :  see declaration of 'std::transform'
argparse.hpp(261,14): error C2672:  'std::transform': no matching overloaded function found
argparse.hpp(262,1): error C2783:  '_OutIt std::transform(const _InIt,const _InIt,_OutIt,_Fn)': could not deduce template argument for '_Fn'
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.21.27702\include\algorithm(1314): message :  see declaration of 'std::transform'

Simplify help output

Hey there,
I just tried to print help if no parameter is given but finding the answer was a bit confusing. I would prefer to find a function call in Quick Start that automatically sets this up for me. Shouldn't be a big deal I think :)

Allow many positional arguments

It would be nice to be able to have a feature whereby you could gather many positional arguments at the end of a command for something like a compiler.

For example, imagine a use case like this:

compiler file1 file2 file3

Currently I don't believe it is possible to do this.

I imagine using it would look something like:

std::vector<std::string> = program.get_trailing();

which would just return all positional arguments at the end regardless of how many.

subcommand support

I think Git-like subcommand can be done by inserting a positional argument and dispatch on subparsers. But this is something argparse should provide directly, with add_subparsers, for example.

Default Value Returns Bad Any Cast

Hello,

Running this with clang on Mac OS, when trying to use the library as such:

argparse::ArgumentParser parser{"program"};
parser.add_argument("-t", "--test")
           .help("Blah")
           .default_value("test");

parser.parse_args(arc, argv);
. . .

parser.get<std::string>("-t"); // Bad any_cast

It seems that the compiler thinks that "test" is of type const char[5].
I tried parser.get<char*>("-t") with no luck as well.
My only workaround was to statically cast within the default value .default_value(static_cast<std::string>("test")).
I realize that this default value arg is getting any casted, but maybe the default value should be templated instead?

error format issue

should this:
"error: -l: expected 1argument. 0 provided"

be:
"error: -l: expected 1 argument. 0 provided"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.