Browse Source

opt: wean off getopt_long, beef up tests.

Doing our own parsing lost a few lines of code, too.

Our coverage is over 99% now.
Rusty Russell 15 years ago
parent
commit
9056c31b46

+ 1 - 3
ccan/opt/helpers.c

@@ -91,10 +91,8 @@ char *opt_set_longval(const char *arg, long *l)
 	*l = strtol(arg, &endp, 0);
 	if (*endp || !arg[0])
 		return arg_bad("'%s' is not a number", arg);
-	if (errno == ERANGE)
-		return arg_bad("'%s' is out of range", arg);
 	if (errno)
-		return opt_invalid_argument(arg);
+		return arg_bad("'%s' is out of range", arg);
 	return NULL;
 }
 

+ 10 - 150
ccan/opt/opt.c

@@ -1,7 +1,6 @@
 #include <ccan/opt/opt.h>
 #include <string.h>
 #include <errno.h>
-#include <getopt.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <err.h>
@@ -53,7 +52,7 @@ static const char *next_opt(const char *p, unsigned *i, unsigned *len)
 	return NULL;
 }
 
-static const char *first_lopt(unsigned *i, unsigned *len)
+const char *first_lopt(unsigned *i, unsigned *len)
 {
 	const char *p;
 	for (p = first_opt(i, len); p; p = next_opt(p, i, len)) {
@@ -67,7 +66,7 @@ static const char *first_lopt(unsigned *i, unsigned *len)
 	return p;
 }
 
-static const char *next_lopt(const char *p, unsigned *i, unsigned *len)
+const char *next_lopt(const char *p, unsigned *i, unsigned *len)
 {
 	for (p = next_opt(p, i, len); p; p = next_opt(p, i, len)) {
 		if (p[0] == '-') {
@@ -128,22 +127,15 @@ static void check_opt(const struct opt_table *entry)
 			if (len != 1)
 				errx(1, "Option %s: invalid short option"
 				     " '%.*s'", entry->names, len+1, p-1);
-			if (*p == ':')
-				errx(1, "Option %s: invalid short option '-:'",
-				     entry->names);
 			opt_num_short++;
-			if (entry->type == OPT_HASARG) {
+			if (entry->type == OPT_HASARG)
 				opt_num_short_arg++;
-				if (*p == '?')
-					errx(1, "Option %s: '-?' cannot take"
-					     " an argument", entry->names);
-			}
 		}
 		/* Don't document args unless there are some. */
 		if (entry->type == OPT_NOARG) {
 			if (p[len] == ' ' || p[len] == '=')
 				errx(1, "Option %s: does not take arguments"
-				     "'%s'", entry->names, p+len+1);
+				     " '%s'", entry->names, p+len+1);
 		}
 	}
 }
@@ -194,151 +186,19 @@ void opt_register_table(const struct opt_table entry[], const char *desc)
 		opt_table[start].arg = (void *)(intptr_t)(opt_count - start);
 }
 
-static char *make_optstring(void)
-{
-	char *str = malloc(1 + opt_num_short + opt_num_short_arg + 1);
-	const char *p;
-	unsigned int i, num = 0;
-
-	/* This tells getopt_long we want a ':' returned for missing arg. */
-	str[num++] = ':';
-	for (p = first_sopt(&i); p; p = next_sopt(p, &i)) {
-		str[num++] = *p;
-		if (opt_table[i].type == OPT_HASARG)
-			str[num++] = ':';
-	}
-	str[num++] = '\0';
-	assert(num == 1 + opt_num_short + opt_num_short_arg + 1);
-	return str;
-}
-
-static struct option *make_options(void)
-{
-	struct option *options = malloc(sizeof(*options) * (opt_num_long + 1));
-	unsigned int i, num = 0, len = 0 /* GCC bogus warning */;
-	const char *p;
-
-	for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) {
-		char *buf = malloc(len + 1);
-		memcpy(buf, p, len);
-		buf[len] = 0;
-		options[num].name = buf;
-		options[num].has_arg = (opt_table[i].type == OPT_HASARG);
-		options[num].flag = NULL;
-		options[num].val = 0;
-		num++;
-	}
-	memset(&options[num], 0, sizeof(options[num]));
-	assert(num == opt_num_long);
-	return options;
-}
-
-static struct opt_table *find_short(char shortopt)
-{
-	unsigned int i;
-	const char *p;
-
-	for (p = first_sopt(&i); p; p = next_sopt(p, &i)) {
-		if (*p == shortopt)
-			return &opt_table[i];
-	}
-	abort();
-}
-
-/* We want the index'th long entry. */
-static struct opt_table *find_long(int index, const char **name)
-{
-	unsigned int i, len;
-	const char *p;
-
-	for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) {
-		if (index == 0) {
-			*name = p;
-			return &opt_table[i];
-		}
-		index--;
-	}
-	abort();
-}
-
-/* glibc does this as:
-/tmp/opt-example: invalid option -- 'x'
-/tmp/opt-example: unrecognized option '--long'
-/tmp/opt-example: option '--someflag' doesn't allow an argument
-/tmp/opt-example: option '--s' is ambiguous
-/tmp/opt-example: option requires an argument -- 's'
-*/
-static void parse_fail(void (*errlog)(const char *fmt, ...),
-		       char shortopt, const char *longopt, const char *problem)
-{
-	if (shortopt)
-		errlog("%s: -%c: %s", opt_argv0, shortopt, problem);
-	else
-		errlog("%s: --%.*s: %s", opt_argv0,
-		       strcspn(longopt, "|"), longopt, problem);
-}
-
 /* Parse your arguments. */
 bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...))
 {
-	char *optstring = make_optstring();
-	struct option *options = make_options();
-	int ret, longidx = 0;
-	struct opt_table *e;
+	int ret;
+	unsigned offset = 0;
 
-	/* We will do our own error reporting. */
-	opterr = 0;
+	/* This helps opt_usage. */
 	opt_argv0 = argv[0];
 
-	/* Reset in case we're called more than once. */
-	optopt = 0;
-	optind = 0;
-	while ((ret = getopt_long(*argc, argv, optstring, options, &longidx))
-	       != -1) {
-		char *problem;
-		const char *name = NULL; /* GCC bogus warning */
-
-		/* optopt is 0 if it's an unknown long option, *or* if
-		 * -? is a valid short option. */
-		if (ret == '?') {
-			if (optopt || strncmp(argv[optind-1], "--", 2) == 0) {
-				parse_fail(errlog, optopt, argv[optind-1]+2,
-					   "unrecognized option");
-				break;
-			}
-		} else if (ret == ':') {
-			/* Missing argument: longidx not updated :( */
-			parse_fail(errlog, optopt, argv[optind-1]+2,
-				   "option requires an argument");
-			break;
-		}
-
-		if (ret != 0)
-			e = find_short(ret);
-		else
-			e = find_long(longidx, &name);
-
-		if (e->type == OPT_HASARG)
-			problem = e->cb_arg(optarg, e->arg);
-		else
-			problem = e->cb(e->arg);
-
-		if (problem) {
-			parse_fail(errlog, ret, name, problem);
-			free(problem);
-			break;
-		}
-	}
-	free(optstring);
-	free(options);
-	if (ret != -1)
-		return false;
-
-	/* We hide everything but remaining arguments. */
-	memmove(&argv[1], &argv[optind], sizeof(argv[1]) * (*argc-optind+1));
-	*argc -= optind - 1;
+	while ((ret = parse_one(argc, argv, &offset, errlog)) == 1);
 
-	return ret == -1 ? true : false;
+	/* parse_one returns 0 on finish, -1 on error */
+	return (ret == 0);
 }
 
 void opt_log_stderr(const char *fmt, ...)

+ 130 - 0
ccan/opt/parse.c

@@ -0,0 +1,130 @@
+/* Actual code to parse commandline. */
+#include <ccan/opt/opt.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "private.h"
+
+/* glibc does this as:
+/tmp/opt-example: invalid option -- 'x'
+/tmp/opt-example: unrecognized option '--long'
+/tmp/opt-example: option '--someflag' doesn't allow an argument
+/tmp/opt-example: option '--s' is ambiguous
+/tmp/opt-example: option requires an argument -- 's'
+*/
+static int parse_err(void (*errlog)(const char *fmt, ...),
+		     const char *argv0, const char *arg, unsigned len,
+		     const char *problem)
+{
+	errlog("%s: %.*s: %s", argv0, len, arg, problem);
+	return -1;
+}
+
+static void consume_option(int *argc, char *argv[], unsigned optnum)
+{
+	memmove(&argv[optnum], &argv[optnum+1],
+		sizeof(argv[optnum]) * (*argc-optnum));
+	(*argc)--;
+}
+
+/* Returns 1 if argument consumed, 0 if all done, -1 on error. */
+int parse_one(int *argc, char *argv[], unsigned *offset,
+	      void (*errlog)(const char *fmt, ...))
+{
+	unsigned i, arg, len;
+	const char *o, *optarg = NULL;
+	char *problem;
+
+	if (getenv("POSIXLY_CORRECT")) {
+		/* Don't find options after non-options. */
+		arg = 1;
+	} else {
+		for (arg = 1; argv[arg]; arg++) {
+			if (argv[arg][0] == '-')
+				break;
+		}
+	}
+
+	if (!argv[arg] || argv[arg][0] != '-')
+		return 0;
+
+	/* Special arg terminator option. */
+	if (strcmp(argv[arg], "--") == 0) {
+		consume_option(argc, argv, arg);
+		return 0;
+	}
+
+	/* Long options start with -- */
+	if (argv[arg][1] == '-') {
+		assert(*offset == 0);
+		for (o = first_lopt(&i, &len); o; o = next_lopt(o, &i, &len)) {
+			if (strncmp(argv[arg] + 2, o, len) != 0)
+				continue;
+			if (argv[arg][2 + len] == '=')
+				optarg = argv[arg] + 2 + len + 1;
+			else if (argv[arg][2 + len] != '\0')
+				continue;
+			break;
+		}
+		if (!o)
+			return parse_err(errlog, argv[0],
+					 argv[arg], strlen(argv[arg]),
+					 "unrecognized option");
+		/* For error messages, we include the leading '--' */
+		o -= 2;
+		len += 2;
+	} else {
+		/* offset allows us to handle -abc */
+		for (o = first_sopt(&i); o; o = next_sopt(o, &i)) {
+			if (argv[arg][*offset + 1] != *o)
+				continue;
+			(*offset)++;
+			break;
+		}
+		if (!o)
+			return parse_err(errlog, argv[0],
+					 argv[arg], strlen(argv[arg]),
+					 "unrecognized option");
+		/* For error messages, we include the leading '-' */
+		o--;
+		len = 2;
+	}
+
+	if (opt_table[i].type == OPT_NOARG) {
+		if (optarg)
+			return parse_err(errlog, argv[0], o, len,
+					 "doesn't allow an argument");
+		problem = opt_table[i].cb(opt_table[i].arg);
+	} else {
+		if (!optarg) {
+			/* Swallow any short options as optarg, eg -afile */
+			if (*offset && argv[arg][*offset + 1]) {
+				optarg = argv[arg] + *offset + 1;
+				*offset = 0;
+			} else
+				optarg = argv[arg+1];
+		}
+		if (!optarg)
+			return parse_err(errlog, argv[0], o, len,
+					 "requires an argument");
+		problem = opt_table[i].cb_arg(optarg, opt_table[i].arg);
+	}
+
+	if (problem) {
+		parse_err(errlog, argv[0], o, len, problem);
+		free(problem);
+		return -1;
+	}
+
+	/* If no more letters in that short opt, reset offset. */
+	if (*offset && !argv[arg][*offset + 1])
+		*offset = 0;
+
+	/* All finished with that option? */
+	if (*offset == 0) {
+		consume_option(argc, argv, arg);
+		if (optarg && optarg == argv[arg])
+			consume_option(argc, argv, arg);
+	}
+	return 1;
+}

+ 5 - 0
ccan/opt/private.h

@@ -10,5 +10,10 @@ extern const char *opt_argv0;
 
 const char *first_sopt(unsigned *i);
 const char *next_sopt(const char *names, unsigned *i);
+const char *first_lopt(unsigned *i, unsigned *len);
+const char *next_lopt(const char *p, unsigned *i, unsigned *len);
+
+int parse_one(int *argc, char *argv[], unsigned *offset,
+	      void (*errlog)(const char *fmt, ...));
 
 #endif /* CCAN_OPT_PRIVATE_H */

+ 144 - 0
ccan/opt/test/run-checkopt.c

@@ -0,0 +1,144 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <ccan/tap/tap.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <err.h>
+#include "utils.h"
+
+/* We don't actually want it to exit... */
+static jmp_buf exited;
+#define errx save_and_jump
+
+static void save_and_jump(int ecode, const char *fmt, ...);
+
+#include <ccan/opt/helpers.c>
+#include <ccan/opt/opt.c>
+#include <ccan/opt/usage.c>
+#include <ccan/opt/parse.c>
+
+static char *output = NULL;
+
+static int saved_vprintf(const char *fmt, va_list ap)
+{
+	char *p;
+	int ret = vasprintf(&p, fmt, ap);
+
+	if (output) {
+		output = realloc(output, strlen(output) + strlen(p) + 1);
+		strcat(output, p);
+		free(p);
+	} else
+		output = p;
+	return ret;
+}
+
+static void save_and_jump(int ecode, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	saved_vprintf(fmt, ap);
+	va_end(ap);
+	longjmp(exited, ecode + 1);
+}
+
+static void reset(void)
+{
+	free(output);
+	output = NULL;
+	free(opt_table);
+	opt_table = NULL;
+	opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int exitval;
+
+	plan_tests(14);
+
+	exitval = setjmp(exited);
+	if (exitval == 0) {
+		/* Bad type. */
+		_opt_register("-a", OPT_SUBTABLE, (void *)opt_version_and_exit,
+			      NULL, NULL, "1.2.3", "");
+		fail("_opt_register returned?");
+	} else {
+		ok1(exitval - 1 == 1);
+		ok1(strstr(output, "Option -a: unknown entry type"));
+	}
+	reset();
+
+	exitval = setjmp(exited);
+	if (exitval == 0) {
+		/* NULL description. */
+		opt_register_noarg("-a", test_noarg, "", NULL);
+		fail("_opt_register returned?");
+	} else {
+		ok1(exitval - 1 == 1);
+		ok1(strstr(output, "Option -a: description cannot be NULL"));
+	}
+	reset();
+
+	exitval = setjmp(exited);
+	if (exitval == 0) {
+		/* Bad option name. */
+		opt_register_noarg("a", test_noarg, "", "");
+		fail("_opt_register returned?");
+	} else {
+		ok1(exitval - 1 == 1);
+		ok1(strstr(output, "Option a: does not begin with '-'"));
+	}
+
+	reset();
+
+	exitval = setjmp(exited);
+	if (exitval == 0) {
+		/* Bad option name. */
+		opt_register_noarg("--", test_noarg, "", "");
+		fail("_opt_register returned?");
+	} else {
+		ok1(exitval - 1 == 1);
+		ok1(strstr(output, "Option --: invalid long option '--'"));
+	}
+
+	reset();
+
+	exitval = setjmp(exited);
+	if (exitval == 0) {
+		/* Bad option name. */
+		opt_register_noarg("--a|-aaa", test_noarg, "", "");
+		fail("_opt_register returned?");
+	} else {
+		ok1(exitval - 1 == 1);
+		ok1(strstr(output,
+			   "Option --a|-aaa: invalid short option '-aaa'"));
+	}
+	reset();
+
+	exitval = setjmp(exited);
+	if (exitval == 0) {
+		/* Documentation for non-optios. */
+		opt_register_noarg("--a foo", test_noarg, "", "");
+		fail("_opt_register returned?");
+	} else {
+		ok1(exitval - 1 == 1);
+		ok1(strstr(output,
+			   "Option --a foo: does not take arguments 'foo'"));
+	}
+	reset();
+
+	exitval = setjmp(exited);
+	if (exitval == 0) {
+		/* Documentation for non-optios. */
+		opt_register_noarg("--a=foo", test_noarg, "", "");
+		fail("_opt_register returned?");
+	} else {
+		ok1(exitval - 1 == 1);
+		ok1(strstr(output,
+			   "Option --a=foo: does not take arguments 'foo'"));
+	}
+	return exit_status();
+}

+ 7 - 6
ccan/opt/test/run-correct-reporting.c

@@ -5,6 +5,7 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
+#include <ccan/opt/parse.c>
 #include "utils.h"
 
 int main(int argc, char *argv[])
@@ -14,30 +15,30 @@ int main(int argc, char *argv[])
 	/* --aaa without args. */
 	opt_register_arg("-a|--aaa", test_arg, NULL, "aaa", "");
 	ok1(!parse_args(&argc, &argv, "--aaa", NULL));
-	ok1(strstr(err_output, ": --aaa: option requires an argument"));
+	ok1(strstr(err_output, ": --aaa: requires an argument"));
 	free(err_output);
 	err_output = NULL;
 	ok1(!parse_args(&argc, &argv, "-a", NULL));
-	ok1(strstr(err_output, ": -a: option requires an argument"));
+	ok1(strstr(err_output, ": -a: requires an argument"));
 	free(err_output);
 	err_output = NULL;
 
 	/* Multiple */
 	opt_register_arg("--bbb|-b|-c|--ccc", test_arg, NULL, "aaa", "");
 	ok1(!parse_args(&argc, &argv, "--bbb", NULL));
-	ok1(strstr(err_output, ": --bbb: option requires an argument"));
+	ok1(strstr(err_output, ": --bbb: requires an argument"));
 	free(err_output);
 	err_output = NULL;
 	ok1(!parse_args(&argc, &argv, "-b", NULL));
-	ok1(strstr(err_output, ": -b: option requires an argument"));
+	ok1(strstr(err_output, ": -b: requires an argument"));
 	free(err_output);
 	err_output = NULL;
 	ok1(!parse_args(&argc, &argv, "-c", NULL));
-	ok1(strstr(err_output, ": -c: option requires an argument"));
+	ok1(strstr(err_output, ": -c: requires an argument"));
 	free(err_output);
 	err_output = NULL;
 	ok1(!parse_args(&argc, &argv, "--ccc", NULL));
-	ok1(strstr(err_output, ": --ccc: option requires an argument"));
+	ok1(strstr(err_output, ": --ccc: requires an argument"));
 	free(err_output);
 	err_output = NULL;
 

+ 1 - 0
ccan/opt/test/run-helpers.c

@@ -22,6 +22,7 @@ static int saved_vprintf(const char *fmt, va_list ap);
 #include <ccan/opt/helpers.c>
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
+#include <ccan/opt/parse.c>
 
 static void reset_options(void)
 {

+ 68 - 56
ccan/opt/test/run-iter.c

@@ -8,70 +8,82 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
+#include <ccan/opt/parse.c>
+
+static void reset_options(void)
+{
+	free(opt_table);
+	opt_table = NULL;
+	opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0;
+}
 
 /* Test iterators. */
 int main(int argc, char *argv[])
 {
-	unsigned i, len;
+	unsigned j, i, len;
 	const char *p;
 
-	plan_tests(37);
-	opt_register_table(subtables, NULL);
+	plan_tests(37 * 2);
+	for (j = 0; j < 2; j ++) {
+		reset_options();
+		/* Giving subtable a title makes an extra entry! */
+		opt_register_table(subtables, j == 0 ? NULL : "subtable");
 
-	p = first_lopt(&i, &len);
-	ok1(i == 0);
-	ok1(len == 3);
-	ok1(strncmp(p, "jjj", len) == 0);
-	p = next_lopt(p, &i, &len);
-	ok1(i == 0);
-	ok1(len == 3);
-	ok1(strncmp(p, "lll", len) == 0);
-	p = next_lopt(p, &i, &len);
-	ok1(i == 1);
-	ok1(len == 3);
-	ok1(strncmp(p, "mmm", len) == 0);
-	p = next_lopt(p, &i, &len);
-	ok1(i == 5);
-	ok1(len == 3);
-	ok1(strncmp(p, "ddd", len) == 0);
-	p = next_lopt(p, &i, &len);
-	ok1(i == 6);
-	ok1(len == 3);
-	ok1(strncmp(p, "eee", len) == 0);
-	p = next_lopt(p, &i, &len);
-	ok1(i == 7);
-	ok1(len == 3);
-	ok1(strncmp(p, "ggg", len) == 0);
-	p = next_lopt(p, &i, &len);
-	ok1(i == 8);
-	ok1(len == 3);
-	ok1(strncmp(p, "hhh", len) == 0);
-	p = next_lopt(p, &i, &len);
-	ok1(!p);
+		p = first_lopt(&i, &len);
+		ok1(i == j + 0);
+		ok1(len == 3);
+		ok1(strncmp(p, "jjj", len) == 0);
+		p = next_lopt(p, &i, &len);
+		ok1(i == j + 0);
+		ok1(len == 3);
+		ok1(strncmp(p, "lll", len) == 0);
+		p = next_lopt(p, &i, &len);
+		ok1(i == j + 1);
+		ok1(len == 3);
+		ok1(strncmp(p, "mmm", len) == 0);
+		p = next_lopt(p, &i, &len);
+		ok1(i == j + 5);
+		ok1(len == 3);
+		ok1(strncmp(p, "ddd", len) == 0);
+		p = next_lopt(p, &i, &len);
+		ok1(i == j + 6);
+		ok1(len == 3);
+		ok1(strncmp(p, "eee", len) == 0);
+		p = next_lopt(p, &i, &len);
+		ok1(i == j + 7);
+		ok1(len == 3);
+		ok1(strncmp(p, "ggg", len) == 0);
+		p = next_lopt(p, &i, &len);
+		ok1(i == j + 8);
+		ok1(len == 3);
+		ok1(strncmp(p, "hhh", len) == 0);
+		p = next_lopt(p, &i, &len);
+		ok1(!p);
 
-	p = first_sopt(&i);
-	ok1(i == 0);
-	ok1(*p == 'j');
-	p = next_sopt(p, &i);
-	ok1(i == 0);
-	ok1(*p == 'l');
-	p = next_sopt(p, &i);
-	ok1(i == 1);
-	ok1(*p == 'm');
-	p = next_sopt(p, &i);
-	ok1(i == 2);
-	ok1(*p == 'a');
-	p = next_sopt(p, &i);
-	ok1(i == 3);
-	ok1(*p == 'b');
-	p = next_sopt(p, &i);
-	ok1(i == 7);
-	ok1(*p == 'g');
-	p = next_sopt(p, &i);
-	ok1(i == 8);
-	ok1(*p == 'h');
-	p = next_sopt(p, &i);
-	ok1(!p);
+		p = first_sopt(&i);
+		ok1(i == j + 0);
+		ok1(*p == 'j');
+		p = next_sopt(p, &i);
+		ok1(i == j + 0);
+		ok1(*p == 'l');
+		p = next_sopt(p, &i);
+		ok1(i == j + 1);
+		ok1(*p == 'm');
+		p = next_sopt(p, &i);
+		ok1(i == j + 2);
+		ok1(*p == 'a');
+		p = next_sopt(p, &i);
+		ok1(i == j + 3);
+		ok1(*p == 'b');
+		p = next_sopt(p, &i);
+		ok1(i == j + 7);
+		ok1(*p == 'g');
+		p = next_sopt(p, &i);
+		ok1(i == j + 8);
+		ok1(*p == 'h');
+		p = next_sopt(p, &i);
+		ok1(!p);
+	}
 
 	return exit_status();
 }

+ 1 - 0
ccan/opt/test/run-no-options.c

@@ -4,6 +4,7 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
+#include <ccan/opt/parse.c>
 #include "utils.h"
 
 int main(int argc, char *argv[])

+ 23 - 1
ccan/opt/test/run-usage.c

@@ -8,18 +8,26 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
+#include <ccan/opt/parse.c>
 
 static char *my_cb(void *p)
 {
 	return NULL;
 }
 
+static void reset_options(void)
+{
+	free(opt_table);
+	opt_table = NULL;
+	opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0;
+}
+
 /* Test helpers. */
 int main(int argc, char *argv[])
 {
 	char *output;
 
-	plan_tests(38);
+	plan_tests(42);
 	opt_register_table(subtables, NULL);
 	opt_register_noarg("--kkk|-k", my_cb, NULL, "magic kkk option");
 	opt_register_noarg("-?", opt_usage_and_exit, "<MyArgs>...",
@@ -73,5 +81,19 @@ int main(int argc, char *argv[])
 	ok1(!strstr(output, "--mmm|-m"));
 	free(output);
 
+	reset_options();
+	/* Empty table test. */
+	output = opt_usage("nothing", NULL);
+	ok1(strstr(output, "Usage: nothing \n"));
+	free(output);
+
+	/* No short args. */
+	opt_register_noarg("--aaa", test_noarg, NULL, "AAAAll");
+	output = opt_usage("onearg", NULL);
+	ok1(strstr(output, "Usage: onearg \n"));
+	ok1(strstr(output, "--aaa"));
+	ok1(strstr(output, "AAAAll"));
+	free(output);
+
 	return exit_status();
 }

+ 77 - 1
ccan/opt/test/run.c

@@ -3,6 +3,7 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
+#include <ccan/opt/parse.c>
 #include "utils.h"
 
 static void reset_options(void)
@@ -18,7 +19,7 @@ int main(int argc, char *argv[])
 {
 	const char *myname = argv[0];
 
-	plan_tests(148);
+	plan_tests(215);
 
 	/* Simple short arg.*/
 	opt_register_noarg("-a", test_noarg, NULL, "All");
@@ -52,6 +53,14 @@ int main(int argc, char *argv[])
 	ok1(strcmp(argv[2], "args") == 0);
 	ok1(test_cb_called == 6);
 
+	/* Malformed versions. */
+	ok1(!parse_args(&argc, &argv, "--aaa=arg", NULL));
+	ok1(strstr(err_output, ": --aaa: doesn't allow an argument"));
+	ok1(!parse_args(&argc, &argv, "--aa", NULL));
+	ok1(strstr(err_output, ": --aa: unrecognized option"));
+	ok1(!parse_args(&argc, &argv, "--aaargh", NULL));
+	ok1(strstr(err_output, ": --aaargh: unrecognized option"));
+
 	/* Argument variants. */
 	reset_options();
 	test_cb_called = 0;
@@ -71,6 +80,16 @@ int main(int argc, char *argv[])
 	ok1(argv[0] == myname);
 	ok1(test_cb_called == 3);
 
+	/* Malformed versions. */
+	ok1(!parse_args(&argc, &argv, "-a", NULL));
+	ok1(strstr(err_output, ": -a: requires an argument"));
+	ok1(!parse_args(&argc, &argv, "--aaa", NULL));
+	ok1(strstr(err_output, ": --aaa: requires an argument"));
+	ok1(!parse_args(&argc, &argv, "--aa", NULL));
+	ok1(strstr(err_output, ": --aa: unrecognized option"));
+	ok1(!parse_args(&argc, &argv, "--aaargh", NULL));
+	ok1(strstr(err_output, ": --aaargh: unrecognized option"));
+
 	/* Now, tables. */
 	/* Short table: */
 	reset_options();
@@ -215,5 +234,62 @@ int main(int argc, char *argv[])
 	test_cb_called = 0;
 	reset_options();
 
+	/* Corner cases involving short arg parsing weirdness. */
+	opt_register_noarg("-a|--aaa", test_noarg, NULL, "a");
+	opt_register_arg("-b|--bbb", test_arg, NULL, "bbb", "b");
+	opt_register_arg("-c|--ccc", test_arg, NULL, "aaa", "c");
+	/* -aa == -a -a */
+	ok1(parse_args(&argc, &argv, "-aa", NULL));
+	ok1(test_cb_called == 2);
+	ok1(parse_args(&argc, &argv, "-aab", NULL) == false);
+	ok1(test_cb_called == 4);
+	ok1(strstr(err_output, ": -b: requires an argument"));
+	ok1(parse_args(&argc, &argv, "-bbbb", NULL));
+	ok1(test_cb_called == 5);
+	ok1(parse_args(&argc, &argv, "-aabbbb", NULL));
+	ok1(test_cb_called == 8);
+	ok1(parse_args(&argc, &argv, "-aabbbb", "-b", "bbb", NULL));
+	ok1(test_cb_called == 12);
+	ok1(parse_args(&argc, &argv, "-aabbbb", "--bbb", "bbb", NULL));
+	ok1(test_cb_called == 16);
+	ok1(parse_args(&argc, &argv, "-aabbbb", "--bbb=bbb", NULL));
+	ok1(test_cb_called == 20);
+	ok1(parse_args(&argc, &argv, "-aacaaa", NULL));
+	ok1(test_cb_called == 23);
+	ok1(parse_args(&argc, &argv, "-aacaaa", "-a", NULL));
+	ok1(test_cb_called == 27);
+	ok1(parse_args(&argc, &argv, "-aacaaa", "--bbb", "bbb", "-aacaaa",
+		       NULL));
+	ok1(test_cb_called == 34);
+
+	test_cb_called = 0;
+	reset_options();
+
+	/* -- and POSIXLY_CORRECT */
+	opt_register_noarg("-a|--aaa", test_noarg, NULL, "a");
+	ok1(parse_args(&argc, &argv, "-a", "--", "-a", NULL));
+	ok1(test_cb_called == 1);
+	ok1(argc == 2);
+	ok1(strcmp(argv[1], "-a") == 0);
+	ok1(!argv[2]);
+
+	unsetenv("POSIXLY_CORRECT");
+	ok1(parse_args(&argc, &argv, "-a", "somearg", "-a", "--", "-a", NULL));
+	ok1(test_cb_called == 3);
+	ok1(argc == 3);
+	ok1(strcmp(argv[1], "somearg") == 0);
+	ok1(strcmp(argv[2], "-a") == 0);
+	ok1(!argv[3]);
+
+	setenv("POSIXLY_CORRECT", "1", 1);
+	ok1(parse_args(&argc, &argv, "-a", "somearg", "-a", "--", "-a", NULL));
+	ok1(test_cb_called == 4);
+	ok1(argc == 5);
+	ok1(strcmp(argv[1], "somearg") == 0);
+	ok1(strcmp(argv[2], "-a") == 0);
+	ok1(strcmp(argv[3], "--") == 0);
+	ok1(strcmp(argv[4], "-a") == 0);
+	ok1(!argv[5]);
+
 	return exit_status();
 }

+ 1 - 1
tools/ccanlint/Makefile

@@ -12,7 +12,7 @@ CORE_OBJS := tools/ccanlint/ccanlint.o \
 	ccan/btree/btree.o \
 	ccan/talloc/talloc.o ccan/noerr/noerr.o \
 	ccan/read_write_all/read_write_all.o \
-	ccan/opt/opt.o ccan/opt/usage.o ccan/opt/helpers.o
+	ccan/opt/opt.o ccan/opt/usage.o ccan/opt/helpers.o ccan/opt/parse.o
 
 OBJS := $(CORE_OBJS) $(TEST_OBJS)