Commit fc0f82a81f4a9ccdee54062a137c03e6e0217586

Authored by Philip Top
Committed by Henry Schreiner
1 parent ba7b29f9

Deprecated retired options (#358)

* add ability to specify deprecated or retired options.

* add retired example and tests

* update some formatting and a few more test executions

* fix formatting on retired.cpp

* add another test to fill coverage gap for existing options that are being retired.

* add example comments

* Update readme with the descriptions of the new helper functions

* fix space on readme

* Apply suggestions from code review

Co-Authored-By: Christoph Bachhuber <cbachhuber89@gmail.com>

* add some flags to the code coverage report and update some names and add more descriptions to deprecated options

* update formatting on App
README.md
@@ -651,7 +651,7 @@ This results in the subcommand being moved from its parent into the option group @@ -651,7 +651,7 @@ This results in the subcommand being moved from its parent into the option group
651 Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first. So care must be taken to make sure of the order when using positional arguments and option groups. 651 Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first. So care must be taken to make sure of the order when using positional arguments and option groups.
652 Option groups work well with `excludes` and `require_options` methods, as an application will treat an option group as a single option for the purpose of counting and requirements, and an option group will be considered used if any of the options or subcommands contained in it are used. Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well. Disabling an option group will turn off all options within the group. 652 Option groups work well with `excludes` and `require_options` methods, as an application will treat an option group as a single option for the purpose of counting and requirements, and an option group will be considered used if any of the options or subcommands contained in it are used. Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well. Disabling an option group will turn off all options within the group.
653 653
654 -The `CLI::TriggerOn` ๐Ÿ†• and `CLI::TriggerOff` ๐Ÿ†• methods are helper methods to allow the use of options/subcommands from one group to trigger another group on or off. 654 +The `CLI::TriggerOn` ๐Ÿ†• and `CLI::TriggerOff` ๐Ÿ†• methods are helper functions to allow the use of options/subcommands from one group to trigger another group on or off.
655 655
656 ```cpp 656 ```cpp
657 CLI::TriggerOn(group1_pointer, triggered_group); 657 CLI::TriggerOn(group1_pointer, triggered_group);
@@ -660,6 +660,19 @@ CLI::TriggerOff(group2_pointer, disabled_group); @@ -660,6 +660,19 @@ CLI::TriggerOff(group2_pointer, disabled_group);
660 660
661 These functions make use of `preparse_callback`, `enabled_by_default()` and `disabled_by_default`. The triggered group may be a vector of group pointers. These methods should only be used once per group and will override any previous use of the underlying functions. More complex arrangements can be accomplished using similar methodology with a custom preparse_callback function that does more. 661 These functions make use of `preparse_callback`, `enabled_by_default()` and `disabled_by_default`. The triggered group may be a vector of group pointers. These methods should only be used once per group and will override any previous use of the underlying functions. More complex arrangements can be accomplished using similar methodology with a custom preparse_callback function that does more.
662 662
  663 +Additional helper functions `deprecate_option`๐Ÿšง and `retire_option`๐Ÿšง are available to deprecate or retire options
  664 +```cpp
  665 +CLI::deprecate_option(option *, replacement_name="");
  666 +CLI::deprecate_option(App,option_name,replacement_name="");
  667 +```
  668 +will specify that the option is deprecated which will display a message in the help and a warning on first usage. Deprecated options function normally but will add a message in the help and display a warning on first use.
  669 +
  670 +```cpp
  671 +CLI::retire_option(App,option *);
  672 +CLI::retire_option(App,option_name);
  673 +```
  674 +will create an option that does nothing by default and will display a warning on first usage that the option is retired and has no effect. If the option exists it is replaces with a dummy option that takes the same arguments.
  675 +
663 If an empty string is passed the option group name the entire group will be hidden in the help results. For example. 676 If an empty string is passed the option group name the entire group will be hidden in the help results. For example.
664 677
665 ```cpp 678 ```cpp
cmake/CodeCoverage.cmake
@@ -87,7 +87,7 @@ elseif(NOT CMAKE_COMPILER_IS_GNUCXX) @@ -87,7 +87,7 @@ elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
87 message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") 87 message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
88 endif() 88 endif()
89 89
90 -set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 90 +set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline"
91 CACHE INTERNAL "") 91 CACHE INTERNAL "")
92 92
93 set(CMAKE_CXX_FLAGS_COVERAGE 93 set(CMAKE_CXX_FLAGS_COVERAGE
examples/CMakeLists.txt
@@ -231,3 +231,21 @@ add_cli_exe(nested nested.cpp) @@ -231,3 +231,21 @@ add_cli_exe(nested nested.cpp)
231 add_cli_exe(subcom_help subcom_help.cpp) 231 add_cli_exe(subcom_help subcom_help.cpp)
232 add_test(NAME subcom_help_normal COMMAND subcom_help sub --help) 232 add_test(NAME subcom_help_normal COMMAND subcom_help sub --help)
233 add_test(NAME subcom_help_reversed COMMAND subcom_help --help sub) 233 add_test(NAME subcom_help_reversed COMMAND subcom_help --help sub)
  234 +
  235 +add_cli_exe(retired retired.cpp)
  236 +add_test(NAME retired_retired_test COMMAND retired --retired_option)
  237 +add_test(NAME retired_retired_test2 COMMAND retired --retired_option 567)
  238 +add_test(NAME retired_retired_test3 COMMAND retired --retired_option2 567 689 789)
  239 +add_test(NAME retired_deprecated COMMAND retired --deprecate 19 20)
  240 +
  241 +set_property(TEST retired_retired_test PROPERTY PASS_REGULAR_EXPRESSION
  242 + "WARNING.*retired")
  243 +
  244 +set_property(TEST retired_retired_test2 PROPERTY PASS_REGULAR_EXPRESSION
  245 + "WARNING.*retired")
  246 +
  247 +set_property(TEST retired_retired_test3 PROPERTY PASS_REGULAR_EXPRESSION
  248 + "WARNING.*retired")
  249 +
  250 +set_property(TEST retired_deprecated PROPERTY PASS_REGULAR_EXPRESSION
  251 + "deprecated.*not_deprecated")
examples/retired.cpp 0 โ†’ 100644
  1 +#include "CLI/CLI.hpp"
  2 +#include <vector>
  3 +
  4 +// This example shows the usage of the retired and deprecated option helper methods
  5 +int main(int argc, char **argv) {
  6 +
  7 + CLI::App app("example for retired/deprecated options");
  8 + std::vector<int> x;
  9 + auto opt1 = app.add_option("--retired_option2", x);
  10 +
  11 + std::pair<int, int> y;
  12 + auto opt2 = app.add_option("--deprecate", y);
  13 +
  14 + app.add_option("--not_deprecated", x);
  15 +
  16 + // specify that a non-existing option is retired
  17 + CLI::retire_option(app, "--retired_option");
  18 +
  19 + // specify that an existing option is retired and non-functional: this will replace the option with another that
  20 + // behaves the same but does nothing
  21 + CLI::retire_option(app, opt1);
  22 +
  23 + // deprecate an existing option and specify the recommended replacement
  24 + CLI::deprecate_option(opt2, "--not_deprecated");
  25 +
  26 + CLI11_PARSE(app, argc, argv);
  27 +
  28 + if(!x.empty()) {
  29 + std::cout << "Retired option example: got --not_deprecated values:";
  30 + for(auto &xval : x) {
  31 + std::cout << xval << " ";
  32 + }
  33 + std::cout << '\n';
  34 + } else if(app.count_all() == 1) {
  35 + std::cout << "Retired option example: no arguments received\n";
  36 + }
  37 + return 0;
  38 +}
include/CLI/App.hpp
@@ -3029,6 +3029,85 @@ inline void TriggerOff(App *trigger_app, std::vector&lt;App *&gt; apps_to_enable) { @@ -3029,6 +3029,85 @@ inline void TriggerOff(App *trigger_app, std::vector&lt;App *&gt; apps_to_enable) {
3029 }); 3029 });
3030 } 3030 }
3031 3031
  3032 +/// Helper function to mark an option as deprecated
  3033 +inline void deprecate_option(Option *opt, const std::string &replacement = "") {
  3034 + Validator deprecate_warning{[opt, replacement](std::string &) {
  3035 + std::cout << opt->get_name() << " is deprecated please use '" << replacement
  3036 + << "' instead\n";
  3037 + return std::string();
  3038 + },
  3039 + "DEPRECATED"};
  3040 + deprecate_warning.application_index(0);
  3041 + opt->check(deprecate_warning);
  3042 + if(!replacement.empty()) {
  3043 + opt->description(opt->get_description() + " DEPRECATED: please use '" + replacement + "' instead");
  3044 + }
  3045 +}
  3046 +
  3047 +/// Helper function to mark an option as deprecated
  3048 +inline void deprecate_option(App *app, const std::string &option_name, const std::string &replacement = "") {
  3049 + auto opt = app->get_option(option_name);
  3050 + deprecate_option(opt, replacement);
  3051 +}
  3052 +
  3053 +/// Helper function to mark an option as deprecated
  3054 +inline void deprecate_option(App &app, const std::string &option_name, const std::string &replacement = "") {
  3055 + auto opt = app.get_option(option_name);
  3056 + deprecate_option(opt, replacement);
  3057 +}
  3058 +
  3059 +/// Helper function to mark an option as retired
  3060 +inline void retire_option(App *app, Option *opt) {
  3061 + App temp;
  3062 + auto option_copy = temp.add_option(opt->get_name(false, true))
  3063 + ->type_size(opt->get_type_size_min(), opt->get_type_size_max())
  3064 + ->expected(opt->get_expected_min(), opt->get_expected_max())
  3065 + ->allow_extra_args(opt->get_allow_extra_args());
  3066 +
  3067 + app->remove_option(opt);
  3068 + auto opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect")
  3069 + ->type_name("RETIRED")
  3070 + ->default_str("RETIRED")
  3071 + ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max())
  3072 + ->expected(option_copy->get_expected_min(), option_copy->get_expected_max())
  3073 + ->allow_extra_args(option_copy->get_allow_extra_args());
  3074 +
  3075 + Validator retired_warning{[opt2](std::string &) {
  3076 + std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
  3077 + return std::string();
  3078 + },
  3079 + ""};
  3080 + retired_warning.application_index(0);
  3081 + opt2->check(retired_warning);
  3082 +}
  3083 +
  3084 +/// Helper function to mark an option as retired
  3085 +inline void retire_option(App &app, Option *opt) { retire_option(&app, opt); }
  3086 +
  3087 +/// Helper function to mark an option as retired
  3088 +inline void retire_option(App *app, const std::string &option_name) {
  3089 +
  3090 + auto opt = app->get_option_no_throw(option_name);
  3091 + if(opt != nullptr) {
  3092 + retire_option(app, opt);
  3093 + return;
  3094 + }
  3095 + auto opt2 = app->add_option(option_name, "option has been retired and has no effect")
  3096 + ->type_name("RETIRED")
  3097 + ->expected(0, 1)
  3098 + ->default_str("RETIRED");
  3099 + Validator retired_warning{[opt2](std::string &) {
  3100 + std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
  3101 + return std::string();
  3102 + },
  3103 + ""};
  3104 + retired_warning.application_index(0);
  3105 + opt2->check(retired_warning);
  3106 +}
  3107 +
  3108 +/// Helper function to mark an option as retired
  3109 +inline void retire_option(App &app, const std::string &option_name) { retire_option(&app, option_name); }
  3110 +
3032 namespace FailureMessage { 3111 namespace FailureMessage {
3033 3112
3034 /// Printout a clean, simple message on error (the default in CLI11 1.5+) 3113 /// Printout a clean, simple message on error (the default in CLI11 1.5+)
include/CLI/Option.hpp
@@ -996,7 +996,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -996,7 +996,7 @@ class Option : public OptionBase&lt;Option&gt; {
996 996
997 /// Set the type function to run when displayed on this option 997 /// Set the type function to run when displayed on this option
998 Option *type_name_fn(std::function<std::string()> typefun) { 998 Option *type_name_fn(std::function<std::string()> typefun) {
999 - type_name_ = typefun; 999 + type_name_ = std::move(typefun);
1000 return this; 1000 return this;
1001 } 1001 }
1002 1002
tests/HelpTest.cpp
@@ -97,6 +97,115 @@ TEST(THelp, Hidden) { @@ -97,6 +97,115 @@ TEST(THelp, Hidden) {
97 EXPECT_THAT(help, Not(HasSubstr("another"))); 97 EXPECT_THAT(help, Not(HasSubstr("another")));
98 } 98 }
99 99
  100 +TEST(THelp, deprecatedOptions) {
  101 + CLI::App app{"My prog"};
  102 +
  103 + std::string x;
  104 + auto soption = app.add_option("--something", x, "My option here");
  105 + app.add_option("--something_else", x, "My option here");
  106 + std::string y;
  107 + app.add_option("--another", y);
  108 +
  109 + CLI::deprecate_option(soption, "something_else");
  110 +
  111 + std::string help = app.help();
  112 +
  113 + EXPECT_THAT(help, HasSubstr("DEPRECATED"));
  114 + EXPECT_THAT(help, HasSubstr("something"));
  115 + EXPECT_NO_THROW(app.parse("--something deprecated"));
  116 +}
  117 +
  118 +TEST(THelp, deprecatedOptions2) {
  119 + CLI::App app{"My prog"};
  120 +
  121 + std::string x;
  122 + app.add_option("--something", x, "My option here");
  123 + app.add_option("--something_else", x, "My option here");
  124 + std::string y;
  125 + app.add_option("--another", y);
  126 +
  127 + CLI::deprecate_option(&app, "--something");
  128 +
  129 + std::string help = app.help();
  130 +
  131 + EXPECT_THAT(help, HasSubstr("DEPRECATED"));
  132 + EXPECT_THAT(help, HasSubstr("something"));
  133 + EXPECT_NO_THROW(app.parse("--something deprecated"));
  134 +}
  135 +
  136 +TEST(THelp, deprecatedOptions3) {
  137 + CLI::App app{"My prog"};
  138 +
  139 + std::string x;
  140 + app.add_option("--something", x, "Some Description");
  141 + app.add_option("--something_else", x, "Some other description");
  142 + std::string y;
  143 + app.add_option("--another", y);
  144 +
  145 + CLI::deprecate_option(app, "--something", "--something_else");
  146 +
  147 + std::string help = app.help();
  148 +
  149 + EXPECT_THAT(help, HasSubstr("DEPRECATED"));
  150 + EXPECT_THAT(help, HasSubstr("'--something_else' instead"));
  151 + EXPECT_NO_THROW(app.parse("--something deprecated"));
  152 +}
  153 +
  154 +TEST(THelp, retiredOptions) {
  155 + CLI::App app{"My prog"};
  156 +
  157 + std::string x;
  158 + auto opt1 = app.add_option("--something", x, "My option here");
  159 + app.add_option("--something_else", x, "My option here");
  160 + std::string y;
  161 + app.add_option("--another", y);
  162 +
  163 + CLI::retire_option(app, opt1);
  164 +
  165 + std::string help = app.help();
  166 +
  167 + EXPECT_THAT(help, HasSubstr("RETIRED"));
  168 + EXPECT_THAT(help, HasSubstr("something"));
  169 +
  170 + EXPECT_NO_THROW(app.parse("--something old"));
  171 +}
  172 +
  173 +TEST(THelp, retiredOptions2) {
  174 + CLI::App app{"My prog"};
  175 +
  176 + std::string x;
  177 + app.add_option("--something_else", x, "My option here");
  178 + std::string y;
  179 + app.add_option("--another", y);
  180 +
  181 + CLI::retire_option(&app, "--something");
  182 +
  183 + std::string help = app.help();
  184 +
  185 + EXPECT_THAT(help, HasSubstr("RETIRED"));
  186 + EXPECT_THAT(help, HasSubstr("something"));
  187 + EXPECT_NO_THROW(app.parse("--something old"));
  188 +}
  189 +
  190 +TEST(THelp, retiredOptions3) {
  191 + CLI::App app{"My prog"};
  192 +
  193 + std::string x;
  194 + app.add_option("--something", x, "My option here");
  195 + app.add_option("--something_else", x, "My option here");
  196 + std::string y;
  197 + app.add_option("--another", y);
  198 +
  199 + CLI::retire_option(app, "--something");
  200 +
  201 + std::string help = app.help();
  202 +
  203 + EXPECT_THAT(help, HasSubstr("RETIRED"));
  204 + EXPECT_THAT(help, HasSubstr("something"));
  205 +
  206 + EXPECT_NO_THROW(app.parse("--something old"));
  207 +}
  208 +
100 TEST(THelp, HiddenGroup) { 209 TEST(THelp, HiddenGroup) {
101 CLI::App app{"My prog"}; 210 CLI::App app{"My prog"};
102 // empty option group name should be hidden 211 // empty option group name should be hidden