From d7d2326f42c8ded5f259e85cbfa8373610ae18e9 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 16 Mar 2024 14:19:29 +0100 Subject: [PATCH] Add a Argument::hidden() method to prevent an argument from appearing in usage or help --- README.md | 16 ++++++++++++++-- include/argparse/argparse.hpp | 35 +++++++++++++++++++++++++++------- test/CMakeLists.txt | 3 ++- test/test_hidden_argument.cpp | 36 +++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 test/test_hidden_argument.cpp diff --git a/README.md b/README.md index f1db7b66..2196ce9b 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 @@ -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 diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index e1718241..ba546e33 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -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) { @@ -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 auto scan() -> std::enable_if_t, Argument &> { static_assert(!(std::is_const_v || std::is_volatile_v), @@ -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; @@ -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(longest_arg_length)); - stream << argument; + if (!argument.m_is_hidden) { + stream.width(static_cast(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(longest_arg_length)); stream << argument; } @@ -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(longest_arg_length)); stream << argument; } @@ -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; @@ -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(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 739402e5..2d36ec01 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 @@ -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 diff --git a/test/test_hidden_argument.cpp b/test/test_hidden_argument.cpp new file mode 100644 index 00000000..23016d7b --- /dev/null +++ b/test/test_hidden_argument.cpp @@ -0,0 +1,36 @@ +#ifdef WITH_MODULE +import argparse; +#else +#include +#endif +#include + +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("--hidden") == true); + REQUIRE(program.get("--regular") == true); + REQUIRE(program.get("regular_positional") == + "regular_positional_val"); + REQUIRE(program.get("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); +}