易于使用、强大且富有表现力的 C++11/14/17 命令行参数处理包含在单个头文件中。
选项、选项+值、位置值、位置命令、嵌套替代项、决策树、可连接标志、自定义值过滤器...
文档生成(使用行、手册页);错误处理
很多例子;大量测试
考虑这个命令行界面:
概要 转换 <输入文件> [-r] [-o <输出格式>] [-utf16] 选项 -r, --recursive 递归转换文件 -utf16 使用UTF-16编码
以下是定义位置值input file
和三个选项-r
、 -o
和-utf16
的代码。如果解析失败,上面的默认手册页片段将被打印到标准输出。
#include <iostream>#include "clipp.h"using 命名空间 Clipp;使用 std::cout;使用 std::string;int main(int argc, char* argv[]) { bool rec = false, utf16 = false; string infile = "", fmt = "csv";auto cli = (value("输入文件", infile),option("-r", "--recursive").set(rec).doc("转换文件递归"),option("-o") & value("输出格式", fmt),option("-utf16").set(utf16).doc("使用UTF-16编码”) );if(!parse(argc, argv, cli)) cout << make_man_page(cli, argv[0]);// ...}
概要 finder make <wordfile> -dict <字典> [--progress] [-v] finder find <infile>... -dict <字典> [-o <outfile>] [-split|-nosplit] [-v] 查找器帮助 [-v] 选项 --progress, -p 显示进度 -o, --output <outfile> 写入文件而不是标准输出 -split, -nosplit(不)分割输出 -v, --version 显示版本
此 CLI 具有三个备用命令( make
、 find
、 help
)、一些位置值参数( <wordfile>
、 <infile>
),其中一个是可重复的、带有值参数的必需标志( -dict <dictionary>
)、带有值参数的选项( -o <outfile>
),一个选项有两个替代选项( -split
, -nosplit
)和两个常规选项( -v
, --progress
)。
以下是定义接口、生成上面的手册页片段并处理解析结果的代码:
使用命名空间 Clipp;使用 std::cout; using std::string;//存储解析结果的变量;使用默认值初始化num类模式{make,find,help}; 选择的模式=模式::帮助; std::vector<字符串> 输入; string dict, out;bool split = false, progr = false;自动字典 = required("-dict") & value("dictionary", dict);auto makeMode = (command("make").set(selected,mode) ::制作), 值(“wordfile”,输入), 字典, option("--progress", "-p").set(progr) % "显示进度");auto findMode = (command("find").set(selected,mode::find), 值(“infile”,输入), 字典, (option("-o", "--output") & value("outfile", out)) % "写入文件而不是 stdout", ( 选项("-split" ).set(split,true) | option("-nosplit").set(split,false) ) % "(不)分割输出" );auto cli = ( (makeMode | findMode | command("help").set(selected,mode::help) ),option("-v", "--version").call([]{cout << "版本 1.0nn" ;}).doc("显示版本") );if(parse(argc, argv, cli)) {switch(selected) {case 模式::make: /* ... */ break;case 模式::find : /* ... */ Break;case 模式::help: cout << make_man_page(cli, "finder");休息; } } 别的 { cout << use_lines(cli, "finder") << 'n'; }
下面是一些示例,可以让您了解 Clipp 的工作原理。考虑这个基本设置,其中包含一些我们想要使用命令行参数设置的变量:
int main(int argc, char* argv[]) { using namespace Clipp;//定义一些变量bool a = false, b = false;int n = 0, k = 0;double x = 0.0, y = 0.0; std::vector<int> ids;auto cli = ( /* 定义命令行接口的代码在此处 */ );parse(argc, argv, cli); // 排除 argv[0]std::cout << use_lines(cli, "exe") << 'n'; }
接口( usage_lines ) | 代码( cli 括号内容) |
---|---|
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 [-ma|b] | option("-m") & (required("a").set(a) | required("b").set(b)) |
有关每个主题的详细说明,请参阅示例部分。
为了更好的可读性,所有示例中都省略了命名空间限定符。所有实体都在namespace clipp
中定义。
int main(int argc, char* argv[]) { using 命名空间 Clipp;auto cli = ( /* 定义命令行接口的代码在此处 */ );parse(argc, argv, cli); //排除argv[0]//如果要包含argv[0]//parse(argv, argv+argc, cli);}
命令行界面有两种构建块:参数和组。方便命名的工厂函数可生成应用了所需设置的参数或组。
布尔 a = 假,f = 假; 字符串 s; vector<string> vs;auto cli = ( // 匹配所需的位置重复命令("push"), // 完全是 yes yes norequired("-f", "--file").set(f), // 完全是 yes no norequired("-a", "--all", "-A").set(a), // 完全没有 no no value("file", s), // 任何参数 yes yes novalues("file", vs), // 任何参数 yes yes yesopt_value("file", s), // 任何参数 no yes noopt_values("file" , vs), // any arg no yes yes//“捕获所有”参数 - 对于错误处理很有用any_other(vs), // any arg no no yes//捕获满足谓词且与其他不匹配的参数parametersany(predicate, vs) // 谓词 no no yes);
上面的函数都是便利工厂:
布尔 f = 真; string s;auto v1 = value("file", s);// 等价于:auto v2 =parameter{match::nonempty}.label("file").blocking(true).repeatable(true).set (s);auto r1 = required("-f", "--file").set(f);//相当于:auto r2 = 参数{"-f", “--file”}.required(true).set(f);
必需参数必须至少匹配一个命令行参数
可重复参数可以匹配任意数量的参数
非位置(=非阻塞)参数可以以任何顺序匹配参数
位置(阻塞)参数定义一个“停止点”,即,直到它匹配它后面的所有参数才允许匹配;一旦匹配,它之前的所有参数(当前组中)将变得无法访问
如果您希望参数按顺序匹配,可以使用operator &
或分组函数in_sequence
将它们连接在一起:
整数 n = 1;字符串 s; vector<int> ls;auto cli = (//带有必需值的选项option("-n", "--repeat") & value("times", n),//带有可选值的必需标志required("--file ") & opt_value("name", s), //恰好有两个值的选项option("-p", "--pos") & value("x") & value("y"),//相同在 v 之前vin_sequence( option("-p", "--pos") , value("x") , value("y") ), //至少有一个值的选项(可选多个)option("-l" ) & 值("行", ls) );
值参数使用过滤函数来测试是否允许它们与参数字符串匹配。 value
、 values
、 opt_value
和opt_values
使用的默认过滤器match::nonempty
将匹配任何非空参数字符串。您可以提供其他过滤器函数/函数对象作为value
、 values
等的第一个参数,或者使用这些涵盖最常见情况的内置速记工厂函数之一:
字符串名称;双 r = 0.0; int n = 0;auto cli = (value("user", name), // 匹配任何非空字符串word("user", name), // 匹配任何非空字母数字字符串number("ratio", r) , // 匹配数字的字符串表示形式integer("times", n) // 匹配整数的字符串表示形式);
与value
、 opt_value
等类似,还有words
、 opt_word
等函数。
auto is_char = [](const string& arg) { return arg.size() == 1 && std::isalpha(arg[0]); };字符c = ' '; // 匹配所需的位置重复值(is_char, "c", c); // 一个字符 yes yes no
用括号和逗号将相互兼容的参数分组:
auto cli = ( 选项("-a"), 选项("-b"), 选项("-c") );
使用operator |
将互斥参数分组为替代项或one_of
:
auto cli1 = ( value("input_file") | command("list") | command("flush") );auto cli2 = one_of( value("input_file") , command("list") , command("flush" ) );
对参数进行分组,以便它们必须使用operator &
或in_sequence
按顺序匹配:
双 x = 0, y = 0, z = 0;auto cli1 = ( 选项("-pos") & 值("X",x) & 值("Y",y) & 值("Z",z ) );auto cli2 = in_sequence( 选项("-pos") , 值("X",x) , 值("Y",y) , 值("Z",z) );
请注意,周围的组不受此影响,因此-a
和-b
可以按任意顺序匹配,而-b
和值X
必须按顺序匹配:
布尔 a = 假,b = 假; int x = 0;auto cli = ( 选项("-a").set(a), 选项("-b").set(b) & value("X",x) );
组可以嵌套和组合以形成任意复杂的接口(请参见此处和此处):
auto cli = ( 命令("推") | ( 命令("拉"), 选项("-f", "--force") ) );
组也可以是可重复的:
auto cli1 = 可重复( 命令("翻转") | 命令("翻牌") );
在一组标志上强制使用公共前缀:
int x = 0;auto cli1 = with_prefix("-", 选项("a"), 选项("b") & 值("x",x), ... ); // => -a -b ^不受影响^auto cli2 = with_prefix_short_long("-", "--", option("a", "all"), option("b"), ... ); // => -a --all -b
在一组标志上强制使用通用后缀:
int x = 0;auto cli1 = with_suffix("=", option("a") & value("x",x), ... ); // => a= ^不受影响^auto cli2 = with_suffix_short_long(":", ":=", option("a", "all"), option("b"), ... ); // => a: 全部:= b:
使一组标志可连接:
auto cli1 = joinable( 选项("-a"), 选项("-b")); // 将匹配 "-a", "-b", "-ab", "-ba" // 也适用于任意公共前缀:auto cli2 = joinable( option("--xA0"), option("- -xB1")); //也将匹配“--xA0B1”或“--xB1A0”
将命令行界面连接到代码其余部分的最简单方法是将对象值或函数(对象)调用绑定到参数(另请参阅此处):
布尔 b = 假;整数 i = 5;整数 m = 0;字符串 x; ifstream fs;auto cli = ( option("-b").set(b), // 检测到 "-b" -> 将 b 设置为 trueoption("-m").set(m,2), // " -m" 检测到 -> 将 m 设置为 2option("-x") & value("X", x), // 从参数字符串设置 x 的值 option("-i") & opt_value("i", i) , // 设置 i 的值arg string option("-v").call( []{ cout << "v"; } ), // 调用函数(对象) / lambdaoption("-v")( []{ cout << "v" ; } ), // 与上一行相同 option("-f") & value("file").call([&](string f){ fs.open(f); }) );
在生产代码中,人们可能会使用设置类:
结构设置 { bool x = false; /* ... */ }; 设置 cmdline_settings(int argc, char* argv[]) { 设置 s;auto cli = ( option("-x").set(sx), /* ... */ );parse(argc, argv, cli);return s; }
请注意,目标必须是:
基本类型( int, long int, float, double, ...
)
可从const char*
转换的类型
一个可调用实体:函数、函数对象/ lambda,它要么具有空参数列表,要么只有一个可从const char*
转换的参数
组和参数的文档字符串可以使用成员函数doc
或运算operator %
设置:
自动 CLI = ( ( option("x").set(x).doc("设置 X"),option("y").set(y) % "设置 Y" ), "记录组 1:" % ( option("-g").set(g).doc("激活 G"), option("-h").set(h) % "激活 H" ), ( option("-i").set(i) % "激活 I", option("-j").set(j) % "激活 J" ).doc("记录组 2:") );
使用线路:
cout << use_lines(cli, "progname") << 'n';//带格式选项auto fmt = doc_formatting{} .first_column(3) .last_column(79); cout << use_lines(cli, "progname", fmt) << 'n';
详细文档:
cout << Documentation(cli) << 'n';//带格式选项auto fmt = doc_formatting{} .first_column(7) .doc_列(15) .last_column(99); cout << 文档(cli, fmt) << 'n';
手册页:
auto cli = ( /*此处定义命令行界面的代码*/ ); cout << make_man_page(cli, "progname") << 'n';//带格式选项auto fmt = doc_formatting{} .first_column(7) .doc_列(15) .last_column(99); cout << make_man_page(cli, "progname", fmt) << 'n';
每个参数都可以附加事件处理函数。对于映射到参数的每个参数调用一次(或每个丢失事件调用一次):
string file = "default.txt";auto param = required("-nof").set(file,"") | 必需(“-f”)和值(“文件”,文件) // 第 2、第 3、第 4、... 匹配(在这种情况下会出现错误) .if_repeated( [] { /* ... */ } ) // 如果缺少必需的值参数 .if_missing( [] { /* ... */ } ) // 如果无法访问,例如文件名前没有标志“-f” .if_blocked( [] { /* ... */ } ) // 如果匹配与其他替代项冲突 "-nof" .if_conflicted( [] { /* ... */ } );
处理函数还可以采用 int,将其设置为事件首次发生的参数索引:
string file = "default.txt";auto param = required("-nof").set(file,"") | 必需(“-f”)和值(“文件”,文件) .if_repeated ( [] (int argIdx) { /* ... */ } ) .if_missing ( [] (int argIdx) { /* ... */ } ) .if_blocked ( [] (int argIdx) { /* ... */ } ) .if_conflicted( [] (int argIdx) { /* ... */ } );
如果我们将-f -b
或-b -f -a
作为以下 CLI 的命令行参数,则会报告错误,因为-f
之后的值不是可选的:
auto cli = ( 选项("-a"), 选项("-f") & 值("文件名"), 选项("-b") );
这种行为对于大多数用例来说都很好。但是,如果我们希望程序将任何字符串作为文件名,因为我们的文件名也可能与标志名称冲突,该怎么办?我们可以使用operator !
。这样,一旦给出-f
-f
之后的下一个字符串将始终与最高优先级匹配:
auto cli = ( option("-a"), option("-f") & !value("文件名"), option("-b") );// ^~~~~~
使用贪婪参数时要非常小心!
auto cli = ( /* 这里是你的接口 */ );auto res = parse(argc, argv, cli);if(res.any_error()) { /* ... */ }//聚合错误 if(res.unmapped_args_count ()) { /* ... */ }if(res.any_bad_repeat()) { /* ... */ }if(res.any_blocked()) { /* ... */ }if(res.any_conflict()) { /* ... */ }