Skip to content

Commit 4631cfc

Browse files
avargitster
authored andcommitted
parse-options: properly align continued usage output
Some commands such as "git stash" emit continued options output with e.g. "git stash -h", because usage_with_options_internal() prefixes with its own whitespace the resulting output wasn't properly aligned. Let's account for the added whitespace, which properly aligns the output. The "git stash" command has usage output with a N_() translation that legitimately stretches across multiple lines; N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" [...] We'd like to have that output aligned with the length of the initial "git stash " output, but since usage_with_options_internal() adds its own whitespace prefixing we fell short, before this change we'd emit: $ git stash -h usage: git stash list [<options>] or: git stash show [<options>] [<stash>] [...] or: git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [-u|--include-untracked] [-a|--all] [-m|--message <message>] [...] Now we'll properly emit aligned output. I.e. the last four lines above will instead be (a whitespace-only change to the above): [...] or: git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [-u|--include-untracked] [-a|--all] [-m|--message <message>] [...] We could also go for an approach where we have the caller support no padding of their own, i.e. (same as the first example, except for the padding on the second line): N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" "[-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" [...] But to do that we'll need to find the length of "git stash". We can discover that from the "cmd" in the "struct cmd_struct", but there might be cases with sub-commands or "git" itself taking arguments that would make that non-trivial. Even if it were I still think this approach is better, because this way we'll get the same legible alignment in the C code. The fact that usage_with_options_internal() is adding its own prefix padding is an implementation detail that callers shouldn't need to worry about. Implementation notes: We could skip the string_list_split() with a strchr(str, '\n') check, but we'd then need to duplicate our state machine for strings that do and don't contain a "\n". It's simpler to just always split into a "struct string_list", even though the common case is that that "struct string_list" will contain only one element. This is not performance-sensitive code. This change is relatively more complex since I've accounted for making it future-proof for RTL translation support. Later in usage_with_options_internal() we have some existing padding code dating back to d7a38c5 (parse-options: be able to generate usages automatically, 2007-10-15) which isn't RTL-safe, but that code would be easy to fix. Let's not introduce new RTL translation problems here. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 84122ec commit 4631cfc

File tree

1 file changed

+64
-12
lines changed

1 file changed

+64
-12
lines changed

parse-options.c

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -917,25 +917,77 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
917917
FILE *outfile = err ? stderr : stdout;
918918
int need_newline;
919919

920+
const char *usage_prefix = _("usage: %s");
921+
/*
922+
* The translation could be anything, but we can count on
923+
* msgfmt(1)'s --check option to have asserted that "%s" is in
924+
* the translation. So compute the length of the "usage: "
925+
* part. We are assuming that the translator wasn't overly
926+
* clever and used e.g. "%1$s" instead of "%s", there's only
927+
* one "%s" in "usage_prefix" above, so there's no reason to
928+
* do so even with a RTL language.
929+
*/
930+
size_t usage_len = strlen(usage_prefix) - strlen("%s");
931+
/*
932+
* TRANSLATORS: the colon here should align with the
933+
* one in "usage: %s" translation.
934+
*/
935+
const char *or_prefix = _(" or: %s");
936+
/*
937+
* TRANSLATORS: You should only need to translate this format
938+
* string if your language is a RTL language (e.g. Arabic,
939+
* Hebrew etc.), not if it's a LTR language (e.g. German,
940+
* Russian, Chinese etc.).
941+
*
942+
* When a translated usage string has an embedded "\n" it's
943+
* because options have wrapped to the next line. The line
944+
* after the "\n" will then be padded to align with the
945+
* command name, such as N_("git cmd [opt]\n<8
946+
* spaces>[opt2]"), where the 8 spaces are the same length as
947+
* "git cmd ".
948+
*
949+
* This format string prints out that already-translated
950+
* line. The "%*s" is whitespace padding to account for the
951+
* padding at the start of the line that we add in this
952+
* function. The "%s" is a line in the (hopefully already
953+
* translated) N_() usage string, which contained embedded
954+
* newlines before we split it up.
955+
*/
956+
const char *usage_continued = _("%*s%s");
957+
const char *prefix = usage_prefix;
958+
int saw_empty_line = 0;
959+
920960
if (!usagestr)
921961
return PARSE_OPT_HELP;
922962

923963
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
924964
fprintf(outfile, "cat <<\\EOF\n");
925965

926-
fprintf_ln(outfile, _("usage: %s"), _(*usagestr++));
927-
while (*usagestr && **usagestr)
928-
/*
929-
* TRANSLATORS: the colon here should align with the
930-
* one in "usage: %s" translation.
931-
*/
932-
fprintf_ln(outfile, _(" or: %s"), _(*usagestr++));
933966
while (*usagestr) {
934-
if (**usagestr)
935-
fprintf_ln(outfile, _(" %s"), _(*usagestr));
936-
else
937-
fputc('\n', outfile);
938-
usagestr++;
967+
const char *str = _(*usagestr++);
968+
struct string_list list = STRING_LIST_INIT_DUP;
969+
unsigned int j;
970+
971+
if (!saw_empty_line && !*str)
972+
saw_empty_line = 1;
973+
974+
string_list_split(&list, str, '\n', -1);
975+
for (j = 0; j < list.nr; j++) {
976+
const char *line = list.items[j].string;
977+
978+
if (saw_empty_line && *line)
979+
fprintf_ln(outfile, _(" %s"), line);
980+
else if (saw_empty_line)
981+
fputc('\n', outfile);
982+
else if (!j)
983+
fprintf_ln(outfile, prefix, line);
984+
else
985+
fprintf_ln(outfile, usage_continued,
986+
(int)usage_len, "", line);
987+
}
988+
string_list_clear(&list, 0);
989+
990+
prefix = or_prefix;
939991
}
940992

941993
need_newline = 1;

0 commit comments

Comments
 (0)