Parse command line arguments by defining a struct

Overview

Parse command line arguments by defining a struct

ci status conan package ci status ci status codacy standard license

Quick Start

#include <structopt/app.hpp>

struct Options {
   // 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 error
   enum class LogLevel { 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 a structopt::app and parse the command line arguments into the Options struct:

int main(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:

[email protected]:~$ ./main config.csv file5.csv file6.json
config_file  = config.csv
bind_address = not provided
verbose      = false
log_level    = 1
user         = not provided
files        = { file5.csv file6.json }

[email protected]:~$ ./main config.csv --bind-address localhost:9000 -v -log-level error file1.txt file2.txt
config_file  = config.csv
bind_address = localhost:9000
verbose      = true
log_level    = 3
user         = not provided
files        = { file1.txt file2.txt }

[email protected]:~$ ./main config_2.csv --bind-address 192.168.7.3 -log-level debug file1.txt file3.txt file4.txt --user "John Doe" "[email protected]"
config_file  = config_2.csv
bind_address = 192.168.7.3
verbose      = false
log_level    = 0
user         = John Doe<[email protected]>
files        = { file1.txt file3.txt file4.txt }

Table of Contents

Getting Started

structopt is a header-only library. Just add include/ to your include_directories and you should be good to go. A single header file version is also available in single_include/.

Positional Arguments

Here's an example of two positional arguments: input_file and output_file. input_file is expected to be the first argument and output_file is expected to be the second argument

#include <structopt/app.hpp>

struct FileOptions {
  // Positional arguments
  // ./main <input_file> <output_file>
  std::string input_file;
  std::string output_file;
};
STRUCTOPT(FileOptions, input_file, output_file);



int main(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();
  }
}
[email protected]:~$ ./main foo.txt bar.csv

Input file  : foo.txt
Output file : bar.csv

[email protected]:~$ ./main foo.csv
Error: expected value for positional argument `output_file`.

USAGE: ./my_app input_file output_file

ARGS:
    input_file
    output_file

Optional Arguments

Now, let's look at optional arguments. To configure an optional argument, use std::optional in the options struct like below.

#include <structopt/app.hpp>

struct GccOptions {
  // 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);


int main(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();
  }
}

NOTE structopt supports two option delimiters, = and : for optional arguments. This is meaningful and commonly used in single-valued optional arguments, e.g., --std=c++17.

[email protected]:~$ ./main -C main.cpp
std        : c++11
verbose    : false
Wall       : false
Compile    : true
Output     : a.out
Input file : main.cpp

[email protected]:~$ ./main -std=c++17 -o main main.cpp
std        : c++17
verbose    : false
Wall       : false
Compile    : false
Output     : main
Input file : main.cpp

[email protected]:~$ ./main main.cpp -v -std:c++14 --output:main -Wall
std        : c++14
verbose    : true
Wall       : true
Compile    : false
Output     : main
Input file : main.cpp

NOTE In summary, for a field in your struct named bind_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 to grep 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 can grep for string -v like this:

#include <structopt/app.hpp>

struct GrepOptions {
  // reverse the matching
  // enable with `-v`
  std::optional<bool> v = false;
  
  // positional arguments
  std::string search;
  std::string pathspec;
};
STRUCTOPT(GrepOptions, v, search, pathspec);



int main(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();
  }

}
[email protected]:~$ ./main -v foo bar.txt
`-v` provided - Matching is now reversed
Search   : foo
Pathspec : bar.txt

[email protected]:~$ ./main -- -v bar.txt
Search   : -v
Pathspec : bar.txt

Flag Arguments

Flag arguments are std::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 of false, then providing the argument will set it to true. If --verbose does not have a default value, then structopt will expect the user to provide a value, e.g., --verbose true.

#include <structopt/app.hpp>

struct Options {
  // verbosity flag
  // -v, --verbose
  // remember to provide a default value
  std::optional<bool> verbose = false;
};
STRUCTOPT(Options, verbose);



int main(int argc, char *argv[]) {
  auto options = structopt::app("my_app").parse<Options>(argc, argv);

  if (options.verbose == true) {
    std::cout << "Verbosity enabled\n";
  }
}
[email protected]:~$ ./main

[email protected]:~$ ./main -v
Verbosity enabled

[email protected]:~$ ./main --verbose
Verbosity enabled

Enum Class Arguments

Thanks to magic_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>

struct StyleOptions {
  enum class Color {red, green, blue};

  // e.g., `--color red`
  std::optional<Color> color = Color::red;
};
STRUCTOPT(StyleOptions, color);



int main(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";
    }
    else if (options.color == StyleOptions::Color::blue) {
        std::cout << "#0000ff\n";
    }
    else if (options.color == StyleOptions::Color::green) {
        std::cout << "#00ff00\n";
    }

  } catch (structopt::exception& e) {
    std::cout << e.what() << "\n";
    std::cout << e.help();
  }
}
[email protected]:~$ ./main --color red
#ff0000

[email protected]:~$ ./main -c blue
#0000ff

[email protected]:~$ ./main --color green
#00ff00

[email protected]:~$ ./main -c black
Error: 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 an std::tuple to pack all the arguments to the calculator:

#include <structopt/app.hpp>

struct CalculatorOptions {

  // types of operations supported
  enum class operation { add, subtract, multiply, divide };

  // single tuple positional argument
  std::tuple<operation, int, int> input;

};
STRUCTOPT(CalculatorOptions, input);



int main(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();
  }

}
[email protected]:~$ ./main add 1 2
3

[email protected]:~$ ./main subtract 5 9
-4

[email protected]:~$ ./main multiply 16 5
80

[email protected]:~$ ./main divide 1331 11
121

[email protected]:~$ ./main add 5
Error: failed to correctly parse tuple `input`. Expected 3 arguments, 2 provided.

USAGE: my_app input

ARGS:
    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 an std::vector<T> (or other STL containers with .push_back(), e.g, std::deque or std::list).

NOTE Vector arguments have a cardinality of 0..*, i.e., zero or more arguments. Unlike array types, you can provide zero arguments to a vector and structopt will (try to) not complain.

#include <structopt/app.hpp>

struct CompilerOptions {
  // 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);



int main(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.

[email protected]:~$ ./main
Standard : not provided
Files    : { }

[email protected]:~$ ./main file1.cpp file2.cpp
Standard : not provided
Files    : { file1.cpp file2.cpp }

[email protected]:~$ ./main file1.cpp file2.cpp --std=c++17
Standard : c++17
Files    : { file1.cpp file2.cpp }

[email protected]:~$ ./main --std:c++20 file1.cpp file2.cpp
Standard : c++20
Files    : { 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>

struct Options {
  // 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);



int main(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();
  }
}
[email protected]:~$ ./main -ac 3.14 2.718
a = true, b = false
c = [3.14, 2.718]

[email protected]:~$ ./main -ba
a = true, b = true

[email protected]:~$ ./main -c 1.5 3.0 -ab
a = true, b = true
c = [1.5, 3]

Parsing Numbers

Integer Literals

structopt supports parsing integer literals including hexadecimal, octal, and binary notation.

#include <structopt/app.hpp>

struct IntegerLiterals {
  std::vector<int> numbers;
};
STRUCTOPT(IntegerLiterals, numbers);

int main(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();
  }
}
[email protected]:~$ ./main 1 0x5B 071 0b0101 -35 +98
1
91
57
5
-35
98

Floating point Literals

As for floating point numbers, structopt supports parsing scientific notation (e/E-notation):

#include <structopt/app.hpp>

struct FloatLiterals {
  std::vector<float> numbers;
};
STRUCTOPT(FloatLiterals, numbers);

int main(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();
  }
}
[email protected]:~$ ./main -3.15 +2.717 2E-4 0.1e2 .5 -.3 +5.999
-3.15
2.717
0.0002
10
0.5
-0.3
5.999

Nested Structures

With structopt, you can define sub-commands, e.g., git init args or git 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 and init:

#include <structopt/app.hpp>

struct Git {
  // Subcommand: git config
  struct Config : 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 init
  struct Init : 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);



int main(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";
    }
    else if (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();
  }
}
[email protected]:~$ ./main config user.email "[email protected]"
You invoked `git config`:
Global : false
Input  : (user.email, [email protected])

[email protected]:~$ ./main config user.name "John Doe" --global
You invoked `git config`:
Global : true
Input  : (user.name, John Doe)

[email protected]:~$ ./main init my_repo
You invoked `git init`:
Repository name : my_repo



[email protected]:~$ ./main -h

USAGE: my_app [OPTIONS] [SUBCOMMANDS]

OPTIONS:
    -h, --help <help>
    -v, --version <version>

SUBCOMMANDS:
    config
    init




[email protected]:~$ ./main config -h

USAGE: config [FLAGS] [OPTIONS] name_value_pair

FLAGS:
    -g, --global

OPTIONS:
    -h, --help <help>
    -v, --version <version>

ARGS:
    name_value_pair




[email protected]:~$ ./main init -h

USAGE: init [OPTIONS] name

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

NOTE structopt does not allow to invoke multiple sub-commands. If one has already been invoked, you will see the following error:

[email protected]:~$ ./main config user.name "John Doe" init my_repo
Error: 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>

struct CommandOptions {
  struct Sed : 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);



int main(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 invoked

      if (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();
  }
}
[email protected]:~$ ./main

USAGE: my_app [OPTIONS] [SUBCOMMANDS]

OPTIONS:
    -h, --help <help>
    -v, --version <version>

SUBCOMMANDS:
    sed



[email protected]:~$ ./main sed --trace X=1 Y=2 Z=3 -- 's/foo/bar/g' foo.txt
Trace enabled!
Args    : X=1 Y=2 Z=3
Pattern : s/foo/bar/g
File    : foo.txt

Printing Help

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

  • 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>

struct Options {
  // 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);



int main(int argc, char *argv[]) {
  auto options = structopt::app("my_app", "1.0.3").parse<Options>(argc, argv);
}
[email protected]:~$ ./main -h

USAGE: my_app [OPTIONS] input_file output_file files

OPTIONS:
    -b, --bind-address <bind_address>
    -h, --help <help>
    -v, --version <version>

ARGS:
    input_file
    output_file
    files

[email protected]:~$ ./main -v
1.0.3

Printing CUSTOM Help

structopt allows users to provide a custom help messages. Simply pass in your custom help as a string argument to structopt::app

#include <structopt/app.hpp>

struct Options {
  // 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);

int main(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();
  }
}
[email protected]:~$ ./main -h
Usage: ./my_app input_file output_file [--bind-address BIND_ADDRESS] [files...]

Building Samples and Tests

git clone https://github.com/p-ranav/structopt
cd structopt
mkdir build && cd build
cmake -DSTRUCTOPT_SAMPLES=ON -DSTRUCTOPT_TESTS=ON ..
make

WinLibs + MinGW

For Windows, if you use WinLibs like I do, the cmake command would look like this:

[email protected]:~$ mkdir build && cd build
[email protected]:~$ cmake -G "MinGW Makefiles" -DCMAKE_CXX_COMPILER="C:/WinLibs/mingw64/bin/g++.exe" -DSTRUCTOPT_SAMPLES=ON -DSTRUCTOPT_TESTS=ON ..
[email protected]:~$ make

[email protected]:~$ .\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 the CONTRIBUTING.md document for more information.

License

The project is available under the MIT license.

Issues
  • Provide help string for arguments

    Provide help string for arguments

    Hello,

    I would like to add some help strings for the arguments / subcommands defined in the options struct.

    i.e. - provide an explanation for what each subcommand does or for the arguments in the help usage.

    Is it currently possible?

    Thanks for the great work!

    opened by speidy 3
  • compile error when structopt is used in namespace

    compile error when structopt is used in namespace

    Hello and first of all, thanks for this nice piece of software !

    When STRUCTOPT(...) is used inside a namespace, I encounter the following error:

    /home/chybz/dev/zap/build/root/include/structopt/third_party/visit_struct/visit_struct.hpp:839:22: error: ‘visitable’ is not a class template
      839 |   template <> struct visitable<STRUCT_NAME, void> {                                      \
          |                      ^~~~~~~~~
    /home/chybz/dev/zap/build/root/include/structopt/app.hpp:13:19: note: in expansion of macro ‘VISITABLE_STRUCT’
       13 | #define STRUCTOPT VISITABLE_STRUCT
          |                   ^~~~~~~~~~~~~~~~
    so.cpp:16:1: note: in expansion of macro ‘STRUCTOPT’
       16 | STRUCTOPT(Options, config_file, bind_address, verbose, user, files);
          | ^~~~~~~~~
    /home/chybz/dev/zap/build/root/include/structopt/third_party/visit_struct/visit_struct.hpp:839:51: error: explicit specialization of non-template ‘foo::visit_struct::traits::visitable’
      839 |   template <> struct visitable<STRUCT_NAME, void> {
    

    Here's the minimal example I used, compiled with gcc (Debian 10.2.1-6) 10.2.1 20210110:

    g++ -Wall -std=c++20 -o test test.cpp
    
    #include <iostream>
    #include <optional>
    #include <vector>
    
    #include <structopt/app.hpp>
    
    namespace foo {
    
    struct Options {
       std::string config_file;
       std::optional<std::string> bind_address;
       std::optional<bool> verbose = false;
       std::optional<std::pair<std::string, std::string>> user;
       std::vector<std::string> files;
    };
    STRUCTOPT(Options, config_file, bind_address, verbose, user, files);
    
    }
    
    int main(int argc, char *argv[]) {
    
      try {
        auto options = structopt::app("my_app").parse<foo::Options>(argc, argv);
    
        std::cout << "config_file  = " << options.config_file << "\n";
      } catch (structopt::exception& e) {
        std::cout << e.what() << "\n";
        std::cout << e.help();
      }
    }
    

    If I remove namespace foo { ... }, it compiles without error.

    Could you please have a look ? Thank you !

    opened by chybz 2
  • Custom help message

    Custom help message

    Hi, do you plan to add ability of setting up a custom help message? structopt adds --help command line flag unconditionally, without ability to override built-in help message format, so I think it makes sense even if you add a help description per argument in the future. It requires only a small change, I made it to the structopt bundled in my project, you can see how it looks here

    opened by kamchatka-volcano 1
  • Only run MSVC related checks if the compiler is actually MSVC

    Only run MSVC related checks if the compiler is actually MSVC

    If for example you want to compile with clang++ on Windows where MSVC can be found this check is run even though MSVC is not used for compilation. In this case the CMAKE_CXX_COMPILER_ID would be Clang and thus the check is skipped.

    In my current project I got the following messages during the configuration step even though I am using Ninja and clang++ (= no Visual Studio or MSVC):

    -- Performing Test _cpp_latest_flag_supported
    -- Performing Test _cpp_latest_flag_supported - Failed
    

    By wrapping this check into an if condition that the compiler ID is actually MSVC (or not) the check is gone for my project.

    Disclaimer: I only think this is working, I have personally no way of checking if the check is actually still being run if MSVC is used because I have never successfully setup a CMake project with MSVC.

    Also I have seen that some libraries use the following wrapper which worked too for my project but I like the current solution more because you can literally read what CMake is checking while just if (MSVC) can also mean other things (like MSVC is installed):

    if (MSVC)
    # ....
    endif()
    

    Edit: I have update all "MSCV"s to "MSVC"

    opened by AnonymerNiklasistanonym 1
  • Exception without try catch?

    Exception without try catch?

    #include <iostream>
    #include <optional>
    #include <string>
    
    #include <structopt/app.hpp>
    
    struct Options {
      std::string db_name;
      std::string path_files;
      std::string path_archive;
      enum class LogLevel { trace, debug, info, warn, err, critical, off };
      std::optional<LogLevel> log_level = LogLevel::debug;
    };
    STRUCTOPT(Options, db_name, path_files, path_archive, log_level);
    
    int main(int argc, char* argv[]) {
      auto opts = structopt::app("test", "1.0.0.1").parse<Options>(argc, argv);
    
      std::cout << "Hello World!\n";
    }
    

    without parameters ./test.exe

    Microsoft Visual Studio Community 2019 Version 16.7.2 VisualStudio.16.Release/16.7.2+30413.136

    opened by ru108 1
  • Show flags as kebab-case in addition to options

    Show flags as kebab-case in addition to options

    structopt supports interpreting flags as kebab-case, but in help, they are printed out as snake_case although options are shown as kebab-case. This PR implements a feature to print flags as kebab-case as well as options do.

    opened by ken-matsui 0
  • Excess positional arguments do not generate an error.

    Excess positional arguments do not generate an error.

    If excess positional arguments are provided the parser does not raise an exception.

    struct FileOptions {
      // Positional arguments
      // ./main <input_file> <output_file>
      std::string input_file;
      std::string output_file;
    };
    STRUCTOPT(FileOptions, input_file, output_file);
    

    Executing ./main file1 file2 file3 does not raise an exception.

    bug 
    opened by agoessling 0
  • At least one positional argument is required to properly raise an error for unknown optional arguments.

    At least one positional argument is required to properly raise an error for unknown optional arguments.

    If you only have optional arguments in the structure no error will be produced when unknown optional or positional arguments are provided.

    struct Options {
      std::optional<std::string> directory;
    };
    STRUCTOPT(Options, directory);
    

    Executing ./a.out --bad or ./a.out bad will not raise an exception. If you add a positional argument, you will get the expected result.

    struct Options {
      std::optional<std::string> directory;
      std::string dummy;
    };
    STRUCTOPT(Options, directory, dummy);
    
    bug 
    opened by agoessling 0
  • GCC with strong compilation flags (Werror) throws errors in include/structopt/is_number.hpp file

    GCC with strong compilation flags (Werror) throws errors in include/structopt/is_number.hpp file

    When including structopt in my custom project which adds strong compilation flags for GCC (-pedantic -Wall -Werror -Wextra -Wformat) the following errors appear:

    In file included from [...]/structopt/include/structopt/parser.hpp:13,
                     from [...]/structopt/include/structopt/app.hpp:8,
                     from [...]/main.cpp:1:
    [...]/structopt/include/structopt/is_number.hpp:9:8: error: type qualifiers ignored on function return type [-Werror=ignored-qualifiers]
        9 | static const bool is_binary_notation(std::string const &input) {
          |        ^~~~~
    [...]/structopt/include/structopt/is_number.hpp:14:8: error: type qualifiers ignored on function return type [-Werror=ignored-qualifiers]
       14 | static const bool is_hex_notation(std::string const &input) {
          |        ^~~~~
    [...]/structopt/include/structopt/is_number.hpp:19:8: error: type qualifiers ignored on function return type [-Werror=ignored-qualifiers]
       19 | static const bool is_octal_notation(std::string const &input) {
          |        ^~~~~
    [...]/structopt/include/structopt/is_number.hpp: In function ‘bool structopt::details::is_valid_number(const string&)’:
    [...]/structopt/include/structopt/is_number.hpp:34:12: error: comparison of unsigned expression in ‘>= 0’ is always true [-Werror=type-limits]
       34 |   while (j >= 0 && input[j] == ' ')
          |          ~~^~~~
    

    My main.cpp:

    #include <structopt/app.hpp>
    
    int main()
    {
      std::cout << "Test" << std::endl;
    }
    

    My CMakeLists.txt:

    # Set minimum CMake version
    cmake_minimum_required(VERSION 3.14)
    
    # Set project name and the programming language
    project("test" LANGUAGES CXX)
    
    # Set path to project root
    set(VENDOR_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor")
    
    option(STRUCTOPT_TESTS "Don't build structopt tests" OFF)
    option(STRUCTOPT_SAMPLES "Don't build structopt examples" OFF)
    add_subdirectory("${VENDOR_DIR}/structopt" build_extern_structopt)
    
    # Create executable with all provided sources
    add_executable(${PROJECT_NAME} main.cpp)
    
    # Link structopt
    target_link_libraries(${PROJECT_NAME} PRIVATE structopt::structopt)
    
    # Set library source files compilation flags for different compilers
    target_compile_options(${PROJECT_NAME}
                           PRIVATE $<$<CXX_COMPILER_ID:GNU>:
                                   -pedantic
                                   -Wall
                                   -Werror
                                   -Wextra
                                   -Wformat
                                   >
                                   $<$<CXX_COMPILER_ID:MSCV>:
                                   /W4
                                   /Wall
                                   /WX
                                   >)
    
    # Set C++ version for the local source files
    set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20)
    target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_20)
    

    My build commands build.sh:

    #!/usr/bin/env bash
    
    rm -rf build && mkdir -p build && cd build
    cmake .. -G "Unix Makefiles"
    cmake --build .
    

    The complete log:

    -- The CXX compiler identification is GNU 10.1.0
    -- Detecting CXX compiler ABI info
    -- Detecting CXX compiler ABI info - done
    -- Check for working CXX compiler: /usr/bin/c++ - skipped
    -- Detecting CXX compile features
    -- Detecting CXX compile features - done
    -- Configuring done
    -- Generating done
    -- Build files have been written to: PROJECT_ROOT/build
    Scanning dependencies of target test
    [ 50%] Building CXX object CMakeFiles/test.dir/main.cpp.o
    In file included from PROJECT_ROOT/vendor/structopt/include/structopt/parser.hpp:13,
                     from PROJECT_ROOT/vendor/structopt/include/structopt/app.hpp:8,
                     from PROJECT_ROOT/main.cpp:1:
    PROJECT_ROOT/vendor/structopt/include/structopt/is_number.hpp:9:8: error: type qualifiers ignored on function return type [-Werror=ignored-qualifiers]
        9 | static const bool is_binary_notation(std::string const &input) {
          |        ^~~~~
    PROJECT_ROOT/vendor/structopt/include/structopt/is_number.hpp:14:8: error: type qualifiers ignored on function return type [-Werror=ignored-qualifiers]
       14 | static const bool is_hex_notation(std::string const &input) {
          |        ^~~~~
    PROJECT_ROOT/vendor/structopt/include/structopt/is_number.hpp:19:8: error: type qualifiers ignored on function return type [-Werror=ignored-qualifiers]
       19 | static const bool is_octal_notation(std::string const &input) {
          |        ^~~~~
    PROJECT_ROOT/vendor/structopt/include/structopt/is_number.hpp: In function ‘bool structopt::details::is_valid_number(const string&)’:
    PROJECT_ROOT/vendor/structopt/include/structopt/is_number.hpp:34:12: error: comparison of unsigned expression in ‘>= 0’ is always true [-Werror=type-limits]
       34 |   while (j >= 0 && input[j] == ' ')
          |          ~~^~~~
    cc1plus: all warnings being treated as errors
    make[2]: *** [CMakeFiles/test.dir/build.make:82: CMakeFiles/test.dir/main.cpp.o] Error 1
    make[1]: *** [CMakeFiles/Makefile2:115: CMakeFiles/test.dir/all] Error 2
    make: *** [Makefile:103: all] Error 2
    

    This was tested on a Linux PC (Kernel: 5.8.1-2-MANJARO, Architecture: x86_64 GNU/Linux) with c++ (GCC) 10.1.0 and cmake 3.18.1.

    It is just a minor issue I just removed the const qualifiers and the j >= 0 as described in the warnings and then everything worked for me (my main.cpp was at one point more advanced but I trimmed it for this issue - --help and the other features were working fine).

    opened by AnonymerNiklasistanonym 0
  • Fixed windows build

    Fixed windows build

    • Added missing include for <optional> in visitor
    • Added CMake configuration for /std c++17 in msvc
    • Fixed all warnings thrown by Visual Studio 2019
    • Tested build and tests in VS 2019
    opened by p-ranav 0
  • Ambiguous help, short name duplicates

    Ambiguous help, short name duplicates

    I seems that first character of long-name is used in a short-name.

    In case:

    struct Options
    {
      // oositional flags
      // ./main [--bar] [--baz]
      std::optional<bool> bar = false;
      std::optional<bool> baz = false;
    };
    STRUCTOPT(Options, bar, baz);
    

    Help prints:

    USAGE: my_app [FLAGS] [OPTIONS] 
    
    FLAGS:
        -b, --bar
        -b, --baz
    
    OPTIONS:
        -h, --help <help>
        -v, --version <version>
    

    There are -b twice. Ii can be confusing, specially with many another options between duplicates.

    BTW, it's possible (or is planned) to set flag name (short name)?

    Maybe something like

    std::optional<bool> baz = false;
    ...
    STRUCTOPT(Options, bar, structopt::short(baz, "z"));
    
    enhancement 
    opened by rysson 1
Releases(v0.1.3)
Owner
Pranav
Pranav
A single header C++ library for parsing command line arguments and options with minimal amount of code

Quick Arg Parser Tired of unwieldy tools like getopt or argp? Quick Arg Parser is a single header C++ library for parsing command line arguments

null 44 Feb 21, 2022
EAMain provides a multi-platform entry point used for platforms that don't support console output, return codes and command-line arguments.

EAMain provides a multi-platform entry point used for platforms that don't support console output, return codes and command-line arguments.

Electronic Arts 30 May 17, 2022
led is a line-oriented text editor in command line

led is a line-oriented text editor in command line. This editor is similar to the standard program on unix systems - GNU ed. But i'm not going to make an exact clone of that program, it's just a pet project.

Artem Mironov 9 Jun 14, 2022
Shpp - Call c++ functions from a shell with any arguments of any types parsed automatically

shpp Call c++ functions from a shell with any arguments of any types parsed automatically Declare a variable or define a function and register it in s

Pedro Moreira 96 Jun 8, 2022
A simple to use, composable, command line parser for C++ 11 and beyond

Clara v1.1.5 !! This repository is unmaintained. Go here for a fork that is somewhat maintained. !! A simple to use, composable, command line parser f

Catch Org 651 Jun 15, 2022
A library for interactive command line interfaces in modern C++

cli A cross-platform header only C++14 library for interactive command line interfaces (Cisco style) Features Header only Cross-platform (linux and wi

Daniele Pallastrelli 803 Jul 2, 2022
CLI11 is a command line parser for C++11 and beyond that provides a rich feature set with a simple and intuitive interface.

CLI11: Command line parser for C++11 What's new • Documentation • API Reference CLI11 is a command line parser for C++11 and beyond that provides a ri

null 2.1k Jun 24, 2022
Lightweight C++ command line option parser

Release versions Note that master is generally a work in progress, and you probably want to use a tagged release version. Version 3 breaking changes I

null 3.1k Jun 25, 2022
A simple to use, composable, command line parser for C++ 11 and beyond

Lyra A simple to use, composing, header only, command line arguments parser for C++ 11 and beyond. Obtain License Standards Stats Tests License Distri

Build Frameworks Group 342 Jun 16, 2022
CLIp is a clipboard emulator for a command line interface written in 100% standard C only. Pipe to it to copy, pipe from it to paste.

CLIp v2 About CLIp is a powerful yet easy to use and minimal clipboard manager for a command line environment, with no dependencies or bloat. Usage Sy

A.P. Jo. 12 Sep 18, 2021
pbr2gltf2 is a command line tool for converting PBR images to a glTF 2.0 material.

pbr2gltf2 is a command line tool for converting PBR images to a glTF 2.0 material. The tool is detecting depending on the filename, which PBR information is stored. It swizzles the images and does reassign the channels to a glTF 2.0 image. The tool stores the images plus a minimal, valid glTF 2.0 file containing the required material, textures and images.

UX3D GmbH 22 Jan 11, 2022
null 76 Apr 18, 2022
A command-line tool to display colorful distro information.

sjfetch A command-line tool to display colorful distro information.

Fikret Musk 6 Apr 6, 2022
LwSHELL is lightweight, platform independent, command line shell for embedded systems.

LwSHELL is lightweight, platform independent, command line shell for embedded systems. It targets communication with embedded systems from remote terminal to quickly send commands and the retrieve data from the device.

Tilen Majerle 60 Jun 26, 2022
Toybox: all-in-one Linux command line.

Toybox: all-in-one Linux command line.

Rob Landley 1.7k Jun 29, 2022
Simple command line tool that processes image files using the FidelityFX Super Resolution (FSR) or Contrast Adaptive Sharpening (CAS) shader systems.

Simple command line tool that processes image files using the FidelityFX Super Resolution (FSR) or Contrast Adaptive Sharpening (CAS) shader systems.

GPUOpen Effects 174 Jun 26, 2022
Command-line flag parsing in C

flag.h Inspired by Go's flag module: https://pkg.go.dev/flag WARNING! The design of the library is not finished and may be a subject to change. Quick

Tsoding 36 Jun 26, 2022
A command line tool with no external dependencies to print information about an X server instance.

xinfo A command line tool with no external dependencies to print information about an X server instance. Building and running To build the code in thi

Jean-Michel Gorius 6 Jan 13, 2022
Windows command line program for Spleeter, written in pure C, no need of Python.

SpleeterMsvcExe is a Windows command line program for Spleeter, which can be used directly. It is written in pure C language, using ffmpeg to read and write audio files, and using Tensorflow C API to make use of Spleeter models. No need to install Python environment, and it does not contain anything related to Python.

Wudi 161 Jun 22, 2022