[PATCH] 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

Also be able to generate the usage automatically.

Acked-by: Kristian Høgsberg <krh@xxxxxxxxxx>
Signed-off-by: Pierre Habouzit <madcoder@xxxxxxxxxx>
---
 Makefile        |    2 +-
 parse-options.c |  227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 parse-options.h |   37 +++++++++
 3 files changed, 265 insertions(+), 1 deletions(-)
 create mode 100644 parse-options.c
 create mode 100644 parse-options.h

diff --git a/Makefile b/Makefile
index 62bdac6..d90e959 100644
--- a/Makefile
+++ b/Makefile
@@ -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
+	transport.o bundle.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..07abb50
--- /dev/null
+++ b/parse-options.c
@@ -0,0 +1,227 @@
+#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 *skippfx(const char *str, const char *prefix)
+{
+	size_t len = strlen(prefix);
+	return strncmp(str, prefix, len) ? NULL : str + len;
+}
+
+static int opterror(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, struct option *opt, int flags)
+{
+	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;
+		} else {
+			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;
+		} else {
+			const char *s;
+			if (!p->opt && p->argc < 1)
+				return opterror(opt, "requires a value", flags);
+			*(int *)opt->value = strtol(*p->argv, (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, struct option *options, int count)
+{
+	int i;
+
+	for (i = 0; i < count; i++) {
+		if (options[i].short_name == *p->opt) {
+			p->opt = p->opt[1] ? p->opt + 1 : NULL;
+			return get_value(p, options + i, OPT_SHORT);
+		}
+	}
+	return error("unknown switch `%c'", *p->opt);
+}
+
+static int parse_long_opt(struct optparse_t *p, const char *arg,
+                          struct option *options, int count)
+{
+	int i;
+
+	for (i = 0; i < count; i++) {
+		const char *rest;
+		int flags = 0;
+		
+		if (!options[i].long_name)
+			continue;
+
+		rest = skippfx(arg, options[i].long_name);
+		if (!rest && !strncmp(arg, "no-", 3)) {
+			rest = skippfx(arg + 3, options[i].long_name);
+			flags |= OPT_SHORT;
+		}
+		if (!rest)
+			continue;
+		if (*rest) {
+			if (*rest != '=')
+				continue;
+			p->opt = rest + 1;
+		}
+		return get_value(p, options + i, flags);
+	}
+	return error("unknown option `%s'", arg);
+}
+
+int parse_options(int argc, const char **argv,
+                  struct option *options, int count,
+				  const char * const usagestr[], int flags)
+{
+	struct optparse_t optp = { argv + 1, argc - 1, NULL };
+	int j = 0;
+
+	for (; optp.argc; optp.argc--, optp.argv++) {
+		const char *arg = optp.argv[0];
+
+		if (*arg != '-' || !arg[1]) {
+			argv[j++] = optp.argv[0];
+			continue;
+		}
+
+		if (arg[1] != '-') {
+			optp.opt = arg + 1;
+			do {
+				if (*optp.opt == 'h')
+					make_usage(usagestr, options, count);
+				if (parse_short_opt(&optp, options, count) < 0)
+					make_usage(usagestr, options, count);
+			} while (optp.opt);
+			continue;
+		}
+
+		if (!arg[2]) { /* "--" */
+			if (!(flags & OPT_COPY_DASHDASH))
+				optp.argc--, optp.argv++;
+			break;
+		}
+
+		if (!strcmp(arg + 2, "help"))
+			make_usage(usagestr, options, count);
+		if (parse_long_opt(&optp, arg + 2, options, count))
+			make_usage(usagestr, options, count);
+	}
+
+	memmove(argv + j, optp.argv, optp.argc * sizeof(argv));
+	argv[j + optp.argc] = NULL;
+	return j + optp.argc;
+}
+
+#define USAGE_OPTS_WIDTH 24
+#define USAGE_GAP         2
+
+void make_usage(const char * const usagestr[], struct option *opts, int cnt)
+{
+	struct strbuf sb;
+
+	strbuf_init(&sb, 4096);
+	do {
+		strbuf_addstr(&sb, *usagestr++);
+		strbuf_addch(&sb, '\n');
+	} while (*usagestr);
+
+	if (cnt && opts->type != OPTION_GROUP)
+		strbuf_addch(&sb, '\n');
+
+	for (; cnt-- > 0; opts++) {
+		size_t pos;
+
+		if (opts->type == OPTION_GROUP) {
+			strbuf_addch(&sb, '\n');
+			if (*opts->help)
+				strbuf_addf(&sb, "%s\n", opts->help);
+			continue;
+		}
+
+		pos = sb.len;
+		strbuf_addstr(&sb, "    ");
+		if (opts->short_name) {
+			strbuf_addf(&sb, "-%c", opts->short_name);
+		}
+		if (opts->long_name) {
+			strbuf_addf(&sb, opts->short_name ? ", --%s" : "--%s",
+						opts->long_name);
+		}
+		switch (opts->type) {
+		case OPTION_INTEGER:
+			strbuf_addstr(&sb, " <n>");
+			break;
+		case OPTION_STRING:
+			if (opts->argh) {
+				strbuf_addf(&sb, " <%s>", opts->argh);
+			} else {
+				strbuf_addstr(&sb, " ...");
+			}
+			break;
+		default:
+			break;
+		}
+		if (sb.len - pos <= USAGE_OPTS_WIDTH) {
+			int pad = USAGE_OPTS_WIDTH - (sb.len - pos) + USAGE_GAP;
+			strbuf_addf(&sb, "%*s%s\n", pad, "", opts->help);
+		} else {
+			strbuf_addf(&sb, "\n%*s%s\n", USAGE_OPTS_WIDTH + USAGE_GAP, "",
+						opts->help);
+		}
+	}
+	usage(sb.buf);
+}
diff --git a/parse-options.h b/parse-options.h
new file mode 100644
index 0000000..4b33d17
--- /dev/null
+++ b/parse-options.h
@@ -0,0 +1,37 @@
+#ifndef PARSE_OPTIONS_H
+#define PARSE_OPTIONS_H
+
+enum option_type {
+	OPTION_BOOLEAN,
+	OPTION_STRING,
+	OPTION_INTEGER,
+	OPTION_GROUP,
+};
+
+struct option {
+	enum option_type type;
+	int short_name;
+	const char *long_name;
+	void *value;
+	const char *argh;
+	const char *help;
+};
+
+#define OPT_BOOLEAN(s, l, v, h)     { OPTION_BOOLEAN, (s), (l), (v), NULL, (h) }
+#define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), NULL, (h) }
+#define OPT_STRING(s, l, v, a, h)   { OPTION_STRING,  (s), (l), (v), (a), (h) }
+#define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
+
+#define OPT_COPY_DASHDASH 1
+
+/* 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,
+                         struct option *options, int count,
+                         const char * const usagestr[], int flags);
+
+extern NORETURN void make_usage(const char * const usagestr[],
+                                struct option *options, int cnt);
+#endif
-- 
1.5.3.GIT

-
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