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.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-08-28 09:29:04 +02:00
parent efd63ddfbf
commit 3cfe456323
3 changed files with 93 additions and 0 deletions
+66
View File
@@ -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[],
+18
View File
@@ -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. */
+9
View File
@@ -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);