diff --git a/README.md b/README.md index cfa48b31..04217873 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 0994c541..063d0254 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -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_) @@ -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_(); @@ -1977,7 +1986,6 @@ class App { opt->run_callback(); } } - for(App_p &sub : subcommands_) { if(!sub->immediate_callback_) { sub->_process_callbacks(); diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp index 4db7f2cf..699d0be4 100644 --- a/tests/SubcommandTest.cpp +++ b/tests/SubcommandTest.cpp @@ -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");