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 featured, 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.
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
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 varialbe the option's argument is tried to be converted to the variable (based on its type), if this is not possible, and 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
.
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);
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);
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.
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 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 prototpye 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 not limits in regards what can be done using this features:
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; } );
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"));
Do not hesite to ask questions and issue pull-requests here on GitHub. Every help is more than welcome.