1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00

remove template for operator[] and adjust some tests

add some comments in readME about performance

move operator[] to return const Option *

Apply suggestions from code review

Co-Authored-By: phlptp <top1@llnl.gov>

update readme and add some IniTests and fix a bug from the tests

add_flag_callback

add a few tests to capture the different paths

fix incorrectly updated CMAKE file, and add some subcommand test for option finding

add disable_flag_override and work out some kinks in the find option functions

add some more tests and fix a few bugs in as<> function for options

Allow general flag types and default values, add shortcut notation for retrieving values
This commit is contained in:
Philip Top 2019-02-20 07:52:45 -08:00 committed by Henry Schreiner
parent 1a1cde98f5
commit ed5cd89636
15 changed files with 748 additions and 268 deletions

View File

@ -177,13 +177,15 @@ The initialization is just one line, adding options is just two each. The parse
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:
```cpp
app.add_option(option_name, help_str="")
app.add_option(option_name,
variable_to_bind_to, // bool, int, float, vector, enum, or string-like
variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or anything with a defined conversion from a string
help_string="",
default=false)
app.add_option_function<type>(option_name,
function <void(const type &value)>, // int, float, enum, vector, or string-like
function <void(const type &value)>, // int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string
help_string="")
app.add_complex(... // Special case: support for complex numbers
@ -192,17 +194,19 @@ app.add_flag(option_name,
help_string="")
app.add_flag(option_name,
int_or_bool,
variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or anything with a defined conversion from a string
help_string="")
app.add_flag_function(option_name,
function <void(int count)>,
function <void(int64_t count)>,
help_string="")
app.add_flag_callback(option_name,function<void(void)>,help_string="")
App* subcom = app.add_subcommand(name, description);
```
An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. 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 help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`.
An option name must start with a alphabetic character, underscore, or a number. For long options, anything but an equals sign or a comma is valid after that, though for the `add_flag*` functions '{' has special meaning. 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 help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`.
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.
@ -210,27 +214,37 @@ Flag options specified through the functions
```cpp
app.add_flag(option_name,
int_or_bool,
help_string="")
app.add_flag(option_name,
variable_to_bind_to,
help_string="")
app.add_flag_function(option_name,
function <void(int count)>,
function <void(int64_t count)>,
help_string="")
app.add_flag_callback(option_name,function<void(void)>,help_string="")
```
which allow a syntax for the option names to default particular options to a false value if some flags are passed. For example:
which 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:
```cpp
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
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 `int_or_bool` is a boolean value the
default behavior is to take the last value given, while if `int_or_bool` 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).
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
```cpp
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.
@ -258,6 +272,7 @@ Before parsing, you can set the following options:
- `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden).
- `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
- `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character
- `->disable_flag_override()`: from the command line long form flag option can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options.
- `->description(str)`: Set/change the description.
- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy).
- `->check(CLI::IsMember(...))`: Require an option be a member of a given set. See below for options.
@ -296,7 +311,7 @@ On the command line, options can be given as:
- `-ffilename` (no space required)
- `-abcf filename` (flags and option can be combined)
- `--long` (long flag)
- `--long_flag=true` (long flag with equals)
- `--long_flag=true` (long flag with equals to override default value)
- `--file filename` (space)
- `--file=filename` (equals)
@ -306,9 +321,10 @@ If `allow_windows_style_options()` is specified in the application or subcommand
- `/long` (long flag)
- `/file filename` (space)
- `/file:filename` (colon)
- `/long_flag:false (long flag with : to override the default value)
= Windows style options do not allow combining short options or values not separated from the short option like with `-` options
Long flag options may be given with and `=<value>` to allow specifying a false value See [config files](#configuration-file) 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 (true or false) values.
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](#configuration-file) 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).
@ -317,10 +333,18 @@ You can access a vector of pointers to the parsed options in the original order
If `--` is present in the command line that does not end an unlimited option, then
everything after that is positional only.
#### 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:
- `results()`: retrieves a vector of strings with all the results in the order they were given.
- `results(variable_to_bind_to)`: gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
- `results(vector_type_variable,delimiter)`: gets the results to a vector type and uses a delimiter to further split the values
- `Value=as<type>()`: returns the result or default value directly as the specified type if possible.
- `Vector_value=as<type>(delimiter): same the results function with the delimiter but returns the value directly.
### 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).
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
@ -335,7 +359,7 @@ You are allowed to throw `CLI::Success` in the callbacks.
Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved).
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 little like groups in the main `App`. 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.
Nameless subcommands function a similarly to groups in the main `App`. 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.
#### Subcommand options
@ -353,7 +377,8 @@ There are several options that are supported on the main app and subcommands. Th
- `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
- `.get_subcommands(filter)`: The list of subcommands given on the command line.
- `.get_parent()`: Get the parent App or nullptr if called on master App.
- `.get_option(name)`: Get an option pointer by option name
- `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched
- `.get_option_no_throw(name)`: Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available.
- `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats).
- `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates).
- `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details.
@ -370,6 +395,7 @@ There are several options that are supported on the main app and subcommands. Th
- `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands.
- `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default).
- `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand.
- `[option_name]`: retrieve a const pointer to an option given by `option_name` for Example `app["--flag1"]` will get a pointer to the option for the "--flag1" value, `app["--flag1"]->as<bool>() will get the results of the command line for a flag
> Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function.
@ -408,7 +434,7 @@ arguments, use `.config_to_str(default_also=false, prefix="", write_description=
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`, 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_underscore`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example:
Options have defaults for `group`, `required`, `disable_flag_override`,`multi_option_policy`, `ignore_underscore`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example:
```cpp
app.option_defaults()->required();

View File

@ -132,6 +132,11 @@ add_test(NAME enum_fail COMMAND enum -l 4)
set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION
"--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}")
add_cli_exe(digit_args digit_args.cpp)
add_test(NAME digit_args COMMAND digit_args -h)
set_property(TEST digit_args PROPERTY PASS_REGULAR_EXPRESSION
"-3{3}")
add_cli_exe(modhelp modhelp.cpp)
add_test(NAME modhelp COMMAND modhelp -a test -h)
set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION

15
examples/digit_args.cpp Normal file
View File

@ -0,0 +1,15 @@
#include <CLI/CLI.hpp>
#include <iostream>
int main(int argc, char **argv) {
CLI::App app;
int val;
// add a set of flags with default values associate with them
app.add_flag("-1{1},-2{2},-3{3},-4{4},-5{5},-6{6}, -7{7}, -8{8}, -9{9}", val, "compression level");
CLI11_PARSE(app, argc, argv);
std::cout << "value = " << val << std::endl;
return 0;
}

View File

@ -402,9 +402,22 @@ class App {
opt->type_name(detail::type_name<T>());
return opt;
}
/// Add option with no description or variable assignment
Option *add_option(std::string option_name) {
return add_option(option_name, CLI::callback_t(), std::string{}, false);
}
/// Add option with description but with no variable assignment or callback
template <typename T,
enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> =
detail::dummy>
Option *add_option(std::string option_name, T &option_description) {
return add_option(option_name, CLI::callback_t(), option_description, false);
}
/// Add option for non-vectors with a default print
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
template <typename T,
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy>
Option *add_option(std::string option_name,
T &variable, ///< The variable to set
std::string option_description,
@ -523,7 +536,8 @@ class App {
}
/// Set a help flag, replace the existing one if present
Option *set_help_flag(std::string flag_name = "", std::string help_description = "") {
Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") {
// take flag_description by const reference otherwise add_flag tries to assign to help_description
if(help_ptr_ != nullptr) {
remove_option(help_ptr_);
help_ptr_ = nullptr;
@ -531,7 +545,7 @@ class App {
// Empty name will simply remove the help flag
if(!flag_name.empty()) {
help_ptr_ = add_flag(flag_name, std::move(help_description));
help_ptr_ = add_flag(flag_name, help_description);
help_ptr_->configurable(false);
}
@ -539,7 +553,8 @@ class App {
}
/// Set a help all flag, replaced the existing one if present
Option *set_help_all_flag(std::string help_name = "", std::string help_description = "") {
Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = "") {
// take flag_description by const reference otherwise add_flag tries to assign to flag_description
if(help_all_ptr_ != nullptr) {
remove_option(help_all_ptr_);
help_all_ptr_ = nullptr;
@ -547,109 +562,149 @@ class App {
// Empty name will simply remove the help all flag
if(!help_name.empty()) {
help_all_ptr_ = add_flag(help_name, std::move(help_description));
help_all_ptr_ = add_flag(help_name, help_description);
help_all_ptr_->configurable(false);
}
return help_all_ptr_;
}
/// Add option for flag
Option *add_flag(std::string flag_name, std::string flag_description = "") {
CLI::callback_t fun = [](CLI::results_t) { return true; };
Option *opt = add_option(flag_name, fun, flag_description, false);
if(opt->get_positional())
throw IncorrectConstruction::PositionalFlag(flag_name);
private:
/// Internal function for adding a flag
Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) {
Option *opt;
if(detail::has_default_flag_values(flag_name)) {
// check for default values and if it has them
auto flag_defaults = detail::get_default_flag_values(flag_name);
detail::remove_default_flag_values(flag_name);
opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
for(const auto &fname : flag_defaults)
opt->fnames_.push_back(fname.first);
opt->default_flag_values_ = std::move(flag_defaults);
} else {
opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
}
// flags cannot have positional values
if(opt->get_positional()) {
auto pos_name = opt->get_name(true);
remove_option(opt);
throw IncorrectConstruction::PositionalFlag(pos_name);
}
opt->type_size(0);
return opt;
}
/// Add option for flag integer
public:
/// Add a flag with no description or variable assignment
Option *add_flag(std::string flag_name) { return _add_flag_internal(flag_name, CLI::callback_t(), std::string{}); }
/// Add flag with description but with no variable assignment or callback
/// takes a constant string, if a variable string is passed that variable will be assigned the results from the
/// flag
template <typename T,
enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> =
detail::dummy>
Option *add_flag(std::string flag_name, T &flag_description) {
return _add_flag_internal(flag_name, CLI::callback_t(), flag_description);
}
/// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one if
/// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
template <typename T,
enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy>
Option *add_flag(std::string flag_name,
T &flag_count, ///< A variable holding the count
std::string flag_description = "") {
flag_count = 0;
Option *opt;
CLI::callback_t fun = [&flag_count](CLI::results_t res) {
detail::sum_flag_vector(res, flag_count);
try {
detail::sum_flag_vector(res, flag_count);
} catch(const std::invalid_argument &) {
return false;
}
return true;
};
if(detail::has_false_flags(flag_name)) {
std::vector<std::string> neg = detail::get_false_flags(flag_name);
detail::remove_false_flag_notation(flag_name);
opt = add_option(flag_name, fun, flag_description, false);
opt->fnames_ = std::move(neg);
} else {
opt = add_option(flag_name, fun, flag_description, false);
}
if(opt->get_positional())
throw IncorrectConstruction::PositionalFlag(flag_name);
opt->type_size(0);
return opt;
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
}
/// Bool version - defaults to allowing multiple passings, but can be forced to one if
/// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
/// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes that
/// can be converted from a string
template <typename T,
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value &&
(!std::is_integral<T>::value || is_bool<T>::value) &&
!std::is_constructible<std::function<void(int)>, T>::value,
detail::enabler> = detail::dummy>
Option *add_flag(std::string flag_name,
T &flag_result, ///< A variable holding true if passed
std::string flag_description = "") {
flag_result = false;
Option *opt;
CLI::callback_t fun = [&flag_result](CLI::results_t res) {
flag_result = (res[0][0] != '-');
return res.size() == 1;
};
if(detail::has_false_flags(flag_name)) {
std::vector<std::string> neg = detail::get_false_flags(flag_name);
detail::remove_false_flag_notation(flag_name);
opt = add_option(flag_name, fun, std::move(flag_description), false);
opt->fnames_ = std::move(neg);
} else {
opt = add_option(flag_name, fun, std::move(flag_description), false);
}
if(opt->get_positional())
throw IncorrectConstruction::PositionalFlag(flag_name);
opt->type_size(0);
CLI::callback_t fun = [&flag_result](CLI::results_t res) {
if(res.size() != 1) {
return false;
}
return CLI::detail::lexical_cast(res[0], flag_result);
};
Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
return opt;
}
/// Add option for callback
Option *add_flag_function(std::string flag_name,
std::function<void(int)> function, ///< A function to call, void(size_t)
/// Vector version to capture multiple flags.
template <typename T,
enable_if_t<!std::is_assignable<std::function<void(int64_t)>, T>::value, detail::enabler> = detail::dummy>
Option *add_flag(std::string flag_name,
std::vector<T> &flag_results, ///< A vector of values with the flag results
std::string flag_description = "") {
CLI::callback_t fun = [&flag_results](CLI::results_t res) {
bool retval = true;
for(const auto &elem : res) {
flag_results.emplace_back();
retval &= detail::lexical_cast(elem, flag_results.back());
}
return retval;
};
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
}
/// Add option for callback that is triggered with a true flag and takes no arguments
Option *add_flag_callback(std::string flag_name,
std::function<void(void)> function, ///< A function to call, void(void)
std::string flag_description = "") {
CLI::callback_t fun = [function](CLI::results_t res) {
int flag_count = 0;
if(res.size() != 1) {
return false;
}
bool trigger;
auto result = CLI::detail::lexical_cast(res[0], trigger);
if(trigger)
function();
return result;
};
Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
return opt;
}
/// Add option for callback with an integer value
Option *add_flag_function(std::string flag_name,
std::function<void(int64_t)> function, ///< A function to call, void(int)
std::string flag_description = "") {
CLI::callback_t fun = [function](CLI::results_t res) {
int64_t flag_count = 0;
detail::sum_flag_vector(res, flag_count);
function(flag_count);
return true;
};
Option *opt;
if(detail::has_false_flags(flag_name)) {
std::vector<std::string> neg = detail::get_false_flags(flag_name);
detail::remove_false_flag_notation(flag_name);
opt = add_option(flag_name, fun, std::move(flag_description), false);
opt->fnames_ = std::move(neg);
} else {
opt = add_option(flag_name, fun, std::move(flag_description), false);
}
if(opt->get_positional())
throw IncorrectConstruction::PositionalFlag(flag_name);
opt->type_size(0);
return opt;
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
}
#ifdef CLI11_CPP14
/// Add option for callback (C++14 or better only)
Option *add_flag(std::string flag_name,
std::function<void(int)> function, ///< A function to call, void(int)
std::function<void(int64_t)> function, ///< A function to call, void(int)
std::string flag_description = "") {
return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description));
}
@ -1187,14 +1242,7 @@ class App {
///@{
/// Counts the number of times the given option was passed.
size_t count(std::string option_name) const {
for(const Option_p &opt : options_) {
if(opt->check_name(option_name)) {
return opt->count();
}
}
throw OptionNotFound(option_name);
}
size_t count(std::string option_name) const { return get_option(option_name)->count(); }
/// Get a subcommand pointer list to the currently selected subcommands (after parsing by by default, in command
/// line order; use parsed = false to get the original definition list.)
@ -1312,26 +1360,68 @@ class App {
return options;
}
/// Get an option by name
const Option *get_option(std::string option_name) const {
for(const Option_p &opt : options_) {
if(opt->check_name(option_name)) {
return opt.get();
}
}
throw OptionNotFound(option_name);
}
/// Get an option by name (non-const version)
Option *get_option(std::string option_name) {
/// Get an option by name (noexcept non-const version)
Option *get_option_no_throw(std::string option_name) noexcept {
for(Option_p &opt : options_) {
if(opt->check_name(option_name)) {
return opt.get();
}
}
throw OptionNotFound(option_name);
for(auto &subc : subcommands_) {
// also check down into nameless subcommands
if(subc->get_name().empty()) {
auto opt = subc->get_option_no_throw(option_name);
if(opt != nullptr) {
return opt;
}
}
}
return nullptr;
}
/// Get an option by name (noexcept const version)
const Option *get_option_no_throw(std::string option_name) const noexcept {
for(const Option_p &opt : options_) {
if(opt->check_name(option_name)) {
return opt.get();
}
}
for(const auto &subc : subcommands_) {
// also check down into nameless subcommands
if(subc->get_name().empty()) {
auto opt = subc->get_option_no_throw(option_name);
if(opt != nullptr) {
return opt;
}
}
}
return nullptr;
}
/// Get an option by name
const Option *get_option(std::string option_name) const {
auto opt = get_option_no_throw(option_name);
if(opt == nullptr) {
throw OptionNotFound(option_name);
}
return opt;
}
/// Get an option by name (non-const version)
Option *get_option(std::string option_name) {
auto opt = get_option_no_throw(option_name);
if(opt == nullptr) {
throw OptionNotFound(option_name);
}
return opt;
}
/// Shortcut bracket operator for getting a pointer to an option
const Option *operator[](const std::string &option_name) const { return get_option(option_name); }
/// Shortcut bracket operator for getting a pointer to an option
const Option *operator[](const char *option_name) const { return get_option(option_name); }
/// Check the status of ignore_case
bool get_ignore_case() const { return ignore_case_; }
@ -1351,7 +1441,7 @@ class App {
const std::string &get_group() const { return group_; }
/// Get footer.
std::string get_footer() const { return footer_; }
const std::string &get_footer() const { return footer_; }
/// Get the required min subcommand value
size_t get_require_subcommand_min() const { return require_subcommand_min_; }
@ -1526,7 +1616,7 @@ class App {
return detail::Classifier::LONG;
if(detail::split_short(current, dummy1, dummy2))
return detail::Classifier::SHORT;
if((allow_windows_style_options_) && (detail::split_windows(current, dummy1, dummy2)))
if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
return detail::Classifier::WINDOWS;
return detail::Classifier::NONE;
}
@ -1735,10 +1825,8 @@ class App {
}
}
Option *op;
try {
op = get_option("--" + item.name);
} catch(const OptionNotFound &) {
Option *op = get_option_no_throw("--" + item.name);
if(op == nullptr) {
// If the option was not present
if(get_allow_config_extras())
// Should we worry about classifying the extras properly?
@ -1753,9 +1841,8 @@ class App {
// Flag parsing
if(op->get_type_size() == 0) {
auto res = config_formatter_->to_flag(item);
if(op->check_fname(item.name)) {
res = (res == "1") ? "-1" : ((res[0] == '-') ? res.substr(1) : std::string("-" + res));
}
res = op->get_flag_value(item.name, res);
op->add_result(res);
} else {
@ -1894,7 +1981,7 @@ class App {
throw HorribleError("Short parsed but missing! You should not see this");
break;
case detail::Classifier::WINDOWS:
if(!detail::split_windows(current, arg_name, value))
if(!detail::split_windows_style(current, arg_name, value))
throw HorribleError("windows option parsed but missing! You should not see this");
break;
default:
@ -1946,16 +2033,9 @@ class App {
int collected = 0;
// deal with flag like things
if(num == 0) {
try {
auto res = (value.empty()) ? std ::string("1") : detail::to_flag_value(value);
if(op->check_fname(arg_name)) {
res = (res == "1") ? "-1" : ((res[0] == '-') ? res.substr(1) : std::string("-" + res));
}
op->add_result(res);
parse_order_.push_back(op.get());
} catch(const std::invalid_argument &) {
throw ConversionError::TrueFalse(arg_name);
}
auto res = op->get_flag_value(arg_name, value);
op->add_result(res);
parse_order_.push_back(op.get());
}
// --this=value
else if(!value.empty()) {

View File

@ -69,14 +69,10 @@ class Config {
/// Convert a configuration into an app
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
/// Convert a flag to a bool representation
/// Get a flag value
virtual std::string to_flag(const ConfigItem &item) const {
if(item.inputs.size() == 1) {
try {
return detail::to_flag_value(item.inputs.at(0));
} catch(const std::invalid_argument &) {
throw ConversionError::TrueFalse(item.fullname());
}
return item.inputs.at(0);
}
throw ConversionError::TooManyInputsFlag(item.fullname());
}
@ -90,7 +86,7 @@ class Config {
return from_config(input);
}
/// virtual destructor
/// Virtual destructor
virtual ~Config() = default;
};

View File

@ -231,6 +231,9 @@ class ArgumentMismatch : public ParseError {
static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
}
static ArgumentMismatch FlagOverride(std::string name) {
return ArgumentMismatch(name + " was given a disallowed flag override");
}
};
/// Thrown when a requires option is missing

View File

@ -28,7 +28,7 @@ class App;
using Option_p = std::unique_ptr<Option>;
enum class MultiOptionPolicy { Throw, TakeLast, TakeFirst, Join };
enum class MultiOptionPolicy : char { Throw, TakeLast, TakeFirst, Join };
/// This is the CRTP base class for Option and OptionDefaults. It was designed this way
/// to share parts of the class; an OptionDefaults can copy to an Option.
@ -50,6 +50,8 @@ template <typename CRTP> class OptionBase {
/// Allow this option to be given in a configuration file
bool configurable_{true};
/// Disable overriding flag values with '=value'
bool disable_flag_override_{false};
/// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
@ -61,6 +63,7 @@ template <typename CRTP> class OptionBase {
other->ignore_case(ignore_case_);
other->ignore_underscore(ignore_underscore_);
other->configurable(configurable_);
other->disable_flag_override(disable_flag_override_);
other->multi_option_policy(multi_option_policy_);
}
@ -100,6 +103,9 @@ template <typename CRTP> class OptionBase {
/// The status of configurable
bool get_configurable() const { return configurable_; }
/// The status of configurable
bool get_disable_flag_override() const { return disable_flag_override_; }
/// The status of the multi option policy
MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
@ -158,6 +164,12 @@ class OptionDefaults : public OptionBase<OptionDefaults> {
ignore_underscore_ = value;
return this;
}
/// Ignore underscores in the option name
OptionDefaults *disable_flag_override(bool value = true) {
disable_flag_override_ = value;
return this;
}
};
class Option : public OptionBase<Option> {
@ -173,8 +185,11 @@ class Option : public OptionBase<Option> {
/// A list of the long names (`--a`) without the leading dashes
std::vector<std::string> lnames_;
/// A list of the negation names, should be duplicates of what is in snames or lnames but trigger a false response
/// on a flag
/// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of
/// what is in snames or lnames but will trigger a particular response on a flag
std::vector<std::pair<std::string, std::string>> default_flag_values_;
/// a list of flag names with specified default values;
std::vector<std::string> fnames_;
/// A positional name
@ -251,7 +266,7 @@ class Option : public OptionBase<Option> {
bool defaulted,
App *parent)
: description_(std::move(option_description)), default_(defaulted), parent_(parent),
callback_(callback ? std::move(callback) : [](results_t) { return true; }) {
callback_(std::move(callback)) {
std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
}
@ -471,6 +486,11 @@ class Option : public OptionBase<Option> {
return this;
}
/// disable flag overrides
Option *disable_flag_override(bool value = true) {
disable_flag_override_ = value;
return this;
}
///@}
/// @name Accessors
///@{
@ -499,7 +519,7 @@ class Option : public OptionBase<Option> {
/// Get the short names
const std::vector<std::string> get_snames() const { return snames_; }
/// get the negative flag names
/// get the flag names with specified default values
const std::vector<std::string> get_fnames() const { return fnames_; }
/// The number of times the option expects to be included
@ -570,14 +590,14 @@ class Option : public OptionBase<Option> {
for(const std::string &sname : snames_) {
name_list.push_back("-" + sname);
if(check_fname(sname)) {
name_list.back() += "{false}";
name_list.back() += "{" + get_flag_value(sname, "") + "}";
}
}
for(const std::string &lname : lnames_) {
name_list.push_back("--" + lname);
if(check_fname(lname)) {
name_list.back() += "{false}";
name_list.back() += "{" + get_flag_value(lname, "") + "}";
}
}
} else {
@ -635,7 +655,9 @@ class Option : public OptionBase<Option> {
throw ValidationError(get_name(), err_msg);
}
}
if(!(callback_)) {
return;
}
bool local_result;
// Num items expected or length of vector, always at least 1
@ -716,11 +738,11 @@ class Option : public OptionBase<Option> {
}
/// Requires "-" to be removed from string
bool check_sname(std::string name) const { return detail::check_is_member(name, snames_, ignore_case_); }
bool check_sname(std::string name) const { return (detail::find_member(name, snames_, ignore_case_) >= 0); }
/// Requires "--" to be removed from string
bool check_lname(std::string name) const {
return detail::check_is_member(name, lnames_, ignore_case_, ignore_underscore_);
return (detail::find_member(name, lnames_, ignore_case_, ignore_underscore_) >= 0);
}
/// Requires "--" to be removed from string
@ -728,7 +750,45 @@ class Option : public OptionBase<Option> {
if(fnames_.empty()) {
return false;
}
return detail::check_is_member(name, fnames_, ignore_case_, ignore_underscore_);
return (detail::find_member(name, fnames_, ignore_case_, ignore_underscore_) >= 0);
}
std::string get_flag_value(std::string name, std::string input_value) const {
static const std::string trueString{"true"};
static const std::string falseString{"false"};
static const std::string emptyString{"{}"};
// check for disable flag override_
if(disable_flag_override_) {
if(!((input_value.empty()) || (input_value == emptyString))) {
auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
if(default_ind >= 0) {
if(default_flag_values_[default_ind].second != input_value) {
throw(ArgumentMismatch::FlagOverride(name));
}
} else {
if(input_value != trueString) {
throw(ArgumentMismatch::FlagOverride(name));
}
}
}
}
auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
if((input_value.empty()) || (input_value == emptyString)) {
return (ind < 0) ? trueString : default_flag_values_[ind].second;
}
if(ind < 0) {
return input_value;
}
if(default_flag_values_[ind].second == falseString) {
try {
auto val = detail::to_flag_value(input_value);
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
} catch(const std::invalid_argument &) {
return input_value;
}
} else {
return input_value;
}
}
/// Puts a result at the end
@ -760,6 +820,74 @@ class Option : public OptionBase<Option> {
/// Get a copy of the results
std::vector<std::string> results() const { return results_; }
/// get the results as a particular type
template <typename T,
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy>
void results(T &output) const {
bool retval;
if(results_.empty()) {
retval = detail::lexical_cast(defaultval_, output);
} else if(results_.size() == 1) {
retval = detail::lexical_cast(results_[0], output);
} else {
switch(multi_option_policy_) {
case MultiOptionPolicy::TakeFirst:
retval = detail::lexical_cast(results_.front(), output);
break;
case MultiOptionPolicy::TakeLast:
default:
retval = detail::lexical_cast(results_.back(), output);
break;
case MultiOptionPolicy::Throw:
throw ConversionError(get_name(), results_);
case MultiOptionPolicy::Join:
retval = detail::lexical_cast(detail::join(results_), output);
break;
}
}
if(!retval) {
throw ConversionError(get_name(), results_);
}
}
/// get the results as a vector of a particular type
template <typename T> void results(std::vector<T> &output, char delim = '\0') const {
output.clear();
bool retval = true;
for(const auto &elem : results_) {
if(delim != '\0') {
for(const auto &var : CLI::detail::split(elem, delim)) {
if(!var.empty()) {
output.emplace_back();
retval &= detail::lexical_cast(var, output.back());
}
}
} else {
output.emplace_back();
retval &= detail::lexical_cast(elem, output.back());
}
}
if(!retval) {
throw ConversionError(get_name(), results_);
}
}
/// return the results as a particular type
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> T as() const {
T output;
results(output);
return output;
}
/// get the results as a vector of a particular type
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
T as(char delim = '\0') const {
T output;
results(output, delim);
return output;
}
/// See if the callback has been run already
bool get_callback_run() const { return callback_run_; }

View File

@ -40,7 +40,7 @@ inline bool split_long(const std::string &current, std::string &name, std::strin
}
// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
inline bool split_windows(const std::string &current, std::string &name, std::string &value) {
inline bool split_windows_style(const std::string &current, std::string &name, std::string &value) {
if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
auto loc = current.find_first_of(':');
if(loc != std::string::npos) {
@ -67,22 +67,29 @@ inline std::vector<std::string> split_names(std::string current) {
return output;
}
/// extract negation arguments basically everything after a '|' and before the next comma
inline std::vector<std::string> get_false_flags(const std::string &str) {
std::vector<std::string> output = split_names(str);
output.erase(std::remove_if(output.begin(),
output.end(),
[](const std::string &name) {
return ((name.empty()) ||
((name.find("{false}") == std::string::npos) && (name[0] != '!')));
}),
output.end());
for(auto &flag : output) {
auto false_loc = flag.find("{false}");
if(false_loc != std::string::npos) {
flag.erase(false_loc, std::string::npos);
/// extract default flag values either {def} or starting with a !
inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
std::vector<std::string> flags = split_names(str);
flags.erase(std::remove_if(flags.begin(),
flags.end(),
[](const std::string &name) {
return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&
(name.back() == '}')) ||
(name[0] == '!'))));
}),
flags.end());
std::vector<std::pair<std::string, std::string>> output;
output.reserve(flags.size());
for(auto &flag : flags) {
auto def_start = flag.find_first_of('{');
std::string defval = "false";
if((def_start != std::string::npos) && (flag.back() == '}')) {
defval = flag.substr(def_start + 1);
defval.pop_back();
flag.erase(def_start, std::string::npos);
}
flag.erase(0, flag.find_first_not_of("-!"));
output.emplace_back(flag, defval);
}
return output;
}

View File

@ -158,7 +158,7 @@ inline std::ostream &format_help(std::ostream &out, std::string name, std::strin
}
/// Verify the first character of an option
template <typename T> bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; }
template <typename T> bool valid_first_char(T c) { return std::isalnum(c, std::locale()) || c == '_'; }
/// Verify following characters of an option
template <typename T> bool valid_later_char(T c) {
@ -203,39 +203,50 @@ inline std::string find_and_replace(std::string str, std::string from, std::stri
}
/// check if the flag definitions has possible false flags
inline bool has_false_flags(const std::string &flags) { return (flags.find_first_of("{!") != std::string::npos); }
inline bool has_default_flag_values(const std::string &flags) {
return (flags.find_first_of("{!") != std::string::npos);
}
inline void remove_false_flag_notation(std::string &flags) {
flags = detail::find_and_replace(flags, "{false}", std::string{});
flags = detail::find_and_replace(flags, "{true}", std::string{});
inline void remove_default_flag_values(std::string &flags) {
size_t loc = flags.find_first_of('{');
while(loc != std::string::npos) {
auto finish = flags.find_first_of("},", loc + 1);
if((finish != std::string::npos) && (flags[finish] == '}')) {
flags.erase(flags.begin() + loc, flags.begin() + finish + 1);
}
loc = flags.find_first_of('{', loc + 1);
}
flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
}
/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
inline bool check_is_member(std::string name,
const std::vector<std::string> names,
bool ignore_case = false,
bool ignore_underscore = false) {
inline std::ptrdiff_t find_member(std::string name,
const std::vector<std::string> names,
bool ignore_case = false,
bool ignore_underscore = false) {
auto it = std::end(names);
if(ignore_case) {
if(ignore_underscore) {
name = detail::to_lower(detail::remove_underscore(name));
return std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
return detail::to_lower(detail::remove_underscore(local_name)) == name;
}) != std::end(names);
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
return detail::to_lower(detail::remove_underscore(local_name)) == name;
});
} else {
name = detail::to_lower(name);
return std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
return detail::to_lower(local_name) == name;
}) != std::end(names);
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
return detail::to_lower(local_name) == name;
});
}
} else if(ignore_underscore) {
name = detail::remove_underscore(name);
return std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
return detail::remove_underscore(local_name) == name;
}) != std::end(names);
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
return detail::remove_underscore(local_name) == name;
});
} else
return std::find(std::begin(names), std::end(names), name) != std::end(names);
it = std::find(std::begin(names), std::end(names), name);
return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
}
/// Find a trigger string and call a modify callable function that takes the current string and starting position of the
@ -248,49 +259,6 @@ template <typename Callable> inline std::string find_and_modify(std::string str,
return str;
}
/// generate a vector of values that represent a boolean they will be either "+" or "-"
inline std::string to_flag_value(std::string val) {
val = detail::to_lower(val);
std::string ret;
if(val.size() == 1) {
switch(val[0]) {
case '0':
case 'f':
case 'n':
case '-':
ret = "-1";
break;
case '1':
case 't':
case 'y':
case '+':
ret = "1";
break;
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
ret = val;
break;
default:
throw std::invalid_argument("unrecognized character");
}
return ret;
}
if(val == "true" || val == "on" || val == "yes" || val == "enable") {
ret = "1";
} else if(val == "false" || val == "off" || val == "no" || val == "disable") {
ret = "-1";
} else {
auto ui = std::stoll(val);
ret = (ui == 0) ? "-1" : val;
}
return ret;
}
/// Split a string '"one two" "three"' into 'one two', 'three'
/// Quote characters can be ` ' or "
inline std::vector<std::string> split_up(std::string str) {

View File

@ -158,10 +158,62 @@ constexpr const char *type_name() {
// Lexical cast
/// Signed integers / enums
template <typename T,
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value, detail::enabler> =
detail::dummy>
/// convert a flag into an integer value typically binary flags
inline int64_t to_flag_value(std::string val) {
static const std::string trueString("true");
static const std::string falseString("false");
if(val == trueString) {
return 1;
}
if(val == falseString) {
return -1;
}
val = detail::to_lower(val);
int64_t ret;
if(val.size() == 1) {
switch(val[0]) {
case '0':
case 'f':
case 'n':
case '-':
ret = -1;
break;
case '1':
case 't':
case 'y':
case '+':
ret = 1;
break;
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
ret = val[0] - '0';
break;
default:
throw std::invalid_argument("unrecognized character");
}
return ret;
}
if(val == trueString || val == "on" || val == "yes" || val == "enable") {
ret = 1;
} else if(val == falseString || val == "off" || val == "no" || val == "disable") {
ret = -1;
} else {
ret = std::stoll(val);
}
return ret;
}
/// Signed integers
template <
typename T,
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) {
try {
size_t n = 0;
@ -200,13 +252,7 @@ template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::
bool lexical_cast(std::string input, T &output) {
try {
auto out = to_flag_value(input);
if(out == "1") {
output = true;
} else if(out == "-1") {
output = false;
} else {
output = (std::stoll(out) > 0);
}
output = (out > 0);
return true;
} catch(const std::invalid_argument &) {
return false;
@ -270,10 +316,8 @@ template <typename T,
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
int64_t count{0};
static const auto trueString = std::string("1");
static const auto falseString = std::string("-1");
for(auto &flag : flags) {
count += (flag == trueString) ? 1 : ((flag == falseString) ? (-1) : std::stoll(flag));
count += detail::to_flag_value(flag);
}
output = (count > 0) ? static_cast<T>(count) : T{0};
}
@ -286,10 +330,8 @@ template <typename T,
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy>
void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
int64_t count{0};
static const auto trueString = std::string("1");
static const auto falseString = std::string("-1");
for(auto &flag : flags) {
count += (flag == trueString) ? 1 : ((flag == falseString) ? (-1) : std::stoll(flag));
count += detail::to_flag_value(flag);
}
output = static_cast<T>(count);
}

View File

@ -10,6 +10,43 @@ TEST_F(TApp, OneFlagShort) {
EXPECT_EQ(1u, app.count("--count"));
}
TEST_F(TApp, OneFlagShortValues) {
app.add_flag("-c{v1},--count{v2}");
args = {"-c"};
run();
EXPECT_EQ(1u, app.count("-c"));
EXPECT_EQ(1u, app.count("--count"));
auto v = app["-c"]->results();
EXPECT_EQ(v[0], "v1");
EXPECT_THROW(app["--invalid"], CLI::OptionNotFound);
}
TEST_F(TApp, OneFlagShortValuesAs) {
auto flg = app.add_flag("-c{1},--count{2}");
args = {"-c"};
run();
auto opt = app["-c"];
EXPECT_EQ(opt->as<int>(), 1);
args = {"--count"};
run();
EXPECT_EQ(opt->as<int>(), 2);
flg->take_first();
args = {"-c", "--count"};
run();
EXPECT_EQ(opt->as<int>(), 1);
flg->take_last();
EXPECT_EQ(opt->as<int>(), 2);
flg->multi_option_policy(CLI::MultiOptionPolicy::Throw);
EXPECT_THROW(opt->as<int>(), CLI::ConversionError);
auto vec = opt->as<std::vector<int>>();
EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 2);
flg->multi_option_policy(CLI::MultiOptionPolicy::Join);
EXPECT_EQ(opt->as<std::string>(), "1,2");
}
TEST_F(TApp, OneFlagShortWindows) {
app.add_flag("-c,--count");
args = {"/c"};
@ -81,6 +118,26 @@ TEST_F(TApp, DashedOptionsSingleString) {
EXPECT_EQ(2u, app.count("--that"));
}
TEST_F(TApp, BoolFlagOverride) {
bool val;
auto flg = app.add_flag("--this,--that", val);
app.parse("--this");
EXPECT_TRUE(val);
app.parse("--this=false");
EXPECT_FALSE(val);
flg->disable_flag_override(true);
app.parse("--this");
EXPECT_TRUE(val);
// this is allowed since the matching string is the default
app.parse("--this=true");
EXPECT_TRUE(val);
EXPECT_THROW(app.parse("--this=false"), CLI::ArgumentMismatch);
// try a string that specifies 'use default val'
EXPECT_NO_THROW(app.parse("--this={}"));
}
TEST_F(TApp, OneFlagRef) {
int ref;
app.add_flag("-c,--count", ref);
@ -103,13 +160,14 @@ TEST_F(TApp, OneFlagRefValue) {
TEST_F(TApp, OneFlagRefValueFalse) {
int ref;
app.add_flag("-c,--count", ref);
auto flg = app.add_flag("-c,--count", ref);
args = {"--count=false"};
run();
EXPECT_EQ(1u, app.count("-c"));
EXPECT_EQ(1u, app.count("--count"));
EXPECT_EQ(-1, ref);
EXPECT_FALSE(flg->check_fname("c"));
args = {"--count=0"};
run();
EXPECT_EQ(1u, app.count("-c"));
@ -122,8 +180,10 @@ TEST_F(TApp, OneFlagRefValueFalse) {
TEST_F(TApp, FlagNegation) {
int ref;
app.add_flag("-c,--count,--ncount{false}", ref);
auto flg = app.add_flag("-c,--count,--ncount{false}", ref);
args = {"--count", "-c", "--ncount"};
EXPECT_FALSE(flg->check_fname("count"));
EXPECT_TRUE(flg->check_fname("ncount"));
run();
EXPECT_EQ(3u, app.count("-c"));
EXPECT_EQ(3u, app.count("--count"));
@ -133,8 +193,8 @@ TEST_F(TApp, FlagNegation) {
TEST_F(TApp, FlagNegationShortcutNotation) {
int ref;
app.add_flag("-c,--count,!--ncount", ref);
args = {"--count", "-c", "--ncount"};
app.add_flag("-c,--count{true},!--ncount", ref);
args = {"--count=TRUE", "-c", "--ncount"};
run();
EXPECT_EQ(3u, app.count("-c"));
EXPECT_EQ(3u, app.count("--count"));
@ -142,6 +202,13 @@ TEST_F(TApp, FlagNegationShortcutNotation) {
EXPECT_EQ(1, ref);
}
TEST_F(TApp, FlagNegationShortcutNotationInvalid) {
int ref;
app.add_flag("-c,--count,!--ncount", ref);
args = {"--ncount=happy"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, OneString) {
std::string str;
app.add_option("-s,--string", str);
@ -325,6 +392,8 @@ TEST_F(TApp, TogetherInt) {
EXPECT_EQ(1u, app.count("--int"));
EXPECT_EQ(1u, app.count("-i"));
EXPECT_EQ(i, 4);
EXPECT_EQ(app["-i"]->as<std::string>(), "4");
EXPECT_EQ(app["--int"]->as<double>(), 4.0);
}
TEST_F(TApp, SepInt) {
@ -369,6 +438,8 @@ TEST_F(TApp, doubleFunction) {
args = {"--val", "-354.356"};
run();
EXPECT_EQ(res, 300.356);
// get the original value as entered as an integer
EXPECT_EQ(app["--val"]->as<float>(), -354.356f);
}
TEST_F(TApp, doubleFunctionFail) {
@ -397,13 +468,18 @@ TEST_F(TApp, doubleVectorFunction) {
TEST_F(TApp, doubleVectorFunctionFail) {
std::vector<double> res;
app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
std::string vstring = "--val";
app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
return true;
});
args = {"--val", "five", "--val", "nine", "--val", "7"};
EXPECT_THROW(run(), CLI::ConversionError);
// check that getting the results through the results function generates the same error
EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError);
auto strvec = app[vstring]->as<std::vector<std::string>>();
EXPECT_EQ(strvec.size(), 3u);
}
TEST_F(TApp, DefaultStringAgain) {
@ -453,6 +529,35 @@ TEST_F(TApp, LotsOfFlags) {
EXPECT_EQ(1u, app.count("-A"));
}
TEST_F(TApp, NumberFlags) {
int val;
app.add_flag("-1{1},-2{2},-3{3},-4{4},-5{5},-6{6}, -7{7}, -8{8}, -9{9}", val);
args = {"-7"};
run();
EXPECT_EQ(1u, app.count("-1"));
EXPECT_EQ(val, 7);
}
TEST_F(TApp, DisableFlagOverrideTest) {
int val;
auto opt = app.add_flag("--1{1},--2{2},--3{3},--4{4},--5{5},--6{6}, --7{7}, --8{8}, --9{9}", val);
EXPECT_FALSE(opt->get_disable_flag_override());
opt->disable_flag_override();
args = {"--7=5"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
EXPECT_TRUE(opt->get_disable_flag_override());
opt->disable_flag_override(false);
EXPECT_FALSE(opt->get_disable_flag_override());
EXPECT_NO_THROW(run());
EXPECT_EQ(val, 5);
opt->disable_flag_override();
args = {"--7=7"};
EXPECT_NO_THROW(run());
}
TEST_F(TApp, LotsOfFlagsSingleString) {
app.add_flag("-a");
@ -1063,10 +1168,31 @@ TEST_F(TApp, CallbackFlags) {
EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
}
TEST_F(TApp, CallbackFlagsFalse) {
int value = 0;
TEST_F(TApp, CallbackBoolFlags) {
auto func = [&value](int x) { value = x; };
bool value = false;
auto func = [&value]() { value = true; };
auto cback = app.add_flag_callback("--val", func);
args = {"--val"};
run();
EXPECT_TRUE(value);
value = false;
args = {"--val=false"};
run();
EXPECT_FALSE(value);
EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction);
cback->multi_option_policy(CLI::MultiOptionPolicy::Throw);
args = {"--val", "--val=false"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, CallbackFlagsFalse) {
int64_t value = 0;
auto func = [&value](int64_t x) { value = x; };
app.add_flag_function("-v,-f{false},--val,--fval{false}", func);
@ -1093,9 +1219,9 @@ TEST_F(TApp, CallbackFlagsFalse) {
}
TEST_F(TApp, CallbackFlagsFalseShortcut) {
int value = 0;
int64_t value = 0;
auto func = [&value](int x) { value = x; };
auto func = [&value](int64_t x) { value = x; };
app.add_flag_function("-v,!-f,--val,!--fval", func);
@ -1361,6 +1487,24 @@ TEST_F(TApp, VectorDefaultedFixedString) {
EXPECT_EQ(answer, strvec);
}
TEST_F(TApp, DefaultedResult) {
std::string sval = "NA";
int ival;
auto opts = app.add_option("--string", sval, "", true);
auto optv = app.add_option("--val", ival);
args = {};
run();
EXPECT_EQ(sval, "NA");
std::string nString;
opts->results(nString);
EXPECT_EQ(nString, "NA");
int newIval;
EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
optv->default_str("442");
optv->results(newIval);
EXPECT_EQ(newIval, 442);
}
TEST_F(TApp, VectorUnlimString) {
std::vector<std::string> strvec;
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
@ -1620,7 +1764,6 @@ TEST_F(TApp, AllowExtras) {
bool val = true;
app.add_flag("-f", val);
EXPECT_FALSE(val);
args = {"-x", "-f"};
@ -1769,7 +1912,7 @@ TEST_F(TApp, RepeatingMultiArgumentOptions) {
// #122
TEST_F(TApp, EmptyOptionEach) {
std::string q;
app.add_option("--each", {})->each([&q](std::string s) { q = s; });
app.add_option("--each")->each([&q](std::string s) { q = s; });
args = {"--each", "that"};
run();
@ -1780,7 +1923,7 @@ TEST_F(TApp, EmptyOptionEach) {
// #122
TEST_F(TApp, EmptyOptionFail) {
std::string q;
app.add_option("--each", {});
app.add_option("--each");
args = {"--each", "that"};
run();
@ -1816,6 +1959,10 @@ TEST_F(TApp, CustomUserSepParse) {
auto opt = app.add_option("--idx", vals, "", ',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
std::vector<int> vals2;
// check that the results vector gets the results in the same way
opt->results(vals2, ',');
EXPECT_EQ(vals2, vals);
app.remove_option(opt);

View File

@ -424,6 +424,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
EXPECT_EQ(app.option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::Throw);
EXPECT_FALSE(app.option_defaults()->get_ignore_case());
EXPECT_FALSE(app.option_defaults()->get_ignore_underscore());
EXPECT_FALSE(app.option_defaults()->get_disable_flag_override());
EXPECT_TRUE(app.option_defaults()->get_configurable());
EXPECT_EQ(app.option_defaults()->get_group(), "Options");
@ -433,6 +434,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
->ignore_case()
->ignore_underscore()
->configurable(false)
->disable_flag_override()
->group("Something");
auto app2 = app.add_subcommand("app2");
@ -442,6 +444,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
EXPECT_TRUE(app2->option_defaults()->get_ignore_case());
EXPECT_TRUE(app2->option_defaults()->get_ignore_underscore());
EXPECT_FALSE(app2->option_defaults()->get_configurable());
EXPECT_TRUE(app.option_defaults()->get_disable_flag_override());
EXPECT_EQ(app2->option_defaults()->get_group(), "Something");
}

View File

@ -69,17 +69,17 @@ TEST(StringTools, Modify3) {
}
TEST(StringTools, flagValues) {
EXPECT_EQ(CLI::detail::to_flag_value("0"), "-1");
EXPECT_EQ(CLI::detail::to_flag_value("t"), "1");
EXPECT_EQ(CLI::detail::to_flag_value("1"), "1");
EXPECT_EQ(CLI::detail::to_flag_value("6"), "6");
EXPECT_EQ(CLI::detail::to_flag_value("-6"), "-6");
EXPECT_EQ(CLI::detail::to_flag_value("false"), "-1");
EXPECT_EQ(CLI::detail::to_flag_value("YES"), "1");
EXPECT_EQ(CLI::detail::to_flag_value("0"), -1);
EXPECT_EQ(CLI::detail::to_flag_value("t"), 1);
EXPECT_EQ(CLI::detail::to_flag_value("1"), 1);
EXPECT_EQ(CLI::detail::to_flag_value("6"), 6);
EXPECT_EQ(CLI::detail::to_flag_value("-6"), -6);
EXPECT_EQ(CLI::detail::to_flag_value("false"), -1);
EXPECT_EQ(CLI::detail::to_flag_value("YES"), 1);
EXPECT_THROW(CLI::detail::to_flag_value("frog"), std::invalid_argument);
EXPECT_THROW(CLI::detail::to_flag_value("q"), std::invalid_argument);
EXPECT_EQ(CLI::detail::to_flag_value("NO"), "-1");
EXPECT_EQ(CLI::detail::to_flag_value("4755263255233"), "4755263255233");
EXPECT_EQ(CLI::detail::to_flag_value("NO"), -1);
EXPECT_EQ(CLI::detail::to_flag_value("475555233"), 475555233);
}
TEST(Trim, Various) {

View File

@ -516,8 +516,13 @@ TEST_F(TApp, IniFlagConvertFailure) {
std::ofstream out{tmpini};
out << "flag=moobook" << std::endl;
}
EXPECT_THROW(run(), CLI::ConversionError);
run();
bool result;
auto *opt = app.get_option("--flag");
EXPECT_THROW(opt->results(result), CLI::ConversionError);
std::string res;
opt->results(res);
EXPECT_EQ(res, "moobook");
}
TEST_F(TApp, IniFlagNumbers) {
@ -664,6 +669,51 @@ TEST_F(TApp, IniFalseFlagsDef) {
EXPECT_TRUE(five);
}
TEST_F(TApp, IniFalseFlagsDefDisableOverrideError) {
TempFile tmpini{"TestIniTmp.ini"};
app.set_config("--config", tmpini);
{
std::ofstream out{tmpini};
out << "[default]" << std::endl;
out << "two=2" << std::endl;
out << "four=on" << std::endl;
out << "five" << std::endl;
}
int two;
bool four, five;
app.add_flag("--two{false}", two)->disable_flag_override();
app.add_flag("!--four", four);
app.add_flag("--five", five);
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, IniFalseFlagsDefDisableOverrideSuccess) {
TempFile tmpini{"TestIniTmp.ini"};
app.set_config("--config", tmpini);
{
std::ofstream out{tmpini};
out << "[default]" << std::endl;
out << "two=2" << std::endl;
out << "four={}" << std::endl;
out << "val=15" << std::endl;
}
int two, four, val;
app.add_flag("--two{2}", two)->disable_flag_override();
app.add_flag("--four{4}", four)->disable_flag_override();
app.add_flag("--val", val);
run();
EXPECT_EQ(2, two);
EXPECT_EQ(4, four);
EXPECT_EQ(15, val);
}
TEST_F(TApp, IniOutputSimple) {
int v;
@ -693,7 +743,7 @@ TEST_F(TApp, IniOutputNoConfigurable) {
TEST_F(TApp, IniOutputShortSingleDescription) {
std::string flag = "some_flag";
std::string description = "Some short description.";
const std::string description = "Some short description.";
app.add_flag("--" + flag, description);
run();
@ -705,8 +755,8 @@ TEST_F(TApp, IniOutputShortSingleDescription) {
TEST_F(TApp, IniOutputShortDoubleDescription) {
std::string flag1 = "flagnr1";
std::string flag2 = "flagnr2";
std::string description1 = "First description.";
std::string description2 = "Second description.";
const std::string description1 = "First description.";
const std::string description2 = "Second description.";
app.add_flag("--" + flag1, description1);
app.add_flag("--" + flag2, description2);
@ -718,7 +768,7 @@ TEST_F(TApp, IniOutputShortDoubleDescription) {
TEST_F(TApp, IniOutputMultiLineDescription) {
std::string flag = "some_flag";
std::string description = "Some short description.\nThat has lines.";
const std::string description = "Some short description.\nThat has lines.";
app.add_flag("--" + flag, description);
run();

View File

@ -856,11 +856,21 @@ TEST_F(ManySubcommands, MaxCommands) {
TEST_F(TApp, UnnamedSub) {
double val;
auto sub = app.add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
auto opt = sub->add_option("-v,--value", val);
args = {"-v", "4.56"};
run();
EXPECT_EQ(val, 4.56);
// make sure unnamed sub options can be found from the main app
auto opt2 = app.get_option("-v");
EXPECT_EQ(opt, opt2);
EXPECT_THROW(app.get_option("--vvvv"), CLI::OptionNotFound);
// now test in the constant context
const auto &appC = app;
auto opt3 = appC.get_option("-v");
EXPECT_EQ(opt3->get_name(), "--value");
EXPECT_THROW(appC.get_option("--vvvv"), CLI::OptionNotFound);
}
TEST_F(TApp, UnnamedSubMix) {