/cxx_argp

Modern C++, header-only command line argument parser interface based on argp

Primary LanguageC++GNU Lesser General Public License v3.0LGPL-3.0

Modern C++ argument parser based on ARGP

Description

This project provides a header-only, C++ (C++11 and later) argument parser library manufactured around GLIBC's ARGP-library.

Its goal is to stick to a maximum to the original concepts and features of ARGP in regards to argument-parsing. However, it adds some more advanced features, especially the association of variables, check-functions and their option-arguments - represented by the argp-struct.

It's licensed under the term of the LGPL3 (as is argp inside the GLIBC). As a header-only library is has the same license-requirements as stated by the Eigen Project.

How to use it

Here's how this library can be used:

#include <cxx_argp_parser.h>
#include <iostream>

int main(int argc, char *argv[])
{
	// initializers are default values for omitted options
	std::string host = "127.0.0.1";

	// create parser object,
	cxx_argp::parser parser;

	// add option and associate to simple variables, the class creates a
	// conversion function (if the type is supported) which does a basic check
	// and fills the variable
	// Note: the first argument is the real 'struct argp_option'
	parser.add_option({nullptr, 'h', "host-address", 0, "IP address of host"},
	                  host);

	if (parser.parse(argc, argv)) {
		std::cerr << "parsing OK\n";
	} else {
		std::cerr << "there was an error - exiting\n";
		return 1;
	}

	std::cerr << "hostname " << host << "\n";

	// here, do more checks on the values passed to the program

	return 0;
}

Running this program (called readme, see the test-folder) without arguments, hostname keeps it default value

$ ./readme
parsing OK
hostname 127.0.0.1

Running it with -h google.com, hostname changes:

$ ./readme -h google.com
parsing OK
hostname google.com

Omitting the argument to the '-h' option, errors out:

$ ./readme -h
./readme: option requires an argument -- 'h'
Try `readme --help' or `readme --usage' for more information.

Printing the help, generated by argp: (note that the program exists before returning to main())

Usage: readme [OPTION...]

  -h host-address            IP address of host
  -?, --help                 Give this help list
      --usage                Give a short usage message

Adding options

The method add_option() adds an option to the parser-object. The first argment is a value of struct argp_option, the second argument is either a variable, which is used by reference, or a lamdba-function, std::function or std::bind (of prototype bool(const char *).

This second argument is used during argument parsing, if it was variable the option's argument is tried to be converted to the variable (based on its type), if this is not possible, an error is produced and argp will bail out.

If the second argument is a function, this function is called with the argument value (a const char *) for further processing. This function returns true if the argument is accepted, otherwise false.

Argument conversion

Basic types

The parser-library internally provides some conversion-functions for some variable-type, which will make parsing fail if conversion has not worked, e.g. using a string on a float-type variable.

Floating point, std::strings and integer argument conversion functions are built in,

float single = 1.1f;
double doublef = 2.2;
uint16_t port = 502;
int16_t sint = 0;
std::string host = "127.0.0.1";

cxx_argp::parser parser;

parser.add_option({"float", 's', "single", 0, "floating-point test"}, single);
parser.add_option({"double", 'd', "double", 0, "floating-point test"}, doublef);
parser.add_option({"port", 'p', "port", 0, "TCP port the server will listen on"}, port);
parser.add_option({"host", 'h', "host-address", 0, "IP address of host"}, host);

Boolean and switches

bool-variable-based options are consider as 'switches', i.e. the option is expected to not have an argument and if the option is present, the associated bool-variable is set to true.

bool enable = false;

parser.add_option({"enable", 'e', nullptr, 0, "enable"}, enable);

File streams and file names

The built-in conversion functions for file-streams is taking the argument as a filename and tries to open the file. If this does not work, the argument is considered as an error.

std::ifstream file;

parser.add_option({"file", 'f', "filename", 0, "a file"}, file);

Sometimes the filename and a stream is required. A paired type can then be used:

std::pair<std::ifstream, std::string> file_and_name;

parser.add_option({"file", 'f', "filename", 0, "a file"}, file_and_name);

This works the same way as for simple streams, except that the open file is the first-part of the pair and the second-part contains the filename as a std::string.

To just get a filename without any file-opening, std::string can be used.

Comma-separated list of integer

A more complex conversion function is built in, this converts are comma-separated list of integers into a std::vector<>:

std::vector<int> vec;

parser_.add_option({"vector", 'V', "list", 0, "list of ints"}, vec);

An argument for such a type can be given as 1,12,3, resulting the version containing 1, 12 and 3.

Custom argument converter

Custom argument converters can be implemented by passing a function as second argument to add_option(). The user has thus complete control of what to do with the raw argument const char *.

The prototype of the function to be given is bool(const char *).

The user has to return true if the argument is acceptable for this option, or false if not.

There are no limit in regards what can be done using this feature:

Lambdas

Toggle a boolean:

bool enable = false;

parser.add_option({"enable", 'e', nullptr, 0, "enable"},
                  [&enable] (const char *) { enable = !enable; return true; } );

A verbosity-level indicator:

int verbose = 0;

parser.add_option({nullptr, 'v', nullptr, 0, "verbosity level increase"},
                  [&verbose] (const char *) { verbose++; return true; } );

std::bind

A useful feature for when using std::bind could be to fill in a map-like object (think of JSON as a more complex example than a std::map).

std::map<std::string, std::string> cfg;

auto to_map = [&cfg] (const char *arg, const std::string &key) {
	map[key] = arg;
	return true;
};

parser.add_option({nullptr, 'a', nullptr, 0, "value A"},
                  std::bind(to_map, std::placeholders::_1, "value1"));
parser.add_option({nullptr, 'b', nullptr, 0, "value B"},
                  std::bind(to_map, std::placeholders::_1, "value2"));
parser.add_option({nullptr, 'c', nullptr, 0, "value C"},
                  std::bind(to_map, std::placeholders::_1, "value3"));

Contributing

Do not hesite to ask questions and issue pull-requests here on GitHub. Every help is more than welcome.