From: Zbigniew Jędrzejewski-Szmek Date: Sat, 25 Apr 2026 11:31:43 +0000 (+0200) Subject: shared/options: split out helper to format -o/--option= X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4339197f5d4f712bc900d8e09c892015d48b19bb;p=thirdparty%2Fsystemd.git shared/options: split out helper to format -o/--option= --- diff --git a/src/shared/options.c b/src/shared/options.c index fbf9a311817..a4c6514508c 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -311,6 +311,59 @@ size_t option_parser_get_n_args(const OptionParser *state) { return state->argc - state->positional_offset; } +char* option_get_synopsis(const char *prefix, const Option *opt, const char *joiner, bool show_metavar) { + assert(opt); + assert(!FLAGS_SET(opt->flags, OPTION_GROUP_MARKER)); /* A group marker should not be displayed */ + + if (!prefix) + prefix = ""; + + if (opt->flags & (OPTION_HELP_ENTRY_VERBATIM | OPTION_POSITIONAL_ENTRY)) + return strjoin(prefix, ASSERT_PTR(opt->long_code)); + + /* The option formatted appropriately for --help strings, error messages, and similar: + * ---=[] + * "=" is shown only when a long form is defined: -l --long=ARG, --long=ARG, -s ARG. + * The joiner arg is used between the short and long forms. + * As a special case, if the option has no long form and show_metavar is true, + * a space is used ('-a ARG' or '-a [ARG]'). + */ + assert(opt->short_code != 0 || opt->long_code); + + char sc[3] = ""; + if (opt->short_code != 0) + xsprintf(sc, "-%c", opt->short_code); + + if (show_metavar && opt->metavar && !opt->long_code) + joiner = " "; /* Return '-x ARG', no matter what joiner was specified. */ + else if (opt->short_code == 0 || !opt->long_code) + joiner = ""; + else if (!joiner) + joiner = " "; + + bool need_eq = option_takes_arg(opt) && opt->long_code; + if (!show_metavar) + return strjoin(prefix, + sc, + joiner, + opt->long_code ? "--" : "", + strempty(opt->long_code), + need_eq ? "=" : ""); + + bool need_quote = opt->metavar && strchr(opt->metavar, ' '); + return strjoin(prefix, + sc, + joiner, + opt->long_code ? "--" : "", + strempty(opt->long_code), + option_arg_optional(opt) ? "[" : "", + need_eq ? "=" : "", + need_quote ? "'" : "", + strempty(opt->metavar), + need_quote ? "'" : "", + option_arg_optional(opt) ? "]" : ""); +} + int _option_parser_get_help_table( const Option options[], const Option options_end[], @@ -341,38 +394,10 @@ int _option_parser_get_help_table( /* No help string — we do not show the option */ continue; - _cleanup_free_ char *s = NULL; - - if (FLAGS_SET(opt->flags, OPTION_HELP_ENTRY_VERBATIM)) { - assert(opt->long_code); - - s = strjoin(" ", - opt->long_code); - } else { - char sc[3] = " "; - if (opt->short_code != 0) - xsprintf(sc, "-%c", opt->short_code); - - /* 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. - * - * "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG. - */ - bool need_eq = option_takes_arg(opt) && opt->long_code; - bool need_quote = opt->metavar && strchr(opt->metavar, ' '); - s = strjoin(" ", - sc, - " ", - opt->long_code ? "--" : "", - strempty(opt->long_code), - option_arg_optional(opt) ? "[" : "", - need_eq ? "=" : "", - need_quote ? "'" : "", - strempty(opt->metavar), - need_quote ? "'" : "", - option_arg_optional(opt) ? "]" : ""); - } + /* 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 = option_get_synopsis(" ", opt, " ", /* show_metavar= */ true); if (!s) return log_oom(); diff --git a/src/shared/options.h b/src/shared/options.h index f0c19af49c3..59b20bc047c 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -172,6 +172,7 @@ char* option_parser_consume_next_arg(OptionParser *state); char** option_parser_get_args(const OptionParser *state); size_t option_parser_get_n_args(const OptionParser *state); +char* option_get_synopsis(const char *prefix, const Option *opt, const char *joiner, bool show_metavar); int _option_parser_get_help_table( const Option options[], diff --git a/src/test/test-options.c b/src/test/test-options.c index 1c9073b7a56..49e1b3069fc 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -1302,4 +1302,53 @@ TEST(option_optional_arg_consume) { } } +static void test_option_get_synopsis_one( + const Option *opt, + const char *joiner, + bool show_metavar, + const char *expected) { + log_debug("%s", expected); + _cleanup_free_ char *s = option_get_synopsis(". ", opt, joiner, show_metavar); + ASSERT_STREQ(s, expected); +} + +TEST(option_get_synopsis) { + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, "/", true, ". -x/--xxx=X"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, NULL, true, ". -x --xxx=X"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, "/", false, ". -x/--xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, " ", true, ". -x --xxx=X"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, " ", false, ". -x --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 0, "xxx", "X" }, "+", true, ". --xxx=X" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 0, "xxx", "X" }, "+", false, ". --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', NULL, "X" }, " ", true, ". -x X" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', NULL, "X" }, "/", false, ". -x" ); + + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "A B" }, "/", true, ". -x/--xxx='A B'"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "A B" }, " ", true, ". -x --xxx='A B'"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 0, "xxx", "A B" }, "+", true, ". --xxx='A B'" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', NULL, "A B" }, " ", true, ". -x 'A B'" ); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, "/", true, ". -x/--xxx[=X]"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, NULL, true, ". -x --xxx[=X]"); + /* Note: --xxx[=] would be silly, so we show --xxx=. It's a corner case. Maybe this should change. */ + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, "/", false, ". -x/--xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, " ", true, ". -x --xxx[=X]"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, " ", false, ". -x --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 0, "xxx", "X" }, "+", true, ". --xxx[=X]" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 0, "xxx", "X" }, "+", false, ". --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', NULL, "X" }, " ", true, ". -x [X]" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', NULL, "X" }, "/", false, ". -x" ); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "A B" }, "/", true, ". -x/--xxx[='A B']"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "A B" }, " ", true, ". -x --xxx[='A B']"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 0, "xxx", "A B" }, "+", true, ". --xxx[='A B']" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', NULL, "A B" }, " ", true, ". -x ['A B']" ); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG | OPTION_HELP_ENTRY | OPTION_STOPS_PARSING, + 'x', "xxx", "A B" }, "/", true, ". -x/--xxx[='A B']"); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_HELP_ENTRY_VERBATIM, 'u', "special special", "unused" }, "/", true, ". special special"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_POSITIONAL_ENTRY, 'u', "(fixed)", "unused" }, "/", true, ". (fixed)"); +} + DEFINE_TEST_MAIN(LOG_DEBUG);