Commit 6c49c299b953297cd9a60a0b2aeaa6b42f1faeab

Authored by Philip Top
Committed by GitHub
1 parent bd3c9cb5

feat: add a more clear force callback (#631)

* Add some missing modifiers on the options to the docs and clarify some of them.

* style: pre-commit.ci fixes

* add a more clear force callback and callback on parse modifier for options.

* update the book with new modifiers

* update documentation and add tests

* style: pre-commit.ci fixes

* more updates to the readme

* update formatting

* rework the trigger_on_parse to better support more complex option types

* fix formatting errors

* Update include/CLI/Option.hpp

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
Co-authored-by: Henry Schreiner <henry.fredrick.schreiner@cern.ch>
README.md
@@ -336,10 +336,12 @@ Before parsing, you can set the following options: @@ -336,10 +336,12 @@ Before parsing, you can set the following options:
336 336
337 * `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. 337 * `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works.
338 * `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option or subcommand. 338 * `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option or subcommand.
  339 +* `->expected(MIN,MAX)`: Set a range of expected values to accompany an option. `expected(0,1)` is the equivalent of making a flag.
339 * `->type_name(typename)`: Set the name of an Option's type (`type_name_fn` allows a function instead) 340 * `->type_name(typename)`: Set the name of an Option's type (`type_name_fn` allows a function instead)
340 -* `->type_size(N)`: Set the intrinsic size of an option. The parser will require multiples of this number if negative.  
341 -* `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer.  
342 -* `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer. 341 +* `->type_size(N)`: Set the intrinsic size of an option value. The parser will require multiples of this number if negative. Most of the time this is detected automatically though can be modified for specific use cases.
  342 +* `->type_size(MIN,MAX)`: Set the intrinsic size of an option to a range.
  343 +* `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer. Options can be removed from the `needs` with `remove_needs(opt)`. The option can also be specified with a string containing the name of the option
  344 +* `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer. Can also be given as a string containing the name of the option. Options can be removed from the excludes list with `->remove_excludes(opt)`
343 * `->envname(name)`: Gets the value from the environment if present and not passed on the command line. 345 * `->envname(name)`: Gets the value from the environment if present and not passed on the command line.
344 * `->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). 346 * `->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).
345 * `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). 347 * `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
@@ -348,7 +350,7 @@ Before parsing, you can set the following options: @@ -348,7 +350,7 @@ Before parsing, you can set the following options:
348 * `->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. 350 * `->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.
349 * `->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. 351 * `->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.
350 * `->description(str)`: Set/change the description. 352 * `->description(str)`: Set/change the description.
351 -* `->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). 353 +* `->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()`
352 * `->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 354 * `->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
353 * `->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. 355 * `->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.
354 * `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options. 356 * `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.
@@ -358,9 +360,12 @@ Before parsing, you can set the following options: @@ -358,9 +360,12 @@ Before parsing, you can set the following options:
358 * `->capture_default_str()`: Store the current value attached and display it in the help string. 360 * `->capture_default_str()`: Store the current value attached and display it in the help string.
359 * `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses. 361 * `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses.
360 * `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. 362 * `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
361 -* `->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.  
362 -* `->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). 363 +* `->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.
  364 +* `->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.
  365 +* `->run_callback_for_default()`: This will force the option callback to be executed or the variable set when the default_val is set.
363 * `->option_text(string)`: Sets the text between the option name and description. 366 * `->option_text(string)`: Sets the text between the option name and description.
  367 +* `->force_callback()`: Causes the option callback or value set to be triggered even if the option was not present in parsing.
  368 +* `->trigger_on_parse()`: if set, causes the callback and all associated validation checks for the option to be executed when the option value is parsed vs. at the end of all parsing. This could cause the callback to be executed multiple times.
364 369
365 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. 370 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.
366 371
@@ -536,9 +541,9 @@ Validators have a few functions to query the current values: @@ -536,9 +541,9 @@ Validators have a few functions to query the current values:
536 541
537 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: 542 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:
538 543
539 -* `results()`: Retrieves a vector of strings with all the results in the order they were given.  
540 -* `results(variable_to_bind_to)`: Gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.  
541 -* `Value=as<type>()`: Returns the result or default value directly as the specified type if possible, can be vector to return all results, and a non-vector to get the result according to the MultiOptionPolicy in place. 544 +* `->results()`: Retrieves a vector of strings with all the results in the order they were given.
  545 +* `->results(variable_to_bind_to)`: Gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
  546 +* `Value=opt->as<type>()`: Returns the result or default value directly as the specified type if possible, can be vector to return all results, and a non-vector to get the result according to the MultiOptionPolicy in place.
542 547
543 ### Subcommands 548 ### Subcommands
544 549
book/chapters/options.md
@@ -20,7 +20,7 @@ You can use any C++ int-like type, not just `int`. CLI11 understands the followi @@ -20,7 +20,7 @@ You can use any C++ int-like type, not just `int`. CLI11 understands the followi
20 20
21 | Type | CLI11 | 21 | Type | CLI11 |
22 |-------------|-------| 22 |-------------|-------|
23 -| number like | Integers, floats, bools, or any type that can be constructed from an integer or floating point number | 23 +| number like | Integers, floats, bools, or any type that can be constructed from an integer or floating point number. Accepts common numerical strings like `0xFF` as well as octal, and decimal |
24 | string-like | std\::string, or anything that can be constructed from or assigned a std\::string | 24 | string-like | std\::string, or anything that can be constructed from or assigned a std\::string |
25 | char | For a single char, single string values are accepted, otherwise longer strings are treated as integral values and a conversion is attempted | 25 | char | For a single char, single string values are accepted, otherwise longer strings are treated as integral values and a conversion is attempted |
26 | complex-number | std::complex or any type which has a real(), and imag() operations available, will allow 1 or 2 string definitions like "1+2j" or two arguments "1","2" | 26 | complex-number | std::complex or any type which has a real(), and imag() operations available, will allow 1 or 2 string definitions like "1+2j" or two arguments "1","2" |
@@ -129,8 +129,8 @@ When you call `add_option`, you get a pointer to the added option. You can use t @@ -129,8 +129,8 @@ When you call `add_option`, you get a pointer to the added option. You can use t
129 | `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. | 129 | `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. |
130 | `->type_size(N)` | specify that each block of values would consist of N elements | 130 | `->type_size(N)` | specify that each block of values would consist of N elements |
131 | `->type_size(Nmin,Nmax)` | specify that each block of values would consist of between Nmin and Nmax elements | 131 | `->type_size(Nmin,Nmax)` | specify that each block of values would consist of between Nmin and Nmax elements |
132 -| `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer. |  
133 -| `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer. | 132 +| `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer or a string with the name of the option. Can be removed with `->remove_needs(opt)` |
  133 +| `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer or a string with the name of the option. Can be removed with `->remove_excludes(opt)` |
134 | `->envname(name)` | Gets the value from the environment if present and not passed on the command line. | 134 | `->envname(name)` | Gets the value from the environment if present and not passed on the command line. |
135 | `->group(name)` | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. | 135 | `->group(name)` | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. |
136 | `->description(string)` | Set/change the description | 136 | `->description(string)` | Set/change the description |
@@ -149,8 +149,14 @@ When you call `add_option`, you get a pointer to the added option. You can use t @@ -149,8 +149,14 @@ When you call `add_option`, you get a pointer to the added option. You can use t
149 | `->transform(Validator)` | Run a transforming validator on each value passed. See [Validators](./validators.md) for more info | 149 | `->transform(Validator)` | Run a transforming validator on each value passed. See [Validators](./validators.md) for more info |
150 | `->each(void(std::string))` | Run a function on each parsed value, *in order*. | 150 | `->each(void(std::string))` | Run a function on each parsed value, *in order*. |
151 | `->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 | 151 | `->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 |
152 -| `->default_function(string())` | Advanced: Change the function that `capture_default_str()` uses. | 152 +| `->default_function(std::string())` | Advanced: Change the function that `capture_default_str()` uses. |
153 | `->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). | 153 | `->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). |
  154 +| `->capture_default_str()` | Store the current value attached and display it in the help string. |
  155 +| `->always_capture_default()` | Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. |
  156 +| `->run_callback_for_default()` | Force the option callback to be executed or the variable set when the `default_val` is used. |
  157 +| `->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 |
  158 +|`->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|
  159 +| `->option_text(string)` | Sets the text between the option name and description. |
154 160
155 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. 161 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.
156 162
include/CLI/App.hpp
@@ -2082,7 +2082,7 @@ class App { @@ -2082,7 +2082,7 @@ class App {
2082 } 2082 }
2083 2083
2084 for(const Option_p &opt : options_) { 2084 for(const Option_p &opt : options_) {
2085 - if(opt->count() > 0 && !opt->get_callback_run()) { 2085 + if((*opt) && !opt->get_callback_run()) {
2086 opt->run_callback(); 2086 opt->run_callback();
2087 } 2087 }
2088 } 2088 }
@@ -2754,6 +2754,9 @@ class App { @@ -2754,6 +2754,9 @@ class App {
2754 op->add_result(std::string{}); 2754 op->add_result(std::string{});
2755 } 2755 }
2756 } 2756 }
  2757 + if(op->get_trigger_on_parse() && op->current_option_state_ == Option::option_state::callback_run) {
  2758 + op->clear();
  2759 + }
2757 int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min()); 2760 int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min());
2758 int max_num = op->get_items_expected_max(); 2761 int max_num = op->get_items_expected_max();
2759 // check container like options to limit the argument size to a single type if the allow_extra_flags argument is 2762 // check container like options to limit the argument size to a single type if the allow_extra_flags argument is
@@ -2826,7 +2829,9 @@ class App { @@ -2826,7 +2829,9 @@ class App {
2826 if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) { 2829 if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) {
2827 op->add_result(std::string{}); 2830 op->add_result(std::string{});
2828 } 2831 }
2829 - 2832 + if(op->get_trigger_on_parse()) {
  2833 + op->run_callback();
  2834 + }
2830 if(!rest.empty()) { 2835 if(!rest.empty()) {
2831 rest = "-" + rest; 2836 rest = "-" + rest;
2832 args.push_back(rest); 2837 args.push_back(rest);
include/CLI/Option.hpp
@@ -340,6 +340,10 @@ class Option : public OptionBase&lt;Option&gt; { @@ -340,6 +340,10 @@ class Option : public OptionBase&lt;Option&gt; {
340 bool run_callback_for_default_{false}; 340 bool run_callback_for_default_{false};
341 /// flag indicating a separator needs to be injected after each argument call 341 /// flag indicating a separator needs to be injected after each argument call
342 bool inject_separator_{false}; 342 bool inject_separator_{false};
  343 + /// flag indicating that the option should trigger the validation and callback chain on each result when loaded
  344 + bool trigger_on_result_{false};
  345 + /// flag indicating that the option should force the callback regardless if any results present
  346 + bool force_callback_{false};
343 ///@} 347 ///@}
344 348
345 /// Making an option by hand is not defined, it must be made by the App class 349 /// Making an option by hand is not defined, it must be made by the App class
@@ -361,8 +365,8 @@ class Option : public OptionBase&lt;Option&gt; { @@ -361,8 +365,8 @@ class Option : public OptionBase&lt;Option&gt; {
361 /// True if the option was not passed 365 /// True if the option was not passed
362 bool empty() const { return results_.empty(); } 366 bool empty() const { return results_.empty(); }
363 367
364 - /// This class is true if option is passed.  
365 - explicit operator bool() const { return !empty(); } 368 + /// This bool operator returns true if any arguments were passed or the option callback is forced
  369 + explicit operator bool() const { return !empty() || force_callback_; }
366 370
367 /// Clear the parsed results (mostly for testing) 371 /// Clear the parsed results (mostly for testing)
368 void clear() { 372 void clear() {
@@ -423,6 +427,21 @@ class Option : public OptionBase&lt;Option&gt; { @@ -423,6 +427,21 @@ class Option : public OptionBase&lt;Option&gt; {
423 } 427 }
424 /// Get the current value of allow extra args 428 /// Get the current value of allow extra args
425 bool get_allow_extra_args() const { return allow_extra_args_; } 429 bool get_allow_extra_args() const { return allow_extra_args_; }
  430 + /// Set the value of trigger_on_parse which specifies that the option callback should be triggered on every parse
  431 + Option *trigger_on_parse(bool value = true) {
  432 + trigger_on_result_ = value;
  433 + return this;
  434 + }
  435 + /// The status of trigger on parse
  436 + bool get_trigger_on_parse() const { return trigger_on_result_; }
  437 +
  438 + /// Set the value of force_callback
  439 + Option *force_callback(bool value = true) {
  440 + force_callback_ = value;
  441 + return this;
  442 + }
  443 + /// The status of force_callback
  444 + bool get_force_callback() const { return force_callback_; }
426 445
427 /// Set the value of run_callback_for_default which controls whether the callback function should be called to set 446 /// Set the value of run_callback_for_default which controls whether the callback function should be called to set
428 /// the default This is controlled automatically but could be manipulated by the user. 447 /// the default This is controlled automatically but could be manipulated by the user.
@@ -669,7 +688,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -669,7 +688,7 @@ class Option : public OptionBase&lt;Option&gt; {
669 /// The maximum number of arguments the option expects 688 /// The maximum number of arguments the option expects
670 int get_type_size_max() const { return type_size_max_; } 689 int get_type_size_max() const { return type_size_max_; }
671 690
672 - /// The number of arguments the option expects 691 + /// Return the inject_separator flag
673 int get_inject_separator() const { return inject_separator_; } 692 int get_inject_separator() const { return inject_separator_; }
674 693
675 /// The environment variable associated to this value 694 /// The environment variable associated to this value
@@ -821,7 +840,9 @@ class Option : public OptionBase&lt;Option&gt; { @@ -821,7 +840,9 @@ class Option : public OptionBase&lt;Option&gt; {
821 840
822 /// Process the callback 841 /// Process the callback
823 void run_callback() { 842 void run_callback() {
824 - 843 + if(force_callback_ && results_.empty()) {
  844 + add_result(default_str_);
  845 + }
825 if(current_option_state_ == option_state::parsing) { 846 if(current_option_state_ == option_state::parsing) {
826 _validate_results(results_); 847 _validate_results(results_);
827 current_option_state_ = option_state::validated; 848 current_option_state_ = option_state::validated;
@@ -977,10 +998,10 @@ class Option : public OptionBase&lt;Option&gt; { @@ -977,10 +998,10 @@ class Option : public OptionBase&lt;Option&gt; {
977 998
978 /// Puts a result at the end 999 /// Puts a result at the end
979 Option *add_result(std::vector<std::string> s) { 1000 Option *add_result(std::vector<std::string> s) {
  1001 + current_option_state_ = option_state::parsing;
980 for(auto &str : s) { 1002 for(auto &str : s) {
981 _add_result(std::move(str), results_); 1003 _add_result(std::move(str), results_);
982 } 1004 }
983 - current_option_state_ = option_state::parsing;  
984 return this; 1005 return this;
985 } 1006 }
986 1007
@@ -1139,7 +1160,8 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1139,7 +1160,8 @@ class Option : public OptionBase&lt;Option&gt; {
1139 results_.clear(); 1160 results_.clear();
1140 try { 1161 try {
1141 add_result(val_str); 1162 add_result(val_str);
1142 - if(run_callback_for_default_) { 1163 + // if trigger_on_result_ is set the callback already ran
  1164 + if(run_callback_for_default_ && !trigger_on_result_) {
1143 run_callback(); // run callback sets the state we need to reset it again 1165 run_callback(); // run callback sets the state we need to reset it again
1144 current_option_state_ = option_state::parsing; 1166 current_option_state_ = option_state::parsing;
1145 } else { 1167 } else {
tests/AppTest.cpp
@@ -2279,3 +2279,32 @@ TEST_CASE_METHOD(TApp, &quot;CustomUserSepParse5&quot;, &quot;[app]&quot;) { @@ -2279,3 +2279,32 @@ TEST_CASE_METHOD(TApp, &quot;CustomUserSepParse5&quot;, &quot;[app]&quot;) {
2279 run(); 2279 run();
2280 CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar); 2280 CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar);
2281 } 2281 }
  2282 +
  2283 +// #218
  2284 +TEST_CASE_METHOD(TApp, "logFormSingleDash", "[app]") {
  2285 + bool verbose{false};
  2286 + bool veryverbose{false};
  2287 + bool veryveryverbose{false};
  2288 + app.name("testargs");
  2289 + app.allow_extras();
  2290 + args = {"-v", "-vv", "-vvv"};
  2291 + app.final_callback([&]() {
  2292 + auto rem = app.remaining();
  2293 + for(auto &arg : rem) {
  2294 + if(arg == "-v") {
  2295 + verbose = true;
  2296 + }
  2297 + if(arg == "-vv") {
  2298 + veryverbose = true;
  2299 + }
  2300 + if(arg == "-vvv") {
  2301 + veryveryverbose = true;
  2302 + }
  2303 + }
  2304 + });
  2305 + run();
  2306 + CHECK(app.remaining().size() == 3U);
  2307 + CHECK(verbose);
  2308 + CHECK(veryverbose);
  2309 + CHECK(veryveryverbose);
  2310 +}
tests/OptionTypeTest.cpp
@@ -331,8 +331,6 @@ TEST_CASE_METHOD(TApp, &quot;pair_check&quot;, &quot;[optiontype]&quot;) { @@ -331,8 +331,6 @@ TEST_CASE_METHOD(TApp, &quot;pair_check&quot;, &quot;[optiontype]&quot;) {
331 CHECK_THROWS_AS(run(), CLI::ValidationError); 331 CHECK_THROWS_AS(run(), CLI::ValidationError);
332 } 332 }
333 333
334 -// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present  
335 -  
336 TEST_CASE_METHOD(TApp, "pair_check_take_first", "[optiontype]") { 334 TEST_CASE_METHOD(TApp, "pair_check_take_first", "[optiontype]") {
337 std::string myfile{"pair_check_file2.txt"}; 335 std::string myfile{"pair_check_file2.txt"};
338 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file 336 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
@@ -926,3 +924,76 @@ TEST_CASE_METHOD(TApp, &quot;vectorDoubleArg&quot;, &quot;[optiontype]&quot;) { @@ -926,3 +924,76 @@ TEST_CASE_METHOD(TApp, &quot;vectorDoubleArg&quot;, &quot;[optiontype]&quot;) {
926 CHECK(2U == cv.size()); 924 CHECK(2U == cv.size());
927 CHECK(2U == extras.size()); 925 CHECK(2U == extras.size());
928 } 926 }
  927 +
  928 +TEST_CASE_METHOD(TApp, "OnParseCall", "[optiontype]") {
  929 +
  930 + int cnt{0};
  931 +
  932 + auto *opt = app.add_option("-c",
  933 + [&cnt](const CLI::results_t &) {
  934 + ++cnt;
  935 + return true;
  936 + })
  937 + ->expected(1, 20)
  938 + ->trigger_on_parse();
  939 + std::vector<std::string> extras;
  940 + app.add_option("args", extras);
  941 + args = {"-c", "1", "-c", "2", "-c", "3"};
  942 + CHECK(opt->get_trigger_on_parse());
  943 + run();
  944 + CHECK(3 == cnt);
  945 +}
  946 +
  947 +TEST_CASE_METHOD(TApp, "OnParseCallVector", "[optiontype]") {
  948 +
  949 + std::vector<std::string> vec;
  950 +
  951 + app.add_option("-c", vec)->trigger_on_parse();
  952 + args = {"-c", "1", "2", "3", "-c", "2", "-c", "3", "4", "5"};
  953 + run();
  954 + CHECK(vec.size() == 3U);
  955 +}
  956 +
  957 +TEST_CASE_METHOD(TApp, "force_callback", "[optiontype]") {
  958 +
  959 + int cnt{0};
  960 +
  961 + auto *opt = app.add_option("-c",
  962 + [&cnt](const CLI::results_t &) {
  963 + ++cnt;
  964 + return true;
  965 + })
  966 + ->expected(1, 20)
  967 + ->force_callback()
  968 + ->default_str("5");
  969 + std::vector<std::string> extras;
  970 + app.add_option("args", extras);
  971 + args = {};
  972 + CHECK(opt->get_force_callback());
  973 + run();
  974 + CHECK(1 == cnt);
  975 + cnt = 0;
  976 + args = {"-c", "10"};
  977 + run();
  978 + CHECK(1 == cnt);
  979 +}
  980 +
  981 +TEST_CASE_METHOD(TApp, "force_callback2", "[optiontype]") {
  982 +
  983 + int cnt{0};
  984 +
  985 + app.add_option("-c", cnt)->force_callback()->default_val(5);
  986 + args = {};
  987 + run();
  988 + CHECK(5 == cnt);
  989 +}
  990 +
  991 +TEST_CASE_METHOD(TApp, "force_callback3", "[optiontype]") {
  992 +
  993 + int cnt{10};
  994 +
  995 + app.add_option("-c", cnt)->force_callback();
  996 + args = {};
  997 + run();
  998 + CHECK(0 == cnt);
  999 +}