Skip to content

Commit 68c8b1b

Browse files
phlptphenryiii
andcommitted
Allow immediate_callback on the main app (#292)
* 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 <[email protected]>
1 parent e6aca64 commit 68c8b1b

File tree

3 files changed

+40
-4
lines changed

3 files changed

+40
-4
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ There are several options that are supported on the main app and subcommands and
520520
- `.count_all()`: 🆕 Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands.
521521
- `.name(name)`: Add or change the name.
522522
- `.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.
523-
- `.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.
523+
- `.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.
524524
- `.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.
525525
- `.allow_extras()`: Do not throw an error if extra arguments are left over.
526526
- `.positionals_at_end()`: 🆕 Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered.
@@ -537,7 +537,7 @@ There are several options that are supported on the main app and subcommands and
537537

538538
#### Callbacks
539539
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.
540-
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.
540+
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.
541541

542542
For example say an application was set up like
543543

include/CLI/App.hpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,6 +1852,12 @@ class App {
18521852
/// Internal function to run (App) callback, bottom up
18531853
void run_callback() {
18541854
pre_callback();
1855+
// in the main app if immediate_callback_ is set it runs the main callback before the used subcommands
1856+
if(immediate_callback_ && parent_ == nullptr) {
1857+
if(callback_) {
1858+
callback_();
1859+
}
1860+
}
18551861
// run the callbacks for the received subcommands
18561862
for(App *subc : get_subcommands()) {
18571863
if(!subc->immediate_callback_)
@@ -1863,7 +1869,10 @@ class App {
18631869
subc->run_callback();
18641870
}
18651871
}
1866-
// finally run the main callback
1872+
if(immediate_callback_ && parent_ == nullptr) {
1873+
return;
1874+
}
1875+
// finally run the main callback if not run already
18671876
if(callback_ && (parsed_ > 0)) {
18681877
if(!name_.empty() || count_all() > 0) {
18691878
callback_();
@@ -1977,7 +1986,6 @@ class App {
19771986
opt->run_callback();
19781987
}
19791988
}
1980-
19811989
for(App_p &sub : subcommands_) {
19821990
if(!sub->immediate_callback_) {
19831991
sub->_process_callbacks();

tests/SubcommandTest.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,34 @@ TEST_F(TApp, CallbackOrderingImmediate) {
519519
EXPECT_EQ(2, sub_val);
520520
}
521521

522+
TEST_F(TApp, CallbackOrderingImmediateMain) {
523+
app.fallthrough();
524+
int val = 0, sub_val = 0;
525+
526+
auto sub = app.add_subcommand("sub");
527+
sub->callback([&val, &sub_val]() {
528+
sub_val = val;
529+
val = 2;
530+
});
531+
app.callback([&val]() { val = 1; });
532+
args = {"sub"};
533+
run();
534+
EXPECT_EQ(1, val);
535+
EXPECT_EQ(0, sub_val);
536+
// the main app callback should run before the subcommand callbacks
537+
app.immediate_callback();
538+
val = 0; // reset value
539+
run();
540+
EXPECT_EQ(2, val);
541+
EXPECT_EQ(1, sub_val);
542+
// the subcommand callback now runs immediately after processing and before the main app callback again
543+
sub->immediate_callback();
544+
val = 0; // reset value
545+
run();
546+
EXPECT_EQ(1, val);
547+
EXPECT_EQ(0, sub_val);
548+
}
549+
522550
TEST_F(TApp, RequiredSubCom) {
523551
app.add_subcommand("sub1");
524552
app.add_subcommand("sub2");

0 commit comments

Comments
 (0)