Skip to content

Deprecated retired options #358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 5, 2019
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ This results in the subcommand being moved from its parent into the option group
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.
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.

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.
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.

```cpp
CLI::TriggerOn(group1_pointer, triggered_group);
Expand All @@ -660,6 +660,19 @@ CLI::TriggerOff(group2_pointer, disabled_group);

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.

Additional helper functions `deprecate_option`🚧 and `retire_option`🚧 are available to deprecate or retire options
```cpp
CLI::deprecate_option(option *, replacement_name="");
CLI::deprecate_option(App,option_name,replacement_name="");
```
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.

```cpp
CLI::retire_option(App,option *);
CLI::retire_option(App,option_name);
```
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.

If an empty string is passed the option group name the entire group will be hidden in the help results. For example.

```cpp
Expand Down
2 changes: 1 addition & 1 deletion cmake/CodeCoverage.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
endif()

set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage"
set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline"
CACHE INTERNAL "")

set(CMAKE_CXX_FLAGS_COVERAGE
Expand Down
18 changes: 18 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,21 @@ add_cli_exe(nested nested.cpp)
add_cli_exe(subcom_help subcom_help.cpp)
add_test(NAME subcom_help_normal COMMAND subcom_help sub --help)
add_test(NAME subcom_help_reversed COMMAND subcom_help --help sub)

add_cli_exe(retired retired.cpp)
add_test(NAME retired_retired_test COMMAND retired --retired_option)
add_test(NAME retired_retired_test2 COMMAND retired --retired_option 567)
add_test(NAME retired_retired_test3 COMMAND retired --retired_option2 567 689 789)
add_test(NAME retired_deprecated COMMAND retired --deprecate 19 20)

set_property(TEST retired_retired_test PROPERTY PASS_REGULAR_EXPRESSION
"WARNING.*retired")

set_property(TEST retired_retired_test2 PROPERTY PASS_REGULAR_EXPRESSION
"WARNING.*retired")

set_property(TEST retired_retired_test3 PROPERTY PASS_REGULAR_EXPRESSION
"WARNING.*retired")

set_property(TEST retired_deprecated PROPERTY PASS_REGULAR_EXPRESSION
"deprecated.*not_deprecated")
38 changes: 38 additions & 0 deletions examples/retired.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "CLI/CLI.hpp"
#include <vector>

// This example shows the usage of the retired and deprecated option helper methods
int main(int argc, char **argv) {

CLI::App app("example for retired/deprecated options");
std::vector<int> x;
auto opt1 = app.add_option("--retired_option2", x);

std::pair<int, int> y;
auto opt2 = app.add_option("--deprecate", y);

app.add_option("--not_deprecated", x);

// specify that a non-existing option is retired
CLI::retire_option(app, "--retired_option");

// specify that an existing option is retired and non-functional: this will replace the option with another that
// behaves the same but does nothing
CLI::retire_option(app, opt1);

// deprecate an existing option and specify the recommended replacement
CLI::deprecate_option(opt2, "--not_deprecated");

CLI11_PARSE(app, argc, argv);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many other examples do a std::cout at the end, to show what is going on. Does that make sense here as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something that prints out if the '--not_deprecated` or no options were passed, just so there is always guaranteed to be some output?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, I like it!


if(!x.empty()) {
std::cout << "Retired option example: got --not_deprecated values:";
for(auto &xval : x) {
std::cout << xval << " ";
}
std::cout << '\n';
} else if(app.count_all() == 1) {
std::cout << "Retired option example: no arguments received\n";
}
return 0;
}
79 changes: 79 additions & 0 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3029,6 +3029,85 @@ inline void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable) {
});
}

/// Helper function to mark an option as deprecated
inline void deprecate_option(Option *opt, const std::string &replacement = "") {
Validator deprecate_warning{[opt, replacement](std::string &) {
std::cout << opt->get_name() << " is deprecated please use '" << replacement
<< "' instead\n";
return std::string();
},
"DEPRECATED"};
deprecate_warning.application_index(0);
opt->check(deprecate_warning);
if(!replacement.empty()) {
opt->description(opt->get_description() + " DEPRECATED: please use '" + replacement + "' instead");
}
}

/// Helper function to mark an option as deprecated
inline void deprecate_option(App *app, const std::string &option_name, const std::string &replacement = "") {
auto opt = app->get_option(option_name);
deprecate_option(opt, replacement);
}

/// Helper function to mark an option as deprecated
inline void deprecate_option(App &app, const std::string &option_name, const std::string &replacement = "") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My IDE complains that this function is never used. Is my IDE wrong? Manually, I also can't find usages and wonder how you get 100% coverage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I was using it one of the tests but I will double check that and make sure

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

auto opt = app.get_option(option_name);
deprecate_option(opt, replacement);
}

/// Helper function to mark an option as retired
inline void retire_option(App *app, Option *opt) {
App temp;
auto option_copy = temp.add_option(opt->get_name(false, true))
->type_size(opt->get_type_size_min(), opt->get_type_size_max())
->expected(opt->get_expected_min(), opt->get_expected_max())
->allow_extra_args(opt->get_allow_extra_args());

app->remove_option(opt);
auto opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect")
->type_name("RETIRED")
->default_str("RETIRED")
->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max())
->expected(option_copy->get_expected_min(), option_copy->get_expected_max())
->allow_extra_args(option_copy->get_allow_extra_args());

Validator retired_warning{[opt2](std::string &) {
std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
return std::string();
},
""};
retired_warning.application_index(0);
opt2->check(retired_warning);
}

/// Helper function to mark an option as retired
inline void retire_option(App &app, Option *opt) { retire_option(&app, opt); }

/// Helper function to mark an option as retired
inline void retire_option(App *app, const std::string &option_name) {

auto opt = app->get_option_no_throw(option_name);
if(opt != nullptr) {
retire_option(app, opt);
return;
}
auto opt2 = app->add_option(option_name, "option has been retired and has no effect")
->type_name("RETIRED")
->expected(0, 1)
->default_str("RETIRED");
Validator retired_warning{[opt2](std::string &) {
std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
return std::string();
},
""};
retired_warning.application_index(0);
opt2->check(retired_warning);
}

/// Helper function to mark an option as retired
inline void retire_option(App &app, const std::string &option_name) { retire_option(&app, option_name); }

namespace FailureMessage {

/// Printout a clean, simple message on error (the default in CLI11 1.5+)
Expand Down
2 changes: 1 addition & 1 deletion include/CLI/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ class Option : public OptionBase<Option> {

/// Set the type function to run when displayed on this option
Option *type_name_fn(std::function<std::string()> typefun) {
type_name_ = typefun;
type_name_ = std::move(typefun);
return this;
}

Expand Down
109 changes: 109 additions & 0 deletions tests/HelpTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,115 @@ TEST(THelp, Hidden) {
EXPECT_THAT(help, Not(HasSubstr("another")));
}

TEST(THelp, deprecatedOptions) {
CLI::App app{"My prog"};

std::string x;
auto soption = app.add_option("--something", x, "My option here");
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);

CLI::deprecate_option(soption, "something_else");

std::string help = app.help();

EXPECT_THAT(help, HasSubstr("DEPRECATED"));
EXPECT_THAT(help, HasSubstr("something"));
EXPECT_NO_THROW(app.parse("--something deprecated"));
}

TEST(THelp, deprecatedOptions2) {
CLI::App app{"My prog"};

std::string x;
app.add_option("--something", x, "My option here");
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);

CLI::deprecate_option(&app, "--something");

std::string help = app.help();

EXPECT_THAT(help, HasSubstr("DEPRECATED"));
EXPECT_THAT(help, HasSubstr("something"));
EXPECT_NO_THROW(app.parse("--something deprecated"));
}

TEST(THelp, deprecatedOptions3) {
CLI::App app{"My prog"};

std::string x;
app.add_option("--something", x, "Some Description");
app.add_option("--something_else", x, "Some other description");
std::string y;
app.add_option("--another", y);

CLI::deprecate_option(app, "--something", "--something_else");

std::string help = app.help();

EXPECT_THAT(help, HasSubstr("DEPRECATED"));
EXPECT_THAT(help, HasSubstr("'--something_else' instead"));
EXPECT_NO_THROW(app.parse("--something deprecated"));
}

TEST(THelp, retiredOptions) {
CLI::App app{"My prog"};

std::string x;
auto opt1 = app.add_option("--something", x, "My option here");
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);

CLI::retire_option(app, opt1);

std::string help = app.help();

EXPECT_THAT(help, HasSubstr("RETIRED"));
EXPECT_THAT(help, HasSubstr("something"));

EXPECT_NO_THROW(app.parse("--something old"));
}

TEST(THelp, retiredOptions2) {
CLI::App app{"My prog"};

std::string x;
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);

CLI::retire_option(&app, "--something");

std::string help = app.help();

EXPECT_THAT(help, HasSubstr("RETIRED"));
EXPECT_THAT(help, HasSubstr("something"));
EXPECT_NO_THROW(app.parse("--something old"));
}

TEST(THelp, retiredOptions3) {
CLI::App app{"My prog"};

std::string x;
app.add_option("--something", x, "My option here");
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);

CLI::retire_option(app, "--something");

std::string help = app.help();

EXPECT_THAT(help, HasSubstr("RETIRED"));
EXPECT_THAT(help, HasSubstr("something"));

EXPECT_NO_THROW(app.parse("--something old"));
}

TEST(THelp, HiddenGroup) {
CLI::App app{"My prog"};
// empty option group name should be hidden
Expand Down