Commit d0a2aa7908aee8d55babab21a22684879f97f97d

Authored by dryleev
Committed by GitHub
1 parent 75f2d774

fix: remove duplicated call to subcommand's final_callback (#584)

* Fix excessive call to subcommand's final_callback

When parse_complete_callback_ is set there is an extra call to
run_callback() inside the App::_parse(std::vector<std::string>&) method.
This extra call also excessively calls a final_callback_ (when it is
also set) for the command and (since it is recursive) for it's
subcommands.

This commit adds extra boolean parameter for the run_callback() method
allowing to explicitly suppress calling to final_callback_. The value of
this parameter is also propagated to recursive calls to run_callback().

Fixes #572

* fix: main app should run final_callback, add tests

Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com>
include/CLI/App.hpp
... ... @@ -113,6 +113,7 @@ class App {
113 113  
114 114 /// This is a function that runs when parsing has finished.
115 115 std::function<void()> parse_complete_callback_{};
  116 +
116 117 /// This is a function that runs when all processing has completed
117 118 std::function<void()> final_callback_{};
118 119  
... ... @@ -1934,8 +1935,9 @@ class App {
1934 1935 app->_configure();
1935 1936 }
1936 1937 }
  1938 +
1937 1939 /// Internal function to run (App) callback, bottom up
1938   - void run_callback(bool final_mode = false) {
  1940 + void run_callback(bool final_mode = false, bool suppress_final_callback = false) {
1939 1941 pre_callback();
1940 1942 // in the main app if immediate_callback_ is set it runs the main callback before the used subcommands
1941 1943 if(!final_mode && parse_complete_callback_) {
... ... @@ -1943,18 +1945,18 @@ class App {
1943 1945 }
1944 1946 // run the callbacks for the received subcommands
1945 1947 for(App *subc : get_subcommands()) {
1946   - subc->run_callback(true);
  1948 + subc->run_callback(true, suppress_final_callback);
1947 1949 }
1948 1950 // now run callbacks for option_groups
1949 1951 for(auto &subc : subcommands_) {
1950 1952 if(subc->name_.empty() && subc->count_all() > 0) {
1951   - subc->run_callback(true);
  1953 + subc->run_callback(true, suppress_final_callback);
1952 1954 }
1953 1955 }
1954 1956  
1955 1957 // finally run the main callback
1956   - if(final_callback_ && (parsed_ > 0)) {
1957   - if(!name_.empty() || count_all() > 0) {
  1958 + if(final_callback_ && (parsed_ > 0) && (!suppress_final_callback)) {
  1959 + if(!name_.empty() || count_all() > 0 || parent_ == nullptr) {
1958 1960 final_callback_();
1959 1961 }
1960 1962 }
... ... @@ -2337,7 +2339,7 @@ class App {
2337 2339 _process_callbacks();
2338 2340 _process_help_flags();
2339 2341 _process_requirements();
2340   - run_callback();
  2342 + run_callback(false, true);
2341 2343 }
2342 2344 }
2343 2345  
... ...
tests/SubcommandTest.cpp
... ... @@ -1874,3 +1874,63 @@ TEST_CASE_METHOD(ManySubcommands, &quot;defaultEnabledSubcommand&quot;, &quot;[subcom]&quot;) {
1874 1874 CHECK(sub2->get_enabled_by_default());
1875 1875 CHECK(!sub2->get_disabled());
1876 1876 }
  1877 +
  1878 +// #572
  1879 +TEST_CASE_METHOD(TApp, "MultiFinalCallbackCounts", "[subcom]") {
  1880 +
  1881 + int app_compl = 0;
  1882 + int sub_compl = 0;
  1883 + int subsub_compl = 0;
  1884 + int app_final = 0;
  1885 + int sub_final = 0;
  1886 + int subsub_final = 0;
  1887 +
  1888 + app.parse_complete_callback([&app_compl]() { app_compl++; });
  1889 + app.final_callback([&app_final]() { app_final++; });
  1890 +
  1891 + auto *sub = app.add_subcommand("sub");
  1892 +
  1893 + sub->parse_complete_callback([&sub_compl]() { sub_compl++; });
  1894 + sub->final_callback([&sub_final]() { sub_final++; });
  1895 +
  1896 + auto *subsub = sub->add_subcommand("subsub");
  1897 +
  1898 + subsub->parse_complete_callback([&subsub_compl]() { subsub_compl++; });
  1899 + subsub->final_callback([&subsub_final]() { subsub_final++; });
  1900 +
  1901 + SECTION("No specified subcommands") {
  1902 + args = {};
  1903 + run();
  1904 +
  1905 + CHECK(app_compl == 1);
  1906 + CHECK(app_final == 1);
  1907 + CHECK(sub_compl == 0);
  1908 + CHECK(sub_final == 0);
  1909 + CHECK(subsub_compl == 0);
  1910 + CHECK(subsub_final == 0);
  1911 + }
  1912 +
  1913 + SECTION("One layer of subcommands") {
  1914 + args = {"sub"};
  1915 + run();
  1916 +
  1917 + CHECK(app_compl == 1);
  1918 + CHECK(app_final == 1);
  1919 + CHECK(sub_compl == 1);
  1920 + CHECK(sub_final == 1);
  1921 + CHECK(subsub_compl == 0);
  1922 + CHECK(subsub_final == 0);
  1923 + }
  1924 +
  1925 + SECTION("Fully specified subcommands") {
  1926 + args = {"sub", "subsub"};
  1927 + run();
  1928 +
  1929 + CHECK(app_compl == 1);
  1930 + CHECK(app_final == 1);
  1931 + CHECK(sub_compl == 1);
  1932 + CHECK(sub_final == 1);
  1933 + CHECK(subsub_compl == 1);
  1934 + CHECK(subsub_final == 1);
  1935 + }
  1936 +}
... ...