Skip to content

Commit

Permalink
Allow immediate_callback on the main app (#292)
Browse files Browse the repository at this point in the history
* Allow immediate_callback on the main app to run the main app callback prior to named subcommand callbacks, and reflect this change in the a new test and docs.

* Update README.md

Co-Authored-By: Henry Schreiner <HenrySchreinerIII@gmail.com>
  • Loading branch information
phlptp and henryiii committed Jul 25, 2019
1 parent e6aca64 commit 68c8b1b
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 4 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ There are several options that are supported on the main app and subcommands and
- `.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.
- `.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.
- `.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.
Expand All @@ -537,7 +537,7 @@ There are several options that are supported on the main app and subcommands and

#### 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()` has no effect on the main app, though it can be inherited. 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.
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

Expand Down
12 changes: 10 additions & 2 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,12 @@ class App {
/// Internal function to run (App) callback, bottom up
void run_callback() {
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_();
}
}
// run the callbacks for the received subcommands
for(App *subc : get_subcommands()) {
if(!subc->immediate_callback_)
Expand All @@ -1863,7 +1869,10 @@ class App {
subc->run_callback();
}
}
// finally run the main callback
if(immediate_callback_ && parent_ == nullptr) {
return;
}
// finally run the main callback if not run already
if(callback_ && (parsed_ > 0)) {
if(!name_.empty() || count_all() > 0) {
callback_();
Expand Down Expand Up @@ -1977,7 +1986,6 @@ class App {
opt->run_callback();
}
}

for(App_p &sub : subcommands_) {
if(!sub->immediate_callback_) {
sub->_process_callbacks();
Expand Down
28 changes: 28 additions & 0 deletions tests/SubcommandTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,34 @@ TEST_F(TApp, CallbackOrderingImmediate) {
EXPECT_EQ(2, sub_val);
}

TEST_F(TApp, CallbackOrderingImmediateMain) {
app.fallthrough();
int val = 0, sub_val = 0;

auto sub = app.add_subcommand("sub");
sub->callback([&val, &sub_val]() {
sub_val = val;
val = 2;
});
app.callback([&val]() { val = 1; });
args = {"sub"};
run();
EXPECT_EQ(1, val);
EXPECT_EQ(0, sub_val);
// the main app callback should run before the subcommand callbacks
app.immediate_callback();
val = 0; // reset value
run();
EXPECT_EQ(2, val);
EXPECT_EQ(1, sub_val);
// the subcommand callback now runs immediately after processing and before the main app callback again
sub->immediate_callback();
val = 0; // reset value
run();
EXPECT_EQ(1, val);
EXPECT_EQ(0, sub_val);
}

TEST_F(TApp, RequiredSubCom) {
app.add_subcommand("sub1");
app.add_subcommand("sub2");
Expand Down

0 comments on commit 68c8b1b

Please sign in to comment.