[PATCH 01/25] Add a simple option parser.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxx>
Signed-off-by: Pierre Habouzit <madcoder@xxxxxxxxxx>
---
 Makefile        |    4 +-
 parse-options.c |  168 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 parse-options.h |   35 +++++++++++
 3 files changed, 205 insertions(+), 2 deletions(-)
 create mode 100644 parse-options.c
 create mode 100644 parse-options.h

diff --git a/Makefile b/Makefile
index bb4873d..1ffdf06 100644
--- a/Makefile
+++ b/Makefile
@@ -287,7 +287,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 transport.h parse-options.h
 
 DIFF_OBJS = \
 	diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -310,7 +310,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
index 0000000..17a68ff
--- /dev/null
+++ b/parse-options.c
@@ -0,0 +1,168 @@
+#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
index 0000000..76d73b2
--- /dev/null
+++ b/parse-options.h
@@ -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
-- 
1.5.3.4.1231.g62b9a

-
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux