Skip to content

Commit

Permalink
Add a Argument::hidden() method to prevent an argument from appearing…
Browse files Browse the repository at this point in the history
… in usage or help
  • Loading branch information
rouault committed Mar 16, 2024
1 parent cebee4b commit d7d2326
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 10 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
* [Parent Parsers](#parent-parsers)
* [Subcommands](#subcommands)
* [Parse Known Args](#parse-known-args)
* [Hidden alias](#hidden-alias)
* [Hidden argument and alias](#hidden-argument-alias)
* [ArgumentParser in bool Context](#argumentparser-in-bool-context)
* [Custom Prefix Characters](#custom-prefix-characters)
* [Custom Assignment Characters](#custom-assignment-characters)
Expand Down Expand Up @@ -1003,7 +1003,7 @@ int main(int argc, char *argv[]) {
}
```

### Hidden alias
### Hidden argument and alias

It is sometimes desirable to offer an alias for an argument, but without it
appearing it in the usage. For example, to phase out a deprecated wording of
Expand All @@ -1017,6 +1017,18 @@ auto &arg = program.add_argument("--suppress").flag();
program.add_hidden_alias_for(arg, "--supress"); // old misspelled alias
```
The ``Argument::hidden()`` method can also be used to prevent a (generally
optional) argument from appearing in the usage or help.
```cpp
argparse::ArgumentParser program("test");
program.add_argument("--non-documented").flag().hidden();
```

This can also be used on positional arguments, but in that later case it only
makes sense in practice for the last ones.

### ArgumentParser in bool Context

An `ArgumentParser` is `false` until it (or one of its subparsers) have extracted
Expand Down
35 changes: 28 additions & 7 deletions include/argparse/argparse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ class Argument {
: m_accepts_optional_like_value(false),
m_is_optional((is_optional(a[I], prefix_chars) || ...)),
m_is_required(false), m_is_repeatable(false), m_is_used(false),
m_prefix_chars(prefix_chars) {
m_is_hidden(false), m_prefix_chars(prefix_chars) {
((void)m_names.emplace_back(a[I]), ...);
std::sort(
m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) {
Expand Down Expand Up @@ -745,6 +745,12 @@ class Argument {
return *this;
}

// Cause the argument to be invisible in usage and help
auto &hidden() {
m_is_hidden = true;
return *this;
}

template <char Shape, typename T>
auto scan() -> std::enable_if_t<std::is_arithmetic_v<T>, Argument &> {
static_assert(!(std::is_const_v<T> || std::is_volatile_v<T>),
Expand Down Expand Up @@ -1531,6 +1537,7 @@ class Argument {
bool m_is_required : 1;
bool m_is_repeatable : 1;
bool m_is_used : 1;
bool m_is_hidden : 1; // if set, does not appear in usage or help
std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars
int m_usage_newline_counter = 0;
std::size_t m_group_idx = 0;
Expand Down Expand Up @@ -1910,22 +1917,30 @@ class ArgumentParser {
stream << parser.m_description << "\n\n";
}

if (!parser.m_positional_arguments.empty()) {
const bool has_visible_positional_args = std::find_if(
parser.m_positional_arguments.begin(),
parser.m_positional_arguments.end(),
[](const auto &argument) {
return !argument.m_is_hidden; }) !=
parser.m_positional_arguments.end();
if (has_visible_positional_args) {
stream << "Positional arguments:\n";
}

for (const auto &argument : parser.m_positional_arguments) {
stream.width(static_cast<std::streamsize>(longest_arg_length));
stream << argument;
if (!argument.m_is_hidden) {
stream.width(static_cast<std::streamsize>(longest_arg_length));
stream << argument;
}
}

if (!parser.m_optional_arguments.empty()) {
stream << (parser.m_positional_arguments.empty() ? "" : "\n")
stream << (!has_visible_positional_args ? "" : "\n")
<< "Optional arguments:\n";
}

for (const auto &argument : parser.m_optional_arguments) {
if (argument.m_group_idx == 0) {
if (argument.m_group_idx == 0 && !argument.m_is_hidden) {
stream.width(static_cast<std::streamsize>(longest_arg_length));
stream << argument;
}
Expand All @@ -1934,7 +1949,7 @@ class ArgumentParser {
for (size_t i_group = 0; i_group < parser.m_group_names.size(); ++i_group) {
stream << "\n" << parser.m_group_names[i_group] << " (detailed usage):\n";
for (const auto &argument : parser.m_optional_arguments) {
if (argument.m_group_idx == i_group + 1) {
if (argument.m_group_idx == i_group + 1 && !argument.m_is_hidden) {
stream.width(static_cast<std::streamsize>(longest_arg_length));
stream << argument;
}
Expand Down Expand Up @@ -2006,6 +2021,9 @@ class ArgumentParser {
const MutuallyExclusiveGroup *cur_mutex = nullptr;
int usage_newline_counter = -1;
for (const auto &argument : this->m_optional_arguments) {
if (argument.m_is_hidden) {
continue;
}
if (multiline_usage) {
if (argument.m_group_idx != group_idx) {
continue;
Expand Down Expand Up @@ -2078,6 +2096,9 @@ class ArgumentParser {
}
// Put positional arguments after the optionals
for (const auto &argument : this->m_positional_arguments) {
if (argument.m_is_hidden) {
continue;
}
const std::string pos_arg = !argument.m_metavar.empty()
? argument.m_metavar
: argument.m_names.front();
Expand Down
3 changes: 2 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_error_reporting.cpp
test_get.cpp
test_help.cpp
test_hidden_alias.cpp
test_hidden_argument.cpp
test_invalid_arguments.cpp
test_is_used.cpp
test_issue_37.cpp
Expand All @@ -56,7 +58,6 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_parse_known_args.cpp
test_equals_form.cpp
test_prefix_chars.cpp
test_hidden_alias.cpp
)
set_source_files_properties(main.cpp
PROPERTIES
Expand Down
36 changes: 36 additions & 0 deletions test/test_hidden_argument.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>

using doctest::test_suite;

TEST_CASE("Test setting a hidden argument" * test_suite("hidden_argument")) {
argparse::ArgumentParser program("program");
program.add_argument("--hidden").flag().hidden();
program.add_argument("--regular").flag();
program.add_argument("regular_positional");
// only makes sense if last and optional...
program.add_argument("hidden_positional").nargs(0, 1).hidden();

program.parse_args({"./test.exe", "--hidden", "--regular",
"regular_positional_val", "hidden_positional_val"});
REQUIRE(program.get<bool>("--hidden") == true);
REQUIRE(program.get<bool>("--regular") == true);
REQUIRE(program.get<std::string>("regular_positional") ==
"regular_positional_val");
REQUIRE(program.get<std::string>("hidden_positional") ==
"hidden_positional_val");

REQUIRE(program.usage() ==
"Usage: program [--help] [--version] [--regular] regular_positional");

std::ostringstream s;
s << program;
// std::cout << "DEBUG:" << s.str() << std::endl;
REQUIRE(s.str().find("hidden") == std::string::npos);
REQUIRE(s.str().find("--regular") != std::string::npos);
REQUIRE(s.str().find("regular_positional") != std::string::npos);
}

0 comments on commit d7d2326

Please sign in to comment.