git/parse-options.c
Jonathan Nieder a02dd4ff7d parse-options: move NODASH sanity checks to parse_options_check
A dashless switch (like '(' passed to 'git grep') cannot be negated,
cannot be attached to an argument, and cannot have a long form.
Currently parse-options runs the related sanity checks when the
dashless option is used; better to always check them at the start of
option parsing, so mistakes can be caught more quickly.

The error message at the new call site is less specific about the
nature of the error, for simplicity.  On the other hand, it prints
which switch was problematic.  Before:

	fatal: BUG: dashless options can't be long

After:

	error: BUG: switch '(' uses feature not supported for dashless options

Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2010-12-07 14:17:49 -08:00

687 lines
17 KiB
C

#include "git-compat-util.h"
#include "parse-options.h"
#include "cache.h"
#include "commit.h"
#include "color.h"
static int parse_options_usage(struct parse_opt_ctx_t *ctx,
const char * const *usagestr,
const struct option *opts, int err);
#define OPT_SHORT 1
#define OPT_UNSET 2
static int optbug(const struct option *opt, const char *reason)
{
if (opt->long_name)
return error("BUG: option '%s' %s", opt->long_name, reason);
return error("BUG: switch '%c' %s", opt->short_name, reason);
}
static int opterror(const struct option *opt, const char *reason, int flags)
{
if (flags & OPT_SHORT)
return error("switch `%c' %s", opt->short_name, reason);
if (flags & OPT_UNSET)
return error("option `no-%s' %s", opt->long_name, reason);
return error("option `%s' %s", opt->long_name, reason);
}
static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
int flags, const char **arg)
{
if (p->opt) {
*arg = p->opt;
p->opt = NULL;
} else if (p->argc == 1 && (opt->flags & PARSE_OPT_LASTARG_DEFAULT)) {
*arg = (const char *)opt->defval;
} else if (p->argc > 1) {
p->argc--;
*arg = *++p->argv;
} else
return opterror(opt, "requires a value", flags);
return 0;
}
static void fix_filename(const char *prefix, const char **file)
{
if (!file || !*file || !prefix || is_absolute_path(*file)
|| !strcmp("-", *file))
return;
*file = xstrdup(prefix_filename(prefix, strlen(prefix), *file));
}
static int get_value(struct parse_opt_ctx_t *p,
const struct option *opt, int flags)
{
const char *s, *arg;
const int unset = flags & OPT_UNSET;
int err;
if (unset && p->opt)
return opterror(opt, "takes no value", flags);
if (unset && (opt->flags & PARSE_OPT_NONEG))
return opterror(opt, "isn't available", flags);
if (!(flags & OPT_SHORT) && p->opt) {
switch (opt->type) {
case OPTION_CALLBACK:
if (!(opt->flags & PARSE_OPT_NOARG))
break;
/* FALLTHROUGH */
case OPTION_BOOLEAN:
case OPTION_BIT:
case OPTION_NEGBIT:
case OPTION_SET_INT:
case OPTION_SET_PTR:
return opterror(opt, "takes no value", flags);
default:
break;
}
}
switch (opt->type) {
case OPTION_BIT:
if (unset)
*(int *)opt->value &= ~opt->defval;
else
*(int *)opt->value |= opt->defval;
return 0;
case OPTION_NEGBIT:
if (unset)
*(int *)opt->value |= opt->defval;
else
*(int *)opt->value &= ~opt->defval;
return 0;
case OPTION_BOOLEAN:
*(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
return 0;
case OPTION_SET_INT:
*(int *)opt->value = unset ? 0 : opt->defval;
return 0;
case OPTION_SET_PTR:
*(void **)opt->value = unset ? NULL : (void *)opt->defval;
return 0;
case OPTION_STRING:
if (unset)
*(const char **)opt->value = NULL;
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
*(const char **)opt->value = (const char *)opt->defval;
else
return get_arg(p, opt, flags, (const char **)opt->value);
return 0;
case OPTION_FILENAME:
err = 0;
if (unset)
*(const char **)opt->value = NULL;
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
*(const char **)opt->value = (const char *)opt->defval;
else
err = get_arg(p, opt, flags, (const char **)opt->value);
if (!err)
fix_filename(p->prefix, (const char **)opt->value);
return err;
case OPTION_CALLBACK:
if (unset)
return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
if (opt->flags & PARSE_OPT_NOARG)
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
if (get_arg(p, opt, flags, &arg))
return -1;
return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
case OPTION_INTEGER:
if (unset) {
*(int *)opt->value = 0;
return 0;
}
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
*(int *)opt->value = opt->defval;
return 0;
}
if (get_arg(p, opt, flags, &arg))
return -1;
*(int *)opt->value = strtol(arg, (char **)&s, 10);
if (*s)
return opterror(opt, "expects a numerical value", flags);
return 0;
default:
die("should not happen, someone must be hit on the forehead");
}
}
static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
{
const struct option *numopt = NULL;
for (; options->type != OPTION_END; options++) {
if (options->short_name == *p->opt) {
p->opt = p->opt[1] ? p->opt + 1 : NULL;
return get_value(p, options, OPT_SHORT);
}
/*
* Handle the numerical option later, explicit one-digit
* options take precedence over it.
*/
if (options->type == OPTION_NUMBER)
numopt = options;
}
if (numopt && isdigit(*p->opt)) {
size_t len = 1;
char *arg;
int rc;
while (isdigit(p->opt[len]))
len++;
arg = xmemdupz(p->opt, len);
p->opt = p->opt[len] ? p->opt + len : NULL;
rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
free(arg);
return rc;
}
return -2;
}
static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
const struct option *options)
{
const char *arg_end = strchr(arg, '=');
const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
int abbrev_flags = 0, ambiguous_flags = 0;
if (!arg_end)
arg_end = arg + strlen(arg);
for (; options->type != OPTION_END; options++) {
const char *rest;
int flags = 0;
if (!options->long_name)
continue;
rest = skip_prefix(arg, options->long_name);
if (options->type == OPTION_ARGUMENT) {
if (!rest)
continue;
if (*rest == '=')
return opterror(options, "takes no value", flags);
if (*rest)
continue;
p->out[p->cpidx++] = arg - 2;
return 0;
}
if (!rest) {
/* abbreviated? */
if (!strncmp(options->long_name, arg, arg_end - arg)) {
is_abbreviated:
if (abbrev_option) {
/*
* If this is abbreviated, it is
* ambiguous. So when there is no
* exact match later, we need to
* error out.
*/
ambiguous_option = abbrev_option;
ambiguous_flags = abbrev_flags;
}
if (!(flags & OPT_UNSET) && *arg_end)
p->opt = arg_end + 1;
abbrev_option = options;
abbrev_flags = flags;
continue;
}
/* negation allowed? */
if (options->flags & PARSE_OPT_NONEG)
continue;
/* negated and abbreviated very much? */
if (!prefixcmp("no-", arg)) {
flags |= OPT_UNSET;
goto is_abbreviated;
}
/* negated? */
if (strncmp(arg, "no-", 3))
continue;
flags |= OPT_UNSET;
rest = skip_prefix(arg + 3, options->long_name);
/* abbreviated and negated? */
if (!rest && !prefixcmp(options->long_name, arg + 3))
goto is_abbreviated;
if (!rest)
continue;
}
if (*rest) {
if (*rest != '=')
continue;
p->opt = rest + 1;
}
return get_value(p, options, flags);
}
if (ambiguous_option)
return error("Ambiguous option: %s "
"(could be --%s%s or --%s%s)",
arg,
(ambiguous_flags & OPT_UNSET) ? "no-" : "",
ambiguous_option->long_name,
(abbrev_flags & OPT_UNSET) ? "no-" : "",
abbrev_option->long_name);
if (abbrev_option)
return get_value(p, abbrev_option, abbrev_flags);
return -2;
}
static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
const struct option *options)
{
for (; options->type != OPTION_END; options++) {
if (!(options->flags & PARSE_OPT_NODASH))
continue;
if (options->short_name == arg[0] && arg[1] == '\0')
return get_value(p, options, OPT_SHORT);
}
return -2;
}
static void check_typos(const char *arg, const struct option *options)
{
if (strlen(arg) < 3)
return;
if (!prefixcmp(arg, "no-")) {
error ("did you mean `--%s` (with two dashes ?)", arg);
exit(129);
}
for (; options->type != OPTION_END; options++) {
if (!options->long_name)
continue;
if (!prefixcmp(options->long_name, arg)) {
error ("did you mean `--%s` (with two dashes ?)", arg);
exit(129);
}
}
}
static void parse_options_check(const struct option *opts)
{
int err = 0;
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");
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");
}
if (err)
exit(128);
}
void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix,
const struct option *options, int flags)
{
memset(ctx, 0, sizeof(*ctx));
ctx->argc = argc - 1;
ctx->argv = argv + 1;
ctx->out = argv;
ctx->prefix = prefix;
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
ctx->flags = flags;
if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
(flags & PARSE_OPT_STOP_AT_NON_OPTION))
die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
parse_options_check(options);
}
static int 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[])
{
int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
/* we must reset ->opt, unknown short option leave it dangling */
ctx->opt = NULL;
for (; ctx->argc; ctx->argc--, ctx->argv++) {
const char *arg = ctx->argv[0];
if (*arg != '-' || !arg[1]) {
if (parse_nodash_opt(ctx, arg, options) == 0)
continue;
if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
break;
ctx->out[ctx->cpidx++] = ctx->argv[0];
continue;
}
if (arg[1] != '-') {
ctx->opt = arg + 1;
if (internal_help && *ctx->opt == 'h')
return parse_options_usage(ctx, usagestr, options, 0);
switch (parse_short_opt(ctx, options)) {
case -1:
return parse_options_usage(ctx, usagestr, options, 1);
case -2:
goto unknown;
}
if (ctx->opt)
check_typos(arg + 1, options);
while (ctx->opt) {
if (internal_help && *ctx->opt == 'h')
return parse_options_usage(ctx, usagestr, options, 0);
switch (parse_short_opt(ctx, options)) {
case -1:
return parse_options_usage(ctx, usagestr, options, 1);
case -2:
/* fake a short option thing to hide the fact that we may have
* started to parse aggregated stuff
*
* This is leaky, too bad.
*/
ctx->argv[0] = xstrdup(ctx->opt - 1);
*(char *)ctx->argv[0] = '-';
goto unknown;
}
}
continue;
}
if (!arg[2]) { /* "--" */
if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
ctx->argc--;
ctx->argv++;
}
break;
}
if (internal_help && !strcmp(arg + 2, "help-all"))
return usage_with_options_internal(ctx, usagestr, options, 1, 0);
if (internal_help && !strcmp(arg + 2, "help"))
return parse_options_usage(ctx, usagestr, options, 0);
switch (parse_long_opt(ctx, arg + 2, options)) {
case -1:
return parse_options_usage(ctx, usagestr, options, 1);
case -2:
goto unknown;
}
continue;
unknown:
if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
return PARSE_OPT_UNKNOWN;
ctx->out[ctx->cpidx++] = ctx->argv[0];
ctx->opt = NULL;
}
return PARSE_OPT_DONE;
}
int parse_options_end(struct parse_opt_ctx_t *ctx)
{
memmove(ctx->out + ctx->cpidx, ctx->argv, ctx->argc * sizeof(*ctx->out));
ctx->out[ctx->cpidx + ctx->argc] = NULL;
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)
{
struct parse_opt_ctx_t ctx;
parse_options_start(&ctx, argc, argv, prefix, options, flags);
switch (parse_options_step(&ctx, options, usagestr)) {
case PARSE_OPT_HELP:
exit(129);
case PARSE_OPT_DONE:
break;
default: /* PARSE_OPT_UNKNOWN */
if (ctx.argv[0][1] == '-') {
error("unknown option `%s'", ctx.argv[0] + 2);
} else {
error("unknown switch `%c'", *ctx.opt);
}
usage_with_options(usagestr, options);
}
return parse_options_end(&ctx);
}
static int usage_argh(const struct option *opts, FILE *outfile)
{
const char *s;
int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) || !opts->argh;
if (opts->flags & PARSE_OPT_OPTARG)
if (opts->long_name)
s = literal ? "[=%s]" : "[=<%s>]";
else
s = literal ? "[%s]" : "[<%s>]";
else
s = literal ? " %s" : " <%s>";
return fprintf(outfile, s, opts->argh ? opts->argh : "...");
}
#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)
{
FILE *outfile = err ? stderr : stdout;
if (!usagestr)
return PARSE_OPT_HELP;
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
fprintf(outfile, "cat <<\\EOF\n");
fprintf(outfile, "usage: %s\n", *usagestr++);
while (*usagestr && **usagestr)
fprintf(outfile, " or: %s\n", *usagestr++);
while (*usagestr) {
fprintf(outfile, "%s%s\n",
**usagestr ? " " : "",
*usagestr);
usagestr++;
}
if (opts->type != OPTION_GROUP)
fputc('\n', outfile);
for (; opts->type != OPTION_END; opts++) {
size_t pos;
int pad;
if (opts->type == OPTION_GROUP) {
fputc('\n', outfile);
if (*opts->help)
fprintf(outfile, "%s\n", opts->help);
continue;
}
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
continue;
pos = fprintf(outfile, " ");
if (opts->short_name && !(opts->flags & PARSE_OPT_NEGHELP)) {
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)
pos += fprintf(outfile, "--%s%s",
(opts->flags & PARSE_OPT_NEGHELP) ? "no-" : "",
opts->long_name);
if (opts->type == OPTION_NUMBER)
pos += fprintf(outfile, "-NUM");
if (!(opts->flags & PARSE_OPT_NOARG))
pos += usage_argh(opts, outfile);
if (pos <= USAGE_OPTS_WIDTH)
pad = USAGE_OPTS_WIDTH - pos;
else {
fputc('\n', outfile);
pad = USAGE_OPTS_WIDTH;
}
fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
}
fputc('\n', outfile);
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
fputs("EOF\n", outfile);
return PARSE_OPT_HELP;
}
void usage_with_options(const char * const *usagestr,
const struct option *opts)
{
usage_with_options_internal(NULL, usagestr, opts, 0, 1);
exit(129);
}
void usage_msg_opt(const char *msg,
const char * const *usagestr,
const struct option *options)
{
fprintf(stderr, "%s\n\n", msg);
usage_with_options(usagestr, options);
}
static int parse_options_usage(struct parse_opt_ctx_t *ctx,
const char * const *usagestr,
const struct option *opts, int err)
{
return usage_with_options_internal(ctx, usagestr, opts, 0, err);
}
/*----- some often used options -----*/
#include "cache.h"
int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset)
{
int v;
if (!arg) {
v = unset ? 0 : DEFAULT_ABBREV;
} else {
v = strtol(arg, (char **)&arg, 10);
if (*arg)
return opterror(opt, "expects a numerical value", 0);
if (v && v < MINIMUM_ABBREV)
v = MINIMUM_ABBREV;
else if (v > 40)
v = 40;
}
*(int *)(opt->value) = v;
return 0;
}
int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
int unset)
{
*(unsigned long *)(opt->value) = approxidate(arg);
return 0;
}
int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
int unset)
{
int value;
if (!arg)
arg = unset ? "never" : (const char *)opt->defval;
value = git_config_colorbool(NULL, arg, -1);
if (value < 0)
return opterror(opt,
"expects \"always\", \"auto\", or \"never\"", 0);
*(int *)opt->value = value;
return 0;
}
int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
int unset)
{
int *target = opt->value;
if (unset)
/* --no-quiet, --no-verbose */
*target = 0;
else if (opt->short_name == 'v') {
if (*target >= 0)
(*target)++;
else
*target = 1;
} else {
if (*target <= 0)
(*target)--;
else
*target = -1;
}
return 0;
}
int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
{
unsigned char sha1[20];
struct commit *commit;
if (!arg)
return -1;
if (get_sha1(arg, sha1))
return error("malformed object name %s", arg);
commit = lookup_commit_reference(sha1);
if (!commit)
return error("no such commit %s", arg);
commit_list_insert(commit, opt->value);
return 0;
}
int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
{
int *target = opt->value;
*target = unset ? 2 : 1;
return 0;
}
int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
{
int i, j;
for (i = 0; i < dst_size; i++)
if (dst[i].type == OPTION_END)
break;
for (j = 0; i < dst_size; i++, j++) {
dst[i] = src[j];
if (src[j].type == OPTION_END)
return 0;
}
return -1;
}