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:
+ * <prefix>-<short><joiner>--<long>=[<metavar>]
+ * "=" 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[],
/* 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();
}
}
+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);