]> git.ozlabs.org Git - ccan/commitdiff
opt: new module to parse commandline options.
authorRusty Russell <rusty@rustcorp.com.au>
Wed, 29 Sep 2010 22:57:58 +0000 (08:27 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Wed, 29 Sep 2010 22:57:58 +0000 (08:27 +0930)
ccan/opt/_info [new file with mode: 0644]
ccan/opt/helpers.c [new file with mode: 0644]
ccan/opt/opt.c [new file with mode: 0644]
ccan/opt/opt.h [new file with mode: 0644]
ccan/opt/private.h [new file with mode: 0644]
ccan/opt/test/run-usage.c [new file with mode: 0644]
ccan/opt/test/run.c [new file with mode: 0644]
ccan/opt/test/utils.c [new file with mode: 0644]
ccan/opt/test/utils.h [new file with mode: 0644]
ccan/opt/usage.c [new file with mode: 0644]

diff --git a/ccan/opt/_info b/ccan/opt/_info
new file mode 100644 (file)
index 0000000..54ae077
--- /dev/null
@@ -0,0 +1,65 @@
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+
+/**
+ * opt - simple command line parsing
+ *
+ * Simple but powerful command line parsing, built on top of getopt_long.
+ *
+ * Example:
+ * #include <ccan/opt/opt.h>
+ * #include <stdio.h>
+ * #include <stdlib.h>
+ * 
+ * static bool someflag;
+ * static int verbose;
+ * static char *somestring;
+ * 
+ * static struct opt_table opts[] = {
+ *     { OPT_WITHOUT_ARG("verbose", 'v', opt_inc_intval, &verbose),
+ *       "Verbose mode (can be specified more than once)" },
+ *     { OPT_WITHOUT_ARG("someflag", 0, opt_set_bool, &someflag),
+ *       "Set someflag" },
+ *     { OPT_WITH_ARG("somestring", 0, opt_set_charp, &somestring),
+ *       "Set somestring to <arg>" },
+ *     { OPT_WITHOUT_ARG("usage", 0, opt_usage_and_exit,
+ *                       "args...\nA silly test program."),
+ *       "Print this message." },
+ *     OPT_ENDTABLE
+ * };
+ * 
+ * int main(int argc, char *argv[])
+ * {
+ *     int i;
+ * 
+ *     opt_register_table(opts);
+ *     // For fun, register an extra one.
+ *     opt_register_noarg("no-someflag", 0, opt_set_invbool, &someflag,
+ *                        "Unset someflag");
+ *     if (!opt_parse(&argc, argv, opt_log_stderr))
+ *             exit(1);
+ * 
+ *     printf("someflag = %i, verbose = %i, somestring = %s\n",
+ *            someflag, verbose, somestring);
+ *     printf("%u args left over:", argc - 1);
+ *     for (i = 1; i < argc; i++)
+ *             printf(" %s", argv[i]);
+ *     printf("\n");
+ *     return 0;
+ * }
+ *
+ * Licence: GPL (3 or any later version)
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ */
+int main(int argc, char *argv[])
+{
+       if (argc != 2)
+               return 1;
+
+       if (strcmp(argv[1], "depends") == 0) {
+               return 0;
+       }
+
+       return 1;
+}
diff --git a/ccan/opt/helpers.c b/ccan/opt/helpers.c
new file mode 100644 (file)
index 0000000..7554c99
--- /dev/null
@@ -0,0 +1,129 @@
+#include <ccan/opt/opt.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include "private.h"
+
+/* FIXME: asprintf module? */
+static char *arg_bad(const char *fmt, const char *arg)
+{
+       char *str = malloc(strlen(fmt) + strlen(arg));
+       sprintf(str, fmt, arg);
+       return str;
+}
+
+char *opt_set_bool(bool *b)
+{
+       *b = true;
+       return NULL;
+}
+
+char *opt_set_invbool(bool *b)
+{
+       *b = false;
+       return NULL;
+}
+
+char *opt_set_bool_arg(const char *arg, bool *b)
+{
+       if (!strcasecmp(arg, "yes") || !strcasecmp(arg, "true"))
+               return opt_set_bool(b);
+       if (!strcasecmp(arg, "no") || !strcasecmp(arg, "false"))
+               return opt_set_invbool(b);
+
+       return opt_invalid_argument(arg);
+}
+
+char *opt_set_invbool_arg(const char *arg, bool *b)
+{
+       char *err = opt_set_bool_arg(arg, b);
+
+       if (!err)
+               *b = !*b;
+       return err;
+}
+
+/* Set a char *. */
+char *opt_set_charp(const char *arg, char **p)
+{
+       *p = (char *)arg;
+       return NULL;
+}
+
+/* Set an integer value, various forms.  Sets to 1 on arg == NULL. */
+char *opt_set_intval(const char *arg, int *i)
+{
+       long l;
+       char *err = opt_set_longval(arg, &l);
+
+       if (err)
+               return err;
+       *i = l;
+       /* Beware truncation... */
+       if (*i != l)
+               return arg_bad("value '%s' does not fit into an integer", arg);
+       return err;
+}
+
+char *opt_set_uintval(const char *arg, unsigned int *ui)
+{
+       int i;
+       char *err = opt_set_intval(arg, &i);
+
+       if (err)
+               return err;
+       if (i < 0)
+               return arg_bad("'%s' is negative", arg);
+       *ui = i;
+       return NULL;
+}
+
+char *opt_set_longval(const char *arg, long *l)
+{
+       char *endp;
+
+       /* This is how the manpage says to do it.  Yech. */
+       errno = 0;
+       *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 NULL;
+}
+
+char *opt_set_ulongval(const char *arg, unsigned long *ul)
+{
+       long int l;
+       char *err;
+       
+       err = opt_set_longval(arg, &l);
+       if (err)
+               return err;
+       *ul = l;
+       if (l < 0)
+               return arg_bad("'%s' is negative", arg);
+       return NULL;
+}
+
+char *opt_inc_intval(int *i)
+{
+       (*i)++;
+       return NULL;
+}
+
+/* Display version string. */
+char *opt_show_version_and_exit(const char *version)
+{
+       printf("%s\n", version);
+       exit(0);
+}
+
+char *opt_usage_and_exit(const char *extra)
+{
+       printf("%s", opt_usage(opt_argv0, extra));
+       exit(0);
+}
diff --git a/ccan/opt/opt.c b/ccan/opt/opt.c
new file mode 100644 (file)
index 0000000..f601bb6
--- /dev/null
@@ -0,0 +1,238 @@
+#include <ccan/opt/opt.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include "private.h"
+
+struct opt_table *opt_table;
+unsigned int opt_count;
+const char *opt_argv0;
+
+static void check_opt(const struct opt_table *entry)
+{
+       assert(entry->flags == OPT_HASARG || entry->flags == OPT_NOARG);
+       assert(entry->shortopt || entry->longopt);
+       assert(entry->shortopt != ':');
+       assert(entry->shortopt != '?' || entry->flags == OPT_NOARG);
+}
+
+static void add_opt(const struct opt_table *entry)
+{
+       opt_table = realloc(opt_table, sizeof(opt_table[0]) * (opt_count+1));
+       opt_table[opt_count++] = *entry;
+}
+
+void _opt_register(const char *longopt, char shortopt, enum opt_flags flags,
+                  char *(*cb)(void *arg),
+                  char *(*cb_arg)(const char *optarg, void *arg),
+                  void *arg, const char *desc)
+{
+       struct opt_table opt;
+       opt.longopt = longopt;
+       opt.shortopt = shortopt;
+       opt.flags = flags;
+       opt.cb = cb;
+       opt.cb_arg = cb_arg;
+       opt.arg = arg;
+       opt.desc = desc;
+       check_opt(&opt);
+       add_opt(&opt);
+}
+
+void opt_register_table(const struct opt_table entry[], const char *desc)
+{
+       unsigned int i, start = opt_count;
+
+       if (desc) {
+               struct opt_table heading = OPT_SUBTABLE(NULL, desc);
+               add_opt(&heading);
+       }
+       for (i = 0; entry[i].flags != OPT_END; i++) {
+               if (entry[i].flags == OPT_SUBTABLE)
+                       opt_register_table(subtable_of(&entry[i]),
+                                          entry[i].desc);
+               else {
+                       check_opt(&entry[i]);
+                       add_opt(&entry[i]);
+               }
+       }
+       /* We store the table length in arg ptr. */
+       if (desc)
+               opt_table[start].arg = (void *)(intptr_t)(opt_count - start);
+}
+
+static char *make_optstring(void)
+{
+       /* Worst case, each one is ":x:", plus nul term. */
+       char *str = malloc(1 + opt_count * 2 + 1);
+       unsigned int num, i;
+
+       /* This tells getopt_long we want a ':' returned for missing arg. */
+       str[0] = ':';
+       num = 1;
+       for (i = 0; i < opt_count; i++) {
+               if (!opt_table[i].shortopt)
+                       continue;
+               str[num++] = opt_table[i].shortopt;
+               if (opt_table[i].flags == OPT_HASARG)
+                       str[num++] = ':';
+       }
+       str[num] = '\0';
+       return str;
+}
+
+static struct option *make_options(void)
+{
+       struct option *options = malloc(sizeof(*options) * (opt_count + 1));
+       unsigned int i, num;
+
+       for (num = i = 0; i < opt_count; i++) {
+               if (!opt_table[i].longopt)
+                       continue;
+               options[num].name = opt_table[i].longopt;
+               options[num].has_arg = (opt_table[i].flags == OPT_HASARG);
+               options[num].flag = NULL;
+               options[num].val = 0;
+               num++;
+       }
+       memset(&options[num], 0, sizeof(options[num]));
+       return options;
+}
+
+static struct opt_table *find_short(char shortopt)
+{
+       unsigned int i;
+       for (i = 0; i < opt_count; i++) {
+               if (opt_table[i].shortopt == shortopt)
+                       return &opt_table[i];
+       }
+       abort();
+}
+
+/* We want the index'th long entry. */
+static struct opt_table *find_long(int index)
+{
+       unsigned int i;
+       for (i = 0; i < opt_count; i++) {
+               if (!opt_table[i].longopt)
+                       continue;
+               if (index == 0)
+                       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, longopt, problem);
+}
+
+void dump_optstate(void);
+void dump_optstate(void)
+{
+       printf("opterr = %i, optind = %i, optopt = %i, optarg = %s\n",
+              opterr, optind, optopt, optarg);
+}
+
+/* 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;
+
+       /* We will do our own error reporting. */
+       opterr = 0;
+       opt_argv0 = argv[0];
+
+       /* Reset in case we're called more than once. */
+       optopt = 0;
+       optind = 1;
+       while ((ret = getopt_long(*argc, argv, optstring, options, &longidx))
+              != -1) {
+               char *problem;
+               bool missing = false;
+
+               /* 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 = true;
+                       ret = optopt;
+               }
+
+               if (ret != 0)
+                       e = find_short(ret);
+               else
+                       e = find_long(longidx);
+
+               /* Missing argument */
+               if (missing) {
+                       parse_fail(errlog, e->shortopt, e->longopt,
+                                  "option requires an argument");
+                       break;
+               }
+
+               if (e->flags == OPT_HASARG)
+                       problem = e->cb_arg(optarg, e->arg);
+               else
+                       problem = e->cb(e->arg);
+
+               if (problem) {
+                       parse_fail(errlog, e->shortopt, e->longopt,
+                                  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;
+
+       return ret == -1 ? true : false;
+}
+
+void opt_log_stderr(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       va_end(ap);
+}
+
+char *opt_invalid_argument(const char *arg)
+{
+       char *str = malloc(sizeof("Invalid argument '%s'") + strlen(arg));
+       sprintf(str, "Invalid argument '%s'", arg);
+       return str;
+}
diff --git a/ccan/opt/opt.h b/ccan/opt/opt.h
new file mode 100644 (file)
index 0000000..f47b1c8
--- /dev/null
@@ -0,0 +1,269 @@
+#ifndef CCAN_OPT_H
+#define CCAN_OPT_H
+#include <ccan/typesafe_cb/typesafe_cb.h>
+#include <stdbool.h>
+
+/* You can use this directly to build tables, but the macros will ensure
+ * consistency and type safety. */
+enum opt_flags {
+       OPT_NOARG = 1,          /* -f/--foo */
+       OPT_HASARG = 2,         /* -f arg/--foo=arg/--foo arg */
+       OPT_SUBTABLE = 4,       /* Actually, longopt points to a subtable... */
+       OPT_END = 8,            /* End of the table. */
+};
+
+struct opt_table {
+       const char *longopt; /* --longopt, or NULL */
+       char shortopt; /* -s, or 0 */
+       enum opt_flags flags;
+       char *(*cb)(void *arg); /* OPT_NOARG */
+       char *(*cb_arg)(const char *optarg, void *arg); /* OPT_HASARG */
+       void *arg;
+       const char *desc;
+};
+
+/**
+ * OPT_WITHOUT_ARG() - macro for initializing an opt_table entry (without arg)
+ * @longopt: the name of the argument (eg. "foo" for "--foo"), or NULL.
+ * @shortopt: the character of the argument (eg. 'f' for "-f"), or 0.
+ * @cb: the callback when the option is found.
+ * @arg: the argument to hand to @cb.
+ *
+ * This is a typesafe wrapper for intializing a struct opt_table.  The callback
+ * of type "char *cb(type *)", "char *cb(const type *)" or "char *cb(void *)",
+ * where "type" is the type of the @arg argument.
+ *
+ * At least one of @longopt and @shortopt must be non-zero.  If the
+ * @cb returns non-NULL, opt_parse() will stop parsing, use the returned
+ * string to form an error message, free() the string and return false.
+ *
+ * See Also:
+ *     OPT_WITH_ARG()
+ */
+#define OPT_WITHOUT_ARG(longopt, shortopt, cb, arg)    \
+       (longopt), (shortopt), OPT_CB_NOARG((cb), (arg))
+
+/**
+ * OPT_WITH_ARG() - macro for initializing long and short option (with arg)
+ * @longopt: the name of the argument (eg. "foo" for "--foo <arg>"), or NULL.
+ * @shortopt: the character of the argument (eg. 'f' for "-f <arg>"), or 0.
+ * @cb: the callback when the option is found (along with <arg>).
+ * @arg: the argument to hand to @cb.
+ *
+ * This is a typesafe wrapper for intializing a struct opt_table.  The callback
+ * is of type "bool cb(const char *, type *)",
+ * "bool cb(const char *, const type *)" or "bool cb(const char *, void *)",
+ * where "type" is the type of the @arg argument.  The first argument to the
+ * @cb is the argument found on the commandline.
+ *
+ * At least one of @longopt and @shortopt must be non-zero.  If the
+ * @cb returns false, opt_parse() will stop parsing and return false.
+ *
+ * See Also:
+ *     OPT_WITH_ARG()
+ */
+#define OPT_WITH_ARG(longopt, shortopt, cb, arg)       \
+       (longopt), (shortopt), OPT_CB_ARG((cb), (arg))
+
+/**
+ * OPT_SUBTABLE() - macro for including another table inside a table.
+ * @table: the table to include in this table.
+ * @desc: description of this subtable (for opt_usage()) or NULL.
+ *
+ * The @desc field can be opt_table_hidden to hide the options from opt_usage().
+ */
+#define OPT_SUBTABLE(table, desc)                                      \
+       { (const char *)(table), sizeof(_check_is_entry(table)),        \
+         OPT_SUBTABLE, NULL, NULL, NULL, (desc) }
+
+/**
+ * opt_table_hidden - string for undocumented option tables.
+ *
+ * This can be used as the desc option to OPT_SUBTABLE or passed to
+ * opt_register_table() if you want the options not to be shown by
+ * opt_usage().
+ */
+extern const char opt_table_hidden[];
+
+/**
+ * OPT_ENDTABLE - macro to create final entry in table.
+ *
+ * This must be the final element in the opt_table array.
+ */
+#define OPT_ENDTABLE { NULL, 0, OPT_END }
+
+/**
+ * opt_register_table - register a table of options
+ * @table: the table of options
+ * @desc: description of this subtable (for opt_usage()) or NULL.
+ *
+ * The table must be terminated by OPT_ENDTABLE.
+ *
+ * Example:
+ * static struct opt_table opts[] = {
+ *     { OPT_WITHOUT_ARG("verbose", 'v', opt_inc_intval, &verbose),
+ *       "Verbose mode (can be specified more than once)" },
+ *     { OPT_WITHOUT_ARG("usage", 0, opt_usage_and_exit,
+ *                       "args...\nA silly test program."),
+ *       "Print this message." },
+ *     OPT_ENDTABLE
+ * };
+ *
+ * ...
+ *     opt_register_table(opts, NULL);
+ */
+void opt_register_table(const struct opt_table table[], const char *desc);
+
+/**
+ * opt_register_noarg - register an option with no arguments
+ * @longopt: the name of the argument (eg. "foo" for "--foo"), or NULL.
+ * @shortopt: the character of the argument (eg. 'f' for "-f"), or 0.
+ * @cb: the callback when the option is found.
+ * @arg: the argument to hand to @cb.
+ * @desc: the verbose desction of the option (for opt_usage()), or NULL.
+ *
+ * This is used for registering a single commandline option which takes
+ * no argument.
+ *
+ * The callback is of type "bool cb(type *)", "bool cb(const type *)"
+ * or "bool cb(void *)", where "type" is the type of the @arg
+ * argument.
+ *
+ * At least one of @longopt and @shortopt must be non-zero.  If the
+ * @cb returns false, opt_parse() will stop parsing and return false.
+ */
+#define opt_register_noarg(longopt, shortopt, cb, arg, desc)   \
+       _opt_register((longopt), (shortopt), OPT_CB_NOARG((cb), (arg)), (desc))
+
+/**
+ * opt_register_arg - register an option with an arguments
+ * @longopt: the name of the argument (eg. "foo" for "--foo"), or NULL.
+ * @shortopt: the character of the argument (eg. 'f' for "-f"), or 0.
+ * @cb: the callback when the option is found.
+ * @arg: the argument to hand to @cb.
+ * @desc: the verbose desction of the option (for opt_usage()), or NULL.
+ *
+ * This is used for registering a single commandline option which takes
+ * an argument.
+ *
+ * The callback is of type "bool cb(const char *, type *)",
+ * "bool cb(const char *, const type *)" or "bool cb(const char *, void *)",
+ * where "type" is the type of the @arg argument.  The first argument to the
+ * @cb is the argument found on the commandline.
+ *
+ * At least one of @longopt and @shortopt must be non-zero.  If the
+ * @cb returns false, opt_parse() will stop parsing and return false.
+ *
+ * Example:
+ *     opt_register_arg("explode", 'e', explode_cb, NULL,
+ *                      "Make the machine explode (developers only)");
+ */
+#define opt_register_arg(longopt, shortopt, cb, arg, desc)     \
+       _opt_register((longopt), (shortopt), OPT_CB_ARG((cb), (arg)), (desc))
+
+/**
+ * opt_parse - parse arguments.
+ * @argc: pointer to argc
+ * @argv: argv array.
+ * @errlog: the function to print errors (usually opt_log_stderr).
+ *
+ * This iterates through the command line and calls callbacks registered with
+ * opt_register_table()/opt_register_arg()/opt_register_noarg().  If there
+ * are unknown options, missing arguments or a callback returns false, then
+ * an error message is printed and false is returned.
+ *
+ * On success, argc and argv are adjusted so only the non-option elements
+ * remain, and true is returned.
+ *
+ * Example:
+ *     if (!opt_parse(argc, argv, opt_log_stderr)) {
+ *             printf("%s", opt_usage(argv[0], "<args>..."));
+ *             exit(1);
+ *     }
+ */
+bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...));
+
+/**
+ * opt_log_stderr - print message to stderr.
+ * @fmt: printf-style format.
+ *
+ * This is the standard helper for opt_parse, to print errors.
+ */
+void opt_log_stderr(const char *fmt, ...);
+
+/**
+ * opt_invalid_argument - helper to allocate an "Invalid argument '%s'" string
+ * @arg: the argument which was invalid.
+ *
+ * This is a helper for callbacks to return a simple error string.
+ */
+char *opt_invalid_argument(const char *arg);
+
+/**
+ * opt_usage - create usage message
+ * @argv0: the program name
+ * @extra: extra details to print after the initial command, or NULL.
+ *
+ * Creates a usage message, with the program name, arguments, some extra details
+ * and a table of all the options with their descriptions.
+ *
+ * The result should be passed to free().
+ */
+char *opt_usage(const char *argv0, const char *extra);
+
+/* Standard helpers.  You can write your own: */
+/* Sets the @b to true. */
+char *opt_set_bool(bool *b);
+/* Sets @b based on arg: (yes/no/true/false). */
+char *opt_set_bool_arg(const char *arg, bool *b);
+/* The inverse */
+char *opt_set_invbool(bool *b);
+char *opt_set_invbool_arg(const char *arg, bool *b);
+
+/* Set a char *. */
+char *opt_set_charp(const char *arg, char **p);
+
+/* Set an integer value, various forms.  Sets to 1 on arg == NULL. */
+char *opt_set_intval(const char *arg, int *i);
+char *opt_set_uintval(const char *arg, unsigned int *ui);
+char *opt_set_longval(const char *arg, long *l);
+char *opt_set_ulongval(const char *arg, unsigned long *ul);
+
+/* Increment. */
+char *opt_inc_intval(int *i);
+
+/* Display version string to stdout, exit(0). */
+char *opt_show_version_and_exit(const char *version);
+
+/* Display usage string to stdout, exit(0). */
+char *opt_usage_and_exit(const char *extra);
+
+/* Below here are private declarations. */
+/* Resolves to the four parameters for non-arg callbacks. */
+#define OPT_CB_NOARG(cb, arg)                          \
+       OPT_NOARG,                                      \
+       cast_if_any(char *(*)(void *), (cb), &*(cb),    \
+                   char *(*)(typeof(*(arg))*),         \
+                   char *(*)(const typeof(*(arg))*),   \
+                   char *(*)(const typeof(*(arg))*)),  \
+       NULL, (arg)
+
+/* Resolves to the four parameters for arg callbacks. */
+#define OPT_CB_ARG(cb, arg)                                            \
+       OPT_HASARG, NULL,                                               \
+       cast_if_any(char *(*)(const char *,void *), (cb), &*(cb),       \
+                   char *(*)(const char *, typeof(*(arg))*),           \
+                   char *(*)(const char *, const typeof(*(arg))*),     \
+                   char *(*)(const char *, const typeof(*(arg))*)),    \
+       (arg)
+
+/* Non-typesafe register function. */
+void _opt_register(const char *longopt, char shortopt, enum opt_flags flags,
+                  char *(*cb)(void *arg),
+                  char *(*cb_arg)(const char *optarg, void *arg),
+                  void *arg, const char *desc);
+
+/* We use this to get typechecking for OPT_SUBTABLE */
+static inline int _check_is_entry(struct opt_table *e) { return 0; }
+
+#endif /* CCAN_OPT_H */
diff --git a/ccan/opt/private.h b/ccan/opt/private.h
new file mode 100644 (file)
index 0000000..b1a1892
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef CCAN_OPT_PRIVATE_H
+#define CCAN_OPT_PRIVATE_H
+
+extern struct opt_table *opt_table;
+extern unsigned int opt_count;
+
+extern const char *opt_argv0;
+
+#define subtable_of(entry) ((struct opt_table *)((entry)->longopt))
+
+#endif /* CCAN_OPT_PRIVATE_H */
diff --git a/ccan/opt/test/run-usage.c b/ccan/opt/test/run-usage.c
new file mode 100644 (file)
index 0000000..85b23b8
--- /dev/null
@@ -0,0 +1,47 @@
+#define _GNU_SOURCE
+#include <ccan/tap/tap.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include "utils.h"
+#include <ccan/opt/opt.c>
+#include <ccan/opt/usage.c>
+
+static char *my_cb(void *p)
+{
+       return NULL;
+}
+
+/* Test helpers. */
+int main(int argc, char *argv[])
+{
+       char *output;
+       plan_tests(18);
+       opt_register_table(subtables, NULL);
+       opt_register_noarg("--kkk", 'k', my_cb, NULL, "magic kkk option");
+       output = opt_usage("my name", "ExTrA Args");
+       diag("%s", output);
+       ok1(strstr(output, "Usage: my name"));
+       ok1(strstr(output, "--jjj/-j <arg>"));
+       ok1(strstr(output, "ExTrA Args"));
+       ok1(strstr(output, "-a "));
+       ok1(strstr(output, " Description of a\n"));
+       ok1(strstr(output, "-b <arg>"));
+       ok1(strstr(output, " Description of b\n"));
+       ok1(strstr(output, "--ddd "));
+       ok1(strstr(output, " Description of ddd\n"));
+       ok1(strstr(output, "--eee <arg> "));
+       ok1(strstr(output, " Description of eee\n"));
+       ok1(strstr(output, "long table options:\n"));
+       /* This table is hidden. */
+       ok1(!strstr(output, "--ggg/-g "));
+       ok1(!strstr(output, " Description of ggg\n"));
+       ok1(!strstr(output, "--hhh/-h <arg>"));
+       ok1(!strstr(output, " Description of hhh\n"));
+       ok1(strstr(output, "--kkk/-k"));
+       ok1(strstr(output, "magic kkk option"));
+       free(output);
+
+       return exit_status();
+}
diff --git a/ccan/opt/test/run.c b/ccan/opt/test/run.c
new file mode 100644 (file)
index 0000000..a06e4b5
--- /dev/null
@@ -0,0 +1,218 @@
+#include <ccan/tap/tap.h>
+#include <stdlib.h>
+#include <ccan/opt/opt.c>
+#include <ccan/opt/usage.c>
+#include "utils.h"
+
+static void reset_options(void)
+{
+       free(opt_table);
+       opt_table = NULL;
+       opt_count = 0;
+       free(err_output);
+       err_output = NULL;
+}
+
+int main(int argc, char *argv[])
+{
+       const char *myname = argv[0];
+
+       plan_tests(148);
+
+       /* Simple short arg.*/
+       opt_register_noarg(NULL, 'a', test_noarg, NULL, NULL);
+       ok1(parse_args(&argc, &argv, "-a", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 1);
+
+       /* Simple long arg. */
+       opt_register_noarg("aaa", 0, test_noarg, NULL, NULL);
+       ok1(parse_args(&argc, &argv, "--aaa", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 2);
+
+       /* Both long and short args. */
+       opt_register_noarg("aaa", 'a', test_noarg, NULL, NULL);
+       ok1(parse_args(&argc, &argv, "--aaa", "-a", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 4);
+
+       /* Extra arguments preserved. */
+       ok1(parse_args(&argc, &argv, "--aaa", "-a", "extra", "args", NULL));
+       ok1(argc == 3);
+       ok1(argv[0] == myname);
+       ok1(strcmp(argv[1], "extra") == 0);
+       ok1(strcmp(argv[2], "args") == 0);
+       ok1(test_cb_called == 6);
+
+       /* Argument variants. */
+       reset_options();
+       test_cb_called = 0;
+       opt_register_arg("aaa", 'a', test_arg, "aaa", NULL);
+       ok1(parse_args(&argc, &argv, "--aaa", "aaa", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(test_cb_called == 1);
+
+       ok1(parse_args(&argc, &argv, "--aaa=aaa", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(test_cb_called == 2);
+
+       ok1(parse_args(&argc, &argv, "-a", "aaa", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(test_cb_called == 3);
+
+       /* Now, tables. */
+       /* Short table: */
+       reset_options();
+       test_cb_called = 0;
+       opt_register_table(short_table, NULL);
+       ok1(parse_args(&argc, &argv, "-a", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 1);
+       /* This one needs an arg. */
+       ok1(parse_args(&argc, &argv, "-b", NULL) == false);
+       ok1(test_cb_called == 1);
+       ok1(parse_args(&argc, &argv, "-b", "b", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 2);
+
+       /* Long table: */
+       reset_options();
+       test_cb_called = 0;
+       opt_register_table(long_table, NULL);
+       ok1(parse_args(&argc, &argv, "--ddd", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 1);
+       /* This one needs an arg. */
+       ok1(parse_args(&argc, &argv, "--eee", NULL) == false);
+       ok1(test_cb_called == 1);
+       ok1(parse_args(&argc, &argv, "--eee", "eee", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 2);
+
+       /* Short and long, both. */
+       reset_options();
+       test_cb_called = 0;
+       opt_register_table(long_and_short_table, NULL);
+       ok1(parse_args(&argc, &argv, "-g", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 1);
+       ok1(parse_args(&argc, &argv, "--ggg", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 2);
+       /* This one needs an arg. */
+       ok1(parse_args(&argc, &argv, "-h", NULL) == false);
+       ok1(test_cb_called == 2);
+       ok1(parse_args(&argc, &argv, "-h", "hhh", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 3);
+       ok1(parse_args(&argc, &argv, "--hhh", NULL) == false);
+       ok1(test_cb_called == 3);
+       ok1(parse_args(&argc, &argv, "--hhh", "hhh", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 4);
+
+       /* Those will all work as tables. */
+       test_cb_called = 0;
+       reset_options();
+       opt_register_table(subtables, NULL);
+       ok1(parse_args(&argc, &argv, "-a", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 1);
+       /* This one needs an arg. */
+       ok1(parse_args(&argc, &argv, "-b", NULL) == false);
+       ok1(test_cb_called == 1);
+       ok1(parse_args(&argc, &argv, "-b", "b", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 2);
+
+       ok1(parse_args(&argc, &argv, "--ddd", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 3);
+       /* This one needs an arg. */
+       ok1(parse_args(&argc, &argv, "--eee", NULL) == false);
+       ok1(test_cb_called == 3);
+       ok1(parse_args(&argc, &argv, "--eee", "eee", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 4);
+
+       /* Short and long, both. */
+       ok1(parse_args(&argc, &argv, "-g", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 5);
+       ok1(parse_args(&argc, &argv, "--ggg", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 6);
+       /* This one needs an arg. */
+       ok1(parse_args(&argc, &argv, "-h", NULL) == false);
+       ok1(test_cb_called == 6);
+       ok1(parse_args(&argc, &argv, "-h", "hhh", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 7);
+       ok1(parse_args(&argc, &argv, "--hhh", NULL) == false);
+       ok1(test_cb_called == 7);
+       ok1(parse_args(&argc, &argv, "--hhh", "hhh", NULL));
+       ok1(argc == 1);
+       ok1(argv[0] == myname);
+       ok1(argv[1] == NULL);
+       ok1(test_cb_called == 8);
+
+       /* Now the tricky one: -? must not be confused with an unknown option */
+       test_cb_called = 0;
+       reset_options();
+
+       /* glibc's getopt does not handle ? with arguments. */
+       opt_register_noarg(NULL, '?', test_noarg, NULL, NULL);
+       ok1(parse_args(&argc, &argv, "-?", NULL));
+       ok1(test_cb_called == 1);
+       ok1(parse_args(&argc, &argv, "-a", NULL) == false);
+       ok1(test_cb_called == 1);
+       ok1(strstr(err_output, ": -a: unrecognized option"));
+       ok1(parse_args(&argc, &argv, "--aaaa", NULL) == false);
+       ok1(test_cb_called == 1);
+       ok1(strstr(err_output, ": --aaaa: unrecognized option"));
+
+       test_cb_called = 0;
+       reset_options();
+
+       return exit_status();
+}
diff --git a/ccan/opt/test/utils.c b/ccan/opt/test/utils.c
new file mode 100644 (file)
index 0000000..899056e
--- /dev/null
@@ -0,0 +1,96 @@
+#define _GNU_SOURCE
+#include <ccan/tap/tap.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <ccan/opt/opt.h>
+#include <getopt.h>
+#include <string.h>
+#include <stdio.h>
+#include "utils.h"
+
+unsigned int test_cb_called;
+char *test_noarg(void *arg)
+{
+       test_cb_called++;
+       return NULL;
+}
+
+char *test_arg(const char *optarg, void *arg)
+{
+       test_cb_called++;
+       ok1(strcmp(optarg, arg) == 0);
+       return NULL;
+}
+
+char *err_output = NULL;
+
+static void save_err_output(const char *fmt, ...)
+{
+       va_list ap;
+       char *p;
+
+       va_start(ap, fmt);
+       vasprintf(&p, fmt, ap);
+       va_end(ap);
+
+       if (err_output) {
+               err_output = realloc(err_output,
+                                    strlen(err_output) + strlen(p) + 1);
+               strcat(err_output, p);
+               free(p);
+       } else
+               err_output = p;
+}      
+
+/* FIXME: This leaks, BTW. */
+bool parse_args(int *argc, char ***argv, ...)
+{
+       char **a;
+       va_list ap;
+
+       va_start(ap, argv);
+       *argc = 1;
+       a = malloc(sizeof(*a) * (*argc + 1));
+       a[0] = (*argv)[0];
+       while ((a[*argc] = va_arg(ap, char *)) != NULL) {
+               (*argc)++;
+               a = realloc(a, sizeof(*a) * (*argc + 1));
+       }
+       *argv = a;
+       /* Re-set before parsing. */
+       optind = 0;
+
+       return opt_parse(argc, *argv, save_err_output);
+}
+
+struct opt_table short_table[] = {
+       /* Short opts, different args. */
+       { OPT_WITHOUT_ARG(NULL, 'a', test_noarg, "a"), "Description of a" },
+       { OPT_WITH_ARG(NULL, 'b', test_arg, "b"), "Description of b" },
+       OPT_ENDTABLE
+};
+
+struct opt_table long_table[] = {
+       /* Long opts, different args. */
+       { OPT_WITHOUT_ARG("ddd", 0, test_noarg, "ddd"), "Description of ddd" },
+       { OPT_WITH_ARG("eee", 0, test_arg, "eee"), "Description of eee" },
+       OPT_ENDTABLE
+};
+
+struct opt_table long_and_short_table[] = {
+       /* Short and long, different args. */
+       { OPT_WITHOUT_ARG("ggg", 'g', test_noarg, "ggg"),
+         "Description of ggg" },
+       { OPT_WITH_ARG("hhh", 'h', test_arg, "hhh"), "Description of hhh"},
+       OPT_ENDTABLE
+};
+
+/* Sub-table test. */
+struct opt_table subtables[] = {
+       /* Short and long, no description */
+       { OPT_WITH_ARG("jjj", 'j', test_arg, "jjj") },
+       OPT_SUBTABLE(short_table, NULL),
+       OPT_SUBTABLE(long_table, "long table options"),
+       OPT_SUBTABLE(long_and_short_table, opt_table_hidden),
+       OPT_ENDTABLE
+};
diff --git a/ccan/opt/test/utils.h b/ccan/opt/test/utils.h
new file mode 100644 (file)
index 0000000..ea99eb0
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef CCAN_OPT_TEST_UTILS_H
+#define CCAN_OPT_TEST_UTILS_H
+#include <ccan/opt/opt.h>
+#include <stdbool.h>
+
+bool parse_args(int *argc, char ***argv, ...);
+extern char *err_output;
+
+extern unsigned int test_cb_called;
+char *test_noarg(void *arg);
+char *test_arg(const char *optarg, void *arg);
+
+extern struct opt_table short_table[];
+extern struct opt_table long_table[];
+extern struct opt_table long_and_short_table[];
+extern struct opt_table subtables[];
+#endif /* CCAN_OPT_TEST_UTILS_H */
diff --git a/ccan/opt/usage.c b/ccan/opt/usage.c
new file mode 100644 (file)
index 0000000..37ba3ed
--- /dev/null
@@ -0,0 +1,103 @@
+#include <ccan/opt/opt.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include "private.h"
+
+/* We only use this for pointer comparisons. */
+const char opt_table_hidden[1];
+
+static unsigned write_short_options(char *str)
+{
+       unsigned int i, num = 0;
+
+       for (i = 0; i < opt_count; i++) {
+               if (opt_table[i].flags == OPT_SUBTABLE) {
+                       if (opt_table[i].desc == opt_table_hidden) {
+                               /* Skip these options. */
+                               i += (intptr_t)opt_table[i].arg - 1;
+                               continue;
+                       }
+               } else if (opt_table[i].shortopt)
+                       str[num++] = opt_table[i].shortopt;
+       }
+       return num;
+}
+
+/* FIXME: Get all purdy. */
+char *opt_usage(const char *argv0, const char *extra)
+{
+       unsigned int i, num, len;
+       char *ret, *p;
+
+       /* An overestimate of our length. */
+       len = strlen("Usage: %s ") + strlen(argv0)
+               + strlen("[-%.*s]") + opt_count + 1
+               + strlen(" ") + strlen(extra)
+               + strlen("\n");
+
+       for (i = 0; i < opt_count; i++) {
+               if (opt_table[i].flags == OPT_SUBTABLE) {
+                       len += strlen("\n") + strlen(opt_table[i].desc)
+                               + strlen(":\n");
+               } else {
+                       len += strlen("--%s/-%c") + strlen(" <arg>");
+                       if (opt_table[i].longopt)
+                               len += strlen(opt_table[i].longopt);
+                       if (opt_table[i].desc)
+                               len += 20 + strlen(opt_table[i].desc);
+                       len += strlen("\n");
+               }
+       }
+
+       p = ret = malloc(len);
+       if (!ret)
+               return NULL;
+
+       p += sprintf(p, "Usage: %s", argv0);
+       p += sprintf(p, " [-");
+       num = write_short_options(p);
+       if (num) {
+               p += num;
+               p += sprintf(p, "]");
+       } else {
+               /* Remove start of single-entry options */
+               p -= 3;
+       }
+       if (extra)
+               p += sprintf(p, " %s", extra);
+       p += sprintf(p, "\n");
+
+       for (i = 0; i < opt_count; i++) {
+               if (opt_table[i].flags == OPT_SUBTABLE) {
+                       if (opt_table[i].desc == opt_table_hidden) {
+                               /* Skip these options. */
+                               i += (intptr_t)opt_table[i].arg - 1;
+                               continue;
+                       }
+                       p += sprintf(p, "%s:\n", opt_table[i].desc);
+                       continue;
+               }
+               if (opt_table[i].shortopt && opt_table[i].longopt)
+                       len = sprintf(p, "--%s/-%c",
+                                    opt_table[i].longopt,
+                                     opt_table[i].shortopt);
+               else if (opt_table[i].shortopt)
+                       len = sprintf(p, "-%c", opt_table[i].shortopt);
+               else
+                       len = sprintf(p, "--%s", opt_table[i].longopt);
+               if (opt_table[i].flags == OPT_HASARG)
+                       len += sprintf(p + len, " <arg>");
+               if (opt_table[i].desc) {
+                       len += sprintf(p + len, "%.*s",
+                                      len < 20 ? 20 - len : 1,
+                                      "                    ");
+                       len += sprintf(p + len, "%s", opt_table[i].desc);
+               }
+               p += len;
+               p += sprintf(p, "\n");
+       }
+       *p = '\0';
+       return ret;
+}