From d7d5abe98caeec82d784ce525e0444ff438acd46 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 30 Sep 2010 08:27:58 +0930 Subject: [PATCH] opt: new module to parse commandline options. --- ccan/opt/_info | 65 +++++++++ ccan/opt/helpers.c | 129 ++++++++++++++++++ ccan/opt/opt.c | 238 +++++++++++++++++++++++++++++++++ ccan/opt/opt.h | 269 ++++++++++++++++++++++++++++++++++++++ ccan/opt/private.h | 11 ++ ccan/opt/test/run-usage.c | 47 +++++++ ccan/opt/test/run.c | 218 ++++++++++++++++++++++++++++++ ccan/opt/test/utils.c | 96 ++++++++++++++ ccan/opt/test/utils.h | 17 +++ ccan/opt/usage.c | 103 +++++++++++++++ 10 files changed, 1193 insertions(+) create mode 100644 ccan/opt/_info create mode 100644 ccan/opt/helpers.c create mode 100644 ccan/opt/opt.c create mode 100644 ccan/opt/opt.h create mode 100644 ccan/opt/private.h create mode 100644 ccan/opt/test/run-usage.c create mode 100644 ccan/opt/test/run.c create mode 100644 ccan/opt/test/utils.c create mode 100644 ccan/opt/test/utils.h create mode 100644 ccan/opt/usage.c diff --git a/ccan/opt/_info b/ccan/opt/_info new file mode 100644 index 00000000..54ae0776 --- /dev/null +++ b/ccan/opt/_info @@ -0,0 +1,65 @@ +#include +#include +#include "config.h" + +/** + * opt - simple command line parsing + * + * Simple but powerful command line parsing, built on top of getopt_long. + * + * Example: + * #include + * #include + * #include + * + * 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 " }, + * { 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 + */ +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 index 00000000..7554c997 --- /dev/null +++ b/ccan/opt/helpers.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#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 index 00000000..f601bb69 --- /dev/null +++ b/ccan/opt/opt.c @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 index 00000000..f47b1c88 --- /dev/null +++ b/ccan/opt/opt.h @@ -0,0 +1,269 @@ +#ifndef CCAN_OPT_H +#define CCAN_OPT_H +#include +#include + +/* 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 "), or NULL. + * @shortopt: the character of the argument (eg. 'f' for "-f "), or 0. + * @cb: the callback when the option is found (along with ). + * @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], "...")); + * 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 index 00000000..b1a18923 --- /dev/null +++ b/ccan/opt/private.h @@ -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 index 00000000..85b23b81 --- /dev/null +++ b/ccan/opt/test/run-usage.c @@ -0,0 +1,47 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include "utils.h" +#include +#include + +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 ")); + ok1(strstr(output, "ExTrA Args")); + ok1(strstr(output, "-a ")); + ok1(strstr(output, " Description of a\n")); + ok1(strstr(output, "-b ")); + ok1(strstr(output, " Description of b\n")); + ok1(strstr(output, "--ddd ")); + ok1(strstr(output, " Description of ddd\n")); + ok1(strstr(output, "--eee ")); + 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 ")); + 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 index 00000000..a06e4b53 --- /dev/null +++ b/ccan/opt/test/run.c @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#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 index 00000000..899056ee --- /dev/null +++ b/ccan/opt/test/utils.c @@ -0,0 +1,96 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#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 index 00000000..ea99eb09 --- /dev/null +++ b/ccan/opt/test/utils.h @@ -0,0 +1,17 @@ +#ifndef CCAN_OPT_TEST_UTILS_H +#define CCAN_OPT_TEST_UTILS_H +#include +#include + +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 index 00000000..37ba3ed7 --- /dev/null +++ b/ccan/opt/usage.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#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(" "); + 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, " "); + 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; +} -- 2.39.2