Easy to use, powerful and expressive command line argument handling for C++11/14/17 contained in a single header file.
options, options+value(s), positional values, positional commands, nested alternatives, decision trees, joinable flags, custom value filters, ...
documentation generation (usage lines, man pages); error handling
lots of examples; large set of tests
Consider this command line interface:
SYNOPSIS convert <input file> [-r] [-o <output format>] [-utf16] OPTIONS -r, --recursive convert files recursively -utf16 use UTF-16 encoding
Here is the code that defines the positional value input file
and the three options -r
, -o
and -utf16
. If parsing fails, the above default man page-like snippet will be printed to stdout.
#include <iostream>#include "clipp.h"using namespace clipp; using std::cout; using std::string;int main(int argc, char* argv[]) { bool rec = false, utf16 = false; string infile = "", fmt = "csv";auto cli = (value("input file", infile),option("-r", "--recursive").set(rec).doc("convert files recursively"),option("-o") & value("output format", fmt),option("-utf16").set(utf16).doc("use UTF-16 encoding") );if(!parse(argc, argv, cli)) cout << make_man_page(cli, argv[0]);// ...}
SYNOPSIS finder make <wordfile> -dict <dictionary> [--progress] [-v] finder find <infile>... -dict <dictionary> [-o <outfile>] [-split|-nosplit] [-v] finder help [-v] OPTIONS --progress, -p show progress -o, --output <outfile> write to file instead of stdout -split, -nosplit (do not) split output -v, --version show version
This CLI has three alternative commands (make
, find
, help
), some positional value-arguments (<wordfile>
, <infile>
) of which one is repeatable, a required flag with value-argument (-dict <dictionary>
), an option with value-argument (-o <outfile>
), one option with two alternatives (-split
, -nosplit
) and two conventional options (-v
, --progress
).
Here is the code that defines the interface, generates the man page snippet above and handles the parsing result:
using namespace clipp; using std::cout; using std::string;//variables storing the parsing result; initialized with their default valuesenum class mode {make, find, help}; mode selected = mode::help; std::vector<string> input; string dict, out;bool split = false, progr = false;auto dictionary = required("-dict") & value("dictionary", dict);auto makeMode = (command("make").set(selected,mode::make), values("wordfile", input), dictionary, option("--progress", "-p").set(progr) % "show progress" );auto findMode = (command("find").set(selected,mode::find), values("infile", input), dictionary, (option("-o", "--output") & value("outfile", out)) % "write to file instead of stdout", ( option("-split" ).set(split,true) | option("-nosplit").set(split,false) ) % "(do not) split output" );auto cli = ( (makeMode | findMode | command("help").set(selected,mode::help) ),option("-v", "--version").call([]{cout << "version 1.0nn";}).doc("show version") );if(parse(argc, argv, cli)) {switch(selected) {case mode::make: /* ... */ break;case mode::find: /* ... */ break;case mode::help: cout << make_man_page(cli, "finder"); break; } } else { cout << usage_lines(cli, "finder") << 'n'; }
Below are a few examples that should give you an idea for how clipp works. Consider this basic setup with a few variables that we want to set using command line arguments:
int main(int argc, char* argv[]) { using namespace clipp;// define some variablesbool a = false, b = false;int n = 0, k = 0;double x = 0.0, y = 0.0; std::vector<int> ids;auto cli = ( /* CODE DEFINING COMMAND LINE INTERFACE GOES HERE */ );parse(argc, argv, cli); //excludes argv[0]std::cout << usage_lines(cli, "exe") << 'n'; }
Interface (usage_lines ) | Code (content of cli parentheses ) |
---|---|
exe [-a] | option("-a", "--all").set(a) |
exe [--all] | option("--all", "-a", "--ALL").set(a) |
exe [-a] [-b] | option("-a").set(a), option("-b").set(b) |
exe -a | required("-a").set(a) |
exe [-a] -b | option("-a").set(a), required("-b").set(b) |
exe [-n <times>] | option("-n", "--iter") & value("times", n) |
exe [-n [<times>]] | option("-n", "--iter") & opt_value("times", n) |
exe -n <times> | required("-n", "--iter") & value("times", n) |
exe -n [<times>] | required("-n", "--iter") & opt_value("times", n) |
exe [-c <x> <y>] | option("-c") & value("x", x) & value("y", y) |
exe -c <x> <y> | required("-c") & value("x", x) & value("y", y) |
exe -c <x> [<y>] | required("-c") & value("x", x) & opt_value("y", y) |
exe [-l <lines>...] | option("-l") & values("lines", ids) |
exe [-l [<lines>...]] | option("-l") & opt_values("lines", ids) |
exe [-l <lines>]... | repeatable( option("-l") & value("lines", ids) ) |
exe -l <lines>... | required("-l") & values("lines", ids) |
exe -l [<lines>...] | required("-l") & opt_values("lines", ids) |
exe (-l <lines>)... | repeatable( required("-l") & value("lines", ids) ) |
exe fetch [-a] | command("fetch").set(k,1), option("-a").set(a) |
exe init | fetch [-a] | command("init").set(k,0) | (command("fetch").set(k,1), option("-a").set(a)) |
exe [-a|-b] | option("-a").set(a) | option("-b").set(b) |
exe [-m a|b] | option("-m") & (required("a").set(a) | required("b").set(b)) |
See the examples section for detailed explanations of each topic.
Namespace qualifiers are omitted from all examples for better readability. All entities are defined in namespace clipp
.
int main(int argc, char* argv[]) { using namespace clipp;auto cli = ( /* CODE DEFINING COMMAND LINE INTERFACE GOES HERE */ );parse(argc, argv, cli); //excludes argv[0]//if you want to include argv[0]//parse(argv, argv+argc, cli);}
There are two kinds of building blocks for command line interfaces: parameters and groups. Convieniently named factory functions produce parameters or groups with the desired settings applied.
bool a = false, f = false; string s; vector<string> vs;auto cli = ( // matches required positional repeatablecommand("push"), // exactly yes yes norequired("-f", "--file").set(f), // exactly yes no norequired("-a", "--all", "-A").set(a), // exactly no no no value("file", s), // any arg yes yes novalues("file", vs), // any arg yes yes yesopt_value("file", s), // any arg no yes noopt_values("file", vs), // any arg no yes yes//"catch all" parameter - useful for error handlingany_other(vs), // any arg no no yes//catches arguments that fulfill a predicate and aren't matched by other parametersany(predicate, vs) // predicate no no yes);
The functions above are convenience factories:
bool f = true; string s;auto v1 = values("file", s);// is equivalent to:auto v2 = parameter{match::nonempty}.label("file").blocking(true).repeatable(true).set(s);auto r1 = required("-f", "--file").set(f);// is equivalent to:auto r2 = parameter{"-f", "--file"}.required(true).set(f);
a required parameter has to match at least one command line argument
a repeatable parameter can match any number of arguments
non-positional (=non-blocking) parameters can match arguments in any order
a positional (blocking) parameter defines a "stop point", i.e., until it matches all parameters following it are not allowed to match; once it matched, all parameters preceding it (wihtin the current group) will become unreachable
If you want parameters to be matched in sequence, you can tie them together using either operator &
or the grouping function in_sequence
:
int n = 1; string s; vector<int> ls;auto cli = (//option with required valueoption("-n", "--repeat") & value("times", n),//required flag with optional valuerequired("--file") & opt_value("name", s), //option with exactly two valuesoption("-p", "--pos") & value("x") & value("y"),//same as before v vin_sequence( option("-p", "--pos") , value("x") , value("y") ), //option with at least one value (and optionally more)option("-l") & values("lines", ls) );
Value parameters use a filter function to test if they are allowed to match an argument string. The default filter match::nonempty
that is used by value
, values
, opt_value
and opt_values
will match any non-empty argument string.
You can either supply other filter functions/function objects as first argument of value
, values
, etc. or use one of these built-in shorthand factory functions covering the most common cases:
string name; double r = 0.0; int n = 0;auto cli = (value("user", name), // matches any non-empty stringword("user", name), // matches any non-empty alphanumeric stringnumber("ratio", r), // matches string representations of numbersinteger("times", n) // matches string representations of integers);
Analogous to value
, opt_value
, etc. there are also functions for words
, opt_word
, etc.
auto is_char = [](const string& arg) { return arg.size() == 1 && std::isalpha(arg[0]); };char c = ' '; // matches required positional repeatablevalue(is_char, "c", c); // one character yes yes no
group mutually compatible parameters with parentheses and commas:
auto cli = ( option("-a"), option("-b"), option("-c") );
group mutually exclusive parameters as alternatives using operator |
or one_of
:
auto cli1 = ( value("input_file") | command("list") | command("flush") );auto cli2 = one_of( value("input_file") , command("list") , command("flush") );
group parameters so that they must be matched in sequence using operator &
or in_sequence
:
double x = 0, y = 0, z = 0;auto cli1 = ( option("-pos") & value("X",x) & value("Y",y) & value("Z",z) );auto cli2 = in_sequence( option("-pos") , value("X",x) , value("Y",y) , value("Z",z) );
Note that surrounding groups are not affected by this, so that -a
and -b
can be matched in any order while -b
and the value X
must match in sequence:
bool a = false, b = false; int x = 0;auto cli = ( option("-a").set(a), option("-b").set(b) & value("X",x) );
groups can be nested and combined to form arbitrarily complex interfaces (see here and here):
auto cli = ( command("push") | ( command("pull"), option("-f", "--force") ) );
groups can be repeatable as well:
auto cli1 = repeatable( command("flip") | command("flop") );
force common prefixes on a group of flags:
int x = 0;auto cli1 = with_prefix("-", option("a"), option("b") & value("x",x), ... ); // => -a -b ^unaffected^auto cli2 = with_prefix_short_long("-", "--", option("a", "all"), option("b"), ... ); // => -a --all -b
force common suffixes on a group of flags:
int x = 0;auto cli1 = with_suffix("=", option("a") & value("x",x), ... ); // => a= ^unaffected^auto cli2 = with_suffix_short_long(":", ":=", option("a", "all"), option("b"), ... ); // => a: all:= b:
make a group of flags joinable:
auto cli1 = joinable( option("-a"), option("-b")); //will match "-a", "-b", "-ab", "-ba"//works also with arbitrary common prefixes:auto cli2 = joinable( option("--xA0"), option("--xB1")); //will also match "--xA0B1" or "--xB1A0"
The easiest way to connect the command line interface to the rest of your code is to bind object values or function (object) calls to parameters (see also here):
bool b = false; int i = 5; int m = 0; string x; ifstream fs;auto cli = ( option("-b").set(b), // "-b" detected -> set b to trueoption("-m").set(m,2), // "-m" detected -> set m to 2option("-x") & value("X", x), // set x's value from arg string option("-i") & opt_value("i", i), // set i's value from arg string option("-v").call( []{ cout << "v"; } ), // call function (object) / lambdaoption("-v")( []{ cout << "v"; } ), // same as previous lineoption("-f") & value("file").call([&](string f){ fs.open(f); }) );
In production code one would probably use a settings class:
struct settings { bool x = false; /* ... */ }; settings cmdline_settings(int argc, char* argv[]) { settings s;auto cli = ( option("-x").set(s.x), /* ... */ );parse(argc, argv, cli);return s; }
Note that the target must either be:
a fundamental type (int, long int, float, double, ...
)
a type that is convertible from const char*
a callable entity: function, function object / lambda
that either has an empty parameter list or exactly one parameter that is
convertible from const char*
Docstrings for groups and for parameters can either be set with the member function doc
or with operator %
:
auto cli = ( ( option("x").set(x).doc("sets X"),option("y").set(y) % "sets Y" ), "documented group 1:" % ( option("-g").set(g).doc("activates G"), option("-h").set(h) % "activates H" ), ( option("-i").set(i) % "activates I", option("-j").set(j) % "activates J" ).doc("documented group 2:") );
Usage Lines:
cout << usage_lines(cli, "progname") << 'n';//with formatting optionsauto fmt = doc_formatting{} .first_column(3) .last_column(79); cout << usage_lines(cli, "progname", fmt) << 'n';
Detailed Documentation:
cout << documentation(cli) << 'n';//with formatting optionsauto fmt = doc_formatting{} .first_column(7) .doc_column(15) .last_column(99); cout << documentation(cli, fmt) << 'n';
Man Pages:
auto cli = ( /*CODE DEFINING COMMAND LINE INTERFACE GOES HERE*/ ); cout << make_man_page(cli, "progname") << 'n';//with formatting optionsauto fmt = doc_formatting{} .first_column(7) .doc_column(15) .last_column(99); cout << make_man_page(cli, "progname", fmt) << 'n';
Each parameter can have event handler functions attached to it. These are invoked once for each argument that is mapped to the parameter (or once per missing event):
string file = "default.txt";auto param = required("-nof").set(file,"") | required("-f") & value("file", file) // on 2nd, 3rd, 4th,... match (would be an error in this case) .if_repeated( [] { /* ... */ } ) // if required value-param was missing .if_missing( [] { /* ... */ } ) // if unreachable, e.g. no flag "-f" before filename .if_blocked( [] { /* ... */ } ) // if match is in conflict with other alternative "-nof" .if_conflicted( [] { /* ... */ } );
The handler functions can also take an int, which is set to the argument index at which the event occurred first:
string file = "default.txt";auto param = required("-nof").set(file,"") | required("-f") & value("file", file) .if_repeated ( [] (int argIdx) { /* ... */ } ) .if_missing ( [] (int argIdx) { /* ... */ } ) .if_blocked ( [] (int argIdx) { /* ... */ } ) .if_conflicted( [] (int argIdx) { /* ... */ } );
If we give -f -b
or -b -f -a
as command line arguments for the following CLI, an error will be reported, since the value after -f
is not optional:
auto cli = ( option("-a"), option("-f") & value("filename"), option("-b") );
This behavior is fine for most use cases.
But what if we want our program to take any string as a filename, because our filenames might also collide with flag names? We can make the value parameter greedy with operator !
. This way, the next string after -f
will always be matched with highest priority as soon as -f
was given:
auto cli = ( option("-a"), option("-f") & !value("filename"), option("-b") );// ^~~~~~
Be very careful with greedy parameters!
auto cli = ( /* your interface here */ );auto res = parse(argc, argv, cli);if(res.any_error()) { /* ... */ }//aggregated errorsif(res.unmapped_args_count()) { /* ... */ }if(res.any_bad_repeat()) { /* ... */ }if(res.any_blocked()) { /* ... */ }if(res.any_conflict()) { /* ... */ }