mirror of
https://github.com/CLIUtils/CLI11.git
synced 2026-01-19 04:52:08 +00:00
feat: add an option to validate optional arguments like in a vector. (#668)
* add an option to validate optional arguments like in a vector. This can resolve some issues with separating positionals from vector arguments * style: pre-commit.ci fixes * add some updates to the book * style: pre-commit.ci fixes * fix some precommit issues Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -591,6 +591,7 @@ There are several options that are supported on the main app and subcommands and
|
||||
* `.enabled_by_default()`: Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others.
|
||||
* `.silent()`: Specify that the subcommand is silent meaning that if used it won't show up in the subcommand list. This allows the use of subcommands as modifiers
|
||||
* `.validate_positionals()`: Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments.
|
||||
* `.validate_optional_arguments()`:🚧 Specify that optional arguments should pass validation before being assigned to an option. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional subcommand or extra arguments.
|
||||
* `.excludes(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error.
|
||||
* `.needs(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, the subcommands will require the given option to have been given before this subcommand is validated which occurs prior to execution of any callback or after parsing is completed.
|
||||
* `.require_option()`: Require 1 or more options or option groups be used.
|
||||
|
||||
@@ -20,7 +20,7 @@ Obviously, you can't access `T` after the `add_` method is over, so it stores th
|
||||
|
||||
Parsing follows the following procedure:
|
||||
|
||||
1. `_validate`: Make sure the defined options are self consistent.
|
||||
1. `_validate`: Make sure the defined options and subcommands are self consistent.
|
||||
2. `_parse`: Main parsing routine. See below.
|
||||
3. `_run_callback`: Run an App callback if present.
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ When you call `add_option`, you get a pointer to the added option. You can use t
|
||||
| `->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. |
|
||||
| `->force_callback()` | Force the option callback to be executed regardless of whether the option was used or not. Will use the default_str if available, if no default is given the callback will be executed with an empty string as an argument, which will translate to a default initialized value, which can be compiler dependent |
|
||||
|`->trigger_on_parse()` | Have the option callback be triggered when the value is parsed vs. at the end of all parsing, the option callback can potentially be executed multiple times. Generally only useful if you have a user defined callback or validation check. Or potentially if a vector input is given multiple times as it will clear the results when a repeat option is given via command line. It will trigger the callbacks once per option call on the command line|
|
||||
| `->trigger_on_parse()` | Have the option callback be triggered when the value is parsed vs. at the end of all parsing, the option callback can potentially be executed multiple times. Generally only useful if you have a user defined callback or validation check. Or potentially if a vector input is given multiple times as it will clear the results when a repeat option is given via command line. It will trigger the callbacks once per option call on the command line|
|
||||
| `->option_text(string)` | Sets the text between the option name and description. |
|
||||
|
||||
The `->check(...)` and `->transform(...)` modifiers can also take a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed.
|
||||
@@ -286,6 +286,10 @@ There are some additional options that can be specified to modify an option for
|
||||
|
||||
* `->run_callback_for_default()` will specify that the callback should be executed when a default_val is set. This is set automatically when appropriate though it can be turned on or off and any user specified callback for an option will be executed when the default value for an option is set.
|
||||
|
||||
* `->force_callback()` will for the callback/value assignment to run at the conclusion of parsing regardless of whether the option was supplied or not. This can be used to force the default or execute some code.
|
||||
|
||||
* `->trigger_on_parse()` will trigger the callback or value assignment each time the argument is passed. The value is reset if the option is supplied multiple times.
|
||||
|
||||
## Unusual circumstances
|
||||
|
||||
There are a few cases where some things break down in the type system managing options and definitions. Using the `add_option` method defines a lambda function to extract a default value if required. In most cases this is either straightforward or a failure is detected automatically and handled. But in a few cases a streaming template is available that several layers down may not actually be defined. This results in CLI11 not being able to detect this circumstance automatically and will result in compile error. One specific known case is `boost::optional` if the boost optional_io header is included. This header defines a template for all boost optional values even if they do not actually have a streaming operator. For example `boost::optional<std::vector>` does not have a streaming operator but one is detected since it is part of a template. For these cases a secondary method `app->add_option_no_stream(...)` is provided that bypasses this operation completely and should compile in these cases.
|
||||
|
||||
@@ -85,6 +85,8 @@ The following values are inherited when you add a new subcommand. This happens a
|
||||
* Fallthrough
|
||||
* Group name
|
||||
* Max required subcommands
|
||||
* validate positional arguments
|
||||
* validate optional arguments
|
||||
|
||||
## Special modes
|
||||
|
||||
@@ -126,3 +128,21 @@ This would allow calling help such as:
|
||||
./app help
|
||||
./app help sub1
|
||||
```
|
||||
|
||||
### Positional Validation
|
||||
|
||||
Some arguments supplied on the command line may be legitamately applied to more than 1 positional argument. In this context enabling `positional_validation` on the application or subcommand will check any validators before applying the command line argument to the positional option. It is not an error to fail validation in this context, positional arguments not matching any validators will go into the `extra_args` field which may generate an error depending on settings.
|
||||
|
||||
### Optional Argument Validation
|
||||
|
||||
Similar to positional validation, there are occasional contexts in which case it might be ambiguous whether an argument should be applied to an option or a positional option.
|
||||
|
||||
```c++
|
||||
std::vector<std::string> vec;
|
||||
std::vector<int> ivec;
|
||||
app.add_option("pos", vec);
|
||||
app.add_option("--args", ivec)->check(CLI::Number);
|
||||
app.validate_optional_arguments();
|
||||
```
|
||||
|
||||
In this case a sequence of integers is expected for the argument and remaining strings go to the positional string vector. Without the `validate_optional_arguments()` active it would be impossible get any later arguments into the positional if the `--args` option is used. The validator in this context is used to make sure the optional arguments match with what the argument is expecting and if not the `-args` option is closed, and remaining arguments fall into the positional.
|
||||
|
||||
@@ -222,6 +222,9 @@ class App {
|
||||
/// If set to true positional options are validated before assigning INHERITABLE
|
||||
bool validate_positionals_{false};
|
||||
|
||||
/// If set to true optional vector arguments are validated before assigning INHERITABLE
|
||||
bool validate_optional_arguments_{false};
|
||||
|
||||
/// indicator that the subcommand is silent and won't show up in subcommands list
|
||||
/// This is potentially useful as a modifier subcommand
|
||||
bool silent_{false};
|
||||
@@ -286,6 +289,7 @@ class App {
|
||||
ignore_underscore_ = parent_->ignore_underscore_;
|
||||
fallthrough_ = parent_->fallthrough_;
|
||||
validate_positionals_ = parent_->validate_positionals_;
|
||||
validate_optional_arguments_ = parent_->validate_optional_arguments_;
|
||||
configurable_ = parent_->configurable_;
|
||||
allow_windows_style_options_ = parent_->allow_windows_style_options_;
|
||||
group_ = parent_->group_;
|
||||
@@ -450,6 +454,12 @@ class App {
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the subcommand to validate optional vector arguments before assigning
|
||||
App *validate_optional_arguments(bool validate = true) {
|
||||
validate_optional_arguments_ = validate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// ignore extras in config files
|
||||
App *allow_config_extras(bool allow = true) {
|
||||
if(allow) {
|
||||
@@ -1715,6 +1725,8 @@ class App {
|
||||
bool get_enabled_by_default() const { return (default_startup == startup_mode::enabled); }
|
||||
/// Get the status of validating positionals
|
||||
bool get_validate_positionals() const { return validate_positionals_; }
|
||||
/// Get the status of validating optional vector arguments
|
||||
bool get_validate_optional_arguments() const { return validate_optional_arguments_; }
|
||||
|
||||
/// Get the status of allow extras
|
||||
config_extras_mode get_allow_config_extras() const { return allow_config_extras_; }
|
||||
@@ -2801,6 +2813,7 @@ class App {
|
||||
throw ArgumentMismatch::TypedAtLeast(op->get_name(), min_num, op->get_type_name());
|
||||
}
|
||||
|
||||
// now check for optional arguments
|
||||
if(max_num > collected || op->get_allow_extra_args()) { // we allow optional arguments
|
||||
auto remreqpos = _count_remaining_positionals(true);
|
||||
// we have met the minimum now optionally check up to the maximum
|
||||
@@ -2810,7 +2823,13 @@ class App {
|
||||
if(remreqpos >= args.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(validate_optional_arguments_) {
|
||||
std::string optarg = args.back();
|
||||
optarg = op->_validate(optarg, 0);
|
||||
if(!optarg.empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
op->add_result(args.back(), result_count);
|
||||
parse_order_.push_back(op.get());
|
||||
args.pop_back();
|
||||
|
||||
@@ -1476,6 +1476,34 @@ TEST_CASE_METHOD(TApp, "BigPositional", "[app]") {
|
||||
CHECK(vec == args);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "VectorArgAndPositional", "[app]") {
|
||||
std::vector<std::string> vec;
|
||||
std::vector<int> ivec;
|
||||
app.add_option("pos", vec);
|
||||
app.add_option("--args", ivec)->check(CLI::Number);
|
||||
app.validate_optional_arguments();
|
||||
args = {"one"};
|
||||
|
||||
run();
|
||||
CHECK(vec == args);
|
||||
|
||||
args = {"--args", "1", "2"};
|
||||
|
||||
run();
|
||||
CHECK(ivec.size() == 2);
|
||||
vec.clear();
|
||||
ivec.clear();
|
||||
|
||||
args = {"--args", "1", "2", "one", "two"};
|
||||
run();
|
||||
|
||||
CHECK(vec.size() == 2);
|
||||
CHECK(ivec.size() == 2);
|
||||
|
||||
app.validate_optional_arguments(false);
|
||||
CHECK_THROWS(run());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "Reset", "[app]") {
|
||||
|
||||
app.add_flag("--simple");
|
||||
|
||||
Reference in New Issue
Block a user