shared/verbs: turn verb argument specification into nulstr

Like the command-level CommandDescription.argspec, a verb's argspec is now
a nulstr, so that a verb may declare multiple alternative usage lines. The
verbs help table renders one synopsis line per nulstr entry in the
synopsis cell.

All verb argument specifications must gain an explicit NUL suffix. This
is a lot of churn, but I think this needs to be done. For now, there'll
be only one user of the new functionality (i.e. a verb with more than
one argspec), but I think we'll eventually want to use this in other
places too.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-08-28 09:29:04 +02:00
parent 057fc4251a
commit efd63ddfbf
42 changed files with 365 additions and 351 deletions
+40 -26
View File
@@ -176,40 +176,54 @@ static int verb_add_help_one(Table *table, const Verb *verb) {
assert(verb);
bool is_default = FLAGS_SET(verb->flags, VERB_DEFAULT);
_cleanup_free_ char *cell = NULL;
int r;
/* We indent the option string by two spaces. We could set the minimum cell width and
* right-align for a similar result, but that'd be more work. This is only used for
* display. */
_cleanup_free_ char *s = strjoin(" ",
is_default ? "[" : "",
verb->verb,
verb->argspec ? " " : "",
strempty(verb->argspec),
is_default ? "]" : "");
if (!s)
return log_oom();
/* The synopsis cell contains one line per entry in the argspec nulstr. An unset argspec is
* equivalent to a single empty specification. */
for (const char *spec = empty_to_null(verb->argspec);;) {
/* We indent the option string by two spaces. We could set the minimum cell width and
* right-align for a similar result, but that'd be more work. This is only used for
* display. */
_cleanup_free_ char *s = strjoin(" ",
is_default ? "[" : "",
verb->verb,
spec ? " " : "",
strempty(spec),
is_default ? "]" : "");
if (!s)
return log_oom();
const char *ss = NULL;
if (columns() < VERB_SYNOPSIS_WIDTH_SANE * 4) {
/* If the synopsis is very wide, try to split it up. But do this only if the terminal
* is not very wide. If it _is_ wide, the broken up synopsis would look silly. */
const char *p = find_point_to_break(s, VERB_SYNOPSIS_WIDTH_SANE), *p2 = NULL;
if (p) {
const char *s1 = strndupa_safe(s, p - s), *s2 = NULL;
const char *ss = NULL;
if (columns() < VERB_SYNOPSIS_WIDTH_SANE * 4) {
/* If the synopsis is very wide, try to split it up. But do this only if the terminal
* is not very wide. If it _is_ wide, the broken up synopsis would look silly. */
const char *p = find_point_to_break(s, VERB_SYNOPSIS_WIDTH_SANE), *p2 = NULL;
if (p) {
const char *s1 = strndupa_safe(s, p - s), *s2 = NULL;
p2 = find_point_to_break(p, VERB_SYNOPSIS_WIDTH_SANE - 4); /* we indent by two spaces more */
if (p2)
s2 = strndupa_safe(p, p2 - p);
p2 = find_point_to_break(p, VERB_SYNOPSIS_WIDTH_SANE - 4); /* we indent by two spaces more */
if (p2)
s2 = strndupa_safe(p, p2 - p);
if (s2)
ss = strjoina(s1, "\n ", s2, "\n ", p2);
else
ss = strjoina(s1, "\n ", p);
if (s2)
ss = strjoina(s1, "\n ", s2, "\n ", p2);
else
ss = strjoina(s1, "\n ", p);
}
}
if (!strextend_with_separator(&cell, "\n", ss ?: s))
return log_oom();
if (!spec)
break;
spec += strlen(spec) + 1; /* Advance to the next entry in the nulstr */
if (isempty(spec))
break;
}
r = table_add_cell(table, NULL, TABLE_STRING, ss ?: s);
r = table_add_cell(table, NULL, TABLE_STRING, cell);
if (r < 0)
return table_log_add_error(r);
+2 -2
View File
@@ -18,7 +18,7 @@ typedef enum CommandFlags {
typedef struct CommandDescription {
const char *names; /* nulstr with the main command name and potentially aliases */
const char *abstract; /* concrete abstract */
const char *argspec; /* optional specification of positional args in synopsis */
const char *argspec; /* optional nulstr with specifications of positional args */
const char *footer; /* optional footer to print right above man page links */
const char *man_pages; /* nulstr with man page names */
const char *option_namespace; /* optional option namespace for this command */
@@ -41,7 +41,7 @@ typedef struct _alignptr_ Verb {
VerbFlags flags;
int (* const dispatch)(int argc, char *argv[], uintptr_t data, void *userdata);
uintptr_t data;
const char *argspec;
const char *argspec; /* optional nulstr with specifications of positional args */
const char *help;
const char *option_namespace; /* optional namespace with the verb's own options */
} Verb;