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 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 521 - `.name(name)`: Add or change the name.
522 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 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 525 - `.allow_extras()`: Do not throw an error if extra arguments are left over.
526 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 537  
538 538 #### Callbacks
539 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 542 For example say an application was set up like
543 543  
... ...
include/CLI/App.hpp
... ... @@ -1852,6 +1852,12 @@ class App {
1852 1852 /// Internal function to run (App) callback, bottom up
1853 1853 void run_callback() {
1854 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 1861 // run the callbacks for the received subcommands
1856 1862 for(App *subc : get_subcommands()) {
1857 1863 if(!subc->immediate_callback_)
... ... @@ -1863,7 +1869,10 @@ class App {
1863 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 1876 if(callback_ && (parsed_ > 0)) {
1868 1877 if(!name_.empty() || count_all() > 0) {
1869 1878 callback_();
... ... @@ -1977,7 +1986,6 @@ class App {
1977 1986 opt->run_callback();
1978 1987 }
1979 1988 }
1980   -
1981 1989 for(App_p &sub : subcommands_) {
1982 1990 if(!sub->immediate_callback_) {
1983 1991 sub->_process_callbacks();
... ...
tests/SubcommandTest.cpp
... ... @@ -519,6 +519,34 @@ TEST_F(TApp, CallbackOrderingImmediate) {
519 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 550 TEST_F(TApp, RequiredSubCom) {
523 551 app.add_subcommand("sub1");
524 552 app.add_subcommand("sub2");
... ...