Add a simple option parser.
authorPierre Habouzit <madcoder@debian.org>
Sun, 14 Oct 2007 23:35:37 +0000 (01:35 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 30 Oct 2007 04:03:30 +0000 (21:03 -0700)
The option parser takes argc, argv, an array of struct option
and a usage string.  Each of the struct option elements in the array
describes a valid option, its type and a pointer to the location where the
value is written.  The entry point is parse_options(), which scans through
the given argv, and matches each option there against the list of valid
options.  During the scan, argv is rewritten to only contain the
non-option command line arguments and the number of these is returned.

Aggregation of single switches is allowed:
  -rC0 is the same as -r -C 0 (supposing that -C wants an arg).

Every long option automatically support the option with the same name,
prefixed with 'no-' to unset the switch. It assumes that initial value for
strings are "NULL" and for integers is "0".

Long options are supported either with '=' or without:
  --some-option=foo is the same as --some-option foo

Acked-by: Kristian Høgsberg <krh@redhat.com>
Signed-off-by: Pierre Habouzit <madcoder@debian.org>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Makefile
parse-options.c [new file with mode: 0644]
parse-options.h [new file with mode: 0644]

index 72f5ef43ce4479a744f7af57fe32021a00b8a10e..7e6e1d65f9b6b7e18ee77fce3786db194cc83404 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -290,7 +290,7 @@ LIB_H = \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
        tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
        utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \
-       mailmap.h remote.h transport.h
+       mailmap.h remote.h parse-options.h transport.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -313,7 +313,7 @@ LIB_OBJS = \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
        convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
-       transport.o bundle.o walker.o
+       transport.o bundle.o walker.o parse-options.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
diff --git a/parse-options.c b/parse-options.c
new file mode 100644 (file)
index 0000000..7bdffdb
--- /dev/null
@@ -0,0 +1,167 @@
+#include "git-compat-util.h"
+#include "parse-options.h"
+#include "strbuf.h"
+
+#define OPT_SHORT 1
+#define OPT_UNSET 2
+
+struct optparse_t {
+       const char **argv;
+       int argc;
+       const char *opt;
+};
+
+static inline const char *get_arg(struct optparse_t *p)
+{
+       if (p->opt) {
+               const char *res = p->opt;
+               p->opt = NULL;
+               return res;
+       }
+       p->argc--;
+       return *++p->argv;
+}
+
+static inline const char *skip_prefix(const char *str, const char *prefix)
+{
+       size_t len = strlen(prefix);
+       return strncmp(str, prefix, len) ? NULL : str + len;
+}
+
+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_value(struct optparse_t *p,
+                     const struct option *opt, int flags)
+{
+       const char *s;
+
+       if (p->opt && (flags & OPT_UNSET))
+               return opterror(opt, "takes no value", flags);
+
+       switch (opt->type) {
+       case OPTION_BOOLEAN:
+               if (!(flags & OPT_SHORT) && p->opt)
+                       return opterror(opt, "takes no value", flags);
+               if (flags & OPT_UNSET)
+                       *(int *)opt->value = 0;
+               else
+                       (*(int *)opt->value)++;
+               return 0;
+
+       case OPTION_STRING:
+               if (flags & OPT_UNSET) {
+                       *(const char **)opt->value = (const char *)NULL;
+                       return 0;
+               }
+               if (!p->opt && p->argc <= 1)
+                       return opterror(opt, "requires a value", flags);
+               *(const char **)opt->value = get_arg(p);
+               return 0;
+
+       case OPTION_INTEGER:
+               if (flags & OPT_UNSET) {
+                       *(int *)opt->value = 0;
+                       return 0;
+               }
+               if (!p->opt && p->argc <= 1)
+                       return opterror(opt, "requires a value", flags);
+               *(int *)opt->value = strtol(get_arg(p), (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 optparse_t *p, const struct option *options)
+{
+       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);
+               }
+       }
+       return error("unknown switch `%c'", *p->opt);
+}
+
+static int parse_long_opt(struct optparse_t *p, const char *arg,
+                          const struct option *options)
+{
+       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 (!rest) {
+                       if (strncmp(arg, "no-", 3))
+                               continue;
+                       flags |= OPT_UNSET;
+                       rest = skip_prefix(arg + 3, options->long_name);
+                       if (!rest)
+                               continue;
+               }
+               if (*rest) {
+                       if (*rest != '=')
+                               continue;
+                       p->opt = rest + 1;
+               }
+               return get_value(p, options, flags);
+       }
+       return error("unknown option `%s'", arg);
+}
+
+int parse_options(int argc, const char **argv, const struct option *options,
+                  const char *usagestr, int flags)
+{
+       struct optparse_t args = { argv + 1, argc - 1, NULL };
+       int j = 0;
+
+       for (; args.argc; args.argc--, args.argv++) {
+               const char *arg = args.argv[0];
+
+               if (*arg != '-' || !arg[1]) {
+                       argv[j++] = args.argv[0];
+                       continue;
+               }
+
+               if (arg[1] != '-') {
+                       args.opt = arg + 1;
+                       do {
+                               if (*args.opt == 'h')
+                                       usage(usagestr);
+                               if (parse_short_opt(&args, options) < 0)
+                                       usage(usagestr);
+                       } while (args.opt);
+                       continue;
+               }
+
+               if (!arg[2]) { /* "--" */
+                       if (!(flags & PARSE_OPT_KEEP_DASHDASH)) {
+                               args.argc--;
+                               args.argv++;
+                       }
+                       break;
+               }
+
+               if (!strcmp(arg + 2, "help"))
+                       usage(usagestr);
+               if (parse_long_opt(&args, arg + 2, options))
+                       usage(usagestr);
+       }
+
+       memmove(argv + j, args.argv, args.argc * sizeof(*argv));
+       argv[j + args.argc] = NULL;
+       return j + args.argc;
+}
diff --git a/parse-options.h b/parse-options.h
new file mode 100644 (file)
index 0000000..76d73b2
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef PARSE_OPTIONS_H
+#define PARSE_OPTIONS_H
+
+enum parse_opt_type {
+       OPTION_END,
+       OPTION_BOOLEAN,
+       OPTION_STRING,
+       OPTION_INTEGER,
+};
+
+enum parse_opt_flags {
+       PARSE_OPT_KEEP_DASHDASH = 1,
+};
+
+struct option {
+       enum parse_opt_type type;
+       int short_name;
+       const char *long_name;
+       void *value;
+};
+
+#define OPT_END()                   { OPTION_END }
+#define OPT_BOOLEAN(s, l, v, h)     { OPTION_BOOLEAN, (s), (l), (v) }
+#define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v) }
+#define OPT_STRING(s, l, v, a, h)   { OPTION_STRING,  (s), (l), (v) }
+
+/* parse_options() will filter out the processed options and leave the
+ * non-option argments in argv[].
+ * Returns the number of arguments left in argv[].
+ */
+extern int parse_options(int argc, const char **argv,
+                         const struct option *options,
+                         const char *usagestr, int flags);
+
+#endif