X-Git-Url: https://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Fopt%2Fopt.c;h=841e9b4eb2d4021c6b97432e2c65edb8e8754d90;hp=236d2af00d20027d8f1b505f2b7ad2ec429f4ac4;hb=be6a5cdadeef4995cc935f2d2443f45f542ed125;hpb=f8b1841d26dabd23c053f5fc61dbd1536cdad43c diff --git a/ccan/opt/opt.c b/ccan/opt/opt.c index 236d2af0..841e9b4e 100644 --- a/ccan/opt/opt.c +++ b/ccan/opt/opt.c @@ -11,15 +11,141 @@ #include "private.h" struct opt_table *opt_table; -unsigned int opt_count; +unsigned int opt_count, opt_num_short, opt_num_short_arg, opt_num_long; const char *opt_argv0; +/* Returns string after first '-'. */ +static const char *first_name(const char *names, unsigned *len) +{ + *len = strcspn(names + 1, "/= "); + return names + 1; +} + +static const char *next_name(const char *names, unsigned *len) +{ + names += *len; + if (names[0] == ' ' || names[0] == '=' || names[0] == '\0') + return NULL; + return first_name(names + 1, len); +} + +static const char *first_opt(unsigned *i, unsigned *len) +{ + for (*i = 0; *i < opt_count; (*i)++) { + if (opt_table[*i].type == OPT_SUBTABLE) + continue; + return first_name(opt_table[*i].names, len); + } + return NULL; +} + +static const char *next_opt(const char *p, unsigned *i, unsigned *len) +{ + for (; *i < opt_count; (*i)++) { + if (opt_table[*i].type == OPT_SUBTABLE) + continue; + if (!p) + return first_name(opt_table[*i].names, len); + p = next_name(p, len); + if (p) + return p; + } + return NULL; +} + +static const char *first_lopt(unsigned *i, unsigned *len) +{ + const char *p; + for (p = first_opt(i, len); p; p = next_opt(p, i, len)) { + if (p[0] == '-') { + /* Skip leading "-" */ + (*len)--; + p++; + break; + } + } + return p; +} + +static 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] == '-') { + /* Skip leading "-" */ + (*len)--; + p++; + break; + } + } + return p; +} + +const char *first_sopt(unsigned *i) +{ + const char *p; + unsigned int len; + + for (p = first_opt(i, &len); p; p = next_opt(p, i, &len)) { + if (p[0] != '-') + break; + } + return p; +} + +const char *next_sopt(const char *p, unsigned *i) +{ + unsigned int len = 1; + for (p = next_opt(p, i, &len); p; p = next_opt(p, i, &len)) { + if (p[0] != '-') + break; + } + return p; +} + 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); + const char *p; + unsigned len; + + if (entry->type != OPT_HASARG && entry->type != OPT_NOARG) + errx(1, "Option %s: unknown entry type %u", + entry->names, entry->type); + + if (!entry->desc) + errx(1, "Option %s: description cannot be NULL", entry->names); + + + if (entry->names[0] != '-') + errx(1, "Option %s: does not begin with '-'", entry->names); + + for (p = first_name(entry->names, &len); p; p = next_name(p, &len)) { + if (*p == '-') { + if (len == 1) + errx(1, "Option %s: invalid long option '--'", + entry->names); + opt_num_long++; + } else { + if (len != 1) + errx(1, "Option %s: invalid short option" + " '%.*s'", entry->names, len+1, p-1); + if (*p == ':') + errx(1, "Option %s: invalid short option '-:'", + entry->names); + opt_num_short++; + if (entry->type == OPT_HASARG) { + opt_num_short_arg++; + if (*p == '?') + errx(1, "Option %s: '-?' cannot take" + " an argument", entry->names); + } + } + /* Don't document args unless there are some. */ + if (entry->type == OPT_NOARG) { + if (p[len] == ' ' || p[len] == '=') + errx(1, "Option %s: does not take arguments" + "'%s'", entry->names, p+len+1); + } + } } static void add_opt(const struct opt_table *entry) @@ -28,16 +154,15 @@ static void add_opt(const struct opt_table *entry) opt_table[opt_count++] = *entry; } -void _opt_register(const char *longopt, char shortopt, 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) { struct opt_table opt; - opt.longopt = longopt; - opt.shortopt = shortopt; - opt.flags = flags; + opt.names = names; + opt.type = type; opt.cb = cb; opt.cb_arg = cb_arg; opt.show = show; @@ -55,8 +180,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 { @@ -71,61 +196,66 @@ void opt_register_table(const struct opt_table entry[], const char *desc) 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; + 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[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++] = ':'; + for (p = first_sopt(&i); p; p = next_sopt(p, &i)) { + str[num++] = *p; + if (opt_table[i].type == OPT_HASARG) str[num++] = ':'; } - str[num] = '\0'; + str[num++] = '\0'; + assert(num == 1 + opt_num_short + opt_num_short_arg + 1); return str; } static struct option *make_options(void) { - struct option *options = malloc(sizeof(*options) * (opt_count + 1)); - unsigned int i, num; + struct option *options = malloc(sizeof(*options) * (opt_num_long + 1)); + unsigned int i, num = 0, len; + const char *p; - 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); + for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) { + char *buf = malloc(len + 1); + memcpy(buf, p, len); + buf[len] = 0; + options[num].name = buf; + options[num].has_arg = (opt_table[i].type == OPT_HASARG); options[num].flag = NULL; options[num].val = 0; num++; } memset(&options[num], 0, sizeof(options[num])); + assert(num == opt_num_long); return options; } static struct opt_table *find_short(char shortopt) { unsigned int i; - for (i = 0; i < opt_count; i++) { - if (opt_table[i].shortopt == shortopt) + const char *p; + + for (p = first_sopt(&i); p; p = next_sopt(p, &i)) { + if (*p == shortopt) return &opt_table[i]; } abort(); } /* We want the index'th long entry. */ -static struct opt_table *find_long(int index) +static struct opt_table *find_long(int index, const char **name) { - unsigned int i; - for (i = 0; i < opt_count; i++) { - if (!opt_table[i].longopt) - continue; - if (index == 0) + unsigned int i, len; + const char *p; + + for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) { + if (index == 0) { + *name = p; return &opt_table[i]; + } index--; } abort(); @@ -144,14 +274,8 @@ static void parse_fail(void (*errlog)(const char *fmt, ...), 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); + errlog("%s: --%.*s: %s", opt_argv0, + strcspn(longopt, "/"), longopt, problem); } /* Parse your arguments. */ @@ -168,11 +292,11 @@ bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...)) /* Reset in case we're called more than once. */ optopt = 0; - optind = 1; + optind = 0; while ((ret = getopt_long(*argc, argv, optstring, options, &longidx)) != -1) { char *problem; - bool missing = false; + const char *name; /* optopt is 0 if it's an unknown long option, *or* if * -? is a valid short option. */ @@ -183,30 +307,24 @@ bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...)) break; } } else if (ret == ':') { - missing = true; - ret = optopt; + /* 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); - - /* Missing argument */ - if (missing) { - parse_fail(errlog, e->shortopt, e->longopt, - "option requires an argument"); - break; - } + e = find_long(longidx, &name); - if (e->flags == OPT_HASARG) + if (e->type == 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); + parse_fail(errlog, ret, name, problem); free(problem); break; }