Skip to content

Commit

Permalink
add a silent option to subcommands to prevent the use from showing up…
Browse files Browse the repository at this point in the history
… in the subcommands list if used.
  • Loading branch information
phlptp committed Oct 22, 2020
1 parent 97e5ebe commit b49a575
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ There are several options that are supported on the main app and subcommands and
- `.disable()`: Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
- `.disabled_by_default()`: Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others.
- `.enabled_by_default()`: Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others.
- `.silent()`: 🚧 Specify that the subcommand is silent meaning that if used it won't show up in the subcommand list. This allows the use of subcommands as modifiers
- `.validate_positionals()`: Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments.
- `.excludes(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error.
- `.needs(option_or_subcommand)`: 🆕 If given an option pointer or pointer to another subcommand, the subcommands will require the given option to have been given before this subcommand is validated which occurs prior to execution of any callback or after parsing is completed.
Expand Down
15 changes: 15 additions & 0 deletions book/chapters/subcommands.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,18 @@ Here, `--shared_flag` was set on the main app, and on the command line it "falls

This is a special mode that allows "prefix" commands, where the parsing completely stops when it gets to an unknown option. Further unknown options are ignored, even if they could match. Git is the traditional example for prefix commands; if you run git with an unknown subcommand, like "`git thing`", it then calls another command called "`git-thing`" with the remaining options intact.

### Silent subcommands

Subcommands can be modified by using the `silent` option. This will prevent the subcommand from showing up in the get_subcommands list. This can be used to make subcommands into modifiers For example a help subcommand might look like

```c++
auto sub1 = app.add_subcommand("help")->silent();
sub1->parse_complete_callback([]() { throw CLI::CallForHelp(); });
```
This would allow calling help such as:
```bash
./app help
./app help sub1
```
38 changes: 28 additions & 10 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,11 @@ class App {
App *parent_{nullptr};

/// Counts the number of times this command/subcommand was parsed
std::size_t parsed_{0};
std::uint32_t parsed_{0U};

/// indicator that the subcommand is silent and won't show up in subcommands list
/// This is potentially useful as a modifier subcommand
bool silent_{false};

/// Minimum required subcommands (not inheritable!)
std::size_t require_subcommand_min_{0};
Expand Down Expand Up @@ -396,6 +400,12 @@ class App {
return this;
}

/// silence the subcommand from showing up in the processed list
App *silent(bool silence = true) {
silent_ = silence;
return this;
}

/// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled
App *disabled_by_default(bool disable = true) {
if(disable) {
Expand Down Expand Up @@ -789,7 +799,8 @@ class App {
/// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one
/// if `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
template <typename T,
enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy>
enable_if_t<std::is_constructible<T, std::int64_t>::value && !is_bool<T>::value, detail::enabler> =
detail::dummy>
Option *add_flag(std::string flag_name,
T &flag_count, ///< A variable holding the count
std::string flag_description = "") {
Expand All @@ -810,7 +821,7 @@ class App {
/// that can be converted from a string
template <typename T,
enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value &&
(!std::is_integral<T>::value || is_bool<T>::value) &&
(!std::is_constructible<T, std::int64_t>::value || is_bool<T>::value) &&
!std::is_constructible<std::function<void(int)>, T>::value,
detail::enabler> = detail::dummy>
Option *add_flag(std::string flag_name,
Expand All @@ -824,9 +835,9 @@ class App {
}

/// Vector version to capture multiple flags.
template <
typename T,
enable_if_t<!std::is_assignable<std::function<void(std::int64_t)>, T>::value, detail::enabler> = detail::dummy>
template <typename T,
enable_if_t<!std::is_assignable<std::function<void(std::int64_t)> &, T>::value, detail::enabler> =
detail::dummy>
Option *add_flag(std::string flag_name,
std::vector<T> &flag_results, ///< A vector of values with the flag results
std::string flag_description = "") {
Expand Down Expand Up @@ -1766,6 +1777,9 @@ class App {
/// Get the status of disabled
bool get_disabled() const { return disabled_; }

/// Get the status of silence
bool get_silent() const { return silent_; }

/// Get the status of disabled
bool get_immediate_callback() const { return immediate_callback_; }

Expand Down Expand Up @@ -2390,8 +2404,8 @@ class App {
///
/// If this has more than one dot.separated.name, go into the subcommand matching it
/// Returns true if it managed to find the option, if false you'll need to remove the arg manually.
void _parse_config(const std::vector<ConfigItem> &args) {
for(const ConfigItem &item : args) {
void _parse_config(std::vector<ConfigItem> &args) {
for(ConfigItem item : args) {
if(!_parse_single_config(item) && allow_config_extras_ == config_extras_mode::error)
throw ConfigError::Extras(item.fullname());
}
Expand Down Expand Up @@ -2669,12 +2683,16 @@ class App {
auto com = _find_subcommand(args.back(), true, true);
if(com != nullptr) {
args.pop_back();
parsed_subcommands_.push_back(com);
if(!com->silent_) {
parsed_subcommands_.push_back(com);
}
com->_parse(args);
auto parent_app = com->parent_;
while(parent_app != this) {
parent_app->_trigger_pre_parse(args.size());
parent_app->parsed_subcommands_.push_back(com);
if(!com->silent_) {
parent_app->parsed_subcommands_.push_back(com);
}
parent_app = parent_app->parent_;
}
return true;
Expand Down
33 changes: 33 additions & 0 deletions tests/SubcommandTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,22 @@ TEST_F(ManySubcommands, SubcommandTriggeredOn) {
EXPECT_THROW(run(), CLI::ExtrasError);
}


TEST_F(ManySubcommands, SubcommandSilence) {

sub1->silent();
args = {"sub1", "sub2"};
EXPECT_NO_THROW(run());

auto subs = app.get_subcommands();
EXPECT_EQ(subs.size(), 1U);
sub1->silent(false);
EXPECT_FALSE(sub1->get_silent());
run();
subs = app.get_subcommands();
EXPECT_EQ(subs.size(), 2U);
}

TEST_F(TApp, UnnamedSub) {
double val{0.0};
auto sub = app.add_subcommand("", "empty name");
Expand Down Expand Up @@ -1622,6 +1638,23 @@ TEST_F(TApp, OptionGroupAlias) {
EXPECT_EQ(val, -3);
}

TEST_F(TApp, subcommand_help) {
auto sub1 = app.add_subcommand("help")->silent();
bool flag{false};
app.add_flag("--one", flag, "FLAGGER");
sub1->parse_complete_callback([]() { throw CLI::CallForHelp(); });
bool called{false};
args = {"help"};
try {
run();
} catch(const CLI::CallForHelp &) {
called = true;
}
auto helpstr = app.help();
EXPECT_THAT(helpstr, HasSubstr("FLAGGER"));
EXPECT_TRUE(called);
}

TEST_F(TApp, AliasErrors) {
auto sub1 = app.add_subcommand("sub1");
auto sub2 = app.add_subcommand("sub2");
Expand Down

0 comments on commit b49a575

Please sign in to comment.