易于使用、功能强大且表达力强的命令行参数解析,适用于现代 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
Consider this command line interface:
SYNOPSIS
convert [-r] [-o ] [-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.
…
This CLI has three alternative commands (make, find, help), some positional value-arguments (, ) of which one is repeatable, a required flag with value-argument (-dict ), an option with value-argument (-o ), 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:
…
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 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:
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);
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 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)
);
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 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.
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
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 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 "
clipp::parameter
documented_value(const std::string& name, Target& tgt, const std::string& docstr) {
using std::to_string;
return clipp::value(name,tgt).doc(docstr + "(default: " + to_string(tgt) + ")");
}
//value that only matches strings without prefix '-'
template
clipp::parameter
nodash_value(std::string label, Target&& tgt, Targets&&... tgts) {
return clipp::value(clipp::match::prefix_not{"-"}, std::move(label),
std::forward(tgt), std::forward(tgts)...);
}
…
man
SYNOPSIS
switch [-a] [-b] [-c] [--hi]
OPTIONS
-a activates a
-b activates b
-c, --noc deactivates c
--hi says hi
bool a = false, b = false, c = true; //target variables
auto cli = (
option("-a").set(a) % "activates a",
option("-b").set(b) % "activates b",
option("-c", "--noc").set(c,false) % "deactivates c",
option("--hi")([]{cout >``` and ```operator > b,
option("-c", "--noc") % "deactivates c" >> set(c,false),
option("--hi") % "says hi" >> []{cout > b,
option("-c", "--noc") % "deactivates c" >> set(c,false),
option("--hi") % "says hi" >> []{cout > b,
"deactivates c" % option("-c", "--noc") >> set(c,false),
"says hi" % option("--hi") >> []{cout >``` which means that you either have to keep the docstrings closer to the command line parameters than the actions or use parentheses.
You should also have a look at [actions](#actions) for more details.
#### Step-by-step configuration of parameters:
```cpp
int n = 1;
auto optN = parameter{"-n", "-N", "--iterations", "--repeats"}.required(true);
auto valN = parameter{match::any}
.label("times")
.set(n)
.call([](string s) { if(!str::represents_number(s)) throw runtime_error{"invalid value for 'times'"}; })
.if_missing([]{ cout [-s]
OPTIONS
infile input filename
outfile output filename
-s, --split split files
string ifile, ofile;
bool split = false;
auto cli = (
value("infile", ifile) % "input filename",
value("outfile", ofile) % "output filename",
option("-s", "--split").set(split) % "split files" );
暂无开放 Issues,或尚未同步最近议题。