diff options
Diffstat (limited to 'parse-options.c')
| -rw-r--r-- | parse-options.c | 356 |
1 files changed, 236 insertions, 120 deletions
diff --git a/parse-options.c b/parse-options.c index 60fae3ad21..edf55d3ef5 100644 --- a/parse-options.c +++ b/parse-options.c @@ -8,23 +8,43 @@ static int disallow_abbreviated_options; -#define OPT_SHORT 1 -#define OPT_UNSET 2 +enum opt_parsed { + OPT_LONG = 0, + OPT_SHORT = 1<<0, + OPT_UNSET = 1<<1, +}; -int optbug(const struct option *opt, const char *reason) +static void optbug(const struct option *opt, const char *reason) { - if (opt->long_name) { - if (opt->short_name) - return error("BUG: switch '%c' (--%s) %s", - opt->short_name, opt->long_name, reason); - return error("BUG: option '%s' %s", opt->long_name, reason); - } - return error("BUG: switch '%c' %s", opt->short_name, reason); + if (opt->long_name && opt->short_name) + bug("switch '%c' (--%s) %s", opt->short_name, + opt->long_name, reason); + else if (opt->long_name) + bug("option '%s' %s", opt->long_name, reason); + else + bug("switch '%c' %s", opt->short_name, reason); +} + +static const char *optname(const struct option *opt, enum opt_parsed flags) +{ + static struct strbuf sb = STRBUF_INIT; + + strbuf_reset(&sb); + if (flags & OPT_SHORT) + strbuf_addf(&sb, "switch `%c'", opt->short_name); + else if (flags & OPT_UNSET) + strbuf_addf(&sb, "option `no-%s'", opt->long_name); + else if (flags == OPT_LONG) + strbuf_addf(&sb, "option `%s'", opt->long_name); + else + BUG("optname() got unknown flags %d", flags); + + return sb.buf; } static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p, const struct option *opt, - int flags, const char **arg) + enum opt_parsed flags, const char **arg) { if (p->opt) { *arg = p->opt; @@ -50,7 +70,7 @@ static void fix_filename(const char *prefix, const char **file) static enum parse_opt_result opt_command_mode_error( const struct option *opt, const struct option *all_opts, - int flags) + enum opt_parsed flags) { const struct option *that; struct strbuf that_name = STRBUF_INIT; @@ -61,7 +81,7 @@ static enum parse_opt_result opt_command_mode_error( */ for (that = all_opts; that->type != OPTION_END; that++) { if (that == opt || - that->type != OPTION_CMDMODE || + !(that->flags & PARSE_OPT_CMDMODE) || that->value != opt->value || that->defval != *(int *)opt->value) continue; @@ -82,7 +102,7 @@ static enum parse_opt_result opt_command_mode_error( static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, const struct option *opt, const struct option *all_opts, - int flags) + enum opt_parsed flags) { const char *s, *arg; const int unset = flags & OPT_UNSET; @@ -95,6 +115,14 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG)) return error(_("%s takes no value"), optname(opt, flags)); + /* + * Giving the same mode option twice, although unnecessary, + * is not a grave error, so let it pass. + */ + if ((opt->flags & PARSE_OPT_CMDMODE) && + *(int *)opt->value && *(int *)opt->value != opt->defval) + return opt_command_mode_error(opt, all_opts, flags); + switch (opt->type) { case OPTION_LOWLEVEL_CALLBACK: return opt->ll_callback(p, opt, NULL, unset); @@ -130,16 +158,6 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, *(int *)opt->value = unset ? 0 : opt->defval; return 0; - case OPTION_CMDMODE: - /* - * Giving the same mode option twice, although is unnecessary, - * is not a grave error, so let it pass. - */ - if (*(int *)opt->value && *(int *)opt->value != opt->defval) - return opt_command_mode_error(opt, all_opts, flags); - *(int *)opt->value = opt->defval; - return 0; - case OPTION_STRING: if (unset) *(const char **)opt->value = NULL; @@ -300,11 +318,11 @@ static enum parse_opt_result parse_long_opt( const struct option *all_opts = options; const char *arg_end = strchrnul(arg, '='); const struct option *abbrev_option = NULL, *ambiguous_option = NULL; - int abbrev_flags = 0, ambiguous_flags = 0; + enum opt_parsed abbrev_flags = OPT_LONG, ambiguous_flags = OPT_LONG; for (; options->type != OPTION_END; options++) { const char *rest, *long_name = options->long_name; - int flags = 0, opt_flags = 0; + enum opt_parsed flags = OPT_LONG, opt_flags = OPT_LONG; if (!long_name) continue; @@ -312,19 +330,6 @@ static enum parse_opt_result parse_long_opt( again: if (!skip_prefix(arg, long_name, &rest)) rest = NULL; - if (options->type == OPTION_ARGUMENT) { - if (!rest) - continue; - if (*rest == '=') - return error(_("%s takes no value"), - optname(options, flags)); - if (*rest) - continue; - if (options->value) - *(int *)options->value = options->defval; - p->out[p->cpidx++] = arg - 2; - return PARSE_OPT_DONE; - } if (!rest) { /* abbreviated? */ if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) && @@ -357,8 +362,7 @@ is_abbreviated: } /* negated? */ if (!starts_with(arg, "no-")) { - if (starts_with(long_name, "no-")) { - long_name += 3; + if (skip_prefix(long_name, "no-", &long_name)) { opt_flags |= OPT_UNSET; goto again; } @@ -400,8 +404,9 @@ is_abbreviated: return PARSE_OPT_UNKNOWN; } -static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg, - const struct option *options) +static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p, + const char *arg, + const struct option *options) { const struct option *all_opts = options; @@ -411,7 +416,7 @@ static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg, if (options->short_name == arg[0] && arg[1] == '\0') return get_value(p, options, all_opts, OPT_SHORT); } - return -2; + return PARSE_OPT_ERROR; } static void check_typos(const char *arg, const struct option *options) @@ -420,7 +425,7 @@ static void check_typos(const char *arg, const struct option *options) return; if (starts_with(arg, "no-")) { - error(_("did you mean `--%s` (with two dashes ?)"), arg); + error(_("did you mean `--%s` (with two dashes)?"), arg); exit(129); } @@ -428,7 +433,7 @@ static void check_typos(const char *arg, const struct option *options) if (!options->long_name) continue; if (starts_with(options->long_name, arg)) { - error(_("did you mean `--%s` (with two dashes ?)"), arg); + error(_("did you mean `--%s` (with two dashes)?"), arg); exit(129); } } @@ -436,28 +441,27 @@ static void check_typos(const char *arg, const struct option *options) static void parse_options_check(const struct option *opts) { - int err = 0; char short_opts[128]; memset(short_opts, '\0', sizeof(short_opts)); for (; opts->type != OPTION_END; opts++) { if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) && (opts->flags & PARSE_OPT_OPTARG)) - err |= optbug(opts, "uses incompatible flags " - "LASTARG_DEFAULT and OPTARG"); + optbug(opts, "uses incompatible flags " + "LASTARG_DEFAULT and OPTARG"); if (opts->short_name) { if (0x7F <= opts->short_name) - err |= optbug(opts, "invalid short name"); + optbug(opts, "invalid short name"); else if (short_opts[opts->short_name]++) - err |= optbug(opts, "short name already used"); + optbug(opts, "short name already used"); } if (opts->flags & PARSE_OPT_NODASH && ((opts->flags & PARSE_OPT_OPTARG) || !(opts->flags & PARSE_OPT_NOARG) || !(opts->flags & PARSE_OPT_NONEG) || opts->long_name)) - err |= optbug(opts, "uses feature " - "not supported for dashless options"); + optbug(opts, "uses feature " + "not supported for dashless options"); switch (opts->type) { case OPTION_COUNTUP: case OPTION_BIT: @@ -466,38 +470,39 @@ static void parse_options_check(const struct option *opts) case OPTION_NUMBER: if ((opts->flags & PARSE_OPT_OPTARG) || !(opts->flags & PARSE_OPT_NOARG)) - err |= optbug(opts, "should not accept an argument"); + optbug(opts, "should not accept an argument"); break; case OPTION_CALLBACK: if (!opts->callback && !opts->ll_callback) - BUG("OPTION_CALLBACK needs one callback"); - if (opts->callback && opts->ll_callback) - BUG("OPTION_CALLBACK can't have two callbacks"); + optbug(opts, "OPTION_CALLBACK needs one callback"); + else if (opts->callback && opts->ll_callback) + optbug(opts, "OPTION_CALLBACK can't have two callbacks"); break; case OPTION_LOWLEVEL_CALLBACK: if (!opts->ll_callback) - BUG("OPTION_LOWLEVEL_CALLBACK needs a callback"); + optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs a callback"); if (opts->callback) - BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback"); + optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs no high level callback"); break; case OPTION_ALIAS: - BUG("OPT_ALIAS() should not remain at this point. " - "Are you using parse_options_step() directly?\n" - "That case is not supported yet."); + optbug(opts, "OPT_ALIAS() should not remain at this point. " + "Are you using parse_options_step() directly?\n" + "That case is not supported yet."); + break; default: ; /* ok. (usually accepts an argument) */ } if (opts->argh && strcspn(opts->argh, " _") != strlen(opts->argh)) - err |= optbug(opts, "multi-word argh should use dash to separate words"); + optbug(opts, "multi-word argh should use dash to separate words"); } - if (err) - exit(128); + BUG_if_bug("invalid 'struct option'"); } static void parse_options_start_1(struct parse_opt_ctx_t *ctx, int argc, const char **argv, const char *prefix, - const struct option *options, int flags) + const struct option *options, + enum parse_opt_flags flags) { ctx->argc = argc; ctx->argv = argv; @@ -522,13 +527,15 @@ static void parse_options_start_1(struct parse_opt_ctx_t *ctx, void parse_options_start(struct parse_opt_ctx_t *ctx, int argc, const char **argv, const char *prefix, - const struct option *options, int flags) + const struct option *options, + enum parse_opt_flags flags) { memset(ctx, 0, sizeof(*ctx)); parse_options_start_1(ctx, argc, argv, prefix, options, flags); } -static void show_negated_gitcomp(const struct option *opts, int nr_noopts) +static void show_negated_gitcomp(const struct option *opts, int show_all, + int nr_noopts) { int printed_dashdash = 0; @@ -538,7 +545,8 @@ static void show_negated_gitcomp(const struct option *opts, int nr_noopts) if (!opts->long_name) continue; - if (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE)) + if (!show_all && + (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE))) continue; if (opts->flags & PARSE_OPT_NONEG) continue; @@ -575,7 +583,7 @@ static void show_negated_gitcomp(const struct option *opts, int nr_noopts) } } -static int show_gitcomp(const struct option *opts) +static int show_gitcomp(const struct option *opts, int show_all) { const struct option *original_opts = opts; int nr_noopts = 0; @@ -585,7 +593,8 @@ static int show_gitcomp(const struct option *opts) if (!opts->long_name) continue; - if (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE)) + if (!show_all && + (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE | PARSE_OPT_FROM_ALIAS))) continue; switch (opts->type) { @@ -613,8 +622,8 @@ static int show_gitcomp(const struct option *opts) nr_noopts++; printf(" --%s%s", opts->long_name, suffix); } - show_negated_gitcomp(original_opts, -1); - show_negated_gitcomp(original_opts, nr_noopts); + show_negated_gitcomp(original_opts, show_all, -1); + show_negated_gitcomp(original_opts, show_all, nr_noopts); fputc('\n', stdout); return PARSE_OPT_COMPLETE; } @@ -625,6 +634,8 @@ static int show_gitcomp(const struct option *opts) * * Right now this is only used to preprocess and substitute * OPTION_ALIAS. + * + * The returned options should be freed using free_preprocessed_options. */ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, const struct option *options) @@ -651,6 +662,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, int short_name; const char *long_name; const char *source; + struct strbuf help = STRBUF_INIT; int j; if (newopt[i].type != OPTION_ALIAS) @@ -662,6 +674,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, if (!long_name) BUG("An alias must have long option name"); + strbuf_addf(&help, _("alias of --%s"), source); for (j = 0; j < nr; j++) { const char *name = options[j].long_name; @@ -672,15 +685,11 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, if (options[j].type == OPTION_ALIAS) BUG("No please. Nested aliases are not supported."); - /* - * NEEDSWORK: this is a bit inconsistent because - * usage_with_options() on the original options[] will print - * help string as "alias of %s" but "git cmd -h" will - * print the original help string. - */ memcpy(newopt + i, options + j, sizeof(*newopt)); newopt[i].short_name = short_name; newopt[i].long_name = long_name; + newopt[i].help = strbuf_detach(&help, NULL); + newopt[i].flags |= PARSE_OPT_FROM_ALIAS; break; } @@ -696,13 +705,28 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, return newopt; } -static int usage_with_options_internal(struct parse_opt_ctx_t *, - const char * const *, - const struct option *, int, int); +static void free_preprocessed_options(struct option *options) +{ + int i; + + if (!options) + return; + + for (i = 0; options[i].type != OPTION_END; i++) { + if (options[i].flags & PARSE_OPT_FROM_ALIAS) + free((void *)options[i].help); + } + free(options); +} + +static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *, + const char * const *, + const struct option *, + int, int); -int parse_options_step(struct parse_opt_ctx_t *ctx, - const struct option *options, - const char * const usagestr[]) +enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, + const struct option *options, + const char * const usagestr[]) { int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP); @@ -729,9 +753,14 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, if (internal_help && ctx->total == 1 && !strcmp(arg + 1, "h")) goto show_usage; - /* lone --git-completion-helper is asked by git-completion.bash */ - if (ctx->total == 1 && !strcmp(arg + 1, "-git-completion-helper")) - return show_gitcomp(options); + /* + * lone --git-completion-helper and --git-completion-helper-all + * are asked by git-completion.bash + */ + if (ctx->total == 1 && !strcmp(arg, "--git-completion-helper")) + return show_gitcomp(options, 0); + if (ctx->total == 1 && !strcmp(arg, "--git-completion-helper-all")) + return show_gitcomp(options, 1); if (arg[1] != '-') { ctx->opt = arg + 1; @@ -831,9 +860,11 @@ int parse_options_end(struct parse_opt_ctx_t *ctx) return ctx->cpidx + ctx->argc; } -int parse_options(int argc, const char **argv, const char *prefix, - const struct option *options, const char * const usagestr[], - int flags) +int parse_options(int argc, const char **argv, + const char *prefix, + const struct option *options, + const char * const usagestr[], + enum parse_opt_flags flags) { struct parse_opt_ctx_t ctx; struct option *real_options; @@ -855,7 +886,7 @@ int parse_options(int argc, const char **argv, const char *prefix, case PARSE_OPT_NON_OPTION: case PARSE_OPT_DONE: break; - default: /* PARSE_OPT_UNKNOWN */ + case PARSE_OPT_UNKNOWN: if (ctx.argv[0][1] == '-') { error(_("unknown option `%s'"), ctx.argv[0] + 2); } else if (isascii(*ctx.opt)) { @@ -867,8 +898,8 @@ int parse_options(int argc, const char **argv, const char *prefix, usage_with_options(usagestr, options); } - precompose_argv(argc, argv); - free(real_options); + precompose_argv_prefix(argc, argv, NULL); + free_preprocessed_options(real_options); free(ctx.alias_groups); return parse_options_end(&ctx); } @@ -891,32 +922,85 @@ static int usage_argh(const struct option *opts, FILE *outfile) #define USAGE_OPTS_WIDTH 24 #define USAGE_GAP 2 -static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, - const char * const *usagestr, - const struct option *opts, int full, int err) +static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx, + const char * const *usagestr, + const struct option *opts, + int full, int err) { FILE *outfile = err ? stderr : stdout; int need_newline; + const char *usage_prefix = _("usage: %s"); + /* + * The translation could be anything, but we can count on + * msgfmt(1)'s --check option to have asserted that "%s" is in + * the translation. So compute the length of the "usage: " + * part. We are assuming that the translator wasn't overly + * clever and used e.g. "%1$s" instead of "%s", there's only + * one "%s" in "usage_prefix" above, so there's no reason to + * do so even with a RTL language. + */ + size_t usage_len = strlen(usage_prefix) - strlen("%s"); + /* + * TRANSLATORS: the colon here should align with the + * one in "usage: %s" translation. + */ + const char *or_prefix = _(" or: %s"); + /* + * TRANSLATORS: You should only need to translate this format + * string if your language is a RTL language (e.g. Arabic, + * Hebrew etc.), not if it's a LTR language (e.g. German, + * Russian, Chinese etc.). + * + * When a translated usage string has an embedded "\n" it's + * because options have wrapped to the next line. The line + * after the "\n" will then be padded to align with the + * command name, such as N_("git cmd [opt]\n<8 + * spaces>[opt2]"), where the 8 spaces are the same length as + * "git cmd ". + * + * This format string prints out that already-translated + * line. The "%*s" is whitespace padding to account for the + * padding at the start of the line that we add in this + * function. The "%s" is a line in the (hopefully already + * translated) N_() usage string, which contained embedded + * newlines before we split it up. + */ + const char *usage_continued = _("%*s%s"); + const char *prefix = usage_prefix; + int saw_empty_line = 0; + if (!usagestr) return PARSE_OPT_HELP; if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL) fprintf(outfile, "cat <<\\EOF\n"); - fprintf_ln(outfile, _("usage: %s"), _(*usagestr++)); - while (*usagestr && **usagestr) - /* - * TRANSLATORS: the colon here should align with the - * one in "usage: %s" translation. - */ - fprintf_ln(outfile, _(" or: %s"), _(*usagestr++)); while (*usagestr) { - if (**usagestr) - fprintf_ln(outfile, _(" %s"), _(*usagestr)); - else - fputc('\n', outfile); - usagestr++; + const char *str = _(*usagestr++); + struct string_list list = STRING_LIST_INIT_DUP; + unsigned int j; + + if (!saw_empty_line && !*str) + saw_empty_line = 1; + + string_list_split(&list, str, '\n', -1); + for (j = 0; j < list.nr; j++) { + const char *line = list.items[j].string; + + if (saw_empty_line && *line) + fprintf_ln(outfile, _(" %s"), line); + else if (saw_empty_line) + fputc('\n', outfile); + else if (!j) + fprintf_ln(outfile, prefix, line); + else + fprintf_ln(outfile, usage_continued, + (int)usage_len, "", line); + } + string_list_clear(&list, 0); + + prefix = or_prefix; } need_newline = 1; @@ -991,21 +1075,53 @@ void NORETURN usage_msg_opt(const char *msg, const char * const *usagestr, const struct option *options) { - fprintf(stderr, "fatal: %s\n\n", msg); + die_message("%s\n", msg); /* The extra \n is intentional */ usage_with_options(usagestr, options); } -const char *optname(const struct option *opt, int flags) +void NORETURN usage_msg_optf(const char * const fmt, + const char * const *usagestr, + const struct option *options, ...) { - static struct strbuf sb = STRBUF_INIT; + struct strbuf msg = STRBUF_INIT; + va_list ap; + va_start(ap, options); + strbuf_vaddf(&msg, fmt, ap); + va_end(ap); - strbuf_reset(&sb); - if (flags & OPT_SHORT) - strbuf_addf(&sb, "switch `%c'", opt->short_name); - else if (flags & OPT_UNSET) - strbuf_addf(&sb, "option `no-%s'", opt->long_name); - else - strbuf_addf(&sb, "option `%s'", opt->long_name); + usage_msg_opt(msg.buf, usagestr, options); +} - return sb.buf; +void die_for_incompatible_opt4(int opt1, const char *opt1_name, + int opt2, const char *opt2_name, + int opt3, const char *opt3_name, + int opt4, const char *opt4_name) +{ + int count = 0; + const char *options[4]; + + if (opt1) + options[count++] = opt1_name; + if (opt2) + options[count++] = opt2_name; + if (opt3) + options[count++] = opt3_name; + if (opt4) + options[count++] = opt4_name; + switch (count) { + case 4: + die(_("options '%s', '%s', '%s', and '%s' cannot be used together"), + opt1_name, opt2_name, opt3_name, opt4_name); + break; + case 3: + die(_("options '%s', '%s', and '%s' cannot be used together"), + options[0], options[1], options[2]); + break; + case 2: + die(_("options '%s' and '%s' cannot be used together"), + options[0], options[1]); + break; + default: + break; + } } |
