From 3cfe4563235a0f420abe5ee49c577daabb36e010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 26 Aug 2026 10:09:19 +0200 Subject: [PATCH] shared/verbs: add command_print_verb_help() A helper to print the help of a single verb, derived entirely from the data in the SYSTEMD_VERBS and SYSTEMD_OPTIONS sections: the synopsis is built from the verb name and argspec, the one-line verb help serves as the abstract, the options come from the verb's option namespace, and the man page links from the command the verb belongs to. A flag allows suppressing the man page links for callers which append custom sections. --- src/shared/verbs.c | 66 +++++++++++++++++++++++++++++++++++++++++++ src/shared/verbs.h | 18 ++++++++++++ src/test/test-verbs.c | 9 ++++++ 3 files changed, 93 insertions(+) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index ddc11cc004..d65999dd70 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -564,6 +564,72 @@ int _command_print_help_full( return 0; } +int _command_print_verb_help( + const Verb verbs[], + const Verb verbs_end[], + const Option options[], + const Option options_end[], + const char *name) { + + int r; + + assert(name); + + const Verb *verb = ASSERT_PTR(_verbs_find_verb(verbs, verbs_end, name)); + + /* The owning command is described by the closest preceding VERB_COMMAND_MARKER entry. */ + const Verb *cmdverb = verb; + while (!FLAGS_SET(cmdverb->flags, VERB_COMMAND_MARKER)) { + assert(cmdverb > verbs); + cmdverb--; + } + const CommandDescription *cmd = (const CommandDescription*) ASSERT_PTR(cmdverb->data); + + if (cmd->pager_flags) + pager_open(*cmd->pager_flags); + + /* If argspec is empty, print one line with no argspec. Use " " as placeholder for the empty spec. */ + NULSTR_FOREACH(spec, verb->argspec ?: " \0") { + bool some = !streq(spec, " "); + _cleanup_free_ char *line = strjoin( + "[OPTION…] ", verb->verb, + verb->option_namespace ? " [OPTION…]" : "", + some ? " " : NULL, + some ? spec : NULL); + if (!line) + return log_oom(); + help_cmdline(line); + }; + + if (verb->help) { + const char *s = strjoina(ansi_highlight(), ansi_add_italics()), + *t = strjoina(verb->help, "."); + + r = print_wrapped(t, s); + if (r < 0) + return r; + } + + if (verb->option_namespace) { + _cleanup_(table_unrefp) Table *table = NULL; + r = _option_parser_get_help_table_full(options, options_end, verb->option_namespace, + /* group= */ NULL, &table); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(table); + if (r < 0) + return r; + } + + r = print_man_links(cmd->man_pages); + if (r < 0) + return log_error_errno(r, "Failed to print man page links: %m"); + + return 0; +} + static int verb_build_json( const Verb *verb, const Option options[], diff --git a/src/shared/verbs.h b/src/shared/verbs.h index cda8db56d3..0288548a8c 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -183,6 +183,24 @@ static inline int verb_help_auto(int argc, char **argv, uintptr_t data, void *us return command_print_help_name((const char*) data); } +/* Print the help for a single verb: the synopsis, the one-line help as abstract, the options from + * the verb's option namespace (see VERB_SCOPE_NS*), and the man page links of the command the verb + * belongs to. The verb is attributed to the closest preceding COMMAND() in the verbs section, so: + * name must refer to an existing verb owned by such a command (asserted otherwise), verb names + * must be unique across the whole binary, commands with COMMAND_VERBS_SHARED are not + * disambiguated, and a declared option namespace must contain at least one non-hidden option. */ +int _command_print_verb_help( + const Verb verbs[], + const Verb verbs_end[], + const Option options[], + const Option options_end[], + const char *name); +#define command_print_verb_help(name) \ + _command_print_verb_help( \ + __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, \ + __start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, \ + name) + /* Print a machine-readable description of the program's commands, verbs, and options in the format * defined by the CLI-Introspection Specification. One command object is emitted for each * VERB_COMMAND() in the verb table, so multicall binaries are described in full. */ diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index 22beda3541..c255c5840c 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -114,4 +114,13 @@ TEST(introspect_cli_verb_options) { SD_JSON_FORMAT_OFF)); } +TEST(command_print_verb_help) { + ASSERT_OK(_command_print_verb_help(introspect_verbs, introspect_verbs + ELEMENTSOF(introspect_verbs) - 1, + introspect_options, introspect_options + ELEMENTSOF(introspect_options) - 1, + "alpha")); + ASSERT_OK(_command_print_verb_help(introspect_verbs, introspect_verbs + ELEMENTSOF(introspect_verbs) - 1, + introspect_options, introspect_options + ELEMENTSOF(introspect_options) - 1, + "beta")); +} + DEFINE_TEST_MAIN(LOG_INFO);