Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Parse command line arguments by defining a struct

License

MIT and 2 other licenses found

Licenses found

MIT
LICENSE
MIT
LICENSE.magic_enum
BSL-1.0
LICENSE.visit_struct
NotificationsYou must be signed in to change notification settings

p-ranav/structopt

Parse command line arguments by defining a struct

ci statusconan packageci statusci statuscodacystandardlicense

Quick Start

#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 }

Table of Contents

Getting Started

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/.

Positional Arguments

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

Optional Arguments

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>

Double dash (--) Argument

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

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

Enum Class Arguments

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>

Tuple Arguments

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

Vector Arguments

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

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]

Parsing Numbers

Integer Literals

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

Floating point Literals

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

Nested Structures

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 fromstructopt::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.

Sub-Commands, Vector Arguments, and Delimited Positional Arguments

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

Printing Help

structopt will insert two optional arguments for the user:help andversion.

  • Using-h or--help will print the help message and exit.
  • Using-v or--version will 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

Printing CUSTOM Help

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...]

Building Samples and Tests

git clone https://github.com/p-ranav/structoptcd structoptmkdir build&&cd buildcmake -DSTRUCTOPT_SAMPLES=ON -DSTRUCTOPT_TESTS=ON ..make

WinLibs + MinGW

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!

Compiler Compatibility

  • Clang/LLVM >= 5
  • MSVC++ >= 14.11 / Visual Studio >= 2017
  • Xcode >= 10
  • GCC >= 9

Generating Single Header

python3 utils/amalgamate/amalgamate.py -c single_include.json -s.

Contributing

Contributions are welcome, have a look at theCONTRIBUTING.md document for more information.

License

The project is available under theMIT license.


[8]ページ先頭

©2009-2026 Movatter.jp