clipp - command line interfaces for modern C++
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
### Quick Reference Table
### Overview (short examples)
### Detailed Examples
#### Why yet another library for parsing command line arguments? / Design goals
#### Requirements / Compilers
Quick Intro
Simple Use Case — Simple Setup!
Consider this command line interface:
- ``` roff
- 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.
- ``` sh
- #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]);
- // ...
- }
- ```
A More Complex Example:
- ``` roff
- 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 andhandles the parsing result:
- ``` sh
- using namespace clipp; using std::cout; using std::string;
- //variables storing the parsing result; initialized with their default values
- enum 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.0\n\n";}).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';
- }
- ```
Quick Reference
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:
- ``` sh
- int main(int argc, char* argv[]) {
- using namespace clipp;
- // define some variables
- bool 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))` |
Overview
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.
Basic Setup
- ``` sh
- 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.
Parameters (flag strings, commands, positional values, required flags, repeatable parameters)
- ``` sh
- bool a = false, f = false;
- string s; vector<string> vs;
- auto cli = ( // matches required positional repeatable
- command("push"), // exactly yes yes no
- required("-f", "--file").set(f), // exactly yes no no
- required("-a", "--all", "-A").set(a), // exactly no no no
- value("file", s), // any arg yes yes no
- values("file", vs), // any arg yes yes yes
- opt_value("file", s), // any arg no yes no
- opt_values("file", vs), // any arg no yes yes
- //"catch all" parameter - useful for error handling
- any_other(vs), // any arg no no yes
- //catches arguments that fulfill a predicate and aren't matched by other parameters
- any(predicate, vs) // predicate no no yes
- );
- ```
The functions above are convenience factories:
- ``` sh
- 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
Flags + Values
If you want parameters to be matched in sequence, you can tie them together using either operator & or the grouping function in_sequence :
- ``` sh
- int n = 1; string s; vector<int> ls;
- auto cli = (
- //option with required value
- option("-n", "--repeat") & value("times", n),
- //required flag with optional value
- required("--file") & opt_value("name", s),
- //option with exactly two values
- option("-p", "--pos") & value("x") & value("y"),
- //same as before v v
- in_sequence( option("-p", "--pos") , value("x") , value("y") ),
- //option with at least one value (and optionally more)
- option("-l") & values("lines", ls)
- );
- ```
Filtering Value Parameters
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:
- ``` sh
- string name; double r = 0.0; int n = 0;
- auto cli = (
- value("user", name), // matches any non-empty string
- word("user", name), // matches any non-empty alphanumeric string
- number("ratio", r), // matches string representations of numbers
- integer("times", n) // matches string representations of integers
- );
- ```
Analogous to value, opt_value, etc. there are also functions for words, opt_word, etc.
Value Parameters With Custom Filters
- ``` sh
- auto is_char = [](const string& arg) { return arg.size() == 1 && std::isalpha(arg[0]); };
- char c = ' ';
- // matches required positional repeatable
- value(is_char, "c", c); // one character yes yes no
- ```
Groups
group mutually compatible parameters with parentheses and commas:
- ``` sh
- auto cli = ( option("-a"), option("-b"), option("-c") );
- ```
group mutually exclusive parameters as alternatives using operator | or one_of :
- ``` sh
- 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 :
- ``` sh
- 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:
- ``` sh
- 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 ):
- ``` sh
- auto cli = ( command("push") | ( command("pull"), option("-f", "--force") ) );
- ```
groups can be repeatable as well:
- ``` sh
- auto cli1 = repeatable( command("flip") | command("flop") );
- ```
force common prefixes on a group of flags:
- ``` sh
- 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:
- ``` sh
- 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 :
- ``` sh
- 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"
- ```
Interfacing With Your Code
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 ):
- ``` sh
- bool b = false; int i = 5; int m = 0; string x; ifstream fs;
- auto cli = (
- option("-b").set(b), // "-b" detected -> set b to true
- option("-m").set(m,2), // "-m" detected -> set m to 2
- option("-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) / lambda
- option("-v")( []{ cout << "v"; } ), // same as previous line
- option("-f") & value("file").call([&](string f){ fs.open(f); })
- );
- ```
In production code one would probably use a settings class:
- ``` sh
- 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*
Generating Documentation (see also here)
Docstrings for groups and for parameters can either be set with the member function doc or with operator % :
- ``` sh
- auto cli = (
- ( option("x").set(x).doc("sets X"),
- option("y").set(y) % "sets Y"
- ),
- "documented group 1:" % (
- option("-g").set(g).doc("acti