Merge branch 'ps/parse-options-subcommand-groups' into jch
The parse-options API has been updated to support grouping subcommands, and the 'git refs' command has been updated to use this new feature to categorize its subcommands in its help output. * ps/parse-options-subcommand-groups: builtin/refs: introduce subcommand groups parse-options: allow grouping subcommands parse-options: extract functions to print single option parse-options: fix completion format when first option is skippedjch
commit
e1fde7a744
|
|
@ -11,6 +11,7 @@ SYNOPSIS
|
|||
[synopsis]
|
||||
git refs migrate --ref-storage-format=<format> [--no-reflog] [--dry-run]
|
||||
git refs verify [--strict] [--verbose]
|
||||
git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]
|
||||
git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
|
||||
[(--sort=<key>)...] [--format=<format>]
|
||||
[--include-root-refs] [--points-at=<object>]
|
||||
|
|
@ -19,7 +20,6 @@ git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
|
|||
[(--exclude=<pattern>)...] [--start-after=<marker>]
|
||||
[ --stdin | (<pattern>...)]
|
||||
git refs exists <ref>
|
||||
git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]
|
||||
git refs create [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value>
|
||||
git refs delete [--message=<reason>] [--no-deref] <ref> [<old-value>]
|
||||
git refs update [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value> [<old-value>]
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ with `flags` set to `0`.
|
|||
Start an option group. `description` is a short string that
|
||||
describes the group or an empty string.
|
||||
Start the description with an upper-case letter.
|
||||
Groups apply to options and subcommands that have a help string.
|
||||
|
||||
`OPT_HIDDEN_GROUP(description)`::
|
||||
Like `OPT_GROUP()`, but the group header carries
|
||||
|
|
@ -362,7 +363,8 @@ with `flags` set to `0`.
|
|||
|
||||
`OPT_SUBCOMMAND(long, &fn_ptr, subcommand_fn)`::
|
||||
Define a subcommand. `subcommand_fn` is put into `fn_ptr` when
|
||||
this subcommand is used.
|
||||
this subcommand is used. The subcommand is not listed in the
|
||||
usage output.
|
||||
|
||||
The last element of the array must be `OPT_END()`.
|
||||
|
||||
|
|
|
|||
|
|
@ -383,9 +383,9 @@ int cmd_refs(int argc,
|
|||
const char * const refs_usage[] = {
|
||||
REFS_MIGRATE_USAGE,
|
||||
REFS_VERIFY_USAGE,
|
||||
REFS_OPTIMIZE_USAGE,
|
||||
"git refs list " COMMON_USAGE_FOR_EACH_REF,
|
||||
REFS_EXISTS_USAGE,
|
||||
REFS_OPTIMIZE_USAGE,
|
||||
REFS_CREATE_USAGE,
|
||||
REFS_DELETE_USAGE,
|
||||
REFS_UPDATE_USAGE,
|
||||
|
|
@ -394,15 +394,27 @@ int cmd_refs(int argc,
|
|||
};
|
||||
parse_opt_subcommand_fn *fn = NULL;
|
||||
struct option opts[] = {
|
||||
OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
|
||||
OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
|
||||
OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
|
||||
OPT_SUBCOMMAND("exists", &fn, cmd_refs_exists),
|
||||
OPT_SUBCOMMAND("optimize", &fn, cmd_refs_optimize),
|
||||
OPT_SUBCOMMAND("create", &fn, cmd_refs_create),
|
||||
OPT_SUBCOMMAND("delete", &fn, cmd_refs_delete),
|
||||
OPT_SUBCOMMAND("update", &fn, cmd_refs_update),
|
||||
OPT_SUBCOMMAND("rename", &fn, cmd_refs_rename),
|
||||
OPT_GROUP(N_("Reference database")),
|
||||
OPT_SUBCOMMAND_F("migrate", &fn, cmd_refs_migrate,
|
||||
N_("migrate the reference database to a different format"), 0),
|
||||
OPT_SUBCOMMAND_F("verify", &fn, cmd_refs_verify,
|
||||
N_("verify the consistency of the reference database"), 0),
|
||||
OPT_SUBCOMMAND_F("optimize", &fn, cmd_refs_optimize,
|
||||
N_("optimize the reference database"), 0),
|
||||
OPT_GROUP(N_("Reading references")),
|
||||
OPT_SUBCOMMAND_F("list", &fn, cmd_refs_list,
|
||||
N_("list references"), 0),
|
||||
OPT_SUBCOMMAND_F("exists", &fn, cmd_refs_exists,
|
||||
N_("check whether a reference exists"), 0),
|
||||
OPT_GROUP(N_("Writing references")),
|
||||
OPT_SUBCOMMAND_F("create", &fn, cmd_refs_create,
|
||||
N_("create a new reference"), 0),
|
||||
OPT_SUBCOMMAND_F("delete", &fn, cmd_refs_delete,
|
||||
N_("delete a reference"), 0),
|
||||
OPT_SUBCOMMAND_F("update", &fn, cmd_refs_update,
|
||||
N_("update an existing reference"), 0),
|
||||
OPT_SUBCOMMAND_F("rename", &fn, cmd_refs_rename,
|
||||
N_("rename a reference"), 0),
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1940,7 +1940,7 @@ int cmd_remote(int argc,
|
|||
OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")),
|
||||
OPT_SUBCOMMAND("add", &fn, add),
|
||||
OPT_SUBCOMMAND("rename", &fn, mv),
|
||||
OPT_SUBCOMMAND_F("rm", &fn, rm, PARSE_OPT_NOCOMPLETE),
|
||||
OPT_SUBCOMMAND_F("rm", &fn, rm, NULL, PARSE_OPT_NOCOMPLETE),
|
||||
OPT_SUBCOMMAND("remove", &fn, rm),
|
||||
OPT_SUBCOMMAND("set-head", &fn, set_head),
|
||||
OPT_SUBCOMMAND("set-branches", &fn, set_branches),
|
||||
|
|
|
|||
|
|
@ -2431,7 +2431,7 @@ int cmd_stash(int argc,
|
|||
OPT_SUBCOMMAND("push", &fn, push_stash_unassumed),
|
||||
OPT_SUBCOMMAND("export", &fn, export_stash),
|
||||
OPT_SUBCOMMAND("import", &fn, import_stash),
|
||||
OPT_SUBCOMMAND_F("save", &fn, save_stash, PARSE_OPT_NOCOMPLETE),
|
||||
OPT_SUBCOMMAND_F("save", &fn, save_stash, NULL, PARSE_OPT_NOCOMPLETE),
|
||||
OPT_END()
|
||||
};
|
||||
const char **args_copy;
|
||||
|
|
|
|||
176
parse-options.c
176
parse-options.c
|
|
@ -845,6 +845,7 @@ static int show_gitcomp(const struct option *opts, int show_all)
|
|||
{
|
||||
const struct option *original_opts = opts;
|
||||
int nr_noopts = 0;
|
||||
bool shown = false;
|
||||
|
||||
for (; opts->type != OPTION_END; opts++) {
|
||||
const char *prefix = "--";
|
||||
|
|
@ -882,8 +883,9 @@ static int show_gitcomp(const struct option *opts, int show_all)
|
|||
suffix = "=";
|
||||
if (starts_with(opts->long_name, "no-"))
|
||||
nr_noopts++;
|
||||
printf("%s%s%s%s", opts == original_opts ? "" : " ",
|
||||
printf("%s%s%s%s", shown ? " " : "",
|
||||
prefix, opts->long_name, suffix);
|
||||
shown = true;
|
||||
}
|
||||
show_negated_gitcomp(original_opts, show_all, -1);
|
||||
show_negated_gitcomp(original_opts, show_all, nr_noopts);
|
||||
|
|
@ -1323,6 +1325,100 @@ static const struct option *find_option_by_long_name(const struct option *opts,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static int usage_print_flag(const struct option *opt,
|
||||
FILE *outfile,
|
||||
const char **positive_name)
|
||||
{
|
||||
int off = 0;
|
||||
|
||||
if (opt->short_name) {
|
||||
if (opt->flags & PARSE_OPT_NODASH)
|
||||
off += fprintf(outfile, "%c", opt->short_name);
|
||||
else
|
||||
off += fprintf(outfile, "-%c", opt->short_name);
|
||||
}
|
||||
if (opt->long_name && opt->short_name)
|
||||
off += fprintf(outfile, ", ");
|
||||
if (opt->long_name) {
|
||||
const char *long_name = opt->long_name;
|
||||
if ((opt->flags & PARSE_OPT_NONEG) ||
|
||||
skip_prefix(long_name, "no-", positive_name))
|
||||
off += fprintf(outfile, "--%s", long_name);
|
||||
else
|
||||
off += fprintf(outfile, "--[no-]%s", long_name);
|
||||
}
|
||||
|
||||
if (opt->type == OPTION_NUMBER)
|
||||
off += utf8_fprintf(outfile, _("-NUM"));
|
||||
|
||||
if ((opt->flags & PARSE_OPT_LITERAL_ARGHELP) ||
|
||||
!(opt->flags & PARSE_OPT_NOARG))
|
||||
off += usage_argh(opt, outfile);
|
||||
|
||||
return off;
|
||||
}
|
||||
|
||||
static void usage_print_option(const struct option *opt,
|
||||
const struct option *all_opts,
|
||||
int full,
|
||||
int *need_newline,
|
||||
FILE *outfile)
|
||||
{
|
||||
const char *positive_name = NULL;
|
||||
const char *cp, *np;
|
||||
size_t pos;
|
||||
|
||||
if (opt->type == OPTION_SUBCOMMAND && !opt->help)
|
||||
return;
|
||||
if (!full && (opt->flags & PARSE_OPT_HIDDEN))
|
||||
return;
|
||||
if (opt->type == OPTION_GROUP) {
|
||||
fputc('\n', outfile);
|
||||
*need_newline = 0;
|
||||
if (*opt->help)
|
||||
fprintf(outfile, "%s\n", _(opt->help));
|
||||
return;
|
||||
}
|
||||
|
||||
if (*need_newline) {
|
||||
fputc('\n', outfile);
|
||||
*need_newline = 0;
|
||||
}
|
||||
|
||||
pos = usage_indent(outfile);
|
||||
if (opt->type == OPTION_SUBCOMMAND)
|
||||
pos += fprintf(outfile, "%s", opt->long_name);
|
||||
else
|
||||
pos += usage_print_flag(opt, outfile, &positive_name);
|
||||
|
||||
if (opt->type == OPTION_ALIAS) {
|
||||
usage_padding(outfile, pos);
|
||||
fprintf_ln(outfile, _("alias of --%s"),
|
||||
(const char *)opt->value);
|
||||
return;
|
||||
}
|
||||
|
||||
for (cp = opt->help ? _(opt->help) : ""; *cp; cp = np) {
|
||||
np = strchrnul(cp, '\n');
|
||||
if (*np)
|
||||
np++;
|
||||
usage_padding(outfile, pos);
|
||||
fwrite(cp, 1, np - cp, outfile);
|
||||
pos = 0;
|
||||
}
|
||||
fputc('\n', outfile);
|
||||
|
||||
if (positive_name) {
|
||||
if (find_option_by_long_name(all_opts, positive_name))
|
||||
return;
|
||||
pos = usage_indent(outfile);
|
||||
pos += fprintf(outfile, "--%s", positive_name);
|
||||
usage_padding(outfile, pos);
|
||||
fprintf_ln(outfile, _("opposite of --no-%s"),
|
||||
positive_name);
|
||||
}
|
||||
}
|
||||
|
||||
static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx,
|
||||
const char * const *usagestr,
|
||||
const struct option *opts,
|
||||
|
|
@ -1408,81 +1504,9 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
|
|||
}
|
||||
|
||||
need_newline = 1;
|
||||
|
||||
for (; opts->type != OPTION_END; opts++) {
|
||||
size_t pos;
|
||||
const char *cp, *np;
|
||||
const char *positive_name = NULL;
|
||||
|
||||
if (opts->type == OPTION_SUBCOMMAND)
|
||||
continue;
|
||||
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
|
||||
continue;
|
||||
if (opts->type == OPTION_GROUP) {
|
||||
fputc('\n', outfile);
|
||||
need_newline = 0;
|
||||
if (*opts->help)
|
||||
fprintf(outfile, "%s\n", _(opts->help));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (need_newline) {
|
||||
fputc('\n', outfile);
|
||||
need_newline = 0;
|
||||
}
|
||||
|
||||
pos = usage_indent(outfile);
|
||||
if (opts->short_name) {
|
||||
if (opts->flags & PARSE_OPT_NODASH)
|
||||
pos += fprintf(outfile, "%c", opts->short_name);
|
||||
else
|
||||
pos += fprintf(outfile, "-%c", opts->short_name);
|
||||
}
|
||||
if (opts->long_name && opts->short_name)
|
||||
pos += fprintf(outfile, ", ");
|
||||
if (opts->long_name) {
|
||||
const char *long_name = opts->long_name;
|
||||
if ((opts->flags & PARSE_OPT_NONEG) ||
|
||||
skip_prefix(long_name, "no-", &positive_name))
|
||||
pos += fprintf(outfile, "--%s", long_name);
|
||||
else
|
||||
pos += fprintf(outfile, "--[no-]%s", long_name);
|
||||
}
|
||||
|
||||
if (opts->type == OPTION_NUMBER)
|
||||
pos += utf8_fprintf(outfile, _("-NUM"));
|
||||
|
||||
if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
|
||||
!(opts->flags & PARSE_OPT_NOARG))
|
||||
pos += usage_argh(opts, outfile);
|
||||
|
||||
if (opts->type == OPTION_ALIAS) {
|
||||
usage_padding(outfile, pos);
|
||||
fprintf_ln(outfile, _("alias of --%s"),
|
||||
(const char *)opts->value);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (cp = opts->help ? _(opts->help) : ""; *cp; cp = np) {
|
||||
np = strchrnul(cp, '\n');
|
||||
if (*np)
|
||||
np++;
|
||||
usage_padding(outfile, pos);
|
||||
fwrite(cp, 1, np - cp, outfile);
|
||||
pos = 0;
|
||||
}
|
||||
fputc('\n', outfile);
|
||||
|
||||
if (positive_name) {
|
||||
if (find_option_by_long_name(all_opts, positive_name))
|
||||
continue;
|
||||
pos = usage_indent(outfile);
|
||||
pos += fprintf(outfile, "--%s", positive_name);
|
||||
usage_padding(outfile, pos);
|
||||
fprintf_ln(outfile, _("opposite of --no-%s"),
|
||||
positive_name);
|
||||
}
|
||||
}
|
||||
for (; opts->type != OPTION_END; opts++)
|
||||
usage_print_option(opts, all_opts, full,
|
||||
&need_newline, outfile);
|
||||
fputc('\n', outfile);
|
||||
|
||||
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
|
||||
|
|
|
|||
|
|
@ -396,14 +396,15 @@ static char *parse_options_noop_ignored_value MAYBE_UNUSED;
|
|||
|
||||
#define OPT_ALIAS(s, l, source_long_name) OPT_ALIAS_F(s, l, source_long_name, 0)
|
||||
|
||||
#define OPT_SUBCOMMAND_F(l, v, fn, f) { \
|
||||
#define OPT_SUBCOMMAND_F(l, v, fn, h, f) { \
|
||||
.type = OPTION_SUBCOMMAND, \
|
||||
.long_name = (l), \
|
||||
.value = (v), \
|
||||
.help = (h), \
|
||||
.flags = (f), \
|
||||
.subcommand_fn = (fn), \
|
||||
}
|
||||
#define OPT_SUBCOMMAND(l, v, fn) OPT_SUBCOMMAND_F((l), (v), (fn), 0)
|
||||
#define OPT_SUBCOMMAND(l, v, fn) OPT_SUBCOMMAND_F((l), (v), (fn), NULL, 0)
|
||||
|
||||
/*
|
||||
* parse_options() will filter out the processed options and leave the
|
||||
|
|
|
|||
|
|
@ -351,8 +351,10 @@ static int parse_subcommand__cmd(int argc, const char **argv,
|
|||
parse_opt_subcommand_fn *fn = NULL;
|
||||
int opt = 0;
|
||||
struct option options[] = {
|
||||
OPT_SUBCOMMAND("subcmd-one", &fn, subcmd_one),
|
||||
OPT_GROUP("Subcommands"),
|
||||
OPT_SUBCOMMAND_F("subcmd-one", &fn, subcmd_one, "the first subcommand", 0),
|
||||
OPT_SUBCOMMAND("subcmd-two", &fn, subcmd_two),
|
||||
OPT_GROUP("Options"),
|
||||
OPT_INTEGER('o', "opt", &opt, "an integer option"),
|
||||
OPT_END()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -629,6 +629,22 @@ test_expect_success 'KEEP_UNKNOWN_OPT | NO_INTERNAL_HELP works' '
|
|||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'subcommand - usage lists subcommands with help text under their group' '
|
||||
test-tool parse-subcommand cmd -h >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
usage: <...> cmd subcmd-one
|
||||
or: <...> cmd subcmd-two
|
||||
|
||||
Subcommands
|
||||
subcmd-one the first subcommand
|
||||
|
||||
Options
|
||||
-o, --[no-]opt <n> an integer option
|
||||
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'subcommand - no subcommand shows error and usage' '
|
||||
test_expect_code 129 test-tool parse-subcommand cmd 2>err &&
|
||||
test_grep "^error: need a subcommand" err &&
|
||||
|
|
|
|||
Loading…
Reference in New Issue