X-Git-Url: https://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Fopt%2Fopt.c;h=d376a598da932445de592c8f12e2466d4bc431bd;hp=a5180706c45e28e52394411ad5ff271f10cdeb2c;hb=4f20b75c6133425f2b8c369bb1ecfbd7d3410353;hpb=f20e4f235ac89f3901f248f91c3ac5a33088349d diff --git a/ccan/opt/opt.c b/ccan/opt/opt.c index a5180706..d376a598 100644 --- a/ccan/opt/opt.c +++ b/ccan/opt/opt.c @@ -1,10 +1,9 @@ +/* Licensed under GPLv2+ - see LICENSE file for details */ #include #include #include -#include #include #include -#include #include #include #include @@ -13,18 +12,21 @@ struct opt_table *opt_table; unsigned int opt_count, opt_num_short, opt_num_short_arg, opt_num_long; const char *opt_argv0; +struct opt_alloc opt_alloc = { + malloc, realloc, free +}; /* Returns string after first '-'. */ static const char *first_name(const char *names, unsigned *len) { - *len = strcspn(names + 1, "/"); + *len = strcspn(names + 1, "|= "); return names + 1; } static const char *next_name(const char *names, unsigned *len) { names += *len; - if (!names[0]) + if (names[0] == ' ' || names[0] == '=' || names[0] == '\0') return NULL; return first_name(names + 1, len); } @@ -32,7 +34,7 @@ static const char *next_name(const char *names, unsigned *len) static const char *first_opt(unsigned *i, unsigned *len) { for (*i = 0; *i < opt_count; (*i)++) { - if (opt_table[*i].flags == OPT_SUBTABLE) + if (opt_table[*i].type == OPT_SUBTABLE) continue; return first_name(opt_table[*i].names, len); } @@ -42,7 +44,7 @@ static const char *first_opt(unsigned *i, unsigned *len) static const char *next_opt(const char *p, unsigned *i, unsigned *len) { for (; *i < opt_count; (*i)++) { - if (opt_table[*i].flags == OPT_SUBTABLE) + if (opt_table[*i].type == OPT_SUBTABLE) continue; if (!p) return first_name(opt_table[*i].names, len); @@ -53,7 +55,7 @@ static const char *next_opt(const char *p, unsigned *i, unsigned *len) return NULL; } -static const char *first_lopt(unsigned *i, unsigned *len) +const char *first_lopt(unsigned *i, unsigned *len) { const char *p; for (p = first_opt(i, len); p; p = next_opt(p, i, len)) { @@ -67,7 +69,7 @@ static const char *first_lopt(unsigned *i, unsigned *len) return p; } -static const char *next_lopt(const char *p, unsigned *i, unsigned *len) +const char *next_lopt(const char *p, unsigned *i, unsigned *len) { for (p = next_opt(p, i, len); p; p = next_opt(p, i, len)) { if (p[0] == '-') { @@ -83,7 +85,7 @@ static const char *next_lopt(const char *p, unsigned *i, unsigned *len) const char *first_sopt(unsigned *i) { const char *p; - unsigned int len; + unsigned int len = 0 /* GCC bogus warning */; for (p = first_opt(i, &len); p; p = next_opt(p, i, &len)) { if (p[0] != '-') @@ -102,55 +104,96 @@ const char *next_sopt(const char *p, unsigned *i) return p; } +/* Avoids dependency on err.h or ccan/err */ +#ifndef failmsg +#define failmsg(fmt, ...) \ + do { fprintf(stderr, fmt, __VA_ARGS__); exit(1); } while(0) +#endif + static void check_opt(const struct opt_table *entry) { const char *p; unsigned len; - assert(entry->flags == OPT_HASARG || entry->flags == OPT_NOARG); + if (entry->type != OPT_HASARG && entry->type != OPT_NOARG + && entry->type != (OPT_EARLY|OPT_HASARG) + && entry->type != (OPT_EARLY|OPT_NOARG)) + failmsg("Option %s: unknown entry type %u", + entry->names, entry->type); + + if (!entry->desc) + failmsg("Option %s: description cannot be NULL", entry->names); + + + if (entry->names[0] != '-') + failmsg("Option %s: does not begin with '-'", entry->names); - assert(entry->names[0] == '-'); for (p = first_name(entry->names, &len); p; p = next_name(p, &len)) { if (*p == '-') { - assert(len > 1); + if (len == 1) + failmsg("Option %s: invalid long option '--'", + entry->names); opt_num_long++; } else { - assert(len == 1); - assert(*p != ':'); + if (len != 1) + failmsg("Option %s: invalid short option" + " '%.*s'", entry->names, len+1, p-1); opt_num_short++; - if (entry->flags == OPT_HASARG) { + if (entry->type == OPT_HASARG) opt_num_short_arg++; - /* FIXME: -? with ops breaks getopt_long */ - assert(*p != '?'); - } + } + /* Don't document args unless there are some. */ + if (entry->type == OPT_NOARG) { + if (p[len] == ' ' || p[len] == '=') + failmsg("Option %s: does not take arguments" + " '%s'", entry->names, p+len+1); } } } static void add_opt(const struct opt_table *entry) { - opt_table = realloc(opt_table, sizeof(opt_table[0]) * (opt_count+1)); + opt_table = opt_alloc.realloc(opt_table, + sizeof(opt_table[0]) * (opt_count+1)); opt_table[opt_count++] = *entry; } -void _opt_register(const char *names, enum opt_flags flags, +void _opt_register(const char *names, enum opt_type type, char *(*cb)(void *arg), char *(*cb_arg)(const char *optarg, void *arg), void (*show)(char buf[OPT_SHOW_LEN], const void *arg), - void *arg, const char *desc) + const void *arg, const char *desc) { struct opt_table opt; opt.names = names; - opt.flags = flags; + opt.type = type; opt.cb = cb; opt.cb_arg = cb_arg; opt.show = show; - opt.arg = arg; + opt.u.carg = arg; opt.desc = desc; check_opt(&opt); add_opt(&opt); } +bool opt_unregister(const char *names) +{ + int found = -1, i; + + for (i = 0; i < opt_count; i++) { + if (opt_table[i].type == OPT_SUBTABLE) + continue; + if (strcmp(opt_table[i].names, names) == 0) + found = i; + } + if (found == -1) + return false; + opt_count--; + memmove(&opt_table[found], &opt_table[found+1], + (opt_count - found) * sizeof(opt_table[found])); + return true; +} + void opt_register_table(const struct opt_table entry[], const char *desc) { unsigned int i, start = opt_count; @@ -159,8 +202,8 @@ void opt_register_table(const struct opt_table entry[], const char *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) + for (i = 0; entry[i].type != OPT_END; i++) { + if (entry[i].type == OPT_SUBTABLE) opt_register_table(subtable_of(&entry[i]), entry[i].desc); else { @@ -170,168 +213,98 @@ void opt_register_table(const struct opt_table entry[], const char *desc) } /* We store the table length in arg ptr. */ if (desc) - opt_table[start].arg = (void *)(intptr_t)(opt_count - start); + opt_table[start].u.tlen = (opt_count - start); } -static char *make_optstring(void) +/* Parse your arguments. */ +bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...)) { - char *str = malloc(1 + opt_num_short + opt_num_short_arg + 1); - const char *p; - unsigned int i, num = 0; - - /* This tells getopt_long we want a ':' returned for missing arg. */ - str[num++] = ':'; - for (p = first_sopt(&i); p; p = next_sopt(p, &i)) { - str[num++] = *p; - if (opt_table[i].flags == OPT_HASARG) - str[num++] = ':'; - } - str[num++] = '\0'; - assert(num == 1 + opt_num_short + opt_num_short_arg + 1); - return str; -} + int ret; + unsigned offset = 0; -static struct option *make_options(void) -{ - struct option *options = malloc(sizeof(*options) * (opt_num_long + 1)); - unsigned int i, num = 0, len; - const char *p; + /* This helps opt_usage. */ + opt_argv0 = argv[0]; - for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) { - char *buf = malloc(len + 1); - memcpy(buf, p, len); - buf[len] = 0; - options[num].name = buf; - options[num].has_arg = (opt_table[i].flags == OPT_HASARG); - options[num].flag = NULL; - options[num].val = 0; - num++; - } - memset(&options[num], 0, sizeof(options[num])); - assert(num == opt_num_long); - return options; + while ((ret = parse_one(argc, argv, 0, &offset, errlog, false)) == 1); + + /* parse_one returns 0 on finish, -1 on error */ + return (ret == 0); } -static struct opt_table *find_short(char shortopt) +static bool early_parse(int argc, char *argv[], + void (*errlog)(const char *fmt, ...), + bool ignore_unknown) { - unsigned int i; - const char *p; + int ret; + unsigned off = 0; + char **tmpargv = opt_alloc.alloc(sizeof(argv[0]) * (argc + 1)); - for (p = first_sopt(&i); p; p = next_sopt(p, &i)) { - if (*p == shortopt) - return &opt_table[i]; - } - abort(); -} + /* We could avoid a copy and skip instead, but this is simple. */ + memcpy(tmpargv, argv, sizeof(argv[0]) * (argc + 1)); -/* We want the index'th long entry. */ -static struct opt_table *find_long(int index, const char **name) -{ - unsigned int i, len; - const char *p; + /* This helps opt_usage. */ + opt_argv0 = argv[0]; - for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) { - if (index == 0) { - *name = p; - return &opt_table[i]; - } - index--; - } - abort(); + while ((ret = parse_one(&argc, tmpargv, OPT_EARLY, &off, errlog, ignore_unknown)) == 1); + + opt_alloc.free(tmpargv); + + /* parse_one returns 0 on finish, -1 on error */ + return (ret == 0); } -/* 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) +bool opt_early_parse(int argc, char *argv[], + void (*errlog)(const char *fmt, ...)) { - if (shortopt) - errlog("%s: -%c: %s", opt_argv0, shortopt, problem); - else - errlog("%s: --%.*s: %s", opt_argv0, - strcspn(longopt, "/"), longopt, problem); + return early_parse(argc, argv, errlog, false); } -/* Parse your arguments. */ -bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...)) +bool opt_early_parse_incomplete(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 = 0; - while ((ret = getopt_long(*argc, argv, optstring, options, &longidx)) - != -1) { - char *problem; - const char *name; - - /* optopt is 0 if it's an unknown long option, *or* if - * -? is a valid short option. */ - if (ret == '?') { - if (optopt || strncmp(argv[optind-1], "--", 2) == 0) { - parse_fail(errlog, optopt, argv[optind-1]+2, - "unrecognized option"); - break; - } - } else if (ret == ':') { - /* Missing argument: longidx not updated :( */ - parse_fail(errlog, optopt, argv[optind-1]+2, - "option requires an argument"); - break; - } - - if (ret != 0) - e = find_short(ret); - else - e = find_long(longidx, &name); - - if (e->flags == OPT_HASARG) - problem = e->cb_arg(optarg, e->arg); - else - problem = e->cb(e->arg); + return early_parse(argc, argv, errlog, true); +} - if (problem) { - parse_fail(errlog, ret, name, problem); - free(problem); - break; - } - } - free(optstring); - free(options); - if (ret != -1) - return false; +void opt_free_table(void) +{ + opt_alloc.free(opt_table); + opt_table = NULL; + opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0; +} - /* We hide everything but remaining arguments. */ - memmove(&argv[1], &argv[optind], sizeof(argv[1]) * (*argc-optind+1)); - *argc -= optind - 1; +void opt_log_stderr(const char *fmt, ...) +{ + va_list ap; - return ret == -1 ? true : false; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); } -void opt_log_stderr(const char *fmt, ...) +void opt_log_stderr_exit(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); va_end(ap); + exit(1); } char *opt_invalid_argument(const char *arg) { - char *str = malloc(sizeof("Invalid argument '%s'") + strlen(arg)); + char *str = opt_alloc.alloc(sizeof("Invalid argument '%s'") + strlen(arg)); sprintf(str, "Invalid argument '%s'", arg); return str; } + +void opt_set_alloc(void *(*allocfn)(size_t size), + void *(*reallocfn)(void *ptr, size_t size), + void (*freefn)(void *ptr)) +{ + opt_alloc.alloc = allocfn; + opt_alloc.realloc = reallocfn; + opt_alloc.free = freefn; +}