易於使用、強大且富有表現力的 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//捕獲滿足謂詞且與其他參數不匹配的參數any (謂詞, 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"),//相同before 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 的值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()) { /* ... */ }