- Notifications
You must be signed in to change notification settings - Fork27
Parse command line arguments by defining a struct
License
MIT and 2 other licenses found
Licenses found
p-ranav/structopt
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Parse command line arguments by defining a struct
#include<structopt/app.hpp>structOptions {// positional argument// e.g., ./main <file> std::string config_file;// optional argument// e.g., -b "192.168.5.3"// e.g., --bind_address "192.168.5.3"//// options can be delimited with `=` or `:`// note: single dash (`-`) is enough for short & long option// e.g., -bind_address=localhost// e.g., -b:192.168.5.3//// the long option can also be provided in kebab case:// e.g., --bind-address 192.168.5.3 std::optional<std::string> bind_address;// flag argument// Use `std::optional<bool>` and provide a default value.// e.g., -v// e.g., --verbose// e.g., -verbose std::optional<bool> verbose =false;// directly define and use enum classes to limit user choice// e.g., --log-level debug// e.g., -l errorenumclassLogLevel { debug, info, warn, error, critical }; std::optional<LogLevel> log_level = LogLevel::info;// pair argument// e.g., -u <first> <second>// e.g., --user <first> <second> std::optional<std::pair<std::string, std::string>> user;// use containers like std::vector// to collect "remaining arguments" into a list std::vector<std::string> files;};STRUCTOPT(Options, config_file, bind_address, verbose, log_level, user, files);
Create astructopt::app and parse the command line arguments into theOptions struct:
intmain(int argc,char *argv[]) {try {// Line of code that does all the work:auto options =structopt::app("my_app").parse<Options>(argc, argv);// Print out parsed arguments:// std::cout << "config_file = " << options.config_file << "\n";// std::cout << "bind_address = " << options.bind_address.value_or("not provided") << "\n";// std::cout << "verbose = " << std::boolalpha << options.verbose.value() << "\n";// ... }catch (structopt::exception& e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
Now let's pass some arguments to this program:
foo@bar:~$./main config.csv file5.csv file6.jsonconfig_file = config.csvbind_address = not providedverbose = falselog_level = 1user = not providedfiles = { file5.csv file6.json }foo@bar:~$./main config.csv --bind-address localhost:9000 -v -log-level error file1.txt file2.txtconfig_file = config.csvbind_address = localhost:9000verbose = truelog_level = 3user = not providedfiles = { file1.txt file2.txt }foo@bar:~$./main config_2.csv --bind-address 192.168.7.3 -log-level debug file1.txt file3.txt file4.txt --user"John Doe""john.doe@foo.com"config_file = config_2.csvbind_address = 192.168.7.3verbose = falselog_level = 0user = John Doe<john.doe@foo.com>files = { file1.txt file3.txt file4.txt }
- Getting Started
- Building Samples and Tests
- Compiler Compatibility
- Generating Single Header
- Contributing
- License
structopt is a header-only library. Just addinclude/ to yourinclude_directories and you should be good to go. A single header file version is also available insingle_include/.
Here's an example of two positional arguments:input_file andoutput_file.input_file is expected to be the first argument andoutput_file is expected to be the second argument
#include<structopt/app.hpp>structFileOptions {// Positional arguments// ./main <input_file> <output_file> std::string input_file; std::string output_file;};STRUCTOPT(FileOptions, input_file, output_file);intmain(int argc,char *argv[]) {try {auto options =structopt::app("my_app").parse<FileOptions>(argc, argv);// Print parsed arguments: std::cout <<"\nInput file :" << options.input_file <<"\n"; std::cout <<"Output file :" << options.output_file <<"\n"; }catch (structopt::exception& e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
foo@bar:~$./main foo.txt bar.csvInput file : foo.txtOutput file : bar.csvfoo@bar:~$./main foo.csvError: expected value for positional argument `output_file`.USAGE: ./my_app input_file output_fileARGS: input_file output_file
Now, let's look at optional arguments. To configure an optional argument, usestd::optional in the options struct like below.
#include<structopt/app.hpp>structGccOptions {// language standard// e.g., -std=c++17// e.g., --std c++20 std::optional<std::string> std ="c++11";// verbosity enabled with `-v` or `--verbose`// or `-verbose` std::optional<bool> verbose =false;// enable all warnings with `-Wall` std::optional<bool> Wall =false;// produce only the compiled code// e.g., gcc -C main.c std::optional<bool> Compile =false;// produce output with `-o <exec_name>` std::optional<std::string> output ="a.out"; std::string input_file;};STRUCTOPT(GccOptions, std, verbose, Wall, Compile, output, input_file);intmain(int argc,char *argv[]) {try {auto options =structopt::app("gcc").parse<GccOptions>(argc, argv);// Print parsed arguments std::cout <<"std :" << options.std.value() <<"\n"; std::cout <<"verbose :" << std::boolalpha << options.verbose.value() <<"\n"; std::cout <<"Wall :" << std::boolalpha << options.Wall.value() <<"\n"; std::cout <<"Compile :" << std::boolalpha << options.Compile.value() <<"\n"; std::cout <<"Output :" << options.output.value() <<"\n"; std::cout <<"Input file :" << options.input_file <<"\n"; }catch (structopt::exception &e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
NOTEstructopt supports two option delimiters,= and: for optional arguments. This is meaningful and commonly used in single-valued optional arguments, e.g.,--std=c++17.
foo@bar:~$./main -C main.cppstd : c++11verbose : falseWall : falseCompile : trueOutput : a.outInput file : main.cppfoo@bar:~$./main -std=c++17 -o main main.cppstd : c++17verbose : falseWall : falseCompile : falseOutput : mainInput file : main.cppfoo@bar:~$./main main.cpp -v -std:c++14 --output:main -Wallstd : c++14verbose : trueWall : trueCompile : falseOutput : mainInput file : main.cpp
NOTE In summary, for a field in your struct namedbind_address, the following are all legal ways to provide a value:
- Short form:
-b <value>
- Long form:
--bind_address <value>-bind_address <value>
- Kebab case:
--bind-address <value>-bind-address <value>
- Equal (
'=') option delimiter-b=<value>--bind_address=<value>-bind_address=<value>--bind-address=<value>-bind-address=<value>
- Colon
':'option delimiter-b:<value>--bind_address:<value>-bind_address:<value>--bind-address:<value>-bind-address:<value>
A double dash (--) is used in most bash built-in commands and many other commands to signify the end of command options, after which only positional parameters are accepted.
Example use: lets say you want togrep a file for the string-v - normally-v will be considered the option to reverse the matching meaning (only show lines that do not match), but with-- you cangrep for string-v like this:
#include<structopt/app.hpp>structGrepOptions {// reverse the matching// enable with `-v` std::optional<bool> v =false;// positional arguments std::string search; std::string pathspec;};STRUCTOPT(GrepOptions, v, search, pathspec);intmain(int argc,char *argv[]) {try {auto options =structopt::app("my_app").parse<GrepOptions>(argc, argv);if (options.v ==true) { std::cout <<"`-v` provided - Matching is now reversed\n"; } std::cout <<"Search :" << options.search <<"\n"; std::cout <<"Pathspec :" << options.pathspec <<"\n"; }catch (structopt::exception& e) { std::cout << e.what(); std::cout << e.help(); }}
foo@bar:~$./main -v foo bar.txt`-v` provided - Matching is now reversedSearch : fooPathspec : bar.txtfoo@bar:~$./main -- -v bar.txtSearch : -vPathspec : bar.txt
Flag arguments arestd::optional<bool> with a default value.
NOTE The default value here is important. It is not a flag if a default value isn't provided. It will simply be an optional argument.
NOTE If--verbose is a flag argument with a default value offalse, then providing the argument will set it totrue. If--verbose does not have a default value, thenstructopt will expect the user to provide a value, e.g.,--verbose true.
#include<structopt/app.hpp>structOptions {// verbosity flag// -v, --verbose// remember to provide a default value std::optional<bool> verbose =false;};STRUCTOPT(Options, verbose);intmain(int argc,char *argv[]) {auto options =structopt::app("my_app").parse<Options>(argc, argv);if (options.verbose ==true) { std::cout <<"Verbosity enabled\n"; }}
foo@bar:~$./mainfoo@bar:~$./main -vVerbosity enabledfoo@bar:~$./main --verboseVerbosity enabled
Thanks tomagic_enum,structopt supports enum classes. You can use an enum classes to ask the user to provide a value given a choice of values, restricting the possible set of allowed input arguments.
#include<structopt/app.hpp>structStyleOptions {enumclassColor {red, green, blue};// e.g., `--color red` std::optional<Color> color = Color::red;};STRUCTOPT(StyleOptions, color);intmain(int argc,char *argv[]) {try {auto options =structopt::app("my_app").parse<StyleOptions>(argc, argv);// Use parsed argument `options.color`if (options.color == StyleOptions::Color::red) { std::cout <<"#ff0000\n"; }elseif (options.color == StyleOptions::Color::blue) { std::cout <<"#0000ff\n"; }elseif (options.color == StyleOptions::Color::green) { std::cout <<"#00ff00\n"; } }catch (structopt::exception& e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
foo@bar:~$./main --color red#ff0000foo@bar:~$./main -c blue#0000fffoo@bar:~$./main --color green#00ff00foo@bar:~$./main -c blackError: unexpected input `black` provided for enum argument `color`. Allowed values are {red, green, blue}USAGE: ./my_app [OPTIONS]OPTIONS: -c, --color <color>
Now that we've looked at enum class support, let's build a simple calculator. In this sample, we will use anstd::tuple to pack all the arguments to the calculator:
#include<structopt/app.hpp>structCalculatorOptions {// types of operations supportedenumclassoperation { add, subtract, multiply, divide };// single tuple positional argument std::tuple<operation,int,int> input;};STRUCTOPT(CalculatorOptions, input);intmain(int argc,char *argv[]) {try {auto options =structopt::app("my_app").parse<CalculatorOptions>(argc, argv);auto op = std::get<0>(options.input);auto lhs = std::get<1>(options.input);auto rhs = std::get<2>(options.input);switch(op) {case CalculatorOptions::operation::add: std::cout << lhs + rhs <<"\n";break;case CalculatorOptions::operation::subtract: std::cout << lhs - rhs <<"\n";break;case CalculatorOptions::operation::multiply: std::cout << lhs * rhs <<"\n";break;case CalculatorOptions::operation::divide: std::cout << lhs / rhs <<"\n";break; } }catch (structopt::exception& e) { std::cout << e.what(); std::cout << e.help(); }}
foo@bar:~$./main add 1 23foo@bar:~$./main subtract 5 9-4foo@bar:~$./main multiply 16 580foo@bar:~$./main divide 1331 11121foo@bar:~$./main add 5Error: failed to correctly parse tuple `input`. Expected 3 arguments, 2 provided.USAGE: my_app inputARGS: input
structopt supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
$ compiler file1 file2 file3
Do this by using anstd::vector<T> (or other STL containers with.push_back(), e.g,std::deque orstd::list).
NOTE Vector arguments have a cardinality of0..*, i.e., zero or more arguments. Unlike array types, you can provide zero arguments to a vector andstructopt will (try to) not complain.
#include<structopt/app.hpp>structCompilerOptions {// Language standard// e.g., --std c++17 std::optional<std::string> std;// remaining arguments// e.g., ./compiler file1 file2 file3 std::vector<std::string> files{};};STRUCTOPT(CompilerOptions, std, files);intmain(int argc,char *argv[]) {try {auto options =structopt::app("my_app").parse<CompilerOptions>(argc, argv); std::cout <<"Standard :" << options.std.value_or("not provided") <<"\n"; std::cout <<"Files : {";std::copy(options.files.begin(), options.files.end(), std::ostream_iterator<std::string>(std::cout,"")); std::cout <<"}" << std::endl; }catch (structopt::exception &e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
NOTE Notice below that the act of gathering remaining arguments is arrested as soon as an optional argument is detected. See the output of./main file1.cpp file2.cpp --std c++17 below. Notice that--std=c++17 is not part of the vector. This is because--std is a valid optional argument.
foo@bar:~$./mainStandard : not providedFiles : { }foo@bar:~$./main file1.cpp file2.cppStandard : not providedFiles : { file1.cpp file2.cpp }foo@bar:~$./main file1.cpp file2.cpp --std=c++17Standard : c++17Files : { file1.cpp file2.cpp }foo@bar:~$./main --std:c++20 file1.cpp file2.cppStandard : c++20Files : { file1.cpp file2.cpp }
Compound arguments are optional arguments that are combined and provided as a single argument. Example:ps -aux
#include<structopt/app.hpp>structOptions {// Flag arguments std::optional<bool> a =false; std::optional<bool> b =false;// Optional argument// e.g., -c 1.1 2.2 std::optional<std::array<float,2>> c = {};};STRUCTOPT(Options, a, b, c);intmain(int argc,char *argv[]) {try {auto options =structopt::app("my_app").parse<Options>(argc, argv);// Print parsed arguments: std::cout << std::boolalpha <<"a =" << options.a.value() <<", b =" << options.b.value() <<"\n";if (options.c.has_value()) { std::cout <<"c = [" << options.c.value()[0] <<"," << options.c.value()[1] <<"]\n"; } }catch (structopt::exception &e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
foo@bar:~$./main -ac 3.14 2.718a = true, b = falsec = [3.14, 2.718]foo@bar:~$./main -baa = true, b = truefoo@bar:~$./main -c 1.5 3.0 -aba = true, b = truec = [1.5, 3]
structopt supports parsing integer literals including hexadecimal, octal, and binary notation.
#include<structopt/app.hpp>structIntegerLiterals { std::vector<int> numbers;};STRUCTOPT(IntegerLiterals, numbers);intmain(int argc,char *argv[]) {try {auto options =structopt::app("my_app").parse<IntegerLiterals>(argc, argv);for (auto &n : options.numbers) std::cout << n <<"\n"; }catch (structopt::exception &e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
foo@bar:~$./main 1 0x5B 071 0b0101 -35 +98191575-3598
As for floating point numbers,structopt supports parsing scientific notation (e/E-notation):
#include<structopt/app.hpp>structFloatLiterals { std::vector<float> numbers;};STRUCTOPT(FloatLiterals, numbers);intmain(int argc,char *argv[]) {try {auto options =structopt::app("my_app").parse<FloatLiterals>(argc, argv);for (auto &n : options.numbers) std::cout << n <<"\n"; }catch (structopt::exception &e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
foo@bar:~$./main -3.15 +2.717 2E-4 0.1e2 .5 -.3 +5.999-3.152.7170.0002100.5-0.35.999
Withstructopt, you can define sub-commands, e.g.,git init args orgit config [flags] args using nested structures.
- Simply create a nested structure that inherits from
structopt::sub_command - You can use
<nested_struct_object>.has_value()to check if it has been invoked.
The following program support two sub-commands:config andinit:
#include<structopt/app.hpp>structGit {// Subcommand: git configstructConfig : structopt::sub_command {// flag argument `--global` std::optional<bool> global =false;// key-value pair, e.g., `user.name "John Doe"` std::array<std::string,2> name_value_pair{}; }; Config config;// Subcommand: git initstructInit : structopt::sub_command {// required argument// repository name std::string name; }; Init init;};STRUCTOPT(Git::Config, global, name_value_pair);STRUCTOPT(Git::Init, name);STRUCTOPT(Git, config, init);intmain(int argc,char *argv[]) {try {auto options =structopt::app("my_app").parse<Git>(argc, argv);if (options.config.has_value()) {// config was invoked std::cout <<"You invoked `git config`:\n"; std::cout <<"Global :" << std::boolalpha << options.config.global.value() <<"\n"; std::cout <<"Input : (" << options.config.name_value_pair[0] <<"," << options.config.name_value_pair[1] <<")\n"; }elseif (options.init.has_value()) {// init was invoked std::cout <<"You invoked `git init`:\n"; std::cout <<"Repository name :" << options.init.name <<"\n"; } }catch (structopt::exception& e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
foo@bar:~$./main config user.email"john.doe@foo.com"You invoked `git config`:Global : falseInput : (user.email, john.doe@foo.com)foo@bar:~$./main config user.name"John Doe" --globalYou invoked `git config`:Global : trueInput : (user.name, John Doe)foo@bar:~$./main init my_repoYou invoked `git init`:Repository name : my_repofoo@bar:~$./main -hUSAGE: my_app [OPTIONS] [SUBCOMMANDS]OPTIONS: -h, --help <help> -v, --version <version>SUBCOMMANDS: config initfoo@bar:~$./main config -hUSAGE: config [FLAGS] [OPTIONS] name_value_pairFLAGS: -g, --globalOPTIONS: -h, --help <help> -v, --version <version>ARGS: name_value_pairfoo@bar:~$./main init -hUSAGE: init [OPTIONS] nameOPTIONS: -h, --help <help> -v, --version <version>ARGS: name
NOTE Notice in the above stdout that the-h help option supports printing help both at the top-level struct and at the sub-command level.
NOTEstructopt does not allow to invoke multiple sub-commands. If one has already been invoked, you will see the following error:
foo@bar:~$./main config user.name"John Doe" init my_repoError: failed to invoke sub-command `init` because a different sub-command, `config`, has already been invoked.
Here's a second example for nested structures with vector arguments and the double dash (--) delimiter
#include<structopt/app.hpp>structCommandOptions {structSed : structopt::sub_command {// --trace std::optional<bool> trace =false;// remaining args std::vector<std::string> args;// pattern std::string pattern;// file std::string file; }; Sed sed;};STRUCTOPT(CommandOptions::Sed, trace, args, pattern, file);STRUCTOPT(CommandOptions, sed);intmain(int argc,char *argv[]) {auto app =structopt::app("my_app");try {auto options = app.parse<CommandOptions>(argc, argv);if (options.sed.has_value()) {// sed has been invokedif (options.sed.trace ==true) { std::cout <<"Trace enabled!\n"; } std::cout <<"Args :";for (auto& a : options.sed.args) std::cout << a <<""; std::cout <<"\n"; std::cout <<"Pattern :" << options.sed.pattern <<"\n"; std::cout <<"File :" << options.sed.file <<"\n"; }else { std::cout << app.help(); } }catch (structopt::exception &e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
foo@bar:~$./mainUSAGE: my_app [OPTIONS] [SUBCOMMANDS]OPTIONS: -h, --help <help> -v, --version <version>SUBCOMMANDS: sedfoo@bar:~$./main sed --trace X=1 Y=2 Z=3 --'s/foo/bar/g' foo.txtTrace enabled!Args : X=1 Y=2 Z=3Pattern : s/foo/bar/gFile : foo.txt
structopt will insert two optional arguments for the user:help andversion.
- Using
-hor--helpwill print the help message and exit. - Using
-vor--versionwill print the program version and exit.
#include<structopt/app.hpp>structOptions {// positional arguments std::string input_file; std::string output_file;// optional arguments std::optional<std::string> bind_address;// remaining arguments std::vector<std::string> files;};STRUCTOPT(Options, input_file, output_file, bind_address, files);intmain(int argc,char *argv[]) {auto options =structopt::app("my_app","1.0.3").parse<Options>(argc, argv);}
foo@bar:~$./main -hUSAGE: my_app [OPTIONS] input_file output_file filesOPTIONS: -b, --bind-address <bind_address> -h, --help <help> -v, --version <version>ARGS: input_file output_file filesfoo@bar:~$./main -v1.0.3
structopt allows users to provide a custom help messages. Simply pass in your custom help as a string argument tostructopt::app
#include<structopt/app.hpp>structOptions {// positional arguments std::string input_file; std::string output_file;// optional arguments std::optional<std::string> bind_address;// remaining arguments std::vector<std::string> files;};STRUCTOPT(Options, input_file, output_file, bind_address, files);intmain(int argc,char *argv[]) {try {const std::string& custom_help ="Usage: ./my_app input_file output_file [--bind-address BIND_ADDRESS] [files...]\n";auto options =structopt::app("my_app","1.0.3", custom_help).parse<Options>(argc, argv); }catch (structopt::exception &e) { std::cout << e.what() <<"\n"; std::cout << e.help(); }}
foo@bar:~$./main -hUsage: ./my_app input_file output_file [--bind-address BIND_ADDRESS] [files...]
git clone https://github.com/p-ranav/structoptcd structoptmkdir build&&cd buildcmake -DSTRUCTOPT_SAMPLES=ON -DSTRUCTOPT_TESTS=ON ..make
For Windows, if you useWinLibs like I do, the cmake command would look like this:
foo@bar:~$mkdir build&&cd buildfoo@bar:~$cmake -G"MinGW Makefiles" -DCMAKE_CXX_COMPILER="C:/WinLibs/mingw64/bin/g++.exe" -DSTRUCTOPT_SAMPLES=ON -DSTRUCTOPT_TESTS=ON ..foo@bar:~$makefoo@bar:~$.\tests\structopt_tests.exe[doctest] doctest version is "2.3.5"[doctest] run with "--help" for options===============================================================================[doctest] test cases: 54 | 54 passed | 0 failed | 0 skipped[doctest] assertions: 393 | 393 passed | 0 failed |[doctest] Status: SUCCESS!
- Clang/LLVM >= 5
- MSVC++ >= 14.11 / Visual Studio >= 2017
- Xcode >= 10
- GCC >= 9
python3 utils/amalgamate/amalgamate.py -c single_include.json -s.Contributions are welcome, have a look at theCONTRIBUTING.md document for more information.
The project is available under theMIT license.
About
Parse command line arguments by defining a struct
Topics
Resources
License
MIT and 2 other licenses found
Licenses found
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Contributors5
Uh oh!
There was an error while loading.Please reload this page.
