Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,11 +491,13 @@ Before parsing, you can set the following options:
- `->default_str(string)`: Set the default string directly (NO VALIDATION OR
CALLBACKS). 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 have a stream operator). The callback may be
triggered if the `run_callback_for_default` is set.
- `->default_val(value)`: Generate the default string from a value. The value is
converted (and any transforms are applied), but `check`s are not run against a
default, so a default that fails a check does not throw at configuration time;
checks apply to values parsed from the command line. 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 have a stream operator). The
callback may be triggered if the `run_callback_for_default` is set.
- `->run_callback_for_default()`: This will force the option callback to be
executed or the variable set when the `default_val` is set.
- `->option_text(string)`: Sets the text between the option name and
Expand Down
2 changes: 1 addition & 1 deletion book/chapters/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ that to add option modifiers. A full listing of the option modifiers:
| `->each(void(std::string))` | Run a function on each parsed value, _in order_. |
| `->default_str(string)` | set a default string for use in the help and as a default value if no arguments are passed and a value is requested |
| `->default_function(std::string())` | Advanced: Change the function that `capture_default_str()` uses. |
| `->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 have a stream operator). |
| `->default_val(value)` | Generate the default string from a value. The value is converted and any transforms are applied, but `check`s are not run against a default, so a default that fails a check does not throw at configuration time (checks apply to parsed input). 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 have a stream operator). |
| `->capture_default_str()` | Store the current value attached and display it in the help string. |
| `->always_capture_default()` | Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. |
| `->run_callback_for_default()` | Force the option callback to be executed or the variable set when the `default_val` is used. |
Expand Down
19 changes: 11 additions & 8 deletions include/CLI/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -671,8 +671,9 @@ class Option : public OptionBase<Option> {
/// @name Parser tools
///@{

/// Process the callback
void run_callback();
/// Process the callback. When @p transform_only is set (running the callback to store a default
/// value) checks are skipped so a default that fails a check does not throw; see default_val.
void run_callback(bool transform_only = false);

/// If options share any of the same names, find it
CLI11_NODISCARD const std::string &matching_name(const Option &other) const;
Expand Down Expand Up @@ -827,10 +828,10 @@ class Option : public OptionBase<Option> {
add_result(val_str);
// if trigger_on_result_ is set the callback already ran
if(run_callback_for_default_ && !trigger_on_result_) {
run_callback(); // run callback sets the state, we need to reset it again
run_callback(true); // run callback sets the state, we need to reset it again
current_option_state_ = option_state::parsing;
} else {
_validate_results(results_);
_validate_results(results_, true);
current_option_state_ = old_option_state;
}
} catch(const ConversionError &err) {
Expand All @@ -854,16 +855,18 @@ class Option : public OptionBase<Option> {
CLI11_NODISCARD std::string get_type_name() const;

private:
/// Run the results through the Validators
void _validate_results(results_t &res) const;
/// Run the results through the Validators. When @p transform_only is set (validating a default
/// value) only modifying validators (transforms) are applied; checks are skipped so a default
/// that fails a check does not throw at configuration time.
void _validate_results(results_t &res, bool transform_only = false) const;

/** reduce the results in accordance with the MultiOptionPolicy
@param[out] out results are assigned to res if there if they are different
*/
void _reduce_results(results_t &out, const results_t &original) const;

// Run a result through the Validators
std::string _validate(std::string &result, int index) const;
// Run a result through the Validators; see _validate_results for the meaning of transform_only
std::string _validate(std::string &result, int index, bool transform_only = false) const;

/// Add a single result to the result set, taking into account delimiters
int _add_result(std::string &&result, std::vector<std::string> &res) const;
Expand Down
17 changes: 11 additions & 6 deletions include/CLI/impl/Option_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,14 +319,14 @@ Option::get_name(bool positional, bool all_options, bool disable_default_flag_va
return pname_;
}

CLI11_INLINE void Option::run_callback() {
CLI11_INLINE void Option::run_callback(bool transform_only) {
bool used_default_str = false;
if(force_callback_ && results_.empty()) {
used_default_str = true;
add_result(default_str_);
}
if(current_option_state_ == option_state::parsing) {
_validate_results(results_);
_validate_results(results_, transform_only);
current_option_state_ = option_state::validated;
}

Expand Down Expand Up @@ -559,7 +559,7 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_type_name() const {
return full_type_name;
}

CLI11_INLINE void Option::_validate_results(results_t &res) const {
CLI11_INLINE void Option::_validate_results(results_t &res, bool transform_only) const {
// Run the Validators (can change the string)
if(!validators_.empty()) {
if(type_size_max_ > 1) { // in this context index refers to the index in the type
Expand All @@ -576,7 +576,7 @@ CLI11_INLINE void Option::_validate_results(results_t &res) const {
index = 0; // reset index for variable size chunks
continue;
}
auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index);
auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index, transform_only);
if(!err_msg.empty())
throw ValidationError(get_name(), err_msg);
++index;
Expand All @@ -590,7 +590,7 @@ CLI11_INLINE void Option::_validate_results(results_t &res) const {
index = expected_max_ - static_cast<int>(res.size());
}
for(std::string &result : res) {
auto err_msg = _validate(result, index);
auto err_msg = _validate(result, index, transform_only);
++index;
if(!err_msg.empty())
throw ValidationError(get_name(), err_msg);
Expand Down Expand Up @@ -678,13 +678,18 @@ CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &origi
}
}

CLI11_INLINE std::string Option::_validate(std::string &result, int index) const {
CLI11_INLINE std::string Option::_validate(std::string &result, int index, bool transform_only) const {
std::string err_msg;
if(result.empty() && expected_min_ == 0) {
// an empty with nothing expected is allowed
return err_msg;
}
for(const auto &vali : validators_) {
// when validating a default value only transforms (modifying validators) are applied; a
// default that fails a check should not throw at configuration time (see default_val)
if(transform_only && !vali->get_modifying()) {
continue;
}
auto v = vali->get_application_index();
if(v == -1 || v == index) {
try {
Expand Down
19 changes: 19 additions & 0 deletions tests/AppTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,25 @@ TEST_CASE_METHOD(TApp, "invalidDefault", "[app]") {
CHECK_THROWS(opt->default_val("4,6,2,8"));
}

// from https://github.com/CLIUtils/CLI11/issues/1375
// A default value that fails a check() registered before default_val() should not throw at
// configuration time; checks apply to parsed input, which is caught by CLI11_PARSE.
TEST_CASE_METHOD(TApp, "defaultValFailingCheck", "[app]") {
int someNumber{0};
CLI::Option *opt{nullptr};
CHECK_NOTHROW(opt = app.add_option("-n,--someNumber", someNumber)->check(CLI::PositiveNumber)->default_val(0));

// default is applied even though it does not satisfy the check
args = {};
run();
CHECK(someNumber == 0);
CHECK(opt->get_default_str() == "0");

// actual input is still validated at parse time
args = {"-n", "-5"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}

TEST_CASE_METHOD(TApp, "TogetherInt", "[app]") {
int i{0};
app.add_option("-i,--int", i);
Expand Down
7 changes: 6 additions & 1 deletion tests/NumericTypeTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ TEST_CASE_METHOD(TApp, "doubleVectorFunctionRunCallbackOnDefault", "[optiontype]
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
opt->check(CLI::Number);
opt->run_callback_for_default(false);
CHECK_THROWS_AS(opt->default_val("this is a string"), CLI::ValidationError);
// Issue #1375: checks are not applied to default values, so a default that fails a check no
// longer throws at configuration time (this previously threw CLI::ValidationError).
CHECK_NOTHROW(opt->default_val("this is a string"));
// the check still applies to values parsed from the command line
args = {"--val", "this is a string"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
#endif
}

Expand Down
Loading