Commit d7d5abe9 authored by Rusty Russell's avatar Rusty Russell

opt: new module to parse commandline options.

parent c4c5fed0
#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;
}
#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);
}
#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;
}
#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 */
#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 */
#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();
}
#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();
}
#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
};
#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 */
#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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment