opt: wean off getopt_long, beef up tests.
authorRusty Russell <rusty@rustcorp.com.au>
Mon, 25 Oct 2010 03:28:50 +0000 (13:58 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Tue, 26 Oct 2010 08:50:27 +0000 (19:20 +1030)
Doing our own parsing lost a few lines of code, too.

Our coverage is over 99% now.

12 files changed:
ccan/opt/helpers.c
ccan/opt/opt.c
ccan/opt/parse.c [new file with mode: 0644]
ccan/opt/private.h
ccan/opt/test/run-checkopt.c [new file with mode: 0644]
ccan/opt/test/run-correct-reporting.c
ccan/opt/test/run-helpers.c
ccan/opt/test/run-iter.c
ccan/opt/test/run-no-options.c
ccan/opt/test/run-usage.c
ccan/opt/test/run.c
tools/ccanlint/Makefile

index 0f3671e526d838264d78d8f68b2120acbaa71e44..e96f5db81ebf9df3c9af2d646875aa944b1d21ff 100644 (file)
@@ -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);
        *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)
        if (errno)
-               return opt_invalid_argument(arg);
+               return arg_bad("'%s' is out of range", arg);
        return NULL;
 }
 
        return NULL;
 }
 
index 0929daff65eaa3af8bade32de748d37dd387d026..8f67740036e7e5c5ac53bb3c2abe8d5a9ccc7fce 100644 (file)
@@ -1,7 +1,6 @@
 #include <ccan/opt/opt.h>
 #include <string.h>
 #include <errno.h>
 #include <ccan/opt/opt.h>
 #include <string.h>
 #include <errno.h>
-#include <getopt.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <err.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;
 }
 
        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)) {
 {
        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;
 }
 
        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] == '-') {
 {
        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 (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++;
                        opt_num_short++;
-                       if (entry->type == OPT_HASARG) {
+                       if (entry->type == OPT_HASARG)
                                opt_num_short_arg++;
                                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"
                }
                /* 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);
 }
 
                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, ...))
 {
 /* 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];
 
        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, ...)
 }
 
 void opt_log_stderr(const char *fmt, ...)
diff --git a/ccan/opt/parse.c b/ccan/opt/parse.c
new file mode 100644 (file)
index 0000000..228808d
--- /dev/null
@@ -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;
+}
index 5d9eca23a0ddf55b81f812009b2b09e0fed104d7..048951e90c55f47285ccf1463567cf8241c1a5f1 100644 (file)
@@ -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_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 */
 
 #endif /* CCAN_OPT_PRIVATE_H */
diff --git a/ccan/opt/test/run-checkopt.c b/ccan/opt/test/run-checkopt.c
new file mode 100644 (file)
index 0000000..9e2f88a
--- /dev/null
@@ -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();
+}
index b51d53eb3faff7f0745db3514ac1764ed65adca1..4f775a29c47fb0fa819417a0690c01a294034cd9 100644 (file)
@@ -5,6 +5,7 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
 #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[])
 #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));
        /* --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));
        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));
        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));
        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));
        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));
        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;
 
        free(err_output);
        err_output = NULL;
 
index 34ffbab834cb11923d2ba7719fd1091850860b5e..3bb70eb4386d4b482cf92987944332ae514e88ee 100644 (file)
@@ -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/helpers.c>
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
+#include <ccan/opt/parse.c>
 
 static void reset_options(void)
 {
 
 static void reset_options(void)
 {
index 652cd316cfca4cdf0065b0d88466f0a766c2afee..36e2c33c0a21ea365bb7ab4a46a5c03fe23d4949 100644 (file)
@@ -8,70 +8,82 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
 #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[])
 {
 
 /* Test iterators. */
 int main(int argc, char *argv[])
 {
-       unsigned i, len;
+       unsigned j, i, len;
        const char *p;
 
        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();
 }
 
        return exit_status();
 }
index 7b05cbae87b79c9761ec0de8f094098f1aef9dd7..5ae2dd35f1ccb256159462cad98edb16bd36f7f2 100644 (file)
@@ -4,6 +4,7 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
 #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[])
 #include "utils.h"
 
 int main(int argc, char *argv[])
index 9d448dc460b7cf6db90ff398ac0558138bfc51c1..dd5e42e72ce4bf3911ec09b09fe2e5b641c00c27 100644 (file)
@@ -8,18 +8,26 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
 #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 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;
 
 /* 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>...",
        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);
 
        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();
 }
        return exit_status();
 }
index 1e04ec169ac2b3dea21ba83909e312accc50a565..8982baa048de8cbed2c4de0f32a799b61388defb 100644 (file)
@@ -3,6 +3,7 @@
 #include <ccan/opt/opt.c>
 #include <ccan/opt/usage.c>
 #include <ccan/opt/helpers.c>
 #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)
 #include "utils.h"
 
 static void reset_options(void)
@@ -18,7 +19,7 @@ int main(int argc, char *argv[])
 {
        const char *myname = argv[0];
 
 {
        const char *myname = argv[0];
 
-       plan_tests(148);
+       plan_tests(215);
 
        /* Simple short arg.*/
        opt_register_noarg("-a", test_noarg, NULL, "All");
 
        /* 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);
 
        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;
        /* 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);
 
        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();
        /* Now, tables. */
        /* Short table: */
        reset_options();
@@ -215,5 +234,62 @@ int main(int argc, char *argv[])
        test_cb_called = 0;
        reset_options();
 
        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();
 }
        return exit_status();
 }
index bbd37054f1affe20ceafd77505bb203ab49f24d2..b84b8353fa2da22c8692963acfd8919e6854f304 100644 (file)
@@ -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/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)
 
 
 OBJS := $(CORE_OBJS) $(TEST_OBJS)