Commit 68c8b1b7899c308f8c63df1e4c825a09b84dee34

Authored by Philip Top
Committed by Henry Schreiner
1 parent e6aca64d

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 <HenrySchreinerIII@gmail.com>
README.md
@@ -520,7 +520,7 @@ There are several options that are supported on the main app and subcommands and @@ -520,7 +520,7 @@ There are several options that are supported on the main app and subcommands and
520 - `.count_all()`: ๐Ÿ†• Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands. 520 - `.count_all()`: ๐Ÿ†• Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands.
521 - `.name(name)`: Add or change the name. 521 - `.name(name)`: Add or change the name.
522 - `.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. 522 - `.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.
524 - `.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. 524 - `.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.
525 - `.allow_extras()`: Do not throw an error if extra arguments are left over. 525 - `.allow_extras()`: Do not throw an error if extra arguments are left over.
526 - `.positionals_at_end()`: ๐Ÿ†• Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered. 526 - `.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 @@ -537,7 +537,7 @@ There are several options that are supported on the main app and subcommands and
537 537
538 #### Callbacks 538 #### Callbacks
539 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. 539 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.
541 541
542 For example say an application was set up like 542 For example say an application was set up like
543 543
include/CLI/App.hpp
@@ -1852,6 +1852,12 @@ class App { @@ -1852,6 +1852,12 @@ class App {
1852 /// Internal function to run (App) callback, bottom up 1852 /// Internal function to run (App) callback, bottom up
1853 void run_callback() { 1853 void run_callback() {
1854 pre_callback(); 1854 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 + }
1855 // run the callbacks for the received subcommands 1861 // run the callbacks for the received subcommands
1856 for(App *subc : get_subcommands()) { 1862 for(App *subc : get_subcommands()) {
1857 if(!subc->immediate_callback_) 1863 if(!subc->immediate_callback_)
@@ -1863,7 +1869,10 @@ class App { @@ -1863,7 +1869,10 @@ class App {
1863 subc->run_callback(); 1869 subc->run_callback();
1864 } 1870 }
1865 } 1871 }
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
1867 if(callback_ && (parsed_ > 0)) { 1876 if(callback_ && (parsed_ > 0)) {
1868 if(!name_.empty() || count_all() > 0) { 1877 if(!name_.empty() || count_all() > 0) {
1869 callback_(); 1878 callback_();
@@ -1977,7 +1986,6 @@ class App { @@ -1977,7 +1986,6 @@ class App {
1977 opt->run_callback(); 1986 opt->run_callback();
1978 } 1987 }
1979 } 1988 }
1980 -  
1981 for(App_p &sub : subcommands_) { 1989 for(App_p &sub : subcommands_) {
1982 if(!sub->immediate_callback_) { 1990 if(!sub->immediate_callback_) {
1983 sub->_process_callbacks(); 1991 sub->_process_callbacks();
tests/SubcommandTest.cpp
@@ -519,6 +519,34 @@ TEST_F(TApp, CallbackOrderingImmediate) { @@ -519,6 +519,34 @@ TEST_F(TApp, CallbackOrderingImmediate) {
519 EXPECT_EQ(2, sub_val); 519 EXPECT_EQ(2, sub_val);
520 } 520 }
521 521
  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 +
522 TEST_F(TApp, RequiredSubCom) { 550 TEST_F(TApp, RequiredSubCom) {
523 app.add_subcommand("sub1"); 551 app.add_subcommand("sub1");
524 app.add_subcommand("sub2"); 552 app.add_subcommand("sub2");