Skip to content
Lukasz Forynski edited this page Nov 4, 2013 · 52 revisions

Before you read

Simply explore / run examples instead of reading the elaborative description below - you should be able to use the library without ever reading it!

Links to examples:

  1. Example 0: Default option
  2. Example 1: Multiple options
  3. Example 2: Options with dependencies
  4. Example 3: Other options and default param values

You can also refer to in-code documentation (or simply run doxygen to generate your docs).

..ok - but if you’re interested in more details, read on.

Introduction

Command line options library is meant to provide an easy way for adding command-line options to your program. The idea is to allow creation and addition of options in the easiest possible way - with only minimal programming effort.

Yes - there are many other similar frameworks already: but all of them still require additional implementation of various things, that could really be done automatically using C++. Some of them also still struggle to extract values properly, e.g. “0x12ag” is not a valid integer but most of them will tell you that the value was “0x12a”). Some of these frameworks are additionally way too complicated to use and require a lot of work to learn how to do it.

Hopefully - (incrementally) futures that are sometimes missing in other frameworks will be implemented making this library even more useful.

Why frameworks like that are needed?

Programs, where command-line options are used, usually need to:

  • define command-line options,
  • parse command line arguments to check if (and which) of these options were specified,
  • attempt to extract parameters if an option requires them,
  • successful extraction usually results in further actions being taken by the program. These actions can, for example:
    • define or alter the behaviour of the program, e.g. by changing values of variable(s), or
    • result in calls to various functions to handle specified options.

    The is often implemented as a ‘switch/case’ statement, usually defined in the “main()” function.

Couldn’t this be easier - e.g. in 3 lines of code?

All you need to do is to express your option requirements as functions. Command-line options are then created automatically using these functions as prototypes. To add the first option at the very minimum (ignoring version/program description) you really need to define a function prototype for it and write 3 lines of code (i.e. one to create the parser + one to add this option + one to start parsing)! And for each additional one: 1 function + 1 line to add it.

The framework’s implementation will take care of all the details with extracting them and notifying about errors etc.

Benefit of this is obvious: unlike with other similar frameworks / libraries - there is no need to explicitly define and later explicitly refer to parameter type(s) when checking results. You already have your function: if parsing was OK - it will be called so you already have your values passed to this function directly. If parsing was not OK - you’ll get a message and function wont’ be called - no need to check anything, print anything to the user etc.

Maintenance is easy - you can change types passed to your functions, add more parameters, etc. - but there’s no need to change the ‘cmd_line_parsing’ part of your program (unless you need to update usage/description after these changes).

Example:

// prototype for a command-line option that takes an 'int' as a parameter..
void hello_few_times(int number_of_times)
{
    for (int i = 0; i < number_of_times; i++)
    {
        std::cout << "hello ";
    }
}

// another prototype for option that takes more parameters:
void do_something(char letter, double param1, unsigned long param2)
{
    // ...
}

int main(int argc, char **argv)
{
    cmd_line_parser parser;
    parser.set_version("0.0.1");

    parser.set_description("This is an example of how to use cmd_line_options library\n"
            "Author: Lukasz Forynski (lukasz.forynski@gmail.com)");

    parser.add_option(hello_few_times, "hello_few_times", "prints \"hello\" a specified number of times");
    parser.add_option(do_something, "-d_sth", "does something (...)");

    parser.run(argc, argv); // this starts the parser..

    // That's it! Now the rest of your program..

    return 0;
}      

help and program information

Library automatically produces a list of all available options (aka ‘help’) that contains information on their usage etc. A brief description of the program and version information can be added too.

Help for example like above will look as follows:

c:\>example  ?

example, version: 0.0.1

This is an example of how to use cmd_line_options library
Author: Lukasz Forynski (lukasz.forynski@gmail.com)

Use "?" or "help" to print more information.

Options:

  -d_sth            : does something (...)
              usage : example -d_sth <char> <double> <unsigned long>

  hello_few_times   : prints "hello" a specified number of times
              usage : example hello_few_times <int>

doxygen syntax support

Now, you can also use doxygen syntax when specifying option description, which will improve information presented on ‘help’ request and also when handling errors, e.g.:

void print_on_screen(std::string what, int how_many_times)
{
    for (int i = 0; i < how_many_times; i++)
    {
        std::cout << what << " ";
    }
    std::cout << std::endl;
}

int main(int argc, char **argv)
{
    // .... 
    parser.add_option(hello_few_times, 
                      "hello_few_times", 
                      "@brief prints \"hello\" a specified number of times."
                      "@param ");
    parser.add_option(do_something, "-d_sth", "does something (...)");
    // ....
}

Error handling and help will now give more descriptive information about parameters, eg.:

./example print hello dd

example3: "print": error while parsing parameter: 2
   expected: "how_many_times"<int>, got: "dd"

   usage: print <what> <how_many_times> 
      what(string): String to be printed.
      how_many_times(int): Number of times it should be printed.
./example --help

example, version: 0.0.1

This is an example of how to use cmd_line_options library
Author: Lukasz Forynski (lukasz.forynski@gmail.com)

Options: 

 print: prints specified string number of times on the screen. 
 usage: print <what> <how_many_times> 
    what(string): String to be printed.
    how_many_times(int): Number of times it should be printed.

verifying values

Integer values can be specified either as decimal (by default) or as hexadecimal (starting with 0x…) and appropriate parsers will automatically work out the format while attempting to extract the value. You don’t need to add annoying usage messages that “a param should be in hex” nor even have to decide on either format declaring it explicitly.

If, at any stage of parsing, any of the parameters can not be extracted, the framework will obviously report this.

An attempt to run a command with wrong values for expected parameters can result is messages like:

./example hello_few_times 0xas

  Error when parsing parameters, expected: <int>, got "0xas".

  Usage:
     example hello_few_times <int>

using aliases for option specification

Now, you can also use aliases for options (i.e.you can create an option that could be invoked using either of specified aliases). In below example, option ‘hello_few_times’ will be invoked if either: “-hft”, “-x” or “hello_few_times” is specified.

int main(int argc, char **argv)
{
    // .... 
    parser.add_option(hello_few_times, 
                      "-hft,-x,hello_few_times", 
                      "@brief prints \"hello\" a specified number of times."
                      "@param ");
    // ....
}

dependencies for options

Options can also have dependencies. For example: an option might require specifying one or more additional options; Another example: an option must not be be used along with other option(s). This library handles this too allowing to specify these dependencies and performing run-time checks internally. The user (of the program) is notified whenever the specified combination of options is not allowed.

Example:

parser.add_option(aa, "aa", "simple option - no specific requirements");
parser.add_option(bb, "bb", "another simple option - no specific requirements");
parser.add_option(a_b, "a_b", "example option that requires specifying another two options");
parser.add_option(a_only, "a_only", "only with a specific sub-set of options.");
parser.add_option(b_only, "b_only", "another one with only specific sub-set of options.");
parser.add_option(standalone, "standalone", "Should be the only option.");

// setup required options using comma/colon/semicolon or space separated list.
parser.setup_required_options("a_b", "aa, bb");
parser.setup_not_wanted_options("a_only", "bb, a_b, standalone");
parser.setup_not_wanted_options("b_only", "aa, a_b, standalone");
parser.setup_as_single_option("standalone");

Above code could produce following example execution results:

____________________________________

~$ ./example2 a_b
option: "a_b" requires also: "aa", "bb"
____________________________________

$ ./example2 a_b bb aa
void a_b()
void bb()
void aa()
____________________________________

$ ./example2 b_only aa
option: "b_only" can't be used with: "aa"
____________________________________

$ ./example2 b_only aa a_b
option: "b_only" can't be used with: "a_b", "aa"
____________________________________

$ ./example2 b_only bb
void b_only()
void bb()
____________________________________

$ ./example2 standalone aa a_b bb
option: "standalone" can't be used with: "a_b", "aa", "bb"

non-option (other) parameters

If, additionally to defined options - other parameters need to be specified (e.g. just like file list for compiler invocation) - this can also be handled by this framework. Implementation is as follows: simply register a ‘handler’ for these options:

/**
* @brief if this handler is added with cmd_line_parser::add_handler_for_other_arguments
* all 'unrecognised' (i.e. not related to any registered options) arguments will be passed
* to this handler. It will be executed after all option-handlers have executed.
*/
void other_cmd_line_arguments(std::vector<std::string>& other_args)
{ 
...
}

int main ()
{
    cmd_line_parser parser;
    ....
    parser.add_handler_for_other_arguments(other_cmd_line_arguments);
}

default (optional) values?

If one (or more) of your parameters can have default value(s) - i.e. just like implicit parameters for functions. This framework also implements this.

With the following syntax you can define an option-handler, that will allow for an optional value in the command line. If value is missing from the command line - a default one will be passed to function.

E.g.:

/**
* @brief another prototype for option with optional value (works exactly as implicit/default
* function argument). Behaviour is as expected: if the param is given in cmd line: value will
* be extracted. Otherwise = a default value is used. See OPTIONAL_VALUE description/documentation
* for more details
*/
void default_param(OPTIONAL_VALUE(int, param2, 14))
{
    std::cout << param2 << std::endl;
}

int main ()
{
    cmd_line_parser parser;
    ....
    // now you add it as usual:
    parser.add_option(default_param, "--dp", "optional parameter..");
    
    // you can still call it as normal function
    default_param();    //  use default value..
    default_param(123); // .. or specify a value:

    // and if you run a parser - it will try to extract the value from cmd-line
    // if this option is specified. But if it's not specified - it will just use a default
    // value instead.
    parser.run(argc, argv); // this starts the parser..
}

Program that takes only values (a.k.a. one - default option)

If you only need a program, that takes options by default (without specifying any ‘–something’ specifiers) you can use this framework as follows:

/**
* @brief prototype for a command-line option that takes string and 'int' as a parameter..
*/
void handle_program_options(std::string what, int how_many_times)
{
    for (int i = 0; i < how_many_times; i++)
    {
        std::cout << what << " ";
    }
    std::cout << std::endl;
}

/**
* @brief main...
*/
int main(int argc, char **argv)
{
    cmd_line_parser parser;
    // leave option name empty = and it will become a 'default' option
    parser.add_option(handle_program_options, "", "");
}

such a program will be expecting parameters as follows:

./example0 

 example0: error while parsing parameters, expected: <string>, got "".

 Usage: 
    example0 <string> <int>

Implementation aiders

checking if function is allowed to be used as an option

If a function is not suitable to define a command-line option (i.e. contains parameters that can’t be parsed/extracted from the command-line, e.g. pointers or references) - attempt to use such a function will result in a compile time error like below.

// if one of the functions from above example is modified as follows:
29: int do_something(char letter, double* param1, unsigned long param2)
   {
   }

56: int main(int argc, char **argv)
    {
       (...)
66:    parser.add_option(do_something, "-d_sth", "does something (...)");

The example will fail to compile with the error message containing the following:

g++ example.cpp 
In file included from example.cpp:13:
cmd_line_options.h: In constructor ‘param_extractor<ParamType>::param_extractor() [with ParamType = double*]’:
cmd_line_options.h:1658:   instantiated from ‘void cmd_line_parser::add_option(RetType (*)(P1, P2, P3), std::string, std::string) [with RetType = int, P1 = char, P2 = double*, P3 = long unsigned int]’
example.cpp:66:   instantiated from here
cmd_line_options.h:302: error: ‘CT_ASSERT_in_line_302__EXTRACTING_PARAMETERS_OF_THIS_TYPE_IS_NOT_SUPPORTED’ has incomplete type

Which should be just enough to figure out what went wrong :) (i.e. in this case: extracting parameters for double* is not supported, and this was attempted in in example.cpp, line 66.

Capturing other implementation errors

There can be other errors when the framework is not used correctly. For example referring to a non-existing option when creating dependencies (like in Example 2 ) - will cause such an error. These errors will result in rising ‘option_error’ exceptions and I strongly encourage you to fix their cause instead of trying to catch them.