Einfach zu verwendende, leistungsstarke und ausdrucksstarke Befehlszeilenargumentverarbeitung für C++11/14/17, enthalten in einer einzigen Header-Datei .
Optionen, Optionen+Wert(e), Positionswerte, Positionsbefehle, verschachtelte Alternativen, Entscheidungsbäume, verknüpfbare Flags, benutzerdefinierte Wertfilter, ...
Dokumentationserstellung (Nutzungszeilen, Manpages); Fehlerbehandlung
viele Beispiele; große Menge an Tests
Betrachten Sie diese Befehlszeilenschnittstelle:
ZUSAMMENFASSUNG <Eingabedatei> konvertieren [-r] [-o <Ausgabeformat>] [-utf16] OPTIONEN -r, --recursive Dateien rekursiv konvertieren -utf16 verwendet die UTF-16-Kodierung
Hier ist der Code, der die Positionswert- input file
und die drei Optionen -r
, -o
und -utf16
definiert. Wenn das Parsen fehlschlägt, wird der obige Standard-Manpage-ähnliche Snippet auf stdout gedruckt.
#include <iostream>#include „clipp.h“using namespace clipp; mit std::cout; using 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("Dateien konvertieren rekursiv"),option("-o") & value("Ausgabeformat", fmt),option("-utf16").set(utf16).doc("UTF-16-Kodierung verwenden") );if(!parse(argc, argv, cli)) cout << make_man_page(cli, argv[0]);// ...}
ZUSAMMENFASSUNG finder make <wordfile> -dict <dictionary> [--progress] [-v] finder find <infile>... -dict <dictionary> [-o <outfile>] [-split|-nosplit] [-v] Finder-Hilfe [-v] OPTIONEN --progress, -p zeigt den Fortschritt an -o, --output <outfile> schreibt in eine Datei statt in stdout -split, -nosplit (nicht) teilt die Ausgabe -v, --version Version anzeigen
Diese CLI verfügt über drei alternative Befehle ( make
, find
, help
), einige Positionswertargumente ( <wordfile>
, <infile>
), von denen eines wiederholbar ist, ein erforderliches Flag mit Wertargument ( -dict <dictionary>
) und Option mit Wertargument ( -o <outfile>
), eine Option mit zwei Alternativen ( -split
, -nosplit
) und zwei konventionelle Optionen ( -v
, --progress
).
Hier ist der Code, der die Schnittstelle definiert, das obige Manpage-Snippet generiert und das Parsing-Ergebnis verarbeitet:
Verwenden des Namespace-Clipp; mit std::cout; Verwenden von std::string;//variables zum Speichern des Parsing-Ergebnisses; mit ihren Standardwerten initialisiertenum class mode {make, find, help}; Modus ausgewählt = mode::help; std::vector<string> input; string dict, out;bool split = false, progr = false;auto dictionary = require("-dict") & value("dictionary", dict);auto makeMode = (command("make").set(selected,mode ::machen), Werte("wordfile", Eingabe), Wörterbuch, option("--progress", "-p").set(progr) % "show progress" );auto findMode = (command("find").set(selected,mode::find), Werte("infile", Eingabe), Wörterbuch, (option("-o", "--output") & value("outfile", out)) % "in Datei schreiben statt in stdout", ( option("-split" ).set(split,true) | option("-nosplit").set(split,false) ) % "Ausgabe (nicht) teilen" );auto cli = ( (makeMode | findMode | command("help").set(selected,mode::help) ),option("-v", "--version").call([]{cout << "version 1.0nn" ;}).doc("show version") );if(parse(argc, argv, cli)) {switch(selected) {case mode::make: /* ... */ break;case mode::find : /* ... */ break;case mode::help: cout << make_man_page(cli, "finder"); brechen; } } anders { cout << use_lines(cli, "finder") << 'n'; }
Nachfolgend finden Sie einige Beispiele, die Ihnen eine Vorstellung davon geben sollen, wie Clipp funktioniert. Betrachten Sie diesen Grundaufbau mit einigen Variablen, die wir mithilfe von Befehlszeilenargumenten festlegen möchten:
int main(int argc, char* argv[]) { using namespace clipp;// einige Variablen definierenbool a = false, b = false;int n = 0, k = 0;double x = 0.0, y = 0.0; std::vector<int> ids;auto cli = ( /* CODE, DER DIE BEFEHLSZEILENSCHNITTSTELLE HIER GEHT */ );parse(argc, argv, cli); //schließt argv[0]std::cout << usage_lines(cli, "exe") << 'n'; }
Schnittstelle ( usage_lines ) | Code (Inhalt der cli -Klammern) |
---|---|
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)) |
Ausführliche Erläuterungen zu den einzelnen Themen finden Sie im Abschnitt „Beispiele“.
Namespace-Qualifizierer werden zur besseren Lesbarkeit in allen Beispielen weggelassen. Alle Entitäten sind im namespace clipp
definiert.
int main(int argc, char* argv[]) { using namespace clipp;auto cli = ( /* CODE DEFINING COMMAND LINE INTERFACE HERE */ );parse(argc, argv, cli); //schließt argv[0] aus//wenn Sie argv[0] einschließen möchten//parse(argv, argv+argc, cli);}
Es gibt zwei Arten von Bausteinen für Befehlszeilenschnittstellen: Parameter und Gruppen. Zweckmäßig benannte Werksfunktionen erzeugen Parameter oder Gruppen mit den gewünschten Einstellungen.
bool a = falsch, f = falsch; Zeichenfolge s; vector<string> vs;auto cli = ( // entspricht dem erforderlichen positionellen Repeatablecommand("push"), // genau ja ja norequired("-f", "--file").set(f), // genau ja nein norequired("-a", "--all", "-A").set(a), // genau nein nein nein value("file", s), // beliebiges Argument ja ja novalues("file", vs), // beliebiges Argument ja ja jaopt_value("file", s), // beliebiges Argument nein ja noopt_values("file" , vs), // jedes Argument, nein, ja, ja//Parameter „Alle abfangen“ – nützlich für die Fehlerbehandlungany_other(vs), // jedes Argument, nein, nein ja//fängt Argumente ab, die ein Prädikat erfüllen und nicht mit anderen Parametern übereinstimmen (Prädikat, vs) // Prädikat nein nein ja);
Die oben genannten Funktionen sind Convenience-Fabriken:
bool f = wahr; string s;auto v1 = value("file", s);// ist äquivalent zu:auto v2 = parameter{match::nonempty}.label("file").blocking(true).repeatable(true).set (s);auto r1 = require("-f", "--file").set(f);// ist äquivalent zu:auto r2 = parameter{"-f", "--file"}.required (true).set(f);
Ein erforderlicher Parameter muss mit mindestens einem Befehlszeilenargument übereinstimmen
Ein wiederholbarer Parameter kann mit einer beliebigen Anzahl von Argumenten übereinstimmen
Nicht positionelle (= nicht blockierende) Parameter können Argumenten in beliebiger Reihenfolge entsprechen
Ein Positionsparameter (Blockierungsparameter) definiert einen „Stopppunkt“, dh bis er übereinstimmt, dürfen alle darauf folgenden Parameter nicht übereinstimmen. Sobald es übereinstimmt, sind alle Parameter davor (innerhalb der aktuellen Gruppe) nicht mehr erreichbar
Wenn Sie möchten, dass Parameter der Reihe nach abgeglichen werden, können Sie sie mithilfe des operator &
oder der Gruppierungsfunktion in_sequence
miteinander verknüpfen:
int n = 1; Zeichenfolge s; vector<int> ls;auto cli = (//Option mit erforderlichem Wertoption("-n", "--repeat") & Wert("times", n),//erforderliches Flag mit optionalem Wertrequired("--file ") & opt_value("name", s), //Option mit genau zwei Wertenoption("-p", "--pos") & value("x") & value("y"),//das Gleiche wie vor v vin_sequence( option("-p", "--pos") , value("x") , value("y") ), //Option mit mindestens einem Wert (und optional mehreren)option("-l") & Werte("Linien", ls) );
Wertparameter verwenden eine Filterfunktion, um zu testen, ob sie mit einer Argumentzeichenfolge übereinstimmen dürfen. Der Standardfilter match::nonempty
der von value
, values
, opt_value
und opt_values
verwendet wird, stimmt mit jeder nicht leeren Argumentzeichenfolge überein. Sie können entweder andere Filterfunktionen/Funktionsobjekte als erstes Argument von value
, values
usw. angeben oder eine dieser integrierten Kurzschrift-Factory-Funktionen verwenden, die die häufigsten Fälle abdecken:
String-Name; doppelt r = 0,0; int n = 0;auto cli = (value("user", name), // entspricht jedem nicht leeren Stringword("user", name), // entspricht jedem nicht leeren alphanumerischen Stringnumber("ratio", r) , // entspricht String-Darstellungen von Zahleninteger("times", n) // entspricht String-Darstellungen von Ganzzahlen);
Analog zu value
, opt_value
usw. gibt es auch Funktionen für words
, opt_word
usw.
auto is_char = [](const string& arg) { return arg.size() == 1 && std::isalpha(arg[0]); };char c = ' '; // stimmt mit dem erforderlichen positionellen wiederholbaren Wert überein (is_char, "c", c); // ein Zeichen ja ja nein
Gruppieren Sie miteinander kompatible Parameter mit Klammern und Kommas:
auto cli = ( option("-a"), option("-b"), option("-c") );
Gruppieren Sie sich gegenseitig ausschließende Parameter als Alternativen mithilfe operator |
oder one_of
:
auto cli1 = ( value("input_file") | command("list") | command("flush") );auto cli2 = one_of( value("input_file") , command("list") , command("flush" ) );
Gruppieren Sie Parameter so, dass sie der Reihe nach mit operator &
oder in_sequence
abgeglichen werden müssen:
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) );
Beachten Sie, dass umliegende Gruppen davon nicht betroffen sind, sodass -a
und -b
in beliebiger Reihenfolge abgeglichen werden können, während -b
und der Wert X
der Reihe nach übereinstimmen müssen:
bool a = falsch, b = falsch; int x = 0;auto cli = ( option("-a").set(a), option("-b").set(b) & value("X",x) );
Gruppen können verschachtelt und zu beliebig komplexen Schnittstellen kombiniert werden (siehe hier und hier):
auto cli = ( command("push") | ( command("pull"), option("-f", "--force") ) );
Gruppen können auch wiederholbar sein:
auto cli1 = reproduzierbar( command("flip") | command("flop") );
gemeinsame Präfixe für eine Gruppe von Flags erzwingen:
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
gemeinsame Suffixe für eine Gruppe von Flags erzwingen:
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:
Machen Sie eine Gruppe von Flags verbindbar:
auto cli1 = joinable( option("-a"), option("-b")); //passt auf „-a“, „-b“, „-ab“, „-ba“//funktioniert auch mit beliebigen allgemeinen Präfixen:auto cli2 = joinable( option(“--xA0“), option(“- -xB1")); //wird auch mit „--xA0B1“ oder „--xB1A0“ übereinstimmen
Der einfachste Weg, die Befehlszeilenschnittstelle mit dem Rest Ihres Codes zu verbinden, besteht darin, Objektwerte oder Funktionsaufrufe (Objektaufrufe) an Parameter zu binden (siehe auch hier):
bool b = false; int i = 5; int m = 0; Zeichenfolge x; ifstream fs;auto cli = ( option("-b").set(b), // "-b" erkannt -> b auf true setzenoption("-m").set(m,2), // " -m" erkannt -> setze m auf 2option("-x") & value("X", x), // setze den Wert von x aus arg string option("-i") & opt_value("i", i) , // Setze den Wert von i aus arg string option("-v").call( []{ cout << "v"; } ), // Funktion aufrufen (Objekt) / lambdaoption("-v")( []{ cout << "v"; } ), // dasselbe wie vorherige lineoption(" -f") & value("file").call([&](string f){ fs.open(f); }) );
Im Produktionscode würde man wahrscheinlich eine Einstellungsklasse verwenden:
Struktureinstellungen { bool x = false; /* ... */ }; Einstellungen cmdline_settings(int argc, char* argv[]) { Einstellungen s;auto cli = ( option("-x").set(sx), /* ... */ );parse(argc, argv, cli);return s; }
Beachten Sie, dass das Ziel entweder Folgendes sein muss:
ein grundlegender Typ ( int, long int, float, double, ...
)
ein Typ, der von const char*
konvertierbar ist
eine aufrufbare Entität: Funktion, Funktionsobjekt/Lambda, die entweder eine leere Parameterliste oder genau einen Parameter hat, der von const char*
konvertierbar ist
Docstrings für Gruppen und für Parameter können entweder mit der Member-Funktion doc
oder mit operator %
gesetzt werden:
auto cli = ( ( option("x").set(x).doc("setzt X"),option("y").set(y) % "setzt Y" ), "dokumentierte Gruppe 1:" % ( option("-g").set(g).doc("aktiviert G"), option("-h").set(h) % "aktiviert H" ), ( option("-i").set(i) % "aktiviert I", option("-j").set(j) % "aktiviert J" ).doc("dokumentierte Gruppe 2:") );
Nutzungslinien:
cout << use_lines(cli, "progname") << 'n';//mit Formatierungsoptionenauto fmt = doc_formatting{} .first_column(3) .last_column(79); cout << use_lines(cli, "progname", fmt) << 'n';
Ausführliche Dokumentation:
cout <<documentation(cli) << 'n';//mit Formatierungsoptionenauto fmt = doc_formatting{} .first_column(7) .doc_column(15) .last_column(99); cout << Dokumentation(cli, fmt) << 'n';
Manpages:
auto cli = ( /*CODE DEFINIERT DIE BEFEHLSZEILENSCHNITTSTELLE HIER GEHT*/ ); cout << make_man_page(cli, "progname") << 'n';//mit Formatierungsoptionenauto fmt = doc_formatting{} .first_column(7) .doc_column(15) .last_column(99); cout << make_man_page(cli, "progname", fmt) << 'n';
An jeden Parameter können Event-Handler-Funktionen angehängt werden. Diese werden einmal für jedes Argument aufgerufen, das dem Parameter zugeordnet ist (oder einmal pro fehlendem Ereignis):
string file = "default.txt";auto param = require("-nof").set(file,"") | erforderlich("-f") & Wert("Datei", Datei) // bei 2., 3., 4.,... Übereinstimmung (wäre in diesem Fall ein Fehler) .if_repeated( [] { /* ... */ } ) // wenn der erforderliche Wertparameter fehlte .if_missing( [] { /* ... */ } ) // wenn nicht erreichbar, z. B. kein Flag „-f“ vor Dateiname .if_blocked( [] { /* ... */ } ) // wenn die Übereinstimmung mit einer anderen Alternative in Konflikt steht „-nof“ .if_conflicted( [] { /* ... */ } );
Die Handlerfunktionen können auch ein int annehmen, das auf den Argumentindex gesetzt wird, bei dem das Ereignis zuerst aufgetreten ist:
string file = "default.txt";auto param = require("-nof").set(file,"") | erforderlich("-f") & Wert("Datei", Datei) .if_repeated ( [] (int argIdx) { /* ... */ } ) .if_missing ( [] (int argIdx) { /* ... */ } ) .if_blocked ( [] (int argIdx) { /* ... */ } ) .if_conflicted( [] (int argIdx) { /* ... */ } );
Wenn wir -f -b
oder -b -f -a
als Befehlszeilenargumente für die folgende CLI angeben, wird ein Fehler gemeldet, da der Wert nach -f
nicht optional ist:
auto cli = ( option("-a"), option("-f") & value("filename"), option("-b") );
Dieses Verhalten ist für die meisten Anwendungsfälle in Ordnung. Aber was ist, wenn wir möchten, dass unser Programm eine beliebige Zeichenfolge als Dateinamen akzeptiert, da unsere Dateinamen möglicherweise auch mit Flag-Namen kollidieren? Wir können den Wertparameter mit operator !
. Auf diese Weise wird der nächste String nach -f
immer mit der höchsten Priorität abgeglichen, sobald -f
angegeben wurde:
auto cli = ( option("-a"), option("-f") & !value("filename"), option("-b") );// ^~~~~~
Seien Sie sehr vorsichtig mit gierigen Parametern!
auto cli = ( /* Ihre Schnittstelle hier */ );auto res = parse(argc, argv, cli);if(res.any_error()) { /* ... */ }//aggregatederrorsif(res.unmapped_args_count ()) { /* ... */ }if(res.any_bad_repeat()) { /* ... */ }if(res.any_blocked()) { /* ... */ }if(res.any_conflict()) { /* ... */ }