Penanganan argumen baris perintah yang mudah digunakan, kuat, dan ekspresif untuk C++11/14/17 yang terdapat dalam satu file header .
opsi, opsi+nilai, nilai posisi, perintah posisi, alternatif bersarang, pohon keputusan, tanda yang dapat digabungkan, filter nilai khusus, ...
pembuatan dokumentasi (baris penggunaan, halaman manual); penanganan kesalahan
banyak contoh; serangkaian tes yang besar
Pertimbangkan antarmuka baris perintah ini:
RINGKASAN konversi <file masukan> [-r] [-o <format keluaran>] [-utf16] OPSI -r, --recursive mengkonversi file secara rekursif -utf16 menggunakan pengkodean UTF-16
Berikut adalah kode yang mendefinisikan input file
nilai posisi dan tiga opsi -r
, -o
dan -utf16
. Jika penguraian gagal, cuplikan seperti halaman manual default di atas akan dicetak ke stdout.
#include <iostream>#include "clipp.h"menggunakan namespace clipp; menggunakan std::cout; menggunakan 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("konversi file secara rekursif"),option("-o") & value("format keluaran", fmt),option("-utf16").set(utf16).doc("gunakan pengkodean UTF-16") );if(!parse(argc, argv, cli)) cout << make_man_page(cli, argv[0]);// ...}
RINGKASAN pencari membuat <wordfile> -dict <kamus> [--kemajuan] [-v] pencari temukan <infile>... -dict <kamus> [-o <file keluar>] [-split|-nosplit] [-v] bantuan pencari [-v] OPSI --kemajuan, -p menunjukkan kemajuan -o, --output <outfile> tulis ke file alih-alih stdout -split, -nosplit (jangan) membagi keluaran -v, --version tampilkan versi
CLI ini memiliki tiga perintah alternatif ( make
, find
, help
), beberapa argumen nilai posisi ( <wordfile>
, <infile>
) yang salah satunya dapat diulang, sebuah flag yang diperlukan dengan argumen nilai ( -dict <dictionary>
), dan opsi dengan argumen nilai ( -o <outfile>
), satu opsi dengan dua alternatif ( -split
, -nosplit
) dan dua opsi konvensional ( -v
, --progress
).
Berikut adalah kode yang mendefinisikan antarmuka, menghasilkan cuplikan halaman manual di atas dan menangani hasil parsing:
menggunakan clipp namespace; menggunakan std::cout; menggunakan std::string;//variabel menyimpan hasil parsing; diinisialisasi dengan mode kelas valuesenum defaultnya {make, find, help}; mode dipilih = mode::bantuan; std::vektor<string> masukan; string dict, out;bool split = false, progr = false; kamus otomatis = diperlukan("-dict") & value("kamus", dict);auto makeMode = (command("make").set(dipilih,mode ::membuat), nilai("file kata", masukan), kamus, option("--kemajuan", "-p").set(progr) % "tampilkan kemajuan" );auto findMode = (command("find").set(dipilih,mode::find), nilai("infile", masukan), kamus, (option("-o", "--output") & value("outfile", out)) % "tulis ke file alih-alih stdout", ( pilihan("-split" ).set(split,benar) | option("-nosplit").set(split,false) ) % "(jangan) membagi keluaran" );auto cli = ( (makeMode | findMode | command("help").set(dipilih,mode::help) ),option("-v", "--version").call([]{cout << "versi 1.0nn" ;}).doc("tampilkan versi") );if(parse(argc, argv, cli)) {switch(dipilih) {case mode::make: /* ... */ break;case mode::find : /* ... */ istirahat;mode kasus::bantuan: cout << make_man_page(cli, "finder"); merusak; } } kalau tidak { cout<< usage_lines(cli, "pencari") << 'n'; }
Berikut adalah beberapa contoh yang akan memberi Anda gambaran tentang cara kerja clipp. Pertimbangkan pengaturan dasar ini dengan beberapa variabel yang ingin kita atur menggunakan argumen baris perintah:
int main(int argc, char* argv[]) { menggunakan namespace clipp;// mendefinisikan beberapa variabelbool a = false, b = false;int n = 0, k = 0;double x = 0.0, y = 0.0; std::vector<int> ids;auto cli = ( /* KODE DEFINISI ANTARMUKA BARIS PERINTAH DI SINI */ );parse(argc, argv, cli); //tidak termasuk argv[0]std::cout << usage_lines(cli, "exe") << 'n'; }
Antarmuka ( usage_lines ) | Kode (isi tanda kurung 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)) |
Lihat bagian contoh untuk penjelasan rinci setiap topik.
Kualifikasi namespace dihilangkan dari semua contoh agar lebih mudah dibaca. Semua entitas didefinisikan dalam namespace clipp
.
int main(int argc, char* argv[]) { menggunakan namespace clipp;auto cli = ( /* KODE DEFINISI ANTARMUKA GARIS PERINTAH DI SINI */ );parse(argc, argv, cli); //tidak termasuk argv[0]//jika Anda ingin memasukkan argv[0]//parse(argv, argv+argc, cli);}
Ada dua jenis blok penyusun untuk antarmuka baris perintah: parameter dan grup. Fungsi pabrik yang diberi nama dengan mudah menghasilkan parameter atau grup dengan pengaturan yang diinginkan diterapkan.
bool a = salah, f = salah; string s; vektor<string> vs;auto cli = ( // cocok dengan perintah yang dapat diulang posisinya("push"), // persis ya ya tidak diperlukan("-f", "--file").set(f), // persis ya tidak tidak diperlukan("-a", "--all", "-A").set(a), // tepatnya tidak tidak tidak value("file", s), // argumen apa pun ya ya novalues("file", vs), // argumen apa pun ya ya yesopt_value("file", s), // argumen apa pun tidak ya noopt_values("file" , vs), // any arg no yes yes//parameter "catch all" - berguna untuk menangani kesalahanany_other(vs), // any arg no no yes//menangkap argumen yang memenuhi predikat dan tidak cocok dengan yang lain parameterany(predikat, vs) // predikat tidak tidak ya);
Fungsi di atas adalah pabrik kenyamanan:
bool f = benar; string s;auto v1 = nilai("file", s);// setara dengan:auto v2 = parameter{match::nonempty}.label("file").blocking(true).repeatable(true).set (s);auto r1 = diperlukan("-f", "--file").set(f);// setara dengan:auto r2 = parameter{"-f", "--file"}.diperlukan (benar).set(f);
parameter yang diperlukan harus cocok dengan setidaknya satu argumen baris perintah
parameter yang dapat diulang dapat cocok dengan sejumlah argumen
parameter non-posisi (= non-pemblokiran) dapat mencocokkan argumen dalam urutan apa pun
parameter posisi (pemblokiran) mendefinisikan "titik berhenti", yaitu, hingga parameter tersebut cocok, semua parameter berikutnya tidak boleh dicocokkan; setelah cocok, semua parameter sebelumnya (dalam grup saat ini) tidak akan dapat dijangkau
Jika Anda ingin parameter dicocokkan secara berurutan, Anda dapat menyatukannya menggunakan operator &
atau fungsi pengelompokan in_sequence
:
ke dalam n = 1; string s; vektor<int> ls;auto cli = (//pilihan dengan nilai yang diperlukanpilihan("-n", "--repeat") & nilai("kali", n),//tanda yang diperlukan dengan nilai opsional yang diperlukan("--file ") & opt_value("name", s), //pilihan dengan tepat dua nilaipilihan("-p", "--pos") & nilai("x") & nilai("y"),//sama dengan sebelum v vin_sequence( option("-p", "--pos") , value("x") , value("y") ), //option dengan setidaknya satu nilai (dan opsional lebih banyak)option("-l") & nilai("garis", ls) );
Parameter nilai menggunakan fungsi filter untuk menguji apakah parameter tersebut diizinkan untuk cocok dengan string argumen. Filter default match::nonempty
yang digunakan oleh value
, values
, opt_value
dan opt_values
akan cocok dengan string argumen yang tidak kosong. Anda dapat menyediakan fungsi filter/objek fungsi lainnya sebagai argumen pertama value
, values
, dll. atau menggunakan salah satu fungsi pabrik steno bawaan berikut yang mencakup kasus paling umum:
nama string; ganda r = 0,0; int n = 0;auto cli = (value("user", name), // cocok dengan stringword("user", name) yang tidak kosong, // cocok dengan stringnumber alfanumerik yang tidak kosong("ratio", r) , // mencocokkan representasi string dari bilangan bulat("kali", n) // mencocokkan representasi string dari bilangan bulat);
Analog dengan value
, opt_value
, dll. ada juga fungsi untuk words
, opt_word
, dll.
auto is_char = [](const string& arg) { return arg.size() == 1 && std::isalpha(arg[0]); };karakter c = ' '; // cocok dengan nilai berulang posisi yang diperlukan(is_char, "c", c); // satu karakter ya ya tidak
kelompokkan parameter yang saling kompatibel dengan tanda kurung dan koma:
auto cli = ( pilihan("-a"), pilihan("-b"), pilihan("-c") );
kelompokkan parameter yang saling eksklusif sebagai alternatif menggunakan operator |
atau one_of
:
auto cli1 = ( nilai("input_file") | perintah("daftar") | perintah("flush") );auto cli2 = one_of( nilai("input_file") , perintah("daftar") , perintah("flush" ) );
parameter grup sehingga harus dicocokkan secara berurutan menggunakan operator &
atau in_sequence
:
ganda x = 0, y = 0, z = 0;auto cli1 = ( pilihan("-pos") & nilai("X",x) & nilai("Y",y) & nilai("Z",z ) );auto cli2 = in_sequence( pilihan("-pos") , nilai("X",x) , nilai("Y",y) , nilai("Z",z) );
Perhatikan bahwa grup di sekitarnya tidak terpengaruh oleh hal ini, sehingga -a
dan -b
dapat dicocokkan dalam urutan apa pun sementara -b
dan nilai X
harus cocok secara berurutan:
bool a = salah, b = salah; int x = 0;auto cli = ( pilihan("-a").set(a), pilihan("-b").set(b) & nilai("X",x) );
grup dapat disarangkan dan digabungkan untuk membentuk antarmuka kompleks yang sewenang-wenang (lihat di sini dan di sini):
auto cli = ( perintah("push") | ( perintah("tarik"), pilihan("-f", "--force") ) );
grup dapat diulang juga:
auto cli1 = dapat diulang( command("flip") | command("flop") );
memaksakan awalan umum pada sekelompok bendera:
int x = 0;auto cli1 = with_prefix("-", option("a"), option("b") & value("x",x), ... ); // => -a -b ^tidak terpengaruh^auto cli2 = with_prefix_short_long("-", "--", option("a", "all"), option("b"), ... ); // => -a --semua -b
memaksakan sufiks umum pada sekelompok bendera:
int x = 0;auto cli1 = with_suffix("=", option("a") & value("x",x), ... ); // => a= ^tidak terpengaruh^auto cli2 = with_suffix_short_long(":", ":=", option("a", "all"), option("b"), ... ); // => a: semua:= b:
membuat sekelompok bendera dapat digabungkan:
auto cli1 = dapat digabungkan( opsi("-a"), opsi("-b")); //akan cocok dengan "-a", "-b", "-ab", "-ba"//berfungsi juga dengan awalan umum yang berubah-ubah:auto cli2 = joinable( option("--xA0"), option("- -xB1")); //juga akan cocok dengan "--xA0B1" atau "--xB1A0"
Cara termudah untuk menghubungkan antarmuka baris perintah ke seluruh kode Anda adalah dengan mengikat nilai objek atau panggilan fungsi (objek) ke parameter (lihat juga di sini):
bool b = salah; ke dalam saya = 5; ke dalam m = 0; tali x; ifstream fs;auto cli = ( option("-b").set(b), // "-b" terdeteksi -> setel b ke trueoption("-m").set(m,2), // " -m" terdeteksi -> setel m ke 2option("-x") & value("X", x), // setel nilai x dari arg string option("-i") & opt_value("i", i) , // tetapkan nilai i dari arg string option("-v").call( []{ cout << "v"; } ), // memanggil fungsi (objek) / lambdaoption("-v")( []{ cout << "v"; } ), // sama seperti lineoption(" sebelumnya -f") & nilai("file").panggilan([&](string f){ fs.open(f); }) );
Dalam kode produksi seseorang mungkin akan menggunakan kelas pengaturan:
pengaturan struct { bool x = false; /* ... */ }; pengaturan cmdline_settings(int argc, char* argv[]) { pengaturan s;auto cli = ( option("-x").set(sx), /* ... */ );parse(argc, argv, cli);return s; }
Perhatikan bahwa targetnya harus:
tipe dasar ( int, long int, float, double, ...
)
tipe yang dapat dikonversi dari const char*
entitas yang dapat dipanggil: fungsi, objek fungsi/lambda yang memiliki daftar parameter kosong atau tepat satu parameter yang dapat dikonversi dari const char*
Dokumen untuk grup dan parameter dapat diatur dengan fungsi anggota doc
atau dengan operator %
:
klik otomatis = ( ( pilihan("x").set(x).doc("kumpulan X"),pilihan("y").set(y) % "kumpulan Y" ), "grup terdokumentasi 1:" % ( option("-g").set(g).doc("mengaktifkan G"), pilihan("-h").set(h) % "mengaktifkan H" ), ( option("-i").set(i) % "mengaktifkan I", pilihan("-j").set(j) % "mengaktifkan J" ).doc("kelompok terdokumentasi 2:") );
Jalur Penggunaan:
cout << usage_lines(cli, "progname") << 'n';//dengan opsi pemformatanauto fmt = doc_formatting{} .kolom_pertama(3) .kolom_terakhir(79); cout<< usage_lines(cli, "namaprog", fmt) << 'n';
Dokumentasi Rinci:
cout << dokumentasi(cli) << 'n';//dengan opsi pemformatanauto fmt = doc_formatting{} .kolom_pertama(7) .doc_kolom(15) .kolom_terakhir(99); cout<<dokumentasi(cli,fmt)<<'n';
Halaman Manusia:
auto cli = ( /*KODE DEFINISI ANTARMUKA BARIS PERINTAH DI SINI*/ ); cout << make_man_page(cli, "progname") << 'n';//dengan opsi pemformatanauto fmt = doc_formatting{} .kolom_pertama(7) .doc_kolom(15) .kolom_terakhir(99); cout<< make_man_page(cli, "namaprog", fmt) << 'n';
Setiap parameter dapat memiliki fungsi pengendali kejadian yang melekat padanya. Ini dipanggil satu kali untuk setiap argumen yang dipetakan ke parameter (atau satu kali per kejadian yang hilang):
string file = "default.txt";auto param = diperlukan("-nof").set(file,"") | diperlukan("-f") & nilai("file", file) // pada tanggal 2, 3, 4,... pertandingan (akan menjadi kesalahan dalam kasus ini) .if_repeated( [] { /* ... */ } ) // jika parameter nilai yang diperlukan tidak ada .if_missing( [] { /* ... */ } ) // jika tidak dapat dijangkau, misalnya tidak ada tanda "-f" sebelum nama file .if_blocked( [] { /* ... */ } ) // jika kecocokan bertentangan dengan alternatif lain "-nof" .if_conflicted( [] { /* ... */ } );
Fungsi handler juga dapat menggunakan int, yang disetel ke indeks argumen tempat peristiwa terjadi pertama kali:
string file = "default.txt";param otomatis = diperlukan("-nof").set(file,"") | diperlukan("-f") & nilai("file", file) .if_repeated ( [] (int argIdx) { /* ... */ } ) .if_missing ( [] (int argIdx) { /* ... */ } ) .if_blocked ( [] (int argIdx) { /* ... */ } ) .if_conflicted( [] (int argIdx) { /* ... */ } );
Jika kita memberikan -f -b
atau -b -f -a
sebagai argumen baris perintah untuk CLI berikut, kesalahan akan dilaporkan, karena nilai setelah -f
bukan opsional:
auto cli = ( pilihan("-a"), pilihan("-f") & nilai("nama file"), pilihan("-b") );
Perilaku ini baik untuk sebagian besar kasus penggunaan. Namun bagaimana jika kita ingin program kita menggunakan string apa pun sebagai nama file, karena nama file kita mungkin juga bertabrakan dengan nama flag? Kita dapat membuat parameter nilai menjadi serakah dengan operator !
. Dengan cara ini, string berikutnya setelah -f
akan selalu dicocokkan dengan prioritas tertinggi segera setelah -f
diberikan:
auto cli = ( pilihan("-a"), pilihan("-f") & !nilai("nama file"), pilihan("-b") );// ^~~~~~~
Berhati-hatilah dengan parameter serakah!
auto cli = ( /* antarmuka Anda di sini */ );auto res = parse(argc, argv, cli);if(res.any_error()) { /* ... */ }//agregated errorif(res.unmapped_args_count ()) { /* ... */ }if(res.any_bad_repeat()) { /* ... */ }if(res.any_blocked()) { /* ... */ }jika(res.any_conflict()) { /* ... */ }