1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 20:23:55 +00:00

Third stage callback (#313)

* fix signed mismatch

add test from readme about callbacks

update callback documentation,

add a subbcommand immediate_callback test

add third callback and readme update

* a few more updates to the readme on `immediate_callback`

* Apply suggestions from code review

Co-Authored-By: Henry Schreiner <HenrySchreinerIII@gmail.com>
This commit is contained in:
Philip Top 2019-09-03 19:46:13 -07:00 committed by Henry Schreiner
parent a145458622
commit cf4933db91
4 changed files with 199 additions and 94 deletions

110
README.md
View File

@ -198,7 +198,7 @@ app.add_option(option_name,
help_string="")
app.add_option_function<type>(option_name,
function <void(const type &value)>, // 🆕 int, bool, float, enum, or string-like, or anything with a defined conversion from a string, or a vector of any of the previous objects.
function <void(const type &value)>, // 🆕 type can be any type supported by add_option
help_string="")
app.add_complex(... // Special case: support for complex numbers
@ -249,7 +249,7 @@ The `add_option_function<type>(...` function will typically require the template
double val
app.add_option<double,unsigned int>("-v",val);
```
which would first verify the input is convertible to an unsigned int before assigning it. Or using some variant type
which would first verify the input is convertible to an `unsigned int` before assigning it. Or using some variant type
```
using vtype=std::variant<int, double, std::string>;
vtype v1;
@ -318,7 +318,7 @@ Before parsing, you can set the following options:
- `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
- `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character
- `->disable_flag_override()`: 🆕 From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options.
- `->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.
- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, 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).
- `->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
@ -408,27 +408,27 @@ will produce a check for a number less than 0.
##### Transforming Validators
There are a few built in Validators that let you transform values if used with the `transform` function. If they also do some checks then they can be used `check` but some may do nothing in that case.
* 🆕 `CLI::Bounded(min,max)` will bound values between min and max and values outside of that range are limited to min or max, it will fail if the value cannot be converted and produce a `ValidationError`
* 🆕 The `IsMember` Validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this validator; the container just needs to be iterable and have a `::value_type`. The key type should be convertible from a string, You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set. The container passed in can be a set, vector, or a map like structure. If used in the `transform` method the output value will be the matching key as it could be modified by filters.
- 🆕 `CLI::Bounded(min,max)` will bound values between min and max and values outside of that range are limited to min or max, it will fail if the value cannot be converted and produce a `ValidationError`
- 🆕 The `IsMember` Validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this validator; the container just needs to be iterable and have a `::value_type`. The key type should be convertible from a string, You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set. The container passed in can be a set, vector, or a map like structure. If used in the `transform` method the output value will be the matching key as it could be modified by filters.
After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`, and `CLI::ignore_space`. These all work on strings but it is possible to define functions that work on other types.
Here are some examples
of `IsMember`:
* `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
* `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
* `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
* `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key. The value member of the map is not used in `IsMember`, so it can be any type.
* `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
* 🆕 The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map. `Transformer` does not do any checking so values not in the map are ignored. `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`. A Transformer placed using `check` will not do anything.
- `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
- `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
- `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key. The value member of the map is not used in `IsMember`, so it can be any type.
- `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
- 🆕 The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map. `Transformer` does not do any checking so values not in the map are ignored. `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`. A Transformer placed using `check` will not do anything.
After specifying a map of options, you can also specify "filter" just like in `CLI::IsMember`.
Here are some examples (`Transformer` and `CheckedTransformer` are interchangeable in the examples)
of `Transformer`:
* `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values.
- `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values.
* `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`: most maplike containers work, the `::value_type` needs to produce a pair of some kind.
* `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value. `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs.
* `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs<T>` is an alias for `std::vector<std::pair<<std::string,T>>`
- `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`: most maplike containers work, the `::value_type` needs to produce a pair of some kind.
- `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value. `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs.
- `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs<T>` is an alias for `std::vector<std::pair<<std::string,T>>`
NOTES: If the container used in `IsMember`, `Transformer`, or `CheckedTransformer` has a `find` function like `std::unordered_map` or `std::map` then that function is used to do the searching. If it does not have a `find` function a linear search is performed. If there are filters present, the fast search is performed first, and if that fails a linear search with the filters on the key values is performed.
@ -436,8 +436,13 @@ NOTES: If the container used in `IsMember`, `Transformer`, or `CheckedTransform
Validators are copyable and have a few operations that can be performed on them to alter settings. Most of the built in Validators have a default description that is displayed in the help. This can be altered via `.description(validator_description)`.
The name of a Validator, which is useful for later reference from the `get_validator(name)` method of an `Option` can be set via `.name(validator_name)`
The operation function of a Validator can be set via
`.operation(std::function<std::string(std::string &>)`. The `.active()` function can activate or deactivate a Validator from the operation.
All the functions return a Validator reference allowing them to be chained. For example
`.operation(std::function<std::string(std::string &>)`. The `.active()` function can activate or deactivate a Validator from the operation. A validator can be set to apply only to a specific element of the output. For example in a pair option `std::pair<int, std::string>` the first element may need to be a positive integer while the second may need to be a valid file. The `.application_index(int)` 🚧function can specify this. It is zero based and negative indices apply to all values.
```cpp
opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
opt->check(CLI::Validator(CLI::ExistingFile).application_index(1));
```
All the validator operation functions return a Validator reference allowing them to be chained. For example
```cpp
opt->check(CLI::Range(10,20).description("range is limited to sensible values").active(false).name("range"));
@ -476,19 +481,27 @@ opt->get_validator(name);
This will retrieve a Validator with the given name or throw a `CLI::OptionNotFound` error. If no name is given or name is empty the first unnamed Validator will be returned or the first Validator if there is only one.
or 🚧
```cpp
opt->get_validator(index);
```
Which will return a validator in the index it is applied which isn't necessarily the order in which was defined. The pointer can be `nullptr` if an invalid index is given.
Validators have a few functions to query the current values
* `get_description()`: 🆕 Will return a description string
* `get_name()`: 🆕 Will return the Validator name
* `get_active()`: 🆕 Will return the current active state, true if the Validator is active.
* `get_modifying()`: 🆕 Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()` 🆕 method, though it is recommended to let `check` and `transform` option methods manipulate it if needed.
- `get_description()`: 🆕 Will return a description string
- `get_name()`: 🆕 Will return the Validator name
- `get_active()`: 🆕 Will return the current active state, true if the Validator is active.
- `get_application_index()`: 🚧 Will return the current application index.
- `get_modifying()`: 🆕 Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()` 🆕 method, though it is recommended to let `check` and `transform` option methods manipulate it if needed.
#### Getting results
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:
- `results()`: Retrieves a vector of strings with all the results in the order they were given.
- `results(variable_to_bind_to)`: 🆕 Gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
- `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.
- `results()`: Retrieves a vector of strings with all the results in the order they were given.
- `results(variable_to_bind_to)`: 🆕 Gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
- `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.
### Subcommands
@ -501,10 +514,10 @@ subcommand name from matching.
If an `App` (main or subcommand) has been parsed on the command line, `->parsed` will be true (or convert directly to bool).
All `App`s have a `get_subcommands()` method, which returns a list of pointers to the subcommands passed on the command line. A `got_subcommand(App_or_name)` method is also provided that will check to see if an `App` pointer or a string name was collected on the command line.
For many cases, however, using an app's callback may be easier. Every app executes a callback function after it parses; just use a lambda function (with capture to get parsed values) to `.callback`. If you throw `CLI::Success` or `CLI::RuntimeError(return_value)`, you can
even exit the program through the callback. The main `App` has a callback slot, as well, but it is generally not as useful.
You are allowed to throw `CLI::Success` in the callbacks.
Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved). 🆕 The same subcommand can be triggered multiple times but all positional arguments will take precedence over the second and future calls of the subcommand. `->count()` on the subcommand will return the number of times the subcommand was called. The subcommand callback will only be triggered once unless the `.immediate_callback()` 🆕 flag is set. In which case the callback executes on completion of the subcommand arguments but after the arguments for that subcommand have been parsed, and can be triggered multiple times.
For many cases, however, using an app's callback capabilities may be easier. Every app has a set of callbacks that can be executed at various stages of parsing; a `C++` lambda function (with capture to get parsed values) can be used as input to the callback definition function. If you throw `CLI::Success` or `CLI::RuntimeError(return_value)`, you can
even exit the program through the callback.
Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved). 🆕 The same subcommand can be triggered multiple times but all positional arguments will take precedence over the second and future calls of the subcommand. `->count()` on the subcommand will return the number of times the subcommand was called. The subcommand callback will only be triggered once unless the `.immediate_callback()` 🆕 flag is set or the callback is specified through the `parse_complete_callback()` function. The `final_callback()` is triggered only once. In which case the callback executes on completion of the subcommand arguments but after the arguments for that subcommand have been parsed, and can be triggered multiple times.
🆕 Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments.
Nameless subcommands function a similarly to groups in the main `App`. See [Option groups](#option-groups) to see how this might work. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed. Callbacks for nameless subcommands are only triggered if any options from the subcommand were parsed.
@ -547,9 +560,11 @@ There are several options that are supported on the main app and subcommands and
- `.count(option_name)`: Returns the number of times a particular option was called.
- `.count_all()`: 🆕 Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands.
- `.name(name)`: Add or change the name.
- `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point. See [Subcommand callbacks](#callbacks) for some additional details.
- `.immediate_callback()`: 🆕 Specify that the callback for a subcommand should run immediately on completion of a subcommand vs at the completion of all parsing if this option is not used. When used on the main app 🚧 it will execute the main app callback prior to the callbacks for a subcommand if they do not also have the `immediate_callback` flag set.
- `.pre_parse_callback(void(size_t) function)`: 🆕 Set a callback that executes after the first argument of an application is processed. See [Subcommand callbacks](#callbacks) for some additional details.
- `.callback(void() function)`: Set the callback for an app. 🚧 either sets the pre_parse_callback or the final_callback depending on the value of `immediate_callback`. See [Subcommand callbacks](#callbacks) for some additional details.
- `.parse_complete_callback(void() function)`: 🚧 Set the callback that runs at the completion of parsing. for subcommands this is executed at the completion of the single subcommand and can be executed multiple times. See [Subcommand callbacks](#callbacks) for some additional details.
- `.final_callback(void() function)`: 🚧 Set the callback that runs at the end of all processing. This is the last thing that is executed before returning. See [Subcommand callbacks](#callbacks) for some additional details.
- `.immediate_callback()`: 🆕 Specifies whether the callback for a subcommand should be run as a `parse_complete_callback`(true) or `final_callback`(false). When used on the main app 🚧 it will execute the main app callback prior to the callbacks for a subcommand if they do not also have the `immediate_callback` flag set. 🚧 It is preferable to use the `parse_complete_callback` or `final_callback` directly instead of the `callback` and `immediate_callback` if one wishes to control the ordering and timing of callback. Though `immediate_callback` can be used to swap them if that is needed.
- `.pre_parse_callback(void(size_t) function)`: 🆕 Set a callback that executes after the first argument of an application is processed. See [Subcommand callbacks](#callbacks) for some additional details.
- `.allow_extras()`: Do not throw an error if extra arguments are left over.
- `.positionals_at_end()`: 🆕 Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered.
- `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognized item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app.
@ -559,21 +574,21 @@ There are several options that are supported on the main app and subcommands and
- `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands.
- `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default).
- `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand.
- `[option_name]`: 🆕 retrieve a const pointer to an option given by `option_name` for Example `app["--flag1"]` will get a pointer to the option for the "--flag1" value, `app["--flag1"]->as<bool>()` will get the results of the command line for a flag. The operation will throw an exception if the option name is not valid.
- `[option_name]`: 🆕 retrieve a const pointer to an option given by `option_name` for Example `app["--flag1"]` will get a pointer to the option for the "--flag1" value, `app["--flag1"]->as<bool>()` will get the results of the command line for a flag. The operation will throw an exception if the option name is not valid.
> Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function, and any positional argument will match before repeated subcommand names.
#### Callbacks
A subcommand has two optional callbacks that are executed at different stages of processing. The `preparse_callback` 🆕 is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process. For the main app the first argument is considered the program name, for subcommands the first argument is the subcommand name. For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group.
The second callback is executed after parsing. The behavior depends on the status of the `immediate_callback` flag 🆕. If true, this runs immediately after the parsing of the subcommand. Or if the flag is false, once after parsing of all arguments. If the `immediate_callback` is set then the callback can be executed multiple times if the subcommand list given multiple times. If the main app or subcommand has a config file, no data from the config file will be reflected in `immediate_callback`. `immediate_callback()` on the main app 🚧 causes the main app callback to execute prior to subcommand callbacks, it is also inherited, option_group callbacks are still executed before the main app callback even if `immediate_callback` is set in the main app. For option_groups `immediate_callback` causes the callback to be run prior to other option groups and options in the main app, effectively giving the options in the group priority.
For example say an application was set up like
A subcommand has three optional callbacks that are executed at different stages of processing. The `preparse_callback` 🆕 is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process. For the main app the first argument is considered the program name, for subcommands the first argument is the subcommand name. For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group.
The second callback is executed after parsing. This is known as the `parse_complete_callback`. For subcommands this is executed immediately after parsing and can be executed multiple times if a subcommand is called multiple times. On the main app this callback is executed after all the `parse_complete_callback`s for the subcommands are executed but prior to any `final_callback` calls in the subcommand or option groups. If the main app or subcommand has a config file, no data from the config file will be reflected in `parse_complete_callback` on named subcommands 🚧. For option_groups the `parse_complete_callback` is executed prior to the `parse_complete_callback` on the main app but after the config_file is loaded(if specified). The 🚧 `final_callback` is executed after all processing is complete. After the `parse_complete_callback` is executed on the main app, the used subcommand `final_callback` are executed followed by the 'final callback' for option groups. The last thing to execute is the `final_callback` for the main_app.
For example say an application was set up like
```cpp
app.callback(ac);
sub1=app.add_subcommand("sub1")->callback(c1)->preparse_callback(pc1)->immediate_callback();
sub2=app.add_subcommand("sub2")->callback(c2)->preparse_callback(pc2);
app.parse_complete_callback(ac1);
app.final_callback(ac2);
auto sub1=app.add_subcommand("sub1")->parse_complete_callback(c1)->preparse_callback(pc1);
auto sub2=app.add_subcommand("sub2")->final_callback(c2)->preparse_callback(pc2);
app.preparse_callback( pa);
... A bunch of other options
@ -586,13 +601,14 @@ Then the command line is given as
program --opt1 opt1_val sub1 --sub1opt --sub1optb val sub2 --sub2opt sub1 --sub1opt2 sub2 --sub2opt2 val
```
* pa will be called prior to parsing any values with an argument of 13.
* pc1 will be called immediately after processing the sub1 command with a value of 10.
* c1 will be called when the `sub2` command is encountered.
* pc2 will be called with value of 6 after the sub2 command is encountered.
* c1 will be called again after the second sub2 command is encountered.
* c2 will be called once after processing all arguments.
* ac will be called after completing the parse and all lower level callbacks have been executed.
- pa will be called prior to parsing any values with an argument of 13.
- pc1 will be called immediately after processing the sub1 command with a value of 10.
- c1 will be called when the `sub2` command is encountered.
- pc2 will be called with value of 6 after the sub2 command is encountered.
- c1 will be called again after the second sub2 command is encountered.
- ac1 will be called after processing of all arguments
- c2 will be called once after processing all arguments.
- ac2 will be called last after completing all lower level callbacks have been executed.
A subcommand is considered terminated when one of the following conditions are met.
1. There are no more arguments to process
@ -600,7 +616,7 @@ A subcommand is considered terminated when one of the following conditions are m
3. The positional_mark(`--`) is encountered and there are no available positional slots in the subcommand.
4. The subcommand_terminator mark(`++`) is encountered
If the `immediate_callback` flag is set then all contained options are processed and the callback is triggered. If a subcommand with an `immediate_callback` flag is called again, then the contained options are reset, and can be triggered again.
Prior to executed a `parse_complete_callback` all contained options are processed before the callback is triggered. If a subcommand with a `parse_complete_callback` is called again, then the contained options are reset, and can be triggered again.

View File

@ -98,8 +98,10 @@ class App {
/// This is a function that runs prior to the start of parsing
std::function<void(size_t)> pre_parse_callback_;
/// This is a function that runs when complete. Great for subcommands. Can throw.
std::function<void()> callback_;
/// This is a function that runs when parsing has finished.
std::function<void()> parse_complete_callback_;
/// This is a function that runs when all processing has completed
std::function<void()> final_callback_;
///@}
/// @name Options
@ -275,14 +277,32 @@ class App {
/// virtual destructor
virtual ~App() = default;
/// Set a callback for the end of parsing.
/// Set a callback for execution when all parsing and processing has completed
///
/// Due to a bug in c++11,
/// it is not possible to overload on std::function (fixed in c++14
/// and backported to c++11 on newer compilers). Use capture by reference
/// to get a pointer to App if needed.
App *callback(std::function<void()> app_callback) {
callback_ = std::move(app_callback);
if(immediate_callback_) {
parse_complete_callback_ = std::move(app_callback);
} else {
final_callback_ = std::move(app_callback);
}
return this;
}
/// Set a callback for execution when all parsing and processing has completed
/// aliased as callback
App *final_callback(std::function<void()> app_callback) {
final_callback_ = std::move(app_callback);
return this;
}
/// Set a callback to execute when parsing has completed for the app
///
App *parse_complete_callback(std::function<void()> pc_callback) {
parse_complete_callback_ = std::move(pc_callback);
return this;
}
@ -334,6 +354,13 @@ class App {
/// Set the subcommand callback to be executed immediately on subcommand completion
App *immediate_callback(bool immediate = true) {
immediate_callback_ = immediate;
if(immediate_callback_) {
if(final_callback_ && !(parse_complete_callback_)) {
std::swap(final_callback_, parse_complete_callback_);
}
} else if(!(final_callback_) && parse_complete_callback_) {
std::swap(final_callback_, parse_complete_callback_);
}
return this;
}
@ -369,7 +396,8 @@ class App {
return this;
}
/// Allow windows style options, such as `/opt`. First matching short or long name used. Subcommands inherit value.
/// Allow windows style options, such as `/opt`. First matching short or long name used. Subcommands inherit
/// value.
App *allow_windows_style_options(bool value = true) {
allow_windows_style_options_ = value;
return this;
@ -484,8 +512,8 @@ class App {
return CLI::detail::checked_to_string<T, XC>(variable);
});
opt->type_name(detail::type_name<XC>());
// these must be actual variable 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.
// these must be actual variable 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.
auto Tcount = detail::type_count<T>::value;
auto XCcount = detail::type_count<XC>::value;
opt->type_size((std::max)(Tcount, XCcount));
@ -600,8 +628,8 @@ class App {
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.
/// 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_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy>
Option *add_flag(std::string flag_name,
@ -619,8 +647,8 @@ class App {
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
}
/// 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
/// 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
template <typename T,
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value &&
(!std::is_integral<T>::value || is_bool<T>::value) &&
@ -789,8 +817,8 @@ class App {
return opt;
}
/// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the set)
/// DEPRECATED
/// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the
/// set) DEPRECATED
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(...)) with a (shared) pointer instead")
Option *add_mutable_set_ignore_case(std::string option_name,
std::string &member, ///< The selected member of the set
@ -815,8 +843,8 @@ class App {
return opt;
}
/// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not destroy
/// the set) DEPRECATED
/// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not
/// destroy the set) DEPRECATED
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead")
Option *add_mutable_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
@ -841,8 +869,8 @@ class App {
return opt;
}
/// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy the
/// set) DEPRECATED
/// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy
/// the set) DEPRECATED
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead")
Option *add_mutable_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
@ -867,10 +895,10 @@ class App {
return opt;
}
/// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do not
/// destroy the set) DEPRECATED
CLI11_DEPRECATED(
"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead")
/// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do
/// not destroy the set) DEPRECATED
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a "
"(shared) pointer instead")
Option *add_mutable_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities
@ -896,8 +924,8 @@ class App {
/// Add set of options, string only, ignore underscore and case (default, set can be changed afterwards - do not
/// destroy the set) DEPRECATED
CLI11_DEPRECATED(
"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead")
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a "
"(shared) pointer instead")
Option *add_mutable_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities
@ -1805,32 +1833,27 @@ class App {
}
}
/// Internal function to run (App) callback, bottom up
void run_callback() {
void run_callback(bool final_mode = false) {
pre_callback();
// in the main app if immediate_callback_ is set it runs the main callback before the used subcommands
if(immediate_callback_ && parent_ == nullptr) {
if(callback_) {
callback_();
}
if(!final_mode && parse_complete_callback_) {
parse_complete_callback_();
}
// run the callbacks for the received subcommands
for(App *subc : get_subcommands()) {
if(!subc->immediate_callback_)
subc->run_callback();
subc->run_callback(true);
}
// now run callbacks for option_groups
for(auto &subc : subcommands_) {
if(!subc->immediate_callback_ && subc->name_.empty() && subc->count_all() > 0) {
subc->run_callback();
if(subc->name_.empty() && subc->count_all() > 0) {
subc->run_callback(true);
}
}
if(immediate_callback_ && parent_ == nullptr) {
return;
}
// finally run the main callback if not run already
if(callback_ && (parsed_ > 0)) {
// finally run the main callback
if(final_callback_ && (parsed_ > 0)) {
if(!name_.empty() || count_all() > 0) {
callback_();
final_callback_();
}
}
}
@ -1924,7 +1947,7 @@ class App {
}
for(App_p &sub : subcommands_) {
if(sub->get_name().empty() || !sub->immediate_callback_)
if(sub->get_name().empty() || !sub->parse_complete_callback_)
sub->_process_env();
}
}
@ -1934,7 +1957,7 @@ class App {
for(App_p &sub : subcommands_) {
// process the priority option_groups first
if(sub->get_name().empty() && sub->immediate_callback_) {
if(sub->get_name().empty() && sub->parse_complete_callback_) {
if(sub->count_all() > 0) {
sub->_process_callbacks();
sub->run_callback();
@ -1948,7 +1971,7 @@ class App {
}
}
for(App_p &sub : subcommands_) {
if(!sub->immediate_callback_) {
if(!sub->parse_complete_callback_) {
sub->_process_callbacks();
}
}
@ -2037,8 +2060,8 @@ class App {
// Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.
// run this loop to check how many unnamed subcommands were actually used since they are considered options from
// the perspective of an App
// run this loop to check how many unnamed subcommands were actually used since they are considered options
// from the perspective of an App
for(App_p &sub : subcommands_) {
if(sub->disabled_)
continue;
@ -2156,7 +2179,7 @@ class App {
// Convert missing (pairs) to extras (string only) ready for processing in another app
args = remaining_for_passthrough(false);
} else if(immediate_callback_) {
} else if(parse_complete_callback_) {
_process_env();
_process_callbacks();
_process_help_flags();
@ -2679,7 +2702,7 @@ class App {
throw OptionNotFound("could not locate the given App");
}
}
};
}; // namespace CLI
/// Extension of App to better manage groups of options
class Option_group : public App {

View File

@ -667,11 +667,11 @@ class Option : public OptionBase<Option> {
// Prefer long name
if(!lnames_.empty())
return std::string("--") + lnames_[0];
return std::string(2, '-') + lnames_[0];
// Or short name if no long name
if(!snames_.empty())
return std::string("-") + snames_[0];
return std::string(1, '-') + snames_[0];
// If positional is the only name, it's okay to use that
return pname_;

View File

@ -229,6 +229,49 @@ TEST_F(TApp, Callbacks) {
EXPECT_TRUE(val);
}
TEST_F(TApp, CallbackOrder) {
std::vector<std::string> cb;
app.parse_complete_callback([&cb]() { cb.push_back("ac1"); });
app.final_callback([&cb]() { cb.push_back("ac2"); });
auto sub1 = app.add_subcommand("sub1")
->parse_complete_callback([&cb]() { cb.push_back("c1"); })
->preparse_callback([&cb](size_t v1) { cb.push_back(std::string("pc1-") + std::to_string(v1)); });
auto sub2 = app.add_subcommand("sub2")
->final_callback([&cb]() { cb.push_back("c2"); })
->preparse_callback([&cb](size_t v1) { cb.push_back(std::string("pc2-") + std::to_string(v1)); });
app.preparse_callback([&cb](size_t v1) { cb.push_back(std::string("pa-") + std::to_string(v1)); });
app.add_option("--opt1");
sub1->add_flag("--sub1opt");
sub1->add_option("--sub1optb");
sub1->add_flag("--sub1opt2");
sub2->add_flag("--sub2opt");
sub2->add_option("--sub2opt2");
args = {"--opt1",
"opt1_val",
"sub1",
"--sub1opt",
"--sub1optb",
"val",
"sub2",
"--sub2opt",
"sub1",
"--sub1opt2",
"sub2",
"--sub2opt2",
"val"};
run();
EXPECT_EQ(cb.size(), 8u);
EXPECT_EQ(cb[0], "pa-13");
EXPECT_EQ(cb[1], "pc1-10");
EXPECT_EQ(cb[2], "c1");
EXPECT_EQ(cb[3], "pc2-6");
EXPECT_EQ(cb[4], "c1");
EXPECT_EQ(cb[5], "ac1");
EXPECT_EQ(cb[6], "c2");
EXPECT_EQ(cb[7], "ac2");
}
TEST_F(TApp, RuntimeErrorInCallback) {
auto sub1 = app.add_subcommand("sub1");
sub1->callback([]() { throw CLI::RuntimeError(); });
@ -547,6 +590,29 @@ TEST_F(TApp, CallbackOrderingImmediateMain) {
EXPECT_EQ(0, sub_val);
}
// Test based on issue #308
TEST_F(TApp, CallbackOrderingImmediateModeOrder) {
app.require_subcommand(1, 1);
std::vector<int> v;
app.callback([&v]() { v.push_back(1); })->immediate_callback(true);
auto sub = app.add_subcommand("hello")->callback([&v]() { v.push_back(2); })->immediate_callback(false);
args = {"hello"};
run();
// immediate_callback inherited
ASSERT_EQ(v.size(), 2u);
EXPECT_EQ(v[0], 1);
EXPECT_EQ(v[1], 2);
v.clear();
sub->immediate_callback(true);
run();
// immediate_callback is now triggered for the main first
ASSERT_EQ(v.size(), 2u);
EXPECT_EQ(v[0], 2);
EXPECT_EQ(v[1], 1);
}
TEST_F(TApp, RequiredSubCom) {
app.add_subcommand("sub1");
app.add_subcommand("sub2");