parse-options: check empty value in OPT_INTEGER and OPT_ABBREV
[gitweb.git] / parse-options.c
index 01c2acbd27c21d5b43ec9039fd4c06ac67536de9..c8ee2211969219b693b68c56f0fefa11aaf10bb0 100644 (file)
@@ -20,8 +20,9 @@ int optbug(const struct option *opt, const char *reason)
        return error("BUG: switch '%c' %s", opt->short_name, reason);
 }
 
-static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
-                  int flags, const char **arg)
+static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p,
+                                    const struct option *opt,
+                                    int flags, const char **arg)
 {
        if (p->opt) {
                *arg = p->opt;
@@ -44,9 +45,10 @@ static void fix_filename(const char *prefix, const char **file)
        *file = prefix_filename(prefix, *file);
 }
 
-static int opt_command_mode_error(const struct option *opt,
-                                 const struct option *all_opts,
-                                 int flags)
+static enum parse_opt_result opt_command_mode_error(
+       const struct option *opt,
+       const struct option *all_opts,
+       int flags)
 {
        const struct option *that;
        struct strbuf that_name = STRBUF_INIT;
@@ -69,16 +71,16 @@ static int opt_command_mode_error(const struct option *opt,
                error(_("%s is incompatible with %s"),
                      optname(opt, flags), that_name.buf);
                strbuf_release(&that_name);
-               return -1;
+               return PARSE_OPT_ERROR;
        }
        return error(_("%s : incompatible with something else"),
                     optname(opt, flags));
 }
 
-static int get_value(struct parse_opt_ctx_t *p,
-                    const struct option *opt,
-                    const struct option *all_opts,
-                    int flags)
+static enum parse_opt_result get_value(struct parse_opt_ctx_t *p,
+                                      const struct option *opt,
+                                      const struct option *all_opts,
+                                      int flags)
 {
        const char *s, *arg;
        const int unset = flags & OPT_UNSET;
@@ -93,7 +95,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 
        switch (opt->type) {
        case OPTION_LOWLEVEL_CALLBACK:
-               return (*(parse_opt_ll_cb *)opt->callback)(p, opt, unset);
+               return opt->ll_callback(p, opt, NULL, unset);
 
        case OPTION_BIT:
                if (unset)
@@ -109,6 +111,13 @@ static int get_value(struct parse_opt_ctx_t *p,
                        *(int *)opt->value &= ~opt->defval;
                return 0;
 
+       case OPTION_BITOP:
+               if (unset)
+                       BUG("BITOP can't have unset form");
+               *(int *)opt->value &= ~opt->extra;
+               *(int *)opt->value |= opt->defval;
+               return 0;
+
        case OPTION_COUNTUP:
                if (*(int *)opt->value < 0)
                        *(int *)opt->value = 0;
@@ -152,16 +161,27 @@ static int get_value(struct parse_opt_ctx_t *p,
                return err;
 
        case OPTION_CALLBACK:
+       {
+               const char *p_arg = NULL;
+               int p_unset;
+
                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))
+                       p_unset = 1;
+               else if (opt->flags & PARSE_OPT_NOARG)
+                       p_unset = 0;
+               else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+                       p_unset = 0;
+               else if (get_arg(p, opt, flags, &arg))
                        return -1;
-               return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
-
+               else {
+                       p_unset = 0;
+                       p_arg = arg;
+               }
+               if (opt->callback)
+                       return (*opt->callback)(opt, p_arg, p_unset) ? (-1) : 0;
+               else
+                       return (*opt->ll_callback)(p, opt, p_arg, p_unset);
+       }
        case OPTION_INTEGER:
                if (unset) {
                        *(int *)opt->value = 0;
@@ -173,6 +193,9 @@ static int get_value(struct parse_opt_ctx_t *p,
                }
                if (get_arg(p, opt, flags, &arg))
                        return -1;
+               if (!*arg)
+                       return error(_("%s expects a numerical value"),
+                                    optname(opt, flags));
                *(int *)opt->value = strtol(arg, (char **)&s, 10);
                if (*s)
                        return error(_("%s expects a numerical value"),
@@ -201,7 +224,8 @@ static int get_value(struct parse_opt_ctx_t *p,
        }
 }
 
-static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
+static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p,
+                                            const struct option *options)
 {
        const struct option *all_opts = options;
        const struct option *numopt = NULL;
@@ -228,15 +252,19 @@ static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *optio
                        len++;
                arg = xmemdupz(p->opt, len);
                p->opt = p->opt[len] ? p->opt + len : NULL;
-               rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
+               if (numopt->callback)
+                       rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
+               else
+                       rc = (*numopt->ll_callback)(p, numopt, arg, 0);
                free(arg);
                return rc;
        }
-       return -2;
+       return PARSE_OPT_UNKNOWN;
 }
 
-static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
-                          const struct option *options)
+static enum parse_opt_result parse_long_opt(
+       struct parse_opt_ctx_t *p, const char *arg,
+       const struct option *options)
 {
        const struct option *all_opts = options;
        const char *arg_end = strchrnul(arg, '=');
@@ -262,11 +290,12 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
                        if (*rest)
                                continue;
                        p->out[p->cpidx++] = arg - 2;
-                       return 0;
+                       return PARSE_OPT_DONE;
                }
                if (!rest) {
                        /* abbreviated? */
-                       if (!strncmp(long_name, arg, arg_end - arg)) {
+                       if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) &&
+                           !strncmp(long_name, arg, arg_end - arg)) {
 is_abbreviated:
                                if (abbrev_option) {
                                        /*
@@ -326,11 +355,11 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
                        ambiguous_option->long_name,
                        (abbrev_flags & OPT_UNSET) ?  "no-" : "",
                        abbrev_option->long_name);
-               return -3;
+               return PARSE_OPT_HELP;
        }
        if (abbrev_option)
                return get_value(p, abbrev_option, all_opts, abbrev_flags);
-       return -2;
+       return PARSE_OPT_UNKNOWN;
 }
 
 static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
@@ -400,6 +429,19 @@ static void parse_options_check(const struct option *opts)
                        if ((opts->flags & PARSE_OPT_OPTARG) ||
                            !(opts->flags & PARSE_OPT_NOARG))
                                err |= 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");
+                       break;
+               case OPTION_LOWLEVEL_CALLBACK:
+                       if (!opts->ll_callback)
+                               BUG("OPTION_LOWLEVEL_CALLBACK needs a callback");
+                       if (opts->callback)
+                               BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback");
+                       break;
                default:
                        ; /* ok. (usually accepts an argument) */
                }
@@ -416,15 +458,24 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
                         const struct option *options, int flags)
 {
        memset(ctx, 0, sizeof(*ctx));
-       ctx->argc = ctx->total = argc - 1;
-       ctx->argv = argv + 1;
-       ctx->out  = argv;
+       ctx->argc = argc;
+       ctx->argv = argv;
+       if (!(flags & PARSE_OPT_ONE_SHOT)) {
+               ctx->argc--;
+               ctx->argv++;
+       }
+       ctx->total = ctx->argc;
+       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))
+           (flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
+           !(flags & PARSE_OPT_ONE_SHOT))
                BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
+       if ((flags & PARSE_OPT_ONE_SHOT) &&
+           (flags & PARSE_OPT_KEEP_ARGV0))
+               BUG("Can't keep argv0 if you don't have it");
        parse_options_check(options);
 }
 
@@ -536,6 +587,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
        for (; ctx->argc; ctx->argc--, ctx->argv++) {
                const char *arg = ctx->argv[0];
 
+               if (ctx->flags & PARSE_OPT_ONE_SHOT &&
+                   ctx->argc != ctx->total)
+                       break;
+
                if (*arg != '-' || !arg[1]) {
                        if (parse_nodash_opt(ctx, arg, options) == 0)
                                continue;
@@ -556,22 +611,28 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                if (arg[1] != '-') {
                        ctx->opt = arg + 1;
                        switch (parse_short_opt(ctx, options)) {
-                       case -1:
+                       case PARSE_OPT_ERROR:
                                return PARSE_OPT_ERROR;
-                       case -2:
+                       case PARSE_OPT_UNKNOWN:
                                if (ctx->opt)
                                        check_typos(arg + 1, options);
                                if (internal_help && *ctx->opt == 'h')
                                        goto show_usage;
                                goto unknown;
+                       case PARSE_OPT_NON_OPTION:
+                       case PARSE_OPT_HELP:
+                       case PARSE_OPT_COMPLETE:
+                               BUG("parse_short_opt() cannot return these");
+                       case PARSE_OPT_DONE:
+                               break;
                        }
                        if (ctx->opt)
                                check_typos(arg + 1, options);
                        while (ctx->opt) {
                                switch (parse_short_opt(ctx, options)) {
-                               case -1:
+                               case PARSE_OPT_ERROR:
                                        return PARSE_OPT_ERROR;
-                               case -2:
+                               case PARSE_OPT_UNKNOWN:
                                        if (internal_help && *ctx->opt == 'h')
                                                goto show_usage;
 
@@ -583,6 +644,12 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                                        ctx->argv[0] = xstrdup(ctx->opt - 1);
                                        *(char *)ctx->argv[0] = '-';
                                        goto unknown;
+                               case PARSE_OPT_NON_OPTION:
+                               case PARSE_OPT_COMPLETE:
+                               case PARSE_OPT_HELP:
+                                       BUG("parse_short_opt() cannot return these");
+                               case PARSE_OPT_DONE:
+                                       break;
                                }
                        }
                        continue;
@@ -601,15 +668,22 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                if (internal_help && !strcmp(arg + 2, "help"))
                        goto show_usage;
                switch (parse_long_opt(ctx, arg + 2, options)) {
-               case -1:
+               case PARSE_OPT_ERROR:
                        return PARSE_OPT_ERROR;
-               case -2:
+               case PARSE_OPT_UNKNOWN:
                        goto unknown;
-               case -3:
+               case PARSE_OPT_HELP:
                        goto show_usage;
+               case PARSE_OPT_NON_OPTION:
+               case PARSE_OPT_COMPLETE:
+                       BUG("parse_long_opt() cannot return these");
+               case PARSE_OPT_DONE:
+                       break;
                }
                continue;
 unknown:
+               if (ctx->flags & PARSE_OPT_ONE_SHOT)
+                       break;
                if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
                        return PARSE_OPT_UNKNOWN;
                ctx->out[ctx->cpidx++] = ctx->argv[0];
@@ -623,6 +697,9 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
 
 int parse_options_end(struct parse_opt_ctx_t *ctx)
 {
+       if (ctx->flags & PARSE_OPT_ONE_SHOT)
+               return ctx->total - ctx->argc;
+
        MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc);
        ctx->out[ctx->cpidx + ctx->argc] = NULL;
        return ctx->cpidx + ctx->argc;