Skip to content

Commit e2debfa

Browse files
committed
ArgsManager: support command-specific options
1 parent 115d298 commit e2debfa

File tree

3 files changed

+87
-23
lines changed

3 files changed

+87
-23
lines changed

src/common/args.cpp

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV
553553
m_settings.forced_settings[SettingName(strArg)] = strValue;
554554
}
555555

556-
void ArgsManager::AddCommand(const std::string& cmd, const std::string& help)
556+
void ArgsManager::AddCommand(const std::string& cmd, const std::string& help, std::set<std::string>&& options)
557557
{
558558
Assert(cmd.find('=') == std::string::npos);
559559
Assert(cmd.at(0) != '-');
@@ -562,6 +562,9 @@ void ArgsManager::AddCommand(const std::string& cmd, const std::string& help)
562562
m_accept_any_command = false; // latch to false
563563
std::map<std::string, Arg>& arg_map = m_available_args[OptionsCategory::COMMANDS];
564564
auto ret = arg_map.emplace(cmd, Arg{"", help, ArgsManager::COMMAND});
565+
if (!options.empty()) {
566+
m_command_args.try_emplace(cmd, std::move(options));
567+
}
565568
Assert(ret.second); // Fail on duplicate commands
566569
}
567570

@@ -618,14 +621,46 @@ void ArgsManager::CheckMultipleCLIArgs() const
618621
}
619622
}
620623

624+
namespace {
625+
/** Helper class for iterating over COMMAND_OPTIONS applicable to a given command */
626+
template <typename T>
627+
class CommandOptionsGetter
628+
{
629+
private:
630+
const typename T::const_iterator m_end;
631+
const typename T::const_iterator m_iter;
632+
public:
633+
CommandOptionsGetter(const T& available_args)
634+
: m_end{available_args.end()},
635+
m_iter{available_args.find(OptionsCategory::COMMAND_OPTIONS)}
636+
{
637+
}
638+
639+
template <typename Fn>
640+
void Iterate(const std::set<std::string>& select, bool with_debug, Fn&& fn) const
641+
{
642+
if (select.empty()) return;
643+
if (m_iter == m_end) return;
644+
for (const auto& [cmdopt_name, cmdopt_info] : m_iter->second) {
645+
if (!with_debug && (cmdopt_info.m_flags & ArgsManager::DEBUG_ONLY)) continue;
646+
if (!select.contains(cmdopt_name)) continue;
647+
fn(cmdopt_name, cmdopt_info);
648+
}
649+
}
650+
};
651+
} // anonymous namespace
652+
621653
std::string ArgsManager::GetHelpMessage() const
622654
{
623655
const bool show_debug = GetBoolArg("-help-debug", false);
624656

625657
std::string usage;
626658
LOCK(cs_args);
627-
for (const auto& arg_map : m_available_args) {
628-
switch(arg_map.first) {
659+
660+
const auto command_options = CommandOptionsGetter(m_available_args);
661+
662+
for (const auto& [category, category_args] : m_available_args) {
663+
switch(category) {
629664
case OptionsCategory::OPTIONS:
630665
usage += HelpMessageGroup("Options:");
631666
break;
@@ -671,22 +706,29 @@ std::string ArgsManager::GetHelpMessage() const
671706
case OptionsCategory::CLI_COMMANDS:
672707
usage += HelpMessageGroup("CLI Commands:");
673708
break;
709+
case OptionsCategory::COMMAND_OPTIONS:
710+
break;
674711
default:
675712
break;
676713
}
677714

715+
if (category == OptionsCategory::COMMAND_OPTIONS) continue;
716+
678717
// When we get to the hidden options, stop
679-
if (arg_map.first == OptionsCategory::HIDDEN) break;
680-
681-
for (const auto& arg : arg_map.second) {
682-
if (show_debug || !(arg.second.m_flags & ArgsManager::DEBUG_ONLY)) {
683-
std::string name;
684-
if (arg.second.m_help_param.empty()) {
685-
name = arg.first;
686-
} else {
687-
name = arg.first + arg.second.m_help_param;
718+
if (category == OptionsCategory::HIDDEN) break;
719+
720+
for (const auto& [arg_name, arg_info] : category_args) {
721+
if (show_debug || !(arg_info.m_flags & ArgsManager::DEBUG_ONLY)) {
722+
usage += HelpMessageOpt(arg_name, arg_info.m_help_param, arg_info.m_help_text);
723+
724+
if (category == OptionsCategory::COMMANDS) {
725+
const auto cmd_args = m_command_args.find(arg_name);
726+
if (cmd_args != m_command_args.end()) {
727+
command_options.Iterate(cmd_args->second, show_debug, [&](const auto& cmdopt_name, const auto& cmdopt_info) {
728+
usage += HelpMessageSubOpt(cmdopt_name, cmdopt_info.m_help_param, cmdopt_info.m_help_text);
729+
});
730+
}
688731
}
689-
usage += HelpMessageOpt(name, arg.second.m_help_text);
690732
}
691733
}
692734
}
@@ -712,11 +754,11 @@ std::string HelpMessageGroup(const std::string &message) {
712754
return std::string(message) + std::string("\n\n");
713755
}
714756

715-
std::string HelpMessageOpt(const std::string &option, const std::string &message) {
716-
return std::string(optIndent,' ') + std::string(option) +
717-
std::string("\n") + std::string(msgIndent,' ') +
718-
FormatParagraph(message, screenWidth - msgIndent, msgIndent) +
719-
std::string("\n\n");
757+
std::string HelpMessageOpt(std::string_view option, std::string_view help_param, std::string_view message, int indent)
758+
{
759+
return strprintf("%*s%s%s\n%*s%s\n\n",
760+
(optIndent + indent), "", option, help_param,
761+
(msgIndent + indent), "", FormatParagraph(message, screenWidth - msgIndent - indent, msgIndent + indent));
720762
}
721763

722764
const std::vector<std::string> TEST_OPTIONS_DOC{
@@ -733,6 +775,11 @@ bool HasTestOption(const ArgsManager& args, const std::string& test_option)
733775
});
734776
}
735777

778+
std::string HelpMessageSubOpt(std::string_view option, std::string_view help_param, std::string_view message)
779+
{
780+
return HelpMessageOpt(option, help_param, message, msgIndent - optIndent);
781+
}
782+
736783
fs::path GetDefaultDataDir()
737784
{
738785
// Windows:

src/common/args.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <optional>
1919
#include <set>
2020
#include <string>
21+
#include <string_view>
2122
#include <variant>
2223
#include <vector>
2324

@@ -66,6 +67,8 @@ enum class OptionsCategory {
6667
CLI_COMMANDS,
6768
IPC,
6869

70+
COMMAND_OPTIONS, // Specific to one or more commands
71+
6972
HIDDEN // Always the last option to avoid printing these in the help
7073
};
7174

@@ -138,6 +141,7 @@ class ArgsManager
138141
std::set<std::string> m_network_only_args GUARDED_BY(cs_args);
139142
std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args);
140143
std::optional<unsigned int> m_default_flags GUARDED_BY(cs_args){};
144+
std::map<std::string, std::set<std::string>> m_command_args GUARDED_BY(cs_args);
141145
bool m_accept_any_command GUARDED_BY(cs_args){true};
142146
std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args);
143147
std::optional<fs::path> m_config_path GUARDED_BY(cs_args);
@@ -348,9 +352,9 @@ class ArgsManager
348352
void AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat);
349353

350354
/**
351-
* Add subcommand
355+
* Add command
352356
*/
353-
void AddCommand(const std::string& cmd, const std::string& help);
357+
void AddCommand(const std::string& cmd, const std::string& help, std::set<std::string>&& options={});
354358

355359
/**
356360
* Add many hidden arguments
@@ -474,10 +478,22 @@ std::string HelpMessageGroup(const std::string& message);
474478
/**
475479
* Format a string to be used as option description in help messages
476480
*
477-
* @param option Option message (e.g. "-rpcuser=<user>")
481+
* @param option Option name (e.g. "-rpcuser")
482+
* @param help_param Help parameter (e.g. "=<user>" or "")
483+
* @param message Option description (e.g. "Username for JSON-RPC connections")
484+
* @param indent Additional indentation
485+
* @return the formatted string
486+
*/
487+
std::string HelpMessageOpt(std::string_view option, std::string_view help_param, std::string_view message, int indent=0);
488+
489+
/**
490+
* Same as HelpMessageOpt, but indents for command-specific options
491+
*
492+
* @param option Option name (e.g. "-rpcuser")
493+
* @param help_param Help parameter (e.g. "=<user>" or "")
478494
* @param message Option description (e.g. "Username for JSON-RPC connections")
479495
* @return the formatted string
480496
*/
481-
std::string HelpMessageOpt(const std::string& option, const std::string& message);
497+
std::string HelpMessageSubOpt(std::string_view option, std::string_view help_param, std::string_view message);
482498

483499
#endif // BITCOIN_COMMON_ARGS_H

src/test/fuzz/string.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ FUZZ_TARGET(string)
6868
(void)HelpExampleCli(random_string_1, random_string_2);
6969
(void)HelpExampleRpc(random_string_1, random_string_2);
7070
(void)HelpMessageGroup(random_string_1);
71-
(void)HelpMessageOpt(random_string_1, random_string_2);
71+
(void)HelpMessageOpt(random_string_1, "", random_string_2);
72+
(void)HelpMessageOpt(random_string_1, random_string_2, "");
7273
(void)IsDeprecatedRPCEnabled(random_string_1);
7374
(void)Join(random_string_vector, random_string_1);
7475
(void)JSONRPCError(fuzzed_data_provider.ConsumeIntegral<int>(), random_string_1);

0 commit comments

Comments
 (0)