cli11-tests

[brief]

Tests package for cli11 library

CLI11 Logo

Build Status Azure Actions Status Build Status AppVeyor Code Coverage Codacy Badge License: BSD DOI

Gitter chat Latest GHA release Latest release Conan.io Conda Version Try CLI11 2.1 online

What's newDocumentationAPI Reference

CLI11 is a command line parser for C++11 and beyond that provides a rich feature set with a simple and intuitive interface.

Table of Contents

Features that were added in the last released minor version are marked with "🆕". Features only available in main are marked with "🚧".

Background

Introduction

CLI11 provides all the features you expect in a powerful command line parser, with a beautiful, minimal syntax and no dependencies beyond C++11. It is header only, and comes in a single file form for easy inclusion in projects. It is easy to use for small projects, but powerful enough for complex command line projects, and can be customized for frameworks. It is tested on Azure and GitHub Actions, and was originally used by the GooFit GPU fitting framework. It was inspired by plumbum.cli for Python. CLI11 has a user friendly introduction in this README, a more in-depth tutorial GitBook, as well as API documentation generated by Travis. See the changelog or GitHub Releases for details for current and past releases. Also see the Version 1.0 post, Version 1.3 post, Version 1.6 post, or Version 2.0 post for more information.

You can be notified when new releases are made by subscribing to https://github.com/CLIUtils/CLI11/releases.atom on an RSS reader, like Feedly, or use the releases mode of the GitHub watching tool.

Why write another CLI parser?

An acceptable CLI parser library should be all of the following:

Other parsers

Library My biased opinion
Boost Program Options A great library if you already depend on Boost, but its pre-C++11 syntax is really odd and setting up the correct call in the main function is poorly documented (and is nearly a page of code). A simple wrapper for the Boost library was originally developed, but was discarded as CLI11 became more powerful. The idea of capturing a value and setting it originated with Boost PO. See this comparison.
The Lean Mean C++ Option Parser One header file is great, but the syntax is atrocious, in my opinion. It was quite impractical to wrap the syntax or to use in a complex project. It seems to handle standard parsing quite well.
TCLAP The not-quite-standard command line parsing causes common shortcuts to fail. It also seems to be poorly supported, with only minimal bugfixes accepted. Header only, but in quite a few files. Has not managed to get enough support to move to GitHub yet. No subcommands. Produces wrapped values.
Cxxopts C++11, single file, and nice CMake support, but requires regex, therefore GCC 4.8 (CentOS 7 default) does not work. Syntax closely based on Boost PO, so not ideal but familiar.
DocOpt Completely different approach to program options in C++11, you write the docs and the interface is generated. Too fragile and specialized.

After I wrote this, I also found the following libraries:

Library My biased opinion
GFlags The Google Commandline Flags library. Uses macros heavily, and is limited in scope, missing things like subcommands. It provides a simple syntax and supports config files/env vars.
GetOpt Very limited C solution with long, convoluted syntax. Does not support much of anything, like help generation. Always available on UNIX, though (but in different flavors).
ProgramOptions.hxx Interesting library, less powerful and no subcommands. Nice callback system.
Args Also interesting, and supports subcommands. I like the optional-like design, but CLI11 is cleaner and provides direct value access, and is less verbose.
Argument Aggregator I'm a big fan of the fmt library, and the try-catch statement looks familiar. :thumbsup: Doesn't seem to support subcommands.
Clara Simple library built for the excellent Catch testing framework. Unique syntax, limited scope.
Argh! Very minimalistic C++11 parser, single header. Don't have many features. No help generation?!?! At least it's exception-free.
CLI Custom language and parser. Huge build-system overkill for very little benefit. Last release in 2009, but still occasionally active.
argparse C++17 single file argument parser. Design seems similar to CLI11 in some ways. The author has several other interesting projects.
lyra a simple header only parser with composable options. Might work well for simple standardized parsing

See Awesome C++ for a less-biased list of parsers. You can also find other single file libraries at Single file libs.

None of these libraries fulfill all the above requirements, or really even come close. As you probably have already guessed, CLI11 does. So, this library was designed to provide a great syntax, good compiler compatibility, and minimal installation fuss.

Features not supported by this library

There are some other possible "features" that are intentionally not supported by this library:

Install

To use, there are several methods:

if(NOT DEFINED CLI11_DIR)
set (CLI11_DIR "/opt/CLI11" CACHE STRING "CLI11 git repository")
endif()
include_directories(${CLI11_DIR}/include)

And then in the source code (adding several headers might be needed to prevent linker errors):

#include "CLI/App.hpp"
#include "CLI/Formatter.hpp"
#include "CLI/Config.hpp"
include(FetchContent)
FetchContent_Declare(
  cli11
  GIT_REPOSITORY https://github.com/CLIUtils/CLI11
  GIT_TAG        v2.2.0
)

FetchContent_MakeAvailable(cli11)

It is highly recommended that you use the git hash for GIT_TAG instead of a tag or branch, as that will both be more secure, as well as faster to reconfigure - CMake will not have to reach out to the internet to see if the tag moved. You can also download just the single header file from the releases using file(DOWNLOAD.

To build the tests, checkout the repository and use CMake:

cmake -S . -B build
cmake --build build
CTEST_OUTPUT_ON_FAILURE=1 cmake --build build -t test

If you are using GCC 8 and using it in C++17 mode with CLI11. CLI11 makes use of the <filesystem> header if available, but specifically for this compiler, the filesystem library is separate from the standard library and needs to be linked separately. So it is available but CLI11 doesn't use it by default.

Specifically libstdc++fs needs to be added to the linking list and CLI11_HAS_FILESYSTEM=1 has to be defined. Then the filesystem variant of the Validators could be used on GCC 8. GCC 9+ does not have this issue so the <filesystem> is used by default.

There may also be other cases where a specific library needs to be linked.

Defining CLI11_HAS_FILESYSTEM=0 which will remove the usage and hence any linking issue.

In some cases certain clang compilations may require linking against libc++fs. These situations have not been encountered so the specific situations requiring them are unknown yet.

Usage

Adding options

To set up, add options, and run, your main function will look something like this:

int main(int argc, char** argv) {
    CLI::App app{"App description"};

    std::string filename = "default";
    app.add_option("-f,--file", filename, "A help string");

    CLI11_PARSE(app, argc, argv);
    return 0;
}
try {
    app.parse(argc, argv);
} catch (const CLI::ParseError &e) {
    return app.exit(e);
}

The try/catch block ensures that -h,--help or a parse error will exit with the correct return code (selected from CLI::ExitCodes). (The return here should be inside main). You should not assume that the option values have been set inside the catch block; for example, help flags intentionally short-circuit all other processing for speed and to ensure required options and the like do not interfere.

The initialization is just one line, adding options is just two each. The parse macro is just one line (or 5 for the contents of the macro). After the app runs, the filename will be set to the correct value if it was passed, otherwise it will be set to the default. You can check to see if this was passed on the command line with app.count("--file").

Option types

While all options internally are the same type, there are several ways to add an option depending on what you need. The supported values are:

// Add options
app.add_option(option_name, help_str="")

app.add_option(option_name,
               variable_to_bind_to, // bool, char(see note), int, float, vector, enum, std::atomic, or string-like, or anything with a defined conversion from a string or that takes an int, double, or string in a constructor. Also allowed are tuples, std::array or std::pair. Also supported are complex numbers, wrapper types, and containers besides vectors of any other supported type.
               help_string="")

app.add_option_function<type>(option_name,
               function <void(const type &value)>, // type can be any type supported by add_option
               help_string="")

// char as an option type is supported before 2.0 but in 2.0 it defaulted to allowing single non numerical characters in addition to the numeric values.

// There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type.  The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version.  If XC is a std::pair and T is some non pair type.  Then a two argument constructor for T is called to assign the value.  For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type
app.add_option<typename T, typename XC>(option_name,
               T &output, // output must be assignable or constructible from a value of type XC
               help_string="")

// Add flags
app.add_flag(option_name,
             help_string="")

app.add_flag(option_name,
             variable_to_bind_to, // bool, int, float, complex, containers, enum, std::atomic, or string-like, or any singular object with a defined conversion from a string like add_option
             help_string="")

app.add_flag_function(option_name,
             function <void(std::int64_t count)>,
             help_string="")

app.add_flag_callback(option_name,function<void(void)>,help_string="")

// Add subcommands
App* subcom = app.add_subcommand(name, description);

Option_group *app.add_option_group(name,description);

An option name may start with any character except ('-', ' ', '\n', and '!'). For long options, after the first character all characters are allowed except ('=',':','{',' ', '\n'). For the add_flag* functions '{' and '!' have special meaning which is why they are not allowed. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using count, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form.

The add_option_function<type>(... function will typically require the template parameter be given unless a std::function object with an exact match is passed. The type can be any type supported by the add_option function. The function should throw an error (CLI::ConversionError or CLI::ValidationError possibly) if the value is not valid.

The two parameter template overload can be used in cases where you want to restrict the input such as

double val
app.add_option<double,unsigned int>("-v",val);

which would first verify the input is convertible to an unsigned int before assigning it. Or using some variant type

using vtype=std::variant<int, double, std::string>;
 vtype v1;
app.add_option<vtype,std:string>("--vs",v1);
app.add_option<vtype,int>("--vi",v1);
app.add_option<vtype,double>("--vf",v1);

otherwise the output would default to a string. The add_option can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type.

Types such as (std or boost) optional<int>, optional<double>, and optional<string> and any other wrapper types are supported directly. For purposes of CLI11 wrapper types are those which value_type definition. See CLI11 Advanced Topics/Custom Converters for information on how you can add your own converters for additional types.

Vector types can also be used in the two parameter template overload

std::vector<double> v1;
app.add_option<std::vector<double>,int>("--vs",v1);

would load a vector of doubles but ensure all values can be represented as integers.

Automatic direct capture of the default string is disabled when using the two parameter template. Use set_default_str(...) or ->default_function(std::string()) to set the default string or capture function directly for these cases.

Flag options specified through the add_flag* functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed. For example:

app.add_flag("--flag,!--no-flag",result,"help for flag");

specifies that if --flag is passed on the command line result will be true or contain a value of 1. If --no-flag is passed result will contain false or -1 if result is a signed integer type, or 0 if it is an unsigned type. An alternative form of the syntax is more explicit: "--flag,--no-flag{false}"; this is equivalent to the previous example. This also works for short form options "-f,!-n" or "-f,-n{false}". If variable_to_bind_to is anything but an integer value the default behavior is to take the last value given, while if variable_to_bind_to is an integer type the behavior will be to sum all the given arguments and return the result. This can be modified if needed by changing the multi_option_policy on each flag (this is not inherited). The default value can be any value. For example if you wished to define a numerical flag:

app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag")

Using any of those flags on the command line will result in the specified number in the output. Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type.

On a C++14 compiler, you can pass a callback function directly to .add_flag, while in C++11 mode you'll need to use .add_flag_function if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant CLI::ParseError to signal a failure.

Example

The add commands return a pointer to an internally stored Option. This option can be used directly to check for the count (->count()) after parsing to avoid a string based lookup.

Option options

Before parsing, you can set the following options:

These options return the Option pointer, so you can chain them together, and even skip storing the pointer entirely. The each function takes any function that has the signature void(const std::string&); it should throw a ValidationError when validation fails. The help message will have the name of the parent option prepended. Since each, check and transform use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through transform are executed first in reverse order of addition, and check and each are run following the transform functions in order of addition. If you just want to see the unconverted values, use .results() to get the std::vector<std::string> of results.

On the command line, options can be given as:

If allow_windows_style_options() is specified in the application or subcommand options can also be given as:

Long flag options may be given with an =<value> to allow specifying a false value, or some other value to the flag. See config files for details on the values supported. NOTE: only the = or : for windows-style options may be used for this, using a space will result in the argument being interpreted as a positional argument. This syntax can override the default values, and can be disabled by using disable_flag_override().

Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments. If you set .allow_extras() on the main App, you will not get an error. You can access the missing options using remaining (if you have subcommands, app.remaining(true) will get all remaining options, subcommands included). If the remaining arguments are to processed by another App then the function remaining_for_passthrough() can be used to get the remaining arguments in reverse order such that app.parse(vector) works directly and could even be used inside a subcommand callback.

You can access a vector of pointers to the parsed options in the original order using parse_order(). If -- is present in the command line that does not end an unlimited option, then everything after that is positional only.

Validators

Validators are structures to check or modify inputs, they can be used to verify that an input meets certain criteria or transform it into another value. They are added through the check or transform functions. The differences between the two function are that checks do not modify the input whereas transforms can and are executed before any Validators added through check.

CLI11 has several Validators built-in that perform some common checks

These Validators can be used by simply passing the name into the check or transform methods on an option

->check(CLI::ExistingFile);
->check(CLI::Range(0,10));

Validators can be merged using & and | and inverted using !. For example:

->check(CLI::Range(0,10)|CLI::Range(20,30));

will produce a check to ensure a value is between 0 and 10 or 20 and 30.

->check(!CLI::PositiveNumber);

will produce a check for a number less than or equal to 0.

Transforming Validators

There are a few built in Validators that let you transform values if used with the transform function. If they also do some checks then they can be used check but some may do nothing in that case.

After specifying a set of options, you can also specify "filter" functions of the form T(T), where T is the type of the values. The most common choices probably will be CLI::ignore_case an CLI::ignore_underscore, and CLI::ignore_space. These all work on strings but it is possible to define functions that work on other types. Here are some examples of IsMember:

After specifying a map of options, you can also specify "filter" just like in CLI::IsMember. Here are some examples (Transformer and CheckedTransformer are interchangeable in the examples) of Transformer:

NOTES: If the container used in IsMember, Transformer, or CheckedTransformer has a find function like std::unordered_map or std::map then that function is used to do the searching. If it does not have a find function a linear search is performed. If there are filters present, the fast search is performed first, and if that fails a linear search with the filters on the key values is performed.

Validator operations

Validators are copyable and have a few operations that can be performed on them to alter settings. Most of the built in Validators have a default description that is displayed in the help. This can be altered via .description(validator_description). The name of a Validator, which is useful for later reference from the get_validator(name) method of an Option can be set via .name(validator_name) The operation function of a Validator can be set via .operation(std::function<std::string(std::string &>). The .active() function can activate or deactivate a Validator from the operation. A validator can be set to apply only to a specific element of the output. For example in a pair option std::pair<int, std::string> the first element may need to be a positive integer while the second may need to be a valid file. The .application_index(int) function can specify this. It is zero based and negative indices apply to all values.

opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
opt->check(CLI::Validator(CLI::ExistingFile).application_index(1));

All the validator operation functions return a Validator reference allowing them to be chained. For example

opt->check(CLI::Range(10,20).description("range is limited to sensible values").active(false).name("range"));

will specify a check on an option with a name "range", but deactivate it for the time being. The check can later be activated through

opt->get_validator("range")->active();
Custom Validators

A validator object with a custom function can be created via

CLI::Validator(std::function<std::string(std::string &)>,validator_description,validator_name="");

or if the operation function is set later they can be created with

CLI::Validator(validator_description);

It is also possible to create a subclass of CLI::Validator, in which case it can also set a custom description function, and operation function.

Querying Validators

Once loaded into an Option, a pointer to a named Validator can be retrieved via

opt->get_validator(name);

This will retrieve a Validator with the given name or throw a CLI::OptionNotFound error. If no name is given or name is empty the first unnamed Validator will be returned or the first Validator if there is only one.

or

opt->get_validator(index);

Which will return a validator in the index it is applied which isn't necessarily the order in which was defined. The pointer can be nullptr if an invalid index is given. Validators have a few functions to query the current values:

Getting results

In most cases, the fastest and easiest way is to return the results through a callback or variable specified in one of the add_* functions. But there are situations where this is not possible or desired. For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code:

Subcommands

Subcommands are supported, and can be nested infinitely. To add a subcommand, call the add_subcommand method with a name and an optional description. This gives a pointer to an App that behaves just like the main app, and can take options or further subcommands. Add ->ignore_case() to a subcommand to allow any variation of caps to also be accepted. ->ignore_underscore() is similar, but for underscores. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including ignore_case and ignore_underscore).

If you want to require that at least one subcommand is given, use .require_subcommand() on the parent app. You can optionally give an exact number of subcommands to require, as well. If you give two arguments, that sets the min and max number allowed. 0 for the max number allowed will allow an unlimited number of subcommands. As a handy shortcut, a single negative value N will set "up to N" values. Limiting the maximum number allows you to keep arguments that match a previous subcommand name from matching.

If an App (main or subcommand) has been parsed on the command line, ->parsed will be true (or convert directly to bool). All Apps have a get_subcommands() method, which returns a list of pointers to the subcommands passed on the command line. A got_subcommand(App_or_name) method is also provided that will check to see if an App pointer or a string name was collected on the command line.

For many cases, however, using an app's callback capabilities may be easier. Every app has a set of callbacks that can be executed at various stages of parsing; a C++ lambda function (with capture to get parsed values) can be used as input to the callback definition function. If you throw CLI::Success or CLI::RuntimeError(return_value), you can even exit the program through the callback.

Multiple subcommands are allowed, to allow Click like series of commands (order is preserved). The same subcommand can be triggered multiple times but all positional arguments will take precedence over the second and future calls of the subcommand. ->count() on the subcommand will return the number of times the subcommand was called. The subcommand callback will only be triggered once unless the .immediate_callback() flag is set or the callback is specified through the parse_complete_callback() function. The final_callback() is triggered only once. In which case the callback executes on completion of the subcommand arguments but after the arguments for that subcommand have been parsed, and can be triggered multiple times.

Subcommands may also have an empty name either by calling add_subcommand with an empty string for the name or with no arguments. Nameless subcommands function a similarly to groups in the main App. See Option groups to see how this might work. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The add_subcommand function has an overload for adding a shared_ptr<App> so the subcommand(s) could be defined in different components and merged into a main App, or possibly multiple Apps. Multiple nameless subcommands are allowed. Callbacks for nameless subcommands are only triggered if any options from the subcommand were parsed. Subcommand names given through the add_subcommand method have the same restrictions as option names.

Subcommand options

There are several options that are supported on the main app and subcommands and option_groups. These are:

Note: if you have a fixed number of required positional options, that will match before subcommand names. {} is an empty filter function, and any positional argument will match before repeated subcommand names.

Callbacks

A subcommand has three optional callbacks that are executed at different stages of processing. The preparse_callback is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process. For the main app the first argument is considered the program name, for subcommands the first argument is the subcommand name. For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group. The second callback is executed after parsing. This is known as the parse_complete_callback. For subcommands this is executed immediately after parsing and can be executed multiple times if a subcommand is called multiple times. On the main app this callback is executed after all the parse_complete_callbacks for the subcommands are executed but prior to any final_callback calls in the subcommand or option groups. If the main app or subcommand has a config file, no data from the config file will be reflected in parse_complete_callback on named subcommands. For option_groups the parse_complete_callback is executed prior to the parse_complete_callback on the main app but after the config_file is loaded (if specified). The final_callback is executed after all processing is complete. After the parse_complete_callback is executed on the main app, the used subcommand final_callback are executed followed by the "final callback" for option groups. The last thing to execute is the final_callback for the main_app. For example say an application was set up like

app.parse_complete_callback(ac1);
app.final_callback(ac2);
auto sub1=app.add_subcommand("sub1")->parse_complete_callback(c1)->preparse_callback(pc1);
auto sub2=app.add_subcommand("sub2")->final_callback(c2)->preparse_callback(pc2);
app.preparse_callback( pa);

... A bunch of other options

Then the command line is given as

program --opt1 opt1_val  sub1 --sub1opt --sub1optb val sub2 --sub2opt sub1 --sub1opt2 sub2 --sub2opt2 val

A subcommand is considered terminated when one of the following conditions are met.

  1. There are no more arguments to process
  2. Another subcommand is encountered that would not fit in an optional slot of the subcommand
  3. The positional_mark (--) is encountered and there are no available positional slots in the subcommand.
  4. The subcommand_terminator mark (++) is encountered

Prior to executed a parse_complete_callback all contained options are processed before the callback is triggered. If a subcommand with a parse_complete_callback is called again, then the contained options are reset, and can be triggered again.

Option groups

The subcommand method

.add_option_group(name,description)

Will create an option group, and return a pointer to it. The argument for description is optional and can be omitted. An option group allows creation of a collection of options, similar to the groups function on options, but with additional controls and requirements. They allow specific sets of options to be composed and controlled as a collective. For an example see range example. Option groups are a specialization of an App so all functions that work with an App or subcommand also work on option groups. Options can be created as part of an option group using the add functions just like a subcommand, or previously created options can be added through. The name given in an option group must not contain newlines or null characters.

ogroup->add_option(option_pointer);
ogroup->add_options(option_pointer);
ogroup->add_options(option1,option2,option3,...);

The option pointers used in this function must be options defined in the parent application of the option group otherwise an error will be generated. Subcommands can also be added via

ogroup->add_subcommand(subcom_pointer);

This results in the subcommand being moved from its parent into the option group.

Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first. So care must be taken to make sure of the order when using positional arguments and option groups. Option groups work well with excludes and require_options methods, as an application will treat an option group as a single option for the purpose of counting and requirements, and an option group will be considered used if any of the options or subcommands contained in it are used. Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well. Disabling an option group will turn off all options within the group.

The CLI::TriggerOn and CLI::TriggerOff methods are helper functions to allow the use of options/subcommands from one group to trigger another group on or off.

CLI::TriggerOn(group1_pointer, triggered_group);
CLI::TriggerOff(group2_pointer, disabled_group);

These functions make use of preparse_callback, enabled_by_default() and disabled_by_default. The triggered group may be a vector of group pointers. These methods should only be used once per group and will override any previous use of the underlying functions. More complex arrangements can be accomplished using similar methodology with a custom preparse_callback function that does more.

Additional helper functions deprecate_option and retire_option are available to deprecate or retire options

CLI::deprecate_option(option *, replacement_name="");
CLI::deprecate_option(App,option_name,replacement_name="");

will specify that the option is deprecated which will display a message in the help and a warning on first usage. Deprecated options function normally but will add a message in the help and display a warning on first use.

CLI::retire_option(App,option *);
CLI::retire_option(App,option_name);

will create an option that does nothing by default and will display a warning on first usage that the option is retired and has no effect. If the option exists it is replaces with a dummy option that takes the same arguments.

If an empty string is passed the option group name the entire group will be hidden in the help results. For example.

auto hidden_group=app.add_option_group("");

will create a group such that no options in that group are displayed in the help string.

Configuration file

app.set_config(option_name="",
               default_file_name="",
               help_string="Read an ini file",
               required=false)

If this is called with no arguments, it will remove the configuration file option (like set_help_flag). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless required is true. Configuration files are in TOML format by default, though the default reader can also accept files in INI format as well. It should be noted that CLI11 does not contain a full TOML parser but can read strings from most TOML file and run them through the CLI11 parser. Other formats can be added by an adept user, some variations are available through customization points in the default formatter. An example of a TOML file:

# Comments are supported, using a #
# The default section is [default], case insensitive

value = 1
str = "A string"
vector = [1,2,3]
str_vector = ["one","two","and three"]

# Sections map to subcommands
[subcommand]
in_subcommand = Wow
sub.subcommand = true

or equivalently in INI format

; Comments are supported, using a ;
; The default section is [default], case insensitive

value = 1
str = "A string"
vector = 1 2 3
str_vector = "one" "two" "and three"

; Sections map to subcommands
[subcommand]
in_subcommand = Wow
sub.subcommand = true

Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be true, on, 1, yes, enable; or false, off, 0, no, disable (case insensitive). Sections (and . separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults"). You cannot set positional-only arguments. Subcommands can be triggered from configuration files if the configurable flag was set on the subcommand. Then the use of [subcommand] notation will trigger a subcommand and cause it to act as if it were on the command line.

To print a configuration file from the passed arguments, use .config_to_str(default_also=false, write_description=false), where default_also will also show any defaulted arguments, and write_description will include the app and option descriptions. See Config files for some additional details and customization points.

If it is desired that multiple configuration be allowed. Use

app.set_config("--config")->expected(1, X);

Where X is some positive number and will allow up to X configuration files to be specified by separate --config arguments. Value strings with quote characters in it will be printed with a single quote. All other arguments will use double quote. Empty strings will use a double quoted argument. Numerical or boolean values are not quoted.

For options or flags which allow 0 arguments to be passed using an empty string in the config file, {}, or [] will convert the result to the default value specified via default_str or default_val on the option 🆕. If no user specified default is given the result is an empty string or the converted value of an empty string.

NOTE: Transforms and checks can be used with the option pointer returned from set_config like any other option to validate the input if needed. It can also be used with the built in transform CLI::FileOnDefaultPath to look in a default path as well as the current one. For example

app.set_config("--config")->transform(CLI::FileOnDefaultPath("/to/default/path/"));

See Transforming Validators for additional details on this validator. Multiple transforms or validators can be used either by multiple calls or using | operations with the transform.

Inheriting defaults

Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are allow_extras, prefix_command, ignore_case, ignore_underscore, fallthrough, group, footer,immediate_callback and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well.

Options have defaults for group, required, multi_option_policy, ignore_case, ignore_underscore, delimiter, and disable_flag_override. To set these defaults, you should set the option_defaults() object, for example:

app.option_defaults()->required();
// All future options will be required

The default settings for options are inherited to subcommands, as well.

Formatting

The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling formatter(fmt) on an App, where fmt is any copyable callable with the correct signature. CLI11 comes with a default App formatter functional, Formatter. It is customizable; you can set label(key, value) to replace the default labels like REQUIRED, and column_width(n) to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do that too; you just need to implement the correct signature. The first argument is a const pointer to the in question. The formatter will get a std::string usage name as the second option, and a AppFormatMode mode for the final option. It should return a std::string.

The AppFormatMode can be Normal, All, or Sub, and it indicates the situation the help was called in. Sub is optional, but the default formatter uses it to make sure expanded subcommands are called with their own formatter since you can't access anything but the call operator once a formatter has been set.

Subclassing

The App class was designed allow toolkits to subclass it, to provide preset default options (see above) and setup/teardown code. Subcommands remain an unsubclassed App, since those are not expected to need setup and teardown. The default App only adds a help flag, -h,--help, than can removed/replaced using .set_help_flag(name, help_string). You can also set a help-all flag with .set_help_all_flag(name, help_string); this will expand the subcommands (one level only). You can remove options if you have pointers to them using .remove_option(opt). You can add a pre_callback override to customize the after parse but before run behavior, while still giving the user freedom to callback on the main app.

The most important parse function is parse(std::vector<std::string>), which takes a reversed list of arguments (so that pop_back processes the args in the correct order). get_help_ptr and get_config_ptr give you access to the help/config option pointers. The standard parse manually sets the name from the first argument, so it should not be in this vector. You can also use parse(string, bool) to split up and parse a single string; the optional boolean should be set to true if you are including the program name in the string, and false otherwise. The program name can contain spaces if it is an existing file, otherwise can be enclosed in quotes(single quote, double quote or backtick). Embedded quote characters can be escaped with \.

Also, in a related note, the App you get a pointer to is stored in the parent App in a shared_ptrs (similar to Options) and are deleted when the main App goes out of scope unless the object has another owner.

How it works

Every add_ option you have seen so far depends on one method that takes a lambda function. Each of these methods is just making a different lambda function with capture to populate the option. The function has full access to the vector of strings, so it knows how many times an option was passed or how many arguments it received. The lambda returns true if it could validate the option strings, and false if it failed.

Other values can be added as long as they support operator>> (and defaults can be printed if they support operator<<). To add a new type, for example, provide a custom operator>> with an istream (inside the CLI namespace is fine if you don't want to interfere with an existing operator>>).

If you wanted to extend this to support a completely new type, use a lambda or add a specialization of the lexical_cast function template in the namespace of the type you need to convert to. Some examples of some new parsers for complex<double> that support all of the features of a standard add_options call are in one of the tests. A simpler example is shown below:

Example

app.add_option("--fancy-count", [](std::vector<std::string> val){
    std::cout << "This option was given " << val.size() << " times." << std::endl;
    return true;
    });

Utilities

There are a few other utilities that are often useful in CLI programming. These are in separate headers, and do not appear in CLI11.hpp, but are completely independent and can be used as needed. The Timer/AutoTimer class allows you to easily time a block of code, with custom print output.

{
CLI::AutoTimer timer {"My Long Process", CLI::Timer::Big};
some_long_running_process();
}

This will create a timer with a title (default: Timer), and will customize the output using the predefined Big output (default: Simple). Because it is an AutoTimer, it will print out the time elapsed when the timer is destroyed at the end of the block. If you use Timer instead, you can use to_string or std::cout << timer << std::endl; to print the time. The print function can be any function that takes two strings, the title and the time, and returns a formatted string for printing.

Other libraries

If you use the excellent Rang library to add color to your terminal in a safe, multi-platform way, you can combine it with CLI11 nicely:

std::atexit([](){std::cout << rang::style::reset;});
try {
    app.parse(argc, argv);
} catch (const CLI::ParseError &e) {
    std::cout << (e.get_exit_code()==0 ? rang::fg::blue : rang::fg::red);
    return app.exit(e);
}

This will print help in blue, errors in red, and will reset before returning the terminal to the user.

If you are on a Unix-like system, and you'd like to handle control-c and color, you can add:

 #include <csignal>
 void signal_handler(int s) {
     std::cout << std::endl << rang::style::reset << rang::fg::red << rang::fg::bold;
     std::cout << "Control-C detected, exiting..." << rang::style::reset << std::endl;
     std::exit(1); // will call the correct exit func, no unwinding of the stack though
 }

And, in your main function:

     // Nice Control-C
     struct sigaction sigIntHandler;
     sigIntHandler.sa_handler = signal_handler;
     sigemptyset(&sigIntHandler.sa_mask);
     sigIntHandler.sa_flags = 0;
     sigaction(SIGINT, &sigIntHandler, nullptr);

API

The API is documented here. Also see the CLI11 tutorial GitBook.

Examples

Several short examples of different features are included in the repository. A brief description of each is included here

Contribute

To contribute, open an issue or pull request on GitHub, or ask a question on gitter. There is also a short note to contributors here. This readme roughly follows the Standard Readme Style and includes a mention of almost every feature of the library. More complex features are documented in more detail in the CLI11 tutorial GitBook.

This project was created by Henry Schreiner and major features were added by Philip Top. Special thanks to all the contributors (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!

License

As of version 1.0, this library is available under a 3-Clause BSD license. See the LICENSE file for details.

CLI11 was developed at the University of Cincinnati to support of the GooFit library under NSF Award 1414736. Version 0.9 was featured in a DIANA/HEP meeting at CERN (see the slides). Please give it a try! Feedback is always welcome.

license BSD-3-Clause
project cli11
url github.com/CLIUtils/CLI11
1 Version
version 2.2.0
repository https://pkg.cppget.org/1/stable
depends 1; catch2