1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-05-01 05:03:52 +00:00

feat: counting flags (#709)

* add a counting flag to address and issue with optional<bool>  and make the flags more consistent

* move the add_flag to a single operation and add a Sum multi option policy

* style: pre-commit.ci fixes

* remove sum_flag_vector overloads

* style: pre-commit.ci fixes

* add limits include

* style: pre-commit.ci fixes

* fix some other warnings

* update docs describing the multi_option_policy

* Apply suggestions from code review

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
This commit is contained in:
Philip Top 2022-03-21 15:56:35 -07:00 committed by GitHub
parent 95e7f81d1d
commit f7d26f26b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 146 additions and 92 deletions

View File

@ -242,7 +242,7 @@ While all options internally are the same type, there are several ways to add an
app.add_option(option_name, help_str="") app.add_option(option_name, help_str="")
app.add_option(option_name, 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 vectorof any other supported type. 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="") help_string="")
app.add_option_function<type>(option_name, app.add_option_function<type>(option_name,
@ -363,7 +363,7 @@ Before parsing, you can set the following options:
* `->allow_extra_args(true/false)`: If set to true the option will take an unlimited number of arguments like a vector, if false it will limit the number of arguments to the size of the type used in the option. Default value depends on the nature of the type use, containers default to true, others default to false. * `->allow_extra_args(true/false)`: If set to true the option will take an unlimited number of arguments like a vector, if false it will limit the number of arguments to the size of the type used in the option. Default value depends on the nature of the type use, containers default to true, others default to false.
* `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value. * `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value.
* `->description(str)`: Set/change the description. * `->description(str)`: Set/change the description.
* `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, 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). `->join(delim)` can also be used to join with a specific delimiter. This equivalent to calling `->delimiter(delim)` and `->join()` * `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, 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). `->join(delim)` can also be used to join with a specific delimiter. This equivalent to calling `->delimiter(delim)` and `->join()`. Valid values are `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::TakeLast`, `CLI::MultiOptionPolicy::TakeFirst`, `CLI::MultiOptionPolicy::Join`, `CLI::MultiOptionPolicy::TakeAll`, and `CLI::MultiOptionPolicy::Sum` 🚧.
* `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails * `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails
* `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones. * `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones.
* `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options. * `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.

View File

@ -15,7 +15,7 @@ This will bind the flag `-f` to the boolean `my_flag`. After the parsing step, `
## Integer flags ## Integer flags
If you want to allow multiple flags, simply use any integer-like instead of a bool: If you want to allow multiple flags and count their value, simply use any integral variables instead of a bool:
```cpp ```cpp
int my_flag{0}; int my_flag{0};
@ -24,6 +24,8 @@ app.add_flag("-f", my_flag, "Optional description");
After the parsing step, `my_flag` will contain the number of times this flag was found on the command line, including 0 if not found. After the parsing step, `my_flag` will contain the number of times this flag was found on the command line, including 0 if not found.
This behavior can also be controlled manually via `->multi_option_policy(CLI::MultiOptionPolicy::Sum)` as of version 2.2.
## Arbitrary type flags ## Arbitrary type flags
CLI11 allows the type of the variable to assign to in the `add_flag` function to be any supported type. This is particularly useful in combination with specifying default values for flags. The allowed types include bool, int, float, vector, enum, or string-like. CLI11 allows the type of the variable to assign to in the `add_flag` function to be any supported type. This is particularly useful in combination with specifying default values for flags. The allowed types include bool, int, float, vector, enum, or string-like.

View File

@ -171,7 +171,7 @@ When you call `add_option`, you get a pointer to the added option. You can use t
| `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. | | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. |
| `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` | | `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` |
| `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' | | `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' |
| `->multi_option_policy( CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, and `Join` are also available. See the next four lines for shortcuts to set this more easily. | | `->multi_option_policy( CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, `Join`, and `Sum` are also available. See the next four lines for shortcuts to set this more easily. |
| `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`. | | `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`. |
| `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` | | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` |
| `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` | | `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` |
@ -211,7 +211,7 @@ One of CLI11's systems to allow customizability without high levels of verbosity
* `group`: The group name starts as "Options" * `group`: The group name starts as "Options"
* `required`: If the option must be given. Defaults to `false`. Is ignored for flags. * `required`: If the option must be given. Defaults to `false`. Is ignored for flags.
* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag. * `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` or `CLI::MultiOptionPolicy::Sum` regardless of the default, so that multiple bool flags does not cause an error. But you can override that setting by calling the `multi_option_policy` directly.
* `ignore_case`: Allow any mixture of cases for the option or flag name * `ignore_case`: Allow any mixture of cases for the option or flag name
* `ignore_underscore`: Allow any number of underscores in the option or flag name * `ignore_underscore`: Allow any number of underscores in the option or flag name
* `configurable`: Specify whether an option can be configured through a config file * `configurable`: Specify whether an option can be configured through a config file

View File

@ -61,6 +61,22 @@ class App;
using App_p = std::shared_ptr<App>; using App_p = std::shared_ptr<App>;
namespace detail {
/// helper functions for adding in appropriate flag modifiers for add_flag
template <typename T, enable_if_t<!std::is_integral<T>::value || (sizeof(T) <= 1U), detail::enabler> = detail::dummy>
Option *default_flag_modifiers(Option *opt) {
return opt->always_capture_default();
}
/// summing modifiers
template <typename T, enable_if_t<std::is_integral<T>::value && (sizeof(T) > 1U), detail::enabler> = detail::dummy>
Option *default_flag_modifiers(Option *opt) {
return opt->multi_option_policy(MultiOptionPolicy::Sum)->default_str("0")->force_callback();
}
} // namespace detail
class Option_group; class Option_group;
/// Creates a command line program, with very few defaults. /// Creates a command line program, with very few defaults.
/** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
@ -807,43 +823,21 @@ class App {
return _add_flag_internal(flag_name, CLI::callback_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_constructible<T, std::int64_t>::value && !std::is_const<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;
CLI::callback_t fun = [&flag_count](const CLI::results_t &res) {
try {
detail::sum_flag_vector(res, flag_count);
} catch(const std::invalid_argument &) {
return false;
}
return true;
};
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
->multi_option_policy(MultiOptionPolicy::TakeAll);
}
/// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes /// 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 /// that can be converted from a string
template <typename T, template <typename T,
enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value && enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value &&
(!std::is_constructible<T, std::int64_t>::value || is_bool<T>::value) &&
!std::is_constructible<std::function<void(int)>, T>::value, !std::is_constructible<std::function<void(int)>, T>::value,
detail::enabler> = detail::dummy> detail::enabler> = detail::dummy>
Option *add_flag(std::string flag_name, Option *add_flag(std::string flag_name,
T &flag_result, ///< A variable holding true if passed T &flag_result, ///< A variable holding the flag result
std::string flag_description = "") { std::string flag_description = "") {
CLI::callback_t fun = [&flag_result](const CLI::results_t &res) { CLI::callback_t fun = [&flag_result](const CLI::results_t &res) {
return CLI::detail::lexical_cast(res[0], flag_result); return CLI::detail::lexical_cast(res[0], flag_result);
}; };
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))->run_callback_for_default(); auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
return detail::default_flag_modifiers<T>(opt);
} }
/// Vector version to capture multiple flags. /// Vector version to capture multiple flags.
@ -888,13 +882,13 @@ class App {
std::string flag_description = "") { std::string flag_description = "") {
CLI::callback_t fun = [function](const CLI::results_t &res) { CLI::callback_t fun = [function](const CLI::results_t &res) {
std::int64_t flag_count = 0; std::int64_t flag_count{0};
detail::sum_flag_vector(res, flag_count); CLI::detail::lexical_cast(res[0], flag_count);
function(flag_count); function(flag_count);
return true; return true;
}; };
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)) return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
->multi_option_policy(MultiOptionPolicy::TakeAll); ->multi_option_policy(MultiOptionPolicy::Sum);
} }
#ifdef CLI11_CPP14 #ifdef CLI11_CPP14

View File

@ -40,7 +40,8 @@ enum class MultiOptionPolicy : char {
TakeLast, //!< take only the last Expected number of arguments TakeLast, //!< take only the last Expected number of arguments
TakeFirst, //!< take only the first Expected number of arguments TakeFirst, //!< take only the first Expected number of arguments
Join, //!< merge all the arguments together into a single string via the delimiter character default('\n') Join, //!< merge all the arguments together into a single string via the delimiter character default('\n')
TakeAll //!< just get all the passed argument regardless TakeAll, //!< just get all the passed argument regardless
Sum //!< sum all the arguments together if numerical or concatenate directly without delimiter
}; };
/// This is the CRTP base class for Option and OptionDefaults. It was designed this way /// This is the CRTP base class for Option and OptionDefaults. It was designed this way
@ -1266,6 +1267,9 @@ class Option : public OptionBase<Option> {
res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_))); res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
} }
break; break;
case MultiOptionPolicy::Sum:
res.push_back(detail::sum_string_vector(original));
break;
case MultiOptionPolicy::Throw: case MultiOptionPolicy::Throw:
default: { default: {
auto num_min = static_cast<std::size_t>(get_items_expected_min()); auto num_min = static_cast<std::size_t>(get_items_expected_min());
@ -1352,7 +1356,7 @@ class Option : public OptionBase<Option> {
} }
return result_count; return result_count;
} }
}; // namespace CLI };
// [CLI11:option_hpp:end] // [CLI11:option_hpp:end]
} // namespace CLI } // namespace CLI

View File

@ -9,6 +9,7 @@
// [CLI11:public_includes:set] // [CLI11:public_includes:set]
#include <cstdint> #include <cstdint>
#include <exception> #include <exception>
#include <limits>
#include <memory> #include <memory>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@ -799,7 +800,16 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
char *val = nullptr; char *val = nullptr;
std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
output = static_cast<T>(output_ll); output = static_cast<T>(output_ll);
return val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll; if(val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll) {
return true;
}
val = nullptr;
std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0);
if(val == (input.c_str() + input.size())) {
output = (output_sll < 0) ? static_cast<T>(0) : static_cast<T>(output_sll);
return (static_cast<std::int64_t>(output) == output_sll);
}
return false;
} }
/// Convert to a signed integral /// Convert to a signed integral
@ -811,7 +821,15 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
char *val = nullptr; char *val = nullptr;
std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0);
output = static_cast<T>(output_ll); output = static_cast<T>(output_ll);
return val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll; if(val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll) {
return true;
}
if(input == "true") {
// this is to deal with a few oddities with flags and wrapper int types
output = static_cast<T>(1);
return true;
}
return false;
} }
/// Convert a flag into an integer value typically binary flags /// Convert a flag into an integer value typically binary flags
@ -1501,61 +1519,40 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu
return false; return false;
} }
/// Sum a vector of flag representations /// Sum a vector of strings
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is inline std::string sum_string_vector(const std::vector<std::string> &values) {
/// by double val{0.0};
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most bool fail{false};
/// common true and false strings then uses stoll to convert the rest for summing std::string output;
template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy> for(const auto &arg : values) {
void sum_flag_vector(const std::vector<std::string> &flags, T &output) { double tv{0.0};
std::int64_t count{0}; auto comp = detail::lexical_cast<double>(arg, tv);
for(auto &flag : flags) { if(!comp) {
count += detail::to_flag_value(flag); try {
tv = static_cast<double>(detail::to_flag_value(arg));
} catch(const std::exception &) {
fail = true;
break;
}
}
val += tv;
} }
output = (count > 0) ? static_cast<T>(count) : T{0}; if(fail) {
} for(const auto &arg : values) {
output.append(arg);
/// Sum a vector of flag representations }
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is } else {
/// by if(val <= static_cast<double>(std::numeric_limits<std::int64_t>::min()) ||
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most val >= static_cast<double>(std::numeric_limits<std::int64_t>::max()) ||
/// common true and false strings then uses stoll to convert the rest for summing val == static_cast<std::int64_t>(val)) {
template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy> output = detail::value_string(static_cast<int64_t>(val));
void sum_flag_vector(const std::vector<std::string> &flags, T &output) { } else {
std::int64_t count{0}; output = detail::value_string(val);
for(auto &flag : flags) { }
count += detail::to_flag_value(flag);
} }
output = static_cast<T>(count); return output;
} }
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4800)
#endif
// with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style so will
// most likely still work
/// Sum a vector of flag representations
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
/// by
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
/// common true and false strings then uses stoll to convert the rest for summing
template <typename T,
enable_if_t<!std::is_signed<T>::value && !std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
std::int64_t count{0};
for(auto &flag : flags) {
count += detail::to_flag_value(flag);
}
std::string out = detail::to_string(count);
lexical_cast(out, output);
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
} // namespace detail } // namespace detail
// [CLI11:type_tools_hpp:end] // [CLI11:type_tools_hpp:end]
} // namespace CLI } // namespace CLI

View File

@ -212,7 +212,7 @@ TEST_CASE_METHOD(TApp, "OneFlagRefValueFalse", "[app]") {
run(); run();
CHECK(app.count("-c") == 1u); CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u); CHECK(app.count("--count") == 1u);
CHECK(ref == -1); CHECK(ref == 0);
args = {"--count=happy"}; args = {"--count=happy"};
CHECK_THROWS_AS(run(), CLI::ConversionError); CHECK_THROWS_AS(run(), CLI::ConversionError);
@ -774,6 +774,42 @@ TEST_CASE_METHOD(TApp, "JoinOpt", "[app]") {
CHECK("one\ntwo" == str); CHECK("one\ntwo" == str);
} }
TEST_CASE_METHOD(TApp, "SumOpt", "[app]") {
int val;
app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
args = {"--val=1", "--val=4"};
run();
CHECK(5 == val);
}
TEST_CASE_METHOD(TApp, "SumOptFloat", "[app]") {
double val;
app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
args = {"--val=1.3", "--val=-0.7"};
run();
CHECK(0.6 == val);
}
TEST_CASE_METHOD(TApp, "SumOptString", "[app]") {
std::string val;
app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
args = {"--val=i", "--val=2"};
run();
CHECK("i2" == val);
}
TEST_CASE_METHOD(TApp, "JoinOpt2", "[app]") { TEST_CASE_METHOD(TApp, "JoinOpt2", "[app]") {
std::string str; std::string str;

View File

@ -2254,7 +2254,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputFlag", "[config]") {
CHECK_THAT(str, Contains("simple=3")); CHECK_THAT(str, Contains("simple=3"));
CHECK_THAT(str, !Contains("nothing")); CHECK_THAT(str, !Contains("nothing"));
CHECK_THAT(str, Contains("onething=true")); CHECK_THAT(str, Contains("onething=true"));
CHECK_THAT(str, Contains("something=[true, true]")); CHECK_THAT(str, Contains("something=2"));
str = app.config_to_str(true); str = app.config_to_str(true);
CHECK_THAT(str, Contains("nothing")); CHECK_THAT(str, Contains("nothing"));
@ -2685,7 +2685,7 @@ TEST_CASE_METHOD(TApp, "IniOutputFlag", "[config]") {
CHECK_THAT(str, Contains("simple=3")); CHECK_THAT(str, Contains("simple=3"));
CHECK_THAT(str, !Contains("nothing")); CHECK_THAT(str, !Contains("nothing"));
CHECK_THAT(str, Contains("onething=true")); CHECK_THAT(str, Contains("onething=true"));
CHECK_THAT(str, Contains("something=true true")); CHECK_THAT(str, Contains("something=2"));
str = app.config_to_str(true); str = app.config_to_str(true);
CHECK_THAT(str, Contains("nothing")); CHECK_THAT(str, Contains("nothing"));

View File

@ -146,7 +146,7 @@ TEST_CASE_METHOD(TApp, "atomic_bool_flags", "[optiontype]") {
std::atomic<int> iflag{0}; std::atomic<int> iflag{0};
app.add_flag("-b", bflag); app.add_flag("-b", bflag);
app.add_flag("-i,--int", iflag); app.add_flag("-i,--int", iflag)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
args = {"-b", "-i"}; args = {"-b", "-i"};
run(); run();

View File

@ -130,6 +130,27 @@ TEST_CASE_METHOD(TApp, "StdOptionalUint", "[optional]") {
CLI::detail::object_category::wrapper_value); CLI::detail::object_category::wrapper_value);
} }
TEST_CASE_METHOD(TApp, "StdOptionalbool", "[optional]") {
std::optional<bool> opt{};
CHECK(!opt);
app.add_flag("--opt,!--no-opt", opt);
CHECK(!opt);
run();
CHECK(!opt);
args = {"--opt"};
run();
CHECK(opt);
CHECK(*opt);
args = {"--no-opt"};
run();
CHECK(opt);
CHECK_FALSE(*opt);
static_assert(CLI::detail::classify_object<std::optional<bool>>::value ==
CLI::detail::object_category::wrapper_value);
}
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning(default : 4244) #pragma warning(default : 4244)
#endif #endif