Re: [PATCH v2 09/20] parse-options: add support for parsing subcommands
From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-08-19 18:02:52
On Fri, Aug 19 2022, SZEDER Gábor wrote:
+static enum parse_opt_result parse_subcommand(const char *arg,
+ const struct option *options)
+{
+ for (; options->type != OPTION_END; options++)
+ if (options->type == OPTION_SUBCOMMAND &&
+ !strcmp(options->long_name, arg)) {
+ *(parse_opt_subcommand_fn **)options->value = options->subcommand_fn;
Nit: Maybe do the cast by assigning to an intermediate variable? Would
make this less dense, and since we already have {}-braces...
quoted hunk ↗ jump to hunk
+ return PARSE_OPT_SUBCOMMAND; + } + + return PARSE_OPT_UNKNOWN; +} + static void check_typos(const char *arg, const struct option *options) { if (strlen(arg) < 3)@@ -442,6 +457,7 @@ static void check_typos(const char *arg, const struct option *options) static void parse_options_check(const struct option *opts) { char short_opts[128]; + void *subcommand_value = NULL; memset(short_opts, '\0', sizeof(short_opts)); for (; opts->type != OPTION_END; opts++) {@@ -489,6 +505,14 @@ static void parse_options_check(const struct option *opts) "Are you using parse_options_step() directly?\n" "That case is not supported yet."); break; + case OPTION_SUBCOMMAND: + if (!opts->value || !opts->subcommand_fn) + optbug(opts, "OPTION_SUBCOMMAND needs a value and a subcommand function");
Better split into two IMO: if (!opts->value) optbug(opts, "OPTION_SUBCOMMAND needs a value"); if (!opts->subcommand_fn) optbug(opts, "OPTION_SUBCOMMAND needs a subcommand_fn"); Then if we have extra checks we don't need to keep adding to a dense || and amend an existing string.
+static int has_subcommands(const struct option *options)
+{
+ for (; options->type != OPTION_END; options++)
+ if (options->type == OPTION_SUBCOMMAND)
+ return 1;
+ return 0;
+}I wondered if parse_options_check() couldn't take a "int *has_subcommands", since we already loop through the options to check it as part of the checks there (but we'd need to re-arrange them). Not for optimization, just wondering if it would make the code flow clearer, maybe not.
+ if (ctx->has_subcommands) {
+ if (flags & PARSE_OPT_STOP_AT_NON_OPTION)
+ BUG("subcommands are incompatible with PARSE_OPT_STOP_AT_NON_OPTION");
+ if (!(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) {
+ if (flags & PARSE_OPT_KEEP_UNKNOWN_OPT)
+ BUG("subcommands are incompatible with PARSE_OPT_KEEP_UNKNOWN_OPT unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL");
+ if (flags & PARSE_OPT_KEEP_DASHDASH)
+ BUG("subcommands are incompatible with PARSE_OPT_KEEP_DASHDASH unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL");
MM, very long lines. Maybe something like:
BUG("%s flag cannot be combined with %s, unless %s is also provided", ...);
quoted hunk ↗ jump to hunk
+ } + } if ((flags & PARSE_OPT_KEEP_UNKNOWN_OPT) && (flags & PARSE_OPT_STOP_AT_NON_OPTION) && !(flags & PARSE_OPT_ONE_SHOT))@@ -589,6 +634,7 @@ static int show_gitcomp(const struct option *opts, int show_all) int nr_noopts = 0; for (; opts->type != OPTION_END; opts++) { + const char *prefix = "--";
Ah, here we have a prefix variable, but it's more of an "infix"... :)
+ return PARSE_OPT_DONE;
+ error(_("unknown subcommand: `%s'"), arg);
+ usage_with_options(usagestr, options);ditto earlier patch comment about usage_msg_opt() (also applies below)
+ case PARSE_OPT_NON_OPTION: + /* Impossible. */
We could just skip this comment as...
+ BUG("parse_subcommand() cannot return these");...this makes it clear.
case PARSE_OPT_DONE:
+ if (ctx.has_subcommands &&
+ !(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) {
+ error(_("need a subcommand"));
+ usage_with_options(usagestr, options);ditto.
+typedef int parse_opt_subcommand_fn(int argc, const char **argv, + const char *prefix);
We usually define function typedefs like: typedef int (*parse_opt_subcommand_fn)(...); But I see that...
quoted hunk ↗ jump to hunk
enum parse_opt_type type;@@ -145,6 +155,7 @@ struct option { intptr_t defval; parse_opt_ll_cb *ll_callback; intptr_t extra; + parse_opt_subcommand_fn *subcommand_fn;
You're doing this consistently with parse_opt_ll_cb etc., fair enough.
+#define OPT_SUBCOMMAND_F(l, v, fn, f) { \
+ .type = OPTION_SUBCOMMAND, \
+ .long_name = (l), \
+ .value = (v), \
+ .flags = (f), \Nice to see this form
+ .subcommand_fn = (fn) }
Almost all other such decls end with: .foo = bar, \ } I.e. we put the closing "}" at the start of the line.
+#define OPT_SUBCOMMAND(l, v, fn) OPT_SUBCOMMAND_F((l), (v), (fn), 0)
I think this would be more readable as: #define OPT_SUBCOMMAND(l, v, fn) \ OPT_SUBCOMMAND_F((l), (v), (fn), 0) Which both aligns nicely, and isn't using a \t in the middle of the line (which in any case we'll end up aligning with nothing else, as the names are all different lengths, if other things continue using this pattern.
enum parse_opt_flags flags; + unsigned has_subcommands;
Maybe make it: "unsigned has_subcommands:1"?
+ printf("fn: subcmd_one\n");s/printf/puts/g here
+ print_args(argc, argv);
+ return 0;
+}
+
+static int subcmd_two(int argc, const char **argv, const char *prefix)
+{
+ printf("fn: subcmd_two\n");ditto.