Add a Argument::hidden() method to prevent an argument from appearing in usage or help

This commit is contained in:
Even Rouault 2024-03-16 14:19:29 +01:00
parent cebee4bb4b
commit d7d2326f42
No known key found for this signature in database
GPG Key ID: 33EBBFC47B3DD87D
4 changed files with 80 additions and 10 deletions

View File

@ -39,7 +39,7 @@
* [Parent Parsers](#parent-parsers) * [Parent Parsers](#parent-parsers)
* [Subcommands](#subcommands) * [Subcommands](#subcommands)
* [Parse Known Args](#parse-known-args) * [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) * [ArgumentParser in bool Context](#argumentparser-in-bool-context)
* [Custom Prefix Characters](#custom-prefix-characters) * [Custom Prefix Characters](#custom-prefix-characters)
* [Custom Assignment Characters](#custom-assignment-characters) * [Custom Assignment Characters](#custom-assignment-characters)
@ -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 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 appearing it in the usage. For example, to phase out a deprecated wording of
@ -1017,6 +1017,18 @@ auto &arg = program.add_argument("--suppress").flag();
program.add_hidden_alias_for(arg, "--supress"); // old misspelled alias 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 ### ArgumentParser in bool Context
An `ArgumentParser` is `false` until it (or one of its subparsers) have extracted An `ArgumentParser` is `false` until it (or one of its subparsers) have extracted

View File

@ -606,7 +606,7 @@ class Argument {
: m_accepts_optional_like_value(false), : m_accepts_optional_like_value(false),
m_is_optional((is_optional(a[I], prefix_chars) || ...)), m_is_optional((is_optional(a[I], prefix_chars) || ...)),
m_is_required(false), m_is_repeatable(false), m_is_used(false), 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]), ...); ((void)m_names.emplace_back(a[I]), ...);
std::sort( std::sort(
m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) { m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) {
@ -745,6 +745,12 @@ public:
return *this; 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> template <char Shape, typename T>
auto scan() -> std::enable_if_t<std::is_arithmetic_v<T>, Argument &> { auto scan() -> std::enable_if_t<std::is_arithmetic_v<T>, Argument &> {
static_assert(!(std::is_const_v<T> || std::is_volatile_v<T>), static_assert(!(std::is_const_v<T> || std::is_volatile_v<T>),
@ -1531,6 +1537,7 @@ private:
bool m_is_required : 1; bool m_is_required : 1;
bool m_is_repeatable : 1; bool m_is_repeatable : 1;
bool m_is_used : 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 std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars
int m_usage_newline_counter = 0; int m_usage_newline_counter = 0;
std::size_t m_group_idx = 0; std::size_t m_group_idx = 0;
@ -1910,22 +1917,30 @@ public:
stream << parser.m_description << "\n\n"; 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"; stream << "Positional arguments:\n";
} }
for (const auto &argument : parser.m_positional_arguments) { for (const auto &argument : parser.m_positional_arguments) {
stream.width(static_cast<std::streamsize>(longest_arg_length)); if (!argument.m_is_hidden) {
stream << argument; stream.width(static_cast<std::streamsize>(longest_arg_length));
stream << argument;
}
} }
if (!parser.m_optional_arguments.empty()) { if (!parser.m_optional_arguments.empty()) {
stream << (parser.m_positional_arguments.empty() ? "" : "\n") stream << (!has_visible_positional_args ? "" : "\n")
<< "Optional arguments:\n"; << "Optional arguments:\n";
} }
for (const auto &argument : parser.m_optional_arguments) { 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.width(static_cast<std::streamsize>(longest_arg_length));
stream << argument; stream << argument;
} }
@ -1934,7 +1949,7 @@ public:
for (size_t i_group = 0; i_group < parser.m_group_names.size(); ++i_group) { 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"; stream << "\n" << parser.m_group_names[i_group] << " (detailed usage):\n";
for (const auto &argument : parser.m_optional_arguments) { 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.width(static_cast<std::streamsize>(longest_arg_length));
stream << argument; stream << argument;
} }
@ -2006,6 +2021,9 @@ public:
const MutuallyExclusiveGroup *cur_mutex = nullptr; const MutuallyExclusiveGroup *cur_mutex = nullptr;
int usage_newline_counter = -1; int usage_newline_counter = -1;
for (const auto &argument : this->m_optional_arguments) { for (const auto &argument : this->m_optional_arguments) {
if (argument.m_is_hidden) {
continue;
}
if (multiline_usage) { if (multiline_usage) {
if (argument.m_group_idx != group_idx) { if (argument.m_group_idx != group_idx) {
continue; continue;
@ -2078,6 +2096,9 @@ public:
} }
// Put positional arguments after the optionals // Put positional arguments after the optionals
for (const auto &argument : this->m_positional_arguments) { for (const auto &argument : this->m_positional_arguments) {
if (argument.m_is_hidden) {
continue;
}
const std::string pos_arg = !argument.m_metavar.empty() const std::string pos_arg = !argument.m_metavar.empty()
? argument.m_metavar ? argument.m_metavar
: argument.m_names.front(); : argument.m_names.front();

View File

@ -37,6 +37,8 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_error_reporting.cpp test_error_reporting.cpp
test_get.cpp test_get.cpp
test_help.cpp test_help.cpp
test_hidden_alias.cpp
test_hidden_argument.cpp
test_invalid_arguments.cpp test_invalid_arguments.cpp
test_is_used.cpp test_is_used.cpp
test_issue_37.cpp test_issue_37.cpp
@ -56,7 +58,6 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_parse_known_args.cpp test_parse_known_args.cpp
test_equals_form.cpp test_equals_form.cpp
test_prefix_chars.cpp test_prefix_chars.cpp
test_hidden_alias.cpp
) )
set_source_files_properties(main.cpp set_source_files_properties(main.cpp
PROPERTIES PROPERTIES

View File

@ -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);
}