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

default_val option call (#387)

* Fix invalid callback calls for default_val Option function.  the update adds a flag variable to control it, makes default_val exception safe and a template to convert from actual value types.

* update readme and fix some compilation issues on older compilers

* revert README.md with mistake erasures

* Update README.md

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
This commit is contained in:
Philip Top 2020-01-02 14:38:26 -08:00 committed by Henry Schreiner
parent c67ab9dd43
commit 5b17abf22f
6 changed files with 90 additions and 12 deletions

View File

@ -331,6 +331,8 @@ Before parsing, you can set the following options:
`->capture_default_str()`: 🆕 Store the current value attached and display it in the help string. `->capture_default_str()`: 🆕 Store the current value attached and display it in the help string.
- `->default_function(std::string())`: 🆕 Advanced: Change the function that `capture_default_str()` uses. - `->default_function(std::string())`: 🆕 Advanced: Change the function that `capture_default_str()` uses.
- `->always_capture_default()`: 🆕 Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. - `->always_capture_default()`: 🆕 Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
- `default_str(string)`: Set the default string directly. This string will also be used as a default value if no arguments are passed and the value is requested.
- `default_val(value)`: 🚧 Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or a stream operator).
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. 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.
@ -990,4 +992,3 @@ CLI11 was developed at the [University of Cincinnati][] to support of the [GooFi
[hunter]: https://docs.hunter.sh/en/latest/packages/pkg/CLI11.html [hunter]: https://docs.hunter.sh/en/latest/packages/pkg/CLI11.html
[standard readme style]: https://github.com/RichardLitt/standard-readme [standard readme style]: https://github.com/RichardLitt/standard-readme
[argparse]: https://github.com/p-ranav/argparse [argparse]: https://github.com/p-ranav/argparse

View File

@ -601,12 +601,13 @@ class App {
return CLI::detail::checked_to_string<AssignTo, ConvertTo>(variable); return CLI::detail::checked_to_string<AssignTo, ConvertTo>(variable);
}); });
opt->type_name(detail::type_name<ConvertTo>()); opt->type_name(detail::type_name<ConvertTo>());
// these must be actual variables since (std::max) sometimes is defined in terms of references and references // these must be actual lvalues since (std::max) sometimes is defined in terms of references and references
// to structs used in the evaluation can be temporary so that would cause issues. // to structs used in the evaluation can be temporary so that would cause issues.
auto Tcount = detail::type_count<AssignTo>::value; auto Tcount = detail::type_count<AssignTo>::value;
auto XCcount = detail::type_count<ConvertTo>::value; auto XCcount = detail::type_count<ConvertTo>::value;
opt->type_size((std::max)(Tcount, XCcount)); opt->type_size((std::max)(Tcount, XCcount));
opt->expected(detail::expected_count<ConvertTo>::value); opt->expected(detail::expected_count<ConvertTo>::value);
opt->run_callback_for_default();
return opt; return opt;
} }
@ -754,7 +755,7 @@ class App {
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)); return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))->run_callback_for_default();
} }
/// Vector version to capture multiple flags. /// Vector version to capture multiple flags.
@ -772,7 +773,8 @@ class App {
return retval; return retval;
}; };
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::TakeAll)
->run_callback_for_default();
} }
/// Add option for callback that is triggered with a true flag and takes no arguments /// Add option for callback that is triggered with a true flag and takes no arguments
@ -911,7 +913,7 @@ class App {
CLI::Option *opt = CLI::Option *opt =
add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function); add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function);
opt->type_name(label)->type_size(1, 2)->delimiter('+'); opt->type_name(label)->type_size(1, 2)->delimiter('+')->run_callback_for_default();
return opt; return opt;
} }

View File

@ -324,6 +324,8 @@ class Option : public OptionBase<Option> {
bool allow_extra_args_{false}; bool allow_extra_args_{false};
/// Specify that the option should act like a flag vs regular option /// Specify that the option should act like a flag vs regular option
bool flag_like_{false}; bool flag_like_{false};
/// Control option to run the callback to set the default
bool run_callback_for_default_{false};
///@} ///@}
/// Making an option by hand is not defined, it must be made by the App class /// Making an option by hand is not defined, it must be made by the App class
@ -408,6 +410,15 @@ class Option : public OptionBase<Option> {
/// Get the current value of allow extra args /// Get the current value of allow extra args
bool get_allow_extra_args() const { return allow_extra_args_; } bool get_allow_extra_args() const { return allow_extra_args_; }
/// Set the value of run_callback_for_default which controls whether the callback function should be called to set
/// the default This is controlled automatically but could be manipulated by the user.
Option *run_callback_for_default(bool value = true) {
run_callback_for_default_ = value;
return this;
}
/// Get the current value of run_callback_for_default
bool get_run_callback_for_default() const { return run_callback_for_default_; }
/// Adds a Validator with a built in type name /// Adds a Validator with a built in type name
Option *check(Validator validator, const std::string &validator_name = "") { Option *check(Validator validator, const std::string &validator_name = "") {
validator.non_modifying(); validator.non_modifying();
@ -1066,15 +1077,30 @@ class Option : public OptionBase<Option> {
return this; return this;
} }
/// Set the default value string representation and evaluate into the bound value /// Set the default value and validate the results and run the callback if appropriate to set the value into the
Option *default_val(const std::string &val) { /// bound value only available for types that can be converted to a string
default_str(val); template <typename X> Option *default_val(const X &val) {
auto old_results = results_; std::string val_str = detail::to_string(val);
auto old_option_state = current_option_state_;
results_t old_results{std::move(results_)};
results_.clear(); results_.clear();
add_result(val); try {
run_callback(); add_result(val_str);
if(run_callback_for_default_) {
run_callback(); // run callback sets the state we need to reset it again
current_option_state_ = option_state::parsing;
} else {
_validate_results(results_);
current_option_state_ = old_option_state;
}
} catch(const CLI::Error &) {
// this should be done
results_ = std::move(old_results);
current_option_state_ = old_option_state;
throw;
}
results_ = std::move(old_results); results_ = std::move(old_results);
current_option_state_ = option_state::parsing; default_str_ = std::move(val_str);
return this; return this;
} }

View File

@ -46,6 +46,9 @@ template <typename T> struct is_vector : std::false_type {};
/// Check to see if something is a vector (true if actually a vector) /// Check to see if something is a vector (true if actually a vector)
template <class T, class A> struct is_vector<std::vector<T, A>> : std::true_type {}; template <class T, class A> struct is_vector<std::vector<T, A>> : std::true_type {};
/// Check to see if something is a vector (true if actually a const vector)
template <class T, class A> struct is_vector<const std::vector<T, A>> : std::true_type {};
/// Check to see if something is bool (fail check by default) /// Check to see if something is bool (fail check by default)
template <typename T> struct is_bool : std::false_type {}; template <typename T> struct is_bool : std::false_type {};

View File

@ -425,6 +425,10 @@ TEST_F(TApp, OneIntFlagLike) {
opt->default_str("7"); opt->default_str("7");
run(); run();
EXPECT_EQ(val, 7); EXPECT_EQ(val, 7);
opt->default_val(9);
run();
EXPECT_EQ(val, 9);
} }
TEST_F(TApp, TogetherInt) { TEST_F(TApp, TogetherInt) {
@ -514,6 +518,33 @@ TEST_F(TApp, doubleVectorFunctionFail) {
EXPECT_EQ(strvec.size(), 3u); EXPECT_EQ(strvec.size(), 3u);
} }
TEST_F(TApp, doubleVectorFunctionRunCallbackOnDefault) {
std::vector<double> res;
auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "5", "--val", "6", "--val", "7"};
run();
EXPECT_EQ(res.size(), 3u);
EXPECT_EQ(res[0], 10.0);
EXPECT_EQ(res[2], 12.0);
EXPECT_FALSE(opt->get_run_callback_for_default());
opt->run_callback_for_default();
opt->default_val(std::vector<int>{2, 1, -2});
EXPECT_EQ(res[0], 7.0);
EXPECT_EQ(res[2], 3.0);
EXPECT_THROW(opt->default_val("this is a string"), CLI::ConversionError);
auto vec = opt->as<std::vector<double>>();
ASSERT_EQ(vec.size(), 3U);
EXPECT_EQ(vec[0], 5.0);
EXPECT_EQ(vec[2], 7.0);
opt->check(CLI::Number);
opt->run_callback_for_default(false);
EXPECT_THROW(opt->default_val("this is a string"), CLI::ValidationError);
}
TEST_F(TApp, DefaultStringAgain) { TEST_F(TApp, DefaultStringAgain) {
std::string str = "previous"; std::string str = "previous";
app.add_option("-s,--string", str); app.add_option("-s,--string", str);

View File

@ -397,6 +397,21 @@ TEST(THelp, ManualSetters) {
EXPECT_EQ(x, 14); EXPECT_EQ(x, 14);
help = app.help(); help = app.help();
EXPECT_THAT(help, HasSubstr("=14")); EXPECT_THAT(help, HasSubstr("=14"));
op1->default_val(12);
EXPECT_EQ(x, 12);
help = app.help();
EXPECT_THAT(help, HasSubstr("=12"));
EXPECT_TRUE(op1->get_run_callback_for_default());
op1->run_callback_for_default(false);
EXPECT_FALSE(op1->get_run_callback_for_default());
op1->default_val(18);
// x should not be modified in this case
EXPECT_EQ(x, 12);
help = app.help();
EXPECT_THAT(help, HasSubstr("=18"));
} }
TEST(THelp, ManualSetterOverFunction) { TEST(THelp, ManualSetterOverFunction) {