diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 8515dbecf7..7f49a8ee0b 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -629,6 +629,10 @@ int _command_print_verb_help( return r; } + r = print_wrapped(verb->footer, /* footer_ansi_seq= */ NULL); + 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"); @@ -672,6 +676,9 @@ static int verb_build_json( !!verb->help, "abstract", SD_JSON_BUILD_STRV(STRV_MAKE(verb->help))), SD_JSON_BUILD_PAIR_CONDITION(!!opts, "options", SD_JSON_BUILD_VARIANT(opts)), + SD_JSON_BUILD_PAIR_CONDITION( + !!verb->footer, + "postscript", SD_JSON_BUILD_STRV(STRV_MAKE(verb->footer))), SD_JSON_BUILD_PAIR_CONDITION( verb->min_args != VERB_ANY, "minArguments", SD_JSON_BUILD_UNSIGNED(verb->min_args)), diff --git a/src/shared/verbs.h b/src/shared/verbs.h index cd679f404e..e2bed04d54 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -44,11 +44,12 @@ typedef struct _alignptr_ Verb { uintptr_t data; const char *argspec; /* optional nulstr with specifications of positional args */ const char *help; + const char *footer; /* optional footer to print in --help */ const char *option_namespace; /* optional namespace with the verb's own options */ } Verb; assert_cc(sizeof(Verb) % sizeof(void*) == 0); -#define _VERB_DATA(d, v, ns, a, amin, amax, f, dat, h) \ +#define _VERB_DATA(d, v, ns, a, amin, amax, f, dat, h, ft) \ _section_("SYSTEMD_VERBS") \ _alignptr_ \ _used_ \ @@ -64,31 +65,32 @@ assert_cc(sizeof(Verb) % sizeof(void*) == 0); .data = dat, \ .argspec = a, \ .help = h, \ + .footer = ft, \ .option_namespace = ns, \ } /* Forward-define function d. scope specifies the scope, e.g. static. ns declares the option * namespace with the verb's own options, which are then shown in the verb's help (see * command_print_verb_help()) and reported in the CLI introspection. */ -#define VERB_SCOPE_NS_FULL(scope, d, v, ns, a, amin, amax, f, dat, h) \ +#define VERB_SCOPE_NS_FULL(scope, d, v, ns, a, amin, amax, f, dat, h, ft) \ DISABLE_WARNING_REDUNDANT_DECLS \ scope int d(int, char**, uintptr_t, void*); \ REENABLE_WARNING \ - _VERB_DATA(d, v, ns, a, amin, amax, f, dat, h) + _VERB_DATA(d, v, ns, a, amin, amax, f, dat, h, ft) /* The same as VERB_SCOPE_NS_FULL, but for verbs without options of their own. */ -#define VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, dat, h) \ - VERB_SCOPE_NS_FULL(scope, d, v, /* ns= */ NULL, a, amin, amax, f, dat, h) +#define VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, dat, h, ft) \ + VERB_SCOPE_NS_FULL(scope, d, v, /* ns= */ NULL, a, amin, amax, f, dat, h, ft) /* The same as VERB_SCOPE_NS_FULL/VERB_SCOPE_FULL with scope hardwired to 'static'. */ -#define VERB_NS_FULL(d, v, ns, a, amin, amax, f, dat, h) \ - VERB_SCOPE_NS_FULL(static, d, v, ns, a, amin, amax, f, dat, h) +#define VERB_NS_FULL(d, v, ns, a, amin, amax, f, dat, h, ft) \ + VERB_SCOPE_NS_FULL(static, d, v, ns, a, amin, amax, f, dat, h, ft) #define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ - VERB_SCOPE_FULL(static, d, v, a, amin, amax, f, dat, h) + VERB_SCOPE_FULL(static, d, v, a, amin, amax, f, dat, h, /* ft= */ NULL) /* The same as the macros above, but without the data argument. */ #define VERB_SCOPE_NS(scope, d, v, ns, a, amin, amax, f, h) \ - VERB_SCOPE_NS_FULL(scope, d, v, ns, a, amin, amax, f, /* dat= */ 0, h) + VERB_SCOPE_NS_FULL(scope, d, v, ns, a, amin, amax, f, /* dat= */ 0, h, /* ft= */ NULL) #define VERB_SCOPE(scope, d, v, a, amin, amax, f, h) \ - VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, /* dat= */ 0, h) + VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, /* dat= */ 0, h, /* ft= */ NULL) #define VERB_NS(d, v, ns, a, amin, amax, f, h) \ VERB_SCOPE_NS(static, d, v, ns, a, amin, amax, f, h) #define VERB(d, v, a, amin, amax, f, h) \ @@ -106,13 +108,15 @@ assert_cc(sizeof(Verb) % sizeof(void*) == 0); * The macro works as a separator between groups and must be between other VERB* stanzas. */ #define VERB_GROUP(gr) \ _VERB_DATA(/* d= */ NULL, /* v= */ gr, /* ns= */ NULL, /* a= */ NULL, \ - /* amin= */ 0, /* amax= */ 0, /* f= */ VERB_GROUP_MARKER, /* dat= */ 0, /* h= */ NULL) + /* amin= */ 0, /* amax= */ 0, /* f= */ VERB_GROUP_MARKER, /* dat= */ 0, \ + /* h= */ NULL, /* ft= */ NULL) -#define _COMMAND(u, ...) \ +#define _COMMAND(u, ...) \ static const CommandDescription UNIQ_T(description, u) = { __VA_ARGS__ }; \ - _VERB_DATA(/* d= */ NULL, /* v= */ NULL, /* ns= */ NULL, /* a= */ NULL, \ - /* amin= */ 0, /* amax= */ 0, /* f= */ VERB_COMMAND_MARKER, \ - /* dat= */ (uintptr_t) &UNIQ_T(description, u), /* h= */ NULL) + _VERB_DATA(/* d= */ NULL, /* v= */ NULL, /* ns= */ NULL, /* a= */ NULL, \ + /* amin= */ 0, /* amax= */ 0, /* f= */ VERB_COMMAND_MARKER, \ + /* dat= */ (uintptr_t) &UNIQ_T(description, u), \ + /* h= */ NULL, /* ft= */ NULL) #define COMMAND(...) _COMMAND(UNIQ, __VA_ARGS__) /* This is magically mapped to the beginning and end of the section */ diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index 42670c9593..9016a6268c 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -101,7 +101,7 @@ static const Verb introspect_verbs[] = { .help = "Beta verb" }, { .verb = "gamma", .flags = VERB_OPTION_REQUIRED, .min_args = VERB_ANY, .max_args = VERB_ANY, .dispatch = noop_dispatcher, - .help = "Gamma verb" }, + .help = "Gamma verb", .footer = "Gamma footer" }, {} };