معالجة وسيطة سطر الأوامر سهلة الاستخدام وقوية ومعبرة لـ C++ 11/14/17 الموجودة في ملف رأس واحد .
الخيارات، الخيارات + القيمة (القيم)، القيم الموضعية، الأوامر الموضعية، البدائل المتداخلة، أشجار القرار، العلامات القابلة للانضمام، مرشحات القيمة المخصصة، ...
توليد الوثائق (خطوط الاستخدام، صفحات الدليل)؛ معالجة الأخطاء
الكثير من الأمثلة؛ مجموعة كبيرة من الاختبارات
خذ بعين الاعتبار واجهة سطر الأوامر هذه:
ملخص تحويل <ملف الإدخال> [-r] [-o <تنسيق الإخراج>] [-utf16] خيارات -r، - تحويل الملفات بشكل متكرر -utf16 يستخدم ترميز UTF-16
إليك الكود الذي يحدد input file
القيمة الموضعية والخيارات الثلاثة -r
و -o
و -utf16
. إذا فشل التحليل، فستتم طباعة المقتطف الافتراضي الذي يشبه صفحة الدليل أعلاه إلى stdout.
#include <iostream> #include "clipp.h" باستخدام مساحة الاسم clipp؛ باستخدام الأمراض المنقولة جنسيا::cout; باستخدام std::string;int main(int argc, char* argv[]) { bool rec = false, utf16 = false; سلسلة 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]);// ...}
ملخص الباحث يصنع <wordfile> -dict <dictionary> [--progress] [-v] الباحث يجد <infile>... -dict <dictionary> [-o <outfile>] [-split|-nosplit] [-v] مساعدة الباحث [-v] خيارات --progress، -p إظهار التقدم -o، --output <outfile> اكتب إلى الملف بدلاً من stdout -تقسيم، -nosplit (لا) تقسيم الإخراج -v، --version عرض الإصدار
يحتوي سطر الأوامر هذا على ثلاثة أوامر بديلة ( make
, find
, help
) وبعض وسيطات القيمة الموضعية ( <wordfile>
, <infile>
) إحداها قابلة للتكرار، وعلامة مطلوبة مع وسيطة القيمة ( -dict <dictionary>
)، و خيار مع وسيطة القيمة ( -o <outfile>
) وخيار واحد مع بديلين ( -split
, -nosplit
) وخيارين تقليديين ( -v
, --progress
).
إليك الكود الذي يحدد الواجهة، وينشئ مقتطف صفحة الدليل أعلاه ويتعامل مع نتيجة التحليل:
باستخدام مساحة الاسم clipp؛ باستخدام الأمراض المنقولة جنسيا::cout; باستخدام std::string;// المتغيرات التي تخزن نتيجة التحليل؛ تمت تهيئته باستخدام وضع فئة القيم الافتراضية {make، find، help}؛ الوضع المحدد = الوضع::مساعدة؛ std::vector<string> input; سلسلة الإملاء، خارج؛ تقسيم منطقي = خطأ، بروجر = خطأ؛ القاموس التلقائي = مطلوب ("-ديكت") & القيمة ("القاموس"، dict)؛ auto makeMode = (command("make").set(selected,mode) ::يصنع)، القيم ("ملف Word"، الإدخال)، قاموس، option("--progress", "-p").set(progr) % "إظهار التقدم" );auto findMode = (command("find").set(selected,mode::find), القيم ("ملف"، الإدخال)، قاموس، (الخيار ("-o"، "--output") & value("outfile"، out)) % "اكتب إلى الملف بدلاً من stdout"، ( خيار("-سبليت" ).set(split,true) | option("-nosplit").set(split,false) ) % "(لا) تقسم الإخراج" );auto cli = ( (makeMode | findMode | Command("help").set(selected,mode::help) ),option("-v", "--version").call([]{cout << "version 1.0nn" ;}).doc("إظهار الإصدار") );if(parse(argc, argv, cli)) {switch(selected) {case mode::make: /* ... */break;case mode::find: /* ... */ Break;وضع الحالة::help: cout << make_man_page(cli, "finder"); استراحة؛ } } آخر { cout << use_lines(cli, "finder") << 'n'; }
فيما يلي بعض الأمثلة التي من شأنها أن تعطيك فكرة عن كيفية عمل Clipp. ضع في اعتبارك هذا الإعداد الأساسي مع بعض المتغيرات التي نريد تعيينها باستخدام وسيطات سطر الأوامر:
int main(int argc, char* argv[]) { باستخدام مساحة الاسم 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[]) { use namespace clipp;auto cli = ( /* كود تعريف واجهة سطر الأوامر يذهب هنا */ );parse(argc, argv, cli); // يستبعد argv[0]//إذا كنت تريد تضمين argv[0]//parse(argv, argv+argc, cli);}
هناك نوعان من العناصر الأساسية لواجهات سطر الأوامر: المعلمات والمجموعات. تعمل وظائف المصنع ذات الأسماء الملائمة على إنتاج معلمات أو مجموعات مع تطبيق الإعدادات المطلوبة.
منطقي أ = خطأ، و = خطأ؛ سلسلة ق؛ Vector<string> vs;auto cli = ( // يتطابق مع الموضع Repeatablecommand("push"), // بالضبط نعم نعم norequired("-f", "--file").set(f), // بالضبط نعم لا norequired("-a"، "--all"، "-A").set(a)، // بالضبط لا لا لا القيمة ("ملف"، s)، // أي وسيطة نعم نعم novalues("file"، vs)، // أي وسيطة نعم نعم Yesopt_value("file"، s)، // أي وسيطة لا نعم noopt_values("file" , vs)، // أي وسيطة لا نعم نعم // معلمة "التقاط الكل" - مفيدة لمعالجة الأخطاءany_other(vs)، // أي وسيطة لا لا نعم // تلتقط الوسائط التي تفي بالمسند و لا تتطابق مع معلمات أخرىany(predicate, vs) // predicate no no Yes);
الوظائف المذكورة أعلاه هي مصانع الراحة:
منطقي و = صحيح؛ string s;auto v1 =values("file", s);// يعادل:auto v2 = المعلمة{match::nonempty}.label("file").blocking(true).repeatable(true).set (s);auto r1 = مطلوب("-f", "--file").set(f);// يعادل:auto r2 = المعلمة{"-f", "--file"}.required (صحيح).مجموعة(و);
يجب أن تتطابق المعلمة المطلوبة مع وسيطة سطر أوامر واحدة على الأقل
يمكن أن تتطابق المعلمة القابلة للتكرار مع أي عدد من الوسائط
يمكن للمعلمات غير الموضعية (= غير المحظورة) مطابقة الوسائط بأي ترتيب
تحدد المعلمة الموضعية (الحظر) "نقطة توقف"، أي أنه حتى تتطابق مع جميع المعلمات التي تتبعها، لا يُسمح لها بالمطابقة؛ بمجرد مطابقتها، ستصبح جميع المعلمات التي تسبقها (ضمن المجموعة الحالية) غير قابلة للوصول
إذا كنت تريد مطابقة المعلمات بالتسلسل، فيمكنك ربطها معًا باستخدام إما operator &
أو وظيفة التجميع in_sequence
:
كثافة العمليات ن = 1؛ سلسلة ق؛ Vector<int> ls;auto cli = (// خيار بالقيمة المطلوبةoption("-n", "--repeat") & value("times", n),// علامة مطلوبة ذات قيمة اختياريةrequired("--file" ") & opt_value("name"، s)، // خيار بقيمتين بالضبطoption("-p"، "--pos") & value("x") & value("y")،// مثل قبل ضد vin_sequence( option("-p", "--pos") , value("x") , value("y") ), // خيار بقيمة واحدة على الأقل (وأكثر اختياريًا)option("-l" ) والقيم("خطوط"، ليرة سورية) );
تستخدم معلمات القيمة وظيفة التصفية لاختبار ما إذا كان مسموحًا لها بمطابقة سلسلة وسيطة. سوف يتطابق عامل التصفية الافتراضي match::nonempty
الذي يتم استخدامه بواسطة value
و values
و opt_value
و opt_values
مع أي سلسلة وسيطات غير فارغة. يمكنك إما توفير وظائف/كائنات دالة مرشح أخرى كوسيطة أولى value
values
وما إلى ذلك أو استخدام إحدى وظائف المصنع المختصرة المضمنة التي تغطي الحالات الأكثر شيوعًا:
اسم السلسلة؛ مزدوج ص = 0.0؛ int n = 0;auto cli = (value("user"، name)، // يطابق أي كلمة سلسلة غير فارغة ("user"، name)، // يطابق أي سلسلة أبجدية رقمية غير فارغة ("ratio"، r) ، // يطابق تمثيلات السلسلة للأعداد الصحيحة ("times"، n) // يطابق تمثيلات السلسلة للأعداد الصحيحة)؛
تشبه value
و opt_value
وما إلى ذلك. وهناك أيضًا وظائف words
و opt_word
وما إلى ذلك.
auto is_char = [](const string& arg) { return arg.size() == 1 && std::isalpha(arg[0]); };شار ج = ' '; // يطابق القيمة المتكررة الموضعية المطلوبة(is_char, "c, c); // حرف واحد نعم نعم لا
تجميع المعلمات المتوافقة بشكل متبادل مع الأقواس والفواصل:
auto cli = ( option("-a"), option("-b"), option("-c") );
قم بتجميع المعلمات الحصرية المتبادلة كبدائل باستخدام operator |
أو one_of
:
تلقائي cli1 = (قيمة("input_file") | أمر("قائمة") | أمر("تدفق") ؛تلقائي cli2 = one_of(قيمة("input_file")، أمر("قائمة")، أمر("تدفق" )));
معلمات المجموعة بحيث يجب مطابقتها بالتسلسل باستخدام operator &
أو 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) );
لاحظ أن المجموعات المحيطة لا تتأثر بهذا، بحيث يمكن مطابقة -a
و -b
بأي ترتيب بينما يجب أن يتطابق -b
والقيمة X
بالتسلسل:
منطقي أ = خطأ، ب = خطأ؛ int x = 0;auto cli = ( option("-a").set(a), option("-b").set(b) & value("X",x) );
يمكن تداخل المجموعات ودمجها لتكوين واجهات معقدة بشكل تعسفي (انظر هنا وهنا):
auto cli = ( Command("push") | ( Command("pull"), option("-f", "--force") ));
يمكن أن تكون المجموعات قابلة للتكرار أيضًا:
تلقائي cli1 = قابل للتكرار( أمر("flip") | أمر("flop"));
فرض البادئات المشتركة على مجموعة من الأعلام:
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"), ... ); // => -أ --الكل -ب
فرض اللواحق المشتركة على مجموعة من الأعلام:
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"), ... ); // => أ: الكل:= ب:
جعل مجموعة من الأعلام قابلة للانضمام:
auto cli1 = joinable( option("-a"), option("-b")); // سوف يتطابق مع "-a"، "-b"، "-ab"، "-ba"// يعمل أيضًا مع البادئات الشائعة العشوائية:auto cli2 = joinable( option("--xA0"), option("- -xB1")); // سيطابق أيضًا "--xA0B1" أو "--xB1A0"
أسهل طريقة لتوصيل واجهة سطر الأوامر ببقية التعليمات البرمجية الخاصة بك هي ربط قيم الكائنات أو استدعاءات الوظائف (الكائنات) بالمعلمات (انظر أيضًا هنا):
منطقي ب = خطأ؛ إنت ط = 5؛ كثافة العمليات م = 0؛ سلسلة س؛ ifstream fs;auto cli = ( option("-b").set(b), // "-b" تم اكتشافه -> set b إلى trueoption("-m").set(m,2), // " تم اكتشاف -m" -> تعيين m على 2option("-x") & value("X"، x)، // تعيين قيمة x من arg string option("-i") & opt_value("i"، i) ، // قم بتعيين قيمة i من سلسلة arg option("-v").call( []{ cout << "v"; } ), // وظيفة الاتصال (كائن) / lambdaoption("-v")( []{ cout << "v"; } ), // نفس الخط السابق("-f") & value("file").call([&](string f){ fs.open(f); }) );
في كود الإنتاج، من المحتمل أن يستخدم المرء فئة الإعدادات:
إعدادات البنية {منطقي x = خطأ؛ /* ... */ }; الإعدادات 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*
كيان قابل للاستدعاء: وظيفة، كائن دالة / لامدا يحتوي إما على قائمة معلمات فارغة أو معلمة واحدة فقط قابلة للتحويل من const char*
يمكن تعيين سلاسل المستندات الخاصة بالمجموعات والمعلمات إما باستخدام دالة العضو doc
أو باستخدام operator %
:
النقر التلقائي = ( ( option("x").set(x).doc("sets X"),option("y").set(y) % "sets Y" )، "المجموعة الموثقة 1:" % ( option("-g").set(g).doc("activates G"), الخيار("-h").set(h) % "ينشط H" )، ( option("-i").set(i) % "ينشط I"، الخيار("-j").set(j) % "ينشط J" ).doc("المجموعة الموثقة 2:") );
خطوط الاستخدام:
cout << use_lines(cli, "progname") << 'n';//مع خيارات التنسيقauto fmt = doc_formatting{} .العمود_الأول(3) .last_column(79); cout << use_lines(cli, "progname", fmt) << 'n';
التوثيق التفصيلي:
cout << توثيق (cli) << 'n';// مع خيارات التنسيق auto fmt = doc_formatting{} .العمود_الأول(7) .doc_column(15) .last_column(99); cout << توثيق (cli, fmt) << 'n';
صفحات الرجل:
auto cli = ( /*تظهر واجهة سطر الأوامر التي تحدد الكود هنا*/ ); cout << make_man_page(cli, "progname") << 'n';//مع خيارات التنسيقauto fmt = doc_formatting{} .العمود_الأول(7) .doc_column(15) .last_column(99); cout << make_man_page(cli, "progname", fmt) << 'n';
يمكن أن تحتوي كل معلمة على وظائف معالج الأحداث المرتبطة بها. يتم استدعاؤها مرة واحدة لكل وسيطة تم تعيينها للمعلمة (أو مرة واحدة لكل حدث مفقود):
ملف سلسلة = "default.txt"؛ auto param = مطلوب ("-nof").set(file،"") | مطلوب("-f") والقيمة("ملف"، ملف) // في اليوم الثاني، الثالث، الرابع،... التطابق (قد يكون خطأ في هذه الحالة) .if_repeated( [] { /* ... */ } ) // إذا كانت القيمة المطلوبة مفقودة .if_missing( [] { /* ... */ } ) // إذا تعذر الوصول إليه، على سبيل المثال، لا توجد إشارة "-f" قبل اسم الملف .if_blocked( [] { /* ... */ } ) // إذا كانت المباراة متعارضة مع بديل آخر "-nof" .if_conflicted( [] { /* ... */ } );
يمكن لوظائف المعالج أيضًا أن تأخذ int، والذي يتم تعيينه على فهرس الوسيطة الذي وقع فيه الحدث أولاً:
ملف سلسلة = "default.txt"؛ auto param = مطلوب ("-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
كوسائط سطر أوامر لواجهة سطر الأوامر التالية، فسيتم الإبلاغ عن خطأ، نظرًا لأن القيمة بعد -f
ليست اختيارية:
auto cli = ( option("-a"), option("-f") & value("filename"), option("-b") );
يعتبر هذا السلوك مناسبًا لمعظم حالات الاستخدام. ولكن ماذا لو أردنا أن يأخذ برنامجنا أي سلسلة كاسم ملف، لأن أسماء ملفاتنا قد تتعارض أيضًا مع أسماء العلامات؟ يمكننا أن نجعل معلمة القيمة جشعة مع operator !
. بهذه الطريقة، ستتم دائمًا مطابقة السلسلة التالية بعد -f
مع الأولوية القصوى بمجرد إعطاء -f
:
auto cli = ( option("-a"), option("-f") & !value("filename"), 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()) { /* ... */ }