X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=ucw%2Fopt.c;h=a388b244e73e62e612d5a07ce06449c09012ad9d;hb=1dc196d10661025b7d658e47af5eb7be131154be;hp=0ae40a905284022cc637ed6d546b0aed6d621ccd;hpb=d824ff891e0a3d36d6c366a14ed2401cbc4401a2;p=libucw.git diff --git a/ucw/opt.c b/ucw/opt.c index 0ae40a90..a388b244 100644 --- a/ucw/opt.c +++ b/ucw/opt.c @@ -2,6 +2,7 @@ * UCW Library -- Parsing of command line options * * (c) 2013 Jan Moskyto Matejka + * (c) 2014 Martin Mares * * This software may be freely distributed and used according to the terms * of the GNU Lesser General Public License. @@ -10,173 +11,305 @@ #include #include #include +#include +#include #include #include #include +#include -static void opt_failure(const char * mesg, ...) FORMAT_CHECK(printf,1,2); +/*** + * Value flags defaults + * ~~~~~~~~~~~~~~~~~~~~ + * + * OPT_NO_VALUE for OPT_BOOL, OPT_SWITCH and OPT_INC + * OPT_MAYBE_VALUE for OPT_STRING, OPT_UNS, OPT_INT + * Some of the value flags (OPT_NO_VALUE, OPT_MAYBE_VALUE, OPT_REQUIRED_VALUE) + * must be specified for OPT_CALL and OPT_USER. + ***/ +static uns opt_default_value_flags[] = { + [OPT_CL_BOOL] = OPT_NO_VALUE, + [OPT_CL_STATIC] = OPT_MAYBE_VALUE, + [OPT_CL_SWITCH] = OPT_NO_VALUE, + [OPT_CL_INC] = OPT_NO_VALUE, + [OPT_CL_CALL] = 0, + [OPT_CL_USER] = 0, + [OPT_CL_SECTION] = 0, + [OPT_CL_HELP] = 0 +}; + +struct opt_precomputed { + struct opt_item * item; + const char * name; + short flags; + short count; +}; + +struct opt_context { + struct opt_precomputed * opts; + struct opt_precomputed ** shortopt; + struct opt_item ** hooks_before_arg; + struct opt_item ** hooks_before_value; + struct opt_item ** hooks_after_value; + short opt_count; + short hooks_before_arg_count; + short hooks_before_value_count; + short hooks_after_value_count; + int positional_max; + int positional_count; +}; + +static void opt_failure(const char * mesg, ...) FORMAT_CHECK(printf,1,2) NONRET; static void opt_failure(const char * mesg, ...) { va_list args; va_start(args, mesg); - stk_vprintf(mesg, args); + vfprintf(stderr, mesg, args); + fprintf(stderr, "\n"); + opt_usage(); exit(OPT_EXIT_BAD_ARGS); - va_end(args); + va_end(args); // FIXME: Does this make a sense after exit()? } -struct opt_section * opt_section_root; +#define FOREACHLINE(text) for (const char * begin = (text), * end = (text); (*end) && (end = strchrnul(begin, '\n')); begin = end+1) -void opt_help_noexit_internal(struct opt_section * help) { - uns first_column = 0; +static inline uns uns_min(uns x, uns y) +{ + return MIN(x, y); +} + +void opt_help(const struct opt_section * help) { + int sections_cnt = 0; + int lines_cnt = 0; for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) { if (item->flags & OPT_NO_HELP) continue; - if (item->cls == OPT_CL_HELP && item->u.help2 == NULL) continue; - if (item->cls == OPT_CL_SECTION) continue; - - uns linelen = 0; - if (item->cls == OPT_CL_HELP) { // two-column help line - if (first_column < strlen(item->help)) - first_column = strlen(item->help); + if (item->cls == OPT_CL_SECTION) { + sections_cnt++; continue; } - - if (item->letter) { // will write sth like "-x, --exclusive" - linelen = strlen("-x, --") + strlen(item->name); - } else { // will write sth like "--exclusive" - linelen = strlen("--") + strlen(item->name); - } - - ASSERT(item->flags & OPT_VALUE_FLAGS); - - if (item->flags & OPT_REQUIRED_VALUE) { - linelen += strlen("=value"); - } else if (item->flags & OPT_MAYBE_VALUE) { - linelen += strlen("(=value)"); + if (!*(item->help)) { + lines_cnt++; + continue; } - - if (linelen > first_column) - first_column = linelen; + FOREACHLINE(item->help) + lines_cnt++; } - char * spaces = alloca(first_column + 1); - char * buf = alloca(first_column + 1); - for (uns i=0;iflags & OPT_REQUIRED_VALUE) ? "=value" : ((it->flags & OPT_NO_VALUE) ? "" : "(=value)")) for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) { if (item->flags & OPT_NO_HELP) continue; - + if (item->cls == OPT_CL_HELP) { - fprintf(stderr, "%s", item->help); - if (item->u.help2 == NULL) - fprintf(stderr, "\n"); - else - fprintf(stderr, "%s %s\n", spaces + strlen(item->help), item->u.help2); - } else if (item->cls == OPT_CL_SECTION) { - opt_help_noexit_internal(item->u.section); - } else if (item->letter) { - sprintf(buf, "-%c, --%s%s", item->letter, item->name, VAL(item)); - fprintf(stderr, "%s%s %s\n", buf, spaces + strlen(buf), item->help); - } else { - sprintf(buf, "--%s%s", item->name, VAL(item)); - fprintf(stderr, "%s%s %s\n", buf, spaces + strlen(buf), item->help); + if (!*(item->help)) { + line++; + continue; + } +#define SPLITLINES(text) do { \ + FOREACHLINE(text) { \ + int cell = 0; \ + for (const char * b = begin, * e = begin; (e < end) && (e = strchrnul(b, '\t')) && (e > end ? (e = end) : end); b = e+1) { \ + lines[line][cell] = b; \ + if (cell >= 2) \ + break; \ + else \ + if (*e == '\t' && linelengths[cell] < (e - b)) \ + linelengths[cell] = e-b; \ + cell++; \ + } \ + line++; \ + } } while (0) + SPLITLINES(item->help); + continue; } - } -} -void opt_init(struct opt_section * options) { - for (struct opt_item * item = options->opt; item->cls != OPT_CL_END; item++) { - if (item->cls == OPT_CL_SECTION) - opt_init(item->u.section); - else if (!(item->flags & OPT_VALUE_FLAGS)) { - if (item->cls == OPT_CL_CALL || item->cls == OPT_CL_USER) { - fprintf(stderr, "You MUST specify some of the value flags for the %c/%s item.\n", item->letter, item->name); - ASSERT(0); - } else - item->flags |= opt_default_value_flags[item->cls]; + if (item->cls == OPT_CL_SECTION) { + sections[s++] = (struct opt_sectlist) { .pos = line, .sect = item->u.section }; + continue; + } + + uns valoff = strchrnul(item->help, '\t') - item->help; + uns eol = strchrnul(item->help, '\n') - item->help; + if (valoff > eol) + valoff = eol; +#define VAL(it) ((it->flags & OPT_REQUIRED_VALUE) ? stk_printf("=%.*s", valoff, item->help) : ((it->flags & OPT_NO_VALUE) ? "" : stk_printf("(=%.*s)", valoff, item->help))) + if (item->name) { + lines[line][1] = stk_printf("--%s%s", item->name, VAL(item)); + if (linelengths[1] < (int) strlen(lines[line][1])) + linelengths[1] = strlen(lines[line][1]); + lines[line][0] = ""; + if (linelengths[0] < 0) + linelengths[0] = 0; + } + if (item->letter) { + lines[line][0] = stk_printf("-%c,", item->letter); + if (linelengths[0] < (int) strlen(lines[line][0])) + linelengths[0] = strlen(lines[line][0]); + } +#undef VAL + + if (eol > valoff) { + lines[line][2] = item->help + valoff + 1; } + + line++; + + if (*(item->help + eol)) + SPLITLINES(item->help + eol + 1); + } +#undef SPLITLINES + + s = 0; +#define FIELD(k) linelengths[k], uns_min(strchrnul(lines[i][k], '\t') - lines[i][k], strchrnul(lines[i][k], '\n') - lines[i][k]), lines[i][k] +#define LASTFIELD(k) uns_min(strchrnul(lines[i][k], '\t') - lines[i][k], strchrnul(lines[i][k], '\n') - lines[i][k]), lines[i][k] + for (int i=0;ishortopt[chr]; + if (!candidate) + opt_failure("Invalid option -%c", chr); + if (candidate->count++ && (candidate->flags & OPT_SINGLE)) + opt_failure("Option -%c appeared the second time.", candidate->item->letter); + return candidate; } -static struct opt_item * opt_find_item_longopt_section(char * str, struct opt_section * options) { +static struct opt_precomputed * opt_find_item_longopt(struct opt_context * oc, char * str) { uns len = strlen(str); - struct opt_item * candidate = NULL; + struct opt_precomputed * candidate = NULL; - for (struct opt_item * item = options->opt; item->cls != OPT_CL_END; item++) { - if (item->cls == OPT_CL_SECTION) { - struct opt_item * out = opt_find_item_longopt_section(str, item->u.section); - if (out) { - if (candidate) - opt_failure("Ambiguous prefix %s: Found matching %s and %s.\n", str, candidate->name, item->name); - else - candidate = out; + for (int i=0; iopt_count; i++) { + if (!oc->opts[i].name) + continue; + if (!strncmp(oc->opts[i].name, str, len)) { + if (strlen(oc->opts[i].name) == len) { + if (oc->opts[i].count++ && (oc->opts[i].flags & OPT_SINGLE)) + opt_failure("Option %s appeared the second time.", oc->opts[i].name); + + return &oc->opts[i]; } - } else if (!strncmp(item->name, str, len)) { - if (strlen(item->name) == len) - return item; + if (candidate) + opt_failure("Ambiguous prefix %s: Found matching %s and %s.", str, candidate->name, oc->opts[i].name); + else + candidate = &oc->opts[i]; + } + if (!strncmp("no-", str, 3) && !strncmp(oc->opts[i].name, str+3, len-3)) { + if (strlen(oc->opts[i].name) == len-3) { + if (oc->opts[i].count++ && (oc->opts[i].flags & OPT_SINGLE)) + opt_failure("Option %s appeared the second time.", oc->opts[i].name); + return &oc->opts[i]; + } if (candidate) - opt_failure("Ambiguous prefix %s: Found matching %s and %s.\n", str, candidate->name, item->name); + opt_failure("Ambiguous prefix %s: Found matching %s and %s.", str, candidate->name, oc->opts[i].name); else - candidate = item; + candidate = &oc->opts[i]; } } if (candidate) return candidate; - else { - } -} -static struct opt_item * opt_find_item_longopt(char * str) { - struct opt_item * out = opt_find_item_longopt_section(str, opt_section_root); - if (out == NULL) - opt_failure("Invalid argument: %s\n", str); - return out; + opt_failure("Invalid option %s.", str); } -#define OPT_NAME (longopt ? stk_printf("--%s", item->name) : stk_printf("-%c", item->letter)) -static void opt_parse_value(struct opt_item * item, char * value, int longopt) { +#define OPT_PTR(type) ({ \ + type * ptr; \ + if (item->flags & OPT_MULTIPLE) { \ + struct { \ + cnode n; \ + type v; \ + } * n = xmalloc(sizeof(*n)); \ + clist_add_tail(item->ptr, &(n->n)); \ + ptr = &(n->v); \ + } else \ + ptr = item->ptr; \ + ptr; }) + +#define OPT_NAME (longopt == 2 ? stk_printf("positional arg #%d", oc->positional_count) : (longopt == 1 ? stk_printf("--%s", opt->name) : stk_printf("-%c", item->letter))) +static void opt_parse_value(struct opt_context * oc, struct opt_precomputed * opt, char * value, int longopt) { + struct opt_item * item = opt->item; + for (int i=0;ihooks_before_value_count;i++) + oc->hooks_before_value[i]->u.call(item, value, oc->hooks_before_value[i]->ptr); + switch (item->cls) { case OPT_CL_BOOL: - if (!strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true")) - *((int *) item->ptr) = 1; - else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false")) - *((int *) item->ptr) = 0; + if (!value || !strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "1")) + *((int *) item->ptr) = 1 ^ (!!(opt->flags & OPT_NEGATIVE)); + else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "0")) + *((int *) item->ptr) = 0 ^ (!!(opt->flags & OPT_NEGATIVE)); else - opt_failure("Boolean argument for %s has a strange value. Supported (case insensitive): y/n, yes/no, true/false.\n", OPT_NAME); + opt_failure("Boolean argument for %s has a strange value. Supported (case insensitive): y/n, yes/no, true/false.", OPT_NAME); break; case OPT_CL_STATIC: { char * e = NULL; switch (item->type) { case CT_INT: - e = cf_parse_int(value, item->ptr); + if (!value) + *OPT_PTR(int) = 0; + else + e = cf_parse_int(value, OPT_PTR(int)); if (e) - opt_failure("Integer value parsing failed for argument %s: %s\n", OPT_NAME, e); + opt_failure("Integer value parsing failed for %s: %s", OPT_NAME, e); break; case CT_U64: - e = cf_parse_u64(value, item->ptr); + if (!value) + *OPT_PTR(u64) = 0; + else + e = cf_parse_u64(value, OPT_PTR(u64)); if (e) - opt_failure("Unsigned 64-bit value parsing failed for argument %s: %s\n", OPT_NAME, e); + opt_failure("Unsigned 64-bit value parsing failed for %s: %s", OPT_NAME, e); break; case CT_DOUBLE: - e = cf_parse_double(value, item->ptr); + if (!value) + *OPT_PTR(double) = NAN; + else + e = cf_parse_double(value, OPT_PTR(double)); if (e) - opt_failure("Double value parsing failed for argument %s: %s\n", OPT_NAME, e); + opt_failure("Double value parsing failed for %s: %s", OPT_NAME, e); break; case CT_IP: - e = cf_parse_ip(value, item->ptr); + if (!value) + e = cf_parse_ip("0.0.0.0", OPT_PTR(u32)); + else + e = cf_parse_ip(value, OPT_PTR(u32)); if (e) - opt_failure("IP parsing failed for argument %s: %s\n", OPT_NAME, e); + opt_failure("IP parsing failed for %s: %s", OPT_NAME, e); break; case CT_STRING: - item->ptr = strdup(value); + if (!value) + *OPT_PTR(const char *) = NULL; + else + *OPT_PTR(const char *) = xstrdup(value); break; default: ASSERT(0); @@ -190,199 +323,299 @@ static void opt_parse_value(struct opt_item * item, char * value, int longopt) { *((int *)item->ptr) = item->u.value; break; case OPT_CL_INC: - if (item->flags | OPT_DECREMENT) + if (opt->flags & OPT_NEGATIVE) (*((int *)item->ptr))--; else (*((int *)item->ptr))++; + break; case OPT_CL_CALL: - + item->u.call(item, value, item->ptr); + break; case OPT_CL_USER: { char * e = NULL; - e = item->u.utype->parser(value, item->ptr); + e = item->u.utype->parser(value, OPT_PTR(void*)); if (e) - opt_failure("User defined type value parsing failed for argument %s: %s\n", OPT_NAME, e); + opt_failure("User defined type value parsing failed for %s: %s", OPT_NAME, e); break; } + default: + ASSERT(0); } + + for (int i=0;ihooks_after_value_count;i++) + oc->hooks_after_value[i]->u.call(item, value, oc->hooks_after_value[i]->ptr); } #undef OPT_NAME -static int opt_longopt(char ** argv, int index) { - int eaten; +static int opt_longopt(struct opt_context * oc, char ** argv, int index) { + int eaten = 0; char * name_in = argv[index] + 2; // skipping the -- on the beginning uns pos = strchrnul(name_in, '=') - name_in; - struct opt_item * item = opt_find_item_longopt(strndupa(name_in, pos)); + struct opt_precomputed * opt = opt_find_item_longopt(oc, strndupa(name_in, pos)); char * value = NULL; - if (item->flags | OPT_REQUIRED_VALUE) { + + if (opt->item->cls == OPT_CL_BOOL && !strncmp(name_in, "no-", 3) && !strncmp(name_in+3, opt->item->name, pos-3)) + value = "n"; + else if (opt->flags & OPT_REQUIRED_VALUE) { if (pos < strlen(name_in)) value = name_in + pos + 1; else { value = argv[index+1]; + if (!value) + opt_failure("Argument --%s must have a value but nothing supplied.", opt->name); eaten++; } } - else if (item->flags | OPT_MAYBE_VALUE) { + else if (opt->flags & OPT_MAYBE_VALUE) { if (pos < strlen(name_in)) value = name_in + pos + 1; } else { if (pos < strlen(name_in)) - opt_failure("Argument %s must not have any value.", item->name); + opt_failure("Argument --%s must not have any value.", opt->name); } + opt_parse_value(oc, opt, value, 1); + return eaten; } -void opt_parse(char ** argv, opt_positional * callback) { - - int force_positional = 0; - for (int i=0;argv[i];i++) { - if (argv[i][0] != '-' || force_positional) { - callback(argv[i]); - } else { - if (argv[i][1] == '-') { - if (argv[i][2] == '\0') - force_positional++; - else - i += opt_longopt(argv, i); +static int opt_shortopt(struct opt_context * oc, char ** argv, int index) { + int chr = 0; + struct opt_precomputed * opt; + while (argv[index][++chr] && (opt = opt_find_item_shortopt(oc, argv[index][chr]))) { + if (opt->flags & OPT_NO_VALUE) { + opt_parse_value(oc, opt, NULL, 0); + } + else if (opt->flags & OPT_REQUIRED_VALUE) { + if (chr == 1 && argv[index][2]) { + opt_parse_value(oc, opt, argv[index] + 2, 0); + return 0; + } + else if (argv[index][chr+1]) + opt_failure("Option -%c must have a value but found inside a bunch of short opts.", opt->item->letter); + else if (!argv[index+1]) + opt_failure("Option -%c must have a value but nothing supplied.", opt->item->letter); + else { + opt_parse_value(oc, opt, argv[index+1], 0); + return 1; + } + } + else if (opt->flags & OPT_MAYBE_VALUE) { + if (chr == 1 && argv[index][2]) { + opt_parse_value(oc, opt, argv[index] + 2, 0); + return 0; } - else if (argv[i][1]) - i += opt_shortopt(argv, i); else - callback(argv[i]); + opt_parse_value(oc, opt, NULL, 0); + } + else { + ASSERT(0); } } -} -#ifdef TEST -#include + if (argv[index][chr]) + opt_failure("Unknown option -%c.", argv[index][chr]); -static int show_version(const char ** param UNUSED) { - printf("This is a simple tea boiling console v0.1.\n"); - exit(EXIT_SUCCESS); + return 0; } -struct teapot_temperature { - enum { - TEMP_CELSIUS = 0, - TEMP_FAHRENHEIT, - TEMP_KELVIN, - TEMP_REAUMUR, - TEMP_RANKINE - } scale; - int value; -} temperature; - -static char * temp_scale_str[] = { "C", "F", "K", "Re", "R" }; - -static enum TEAPOT_TYPE { - TEAPOT_STANDARD = 0, - TEAPOT_EXCLUSIVE, - TEAPOT_GLASS, - TEAPOT_HANDS, - TEAPOT_UNDEFINED = -1 -} set = TEAPOT_UNDEFINED; - -static int english = 0; -static char * name = NULL; -static int sugar = 0; -static int verbose = 1; -static int with_gas = 0; -static int black_magic = 0; -static int pray = 0; -static int water_amount = 0; - -static const char * teapot_temperature_parser(char * in, void * ptr) { - struct teapot_temperature * temp = ptr; - const char * next; - const char * err = str_to_int(&temp->value, in, &next, 0); - if (err) - return err; - if (!strcmp("C", next)) - temp->scale = TEMP_CELSIUS; - else if (!strcmp("F", next)) - temp->scale = TEMP_FAHRENHEIT; - else if (!strcmp("K", next)) - temp->scale = TEMP_KELVIN; - else if (!strcmp("R", next)) - temp->scale = TEMP_RANKINE; - else if (!strcmp("Re", next)) - temp->scale = TEMP_REAUMUR; - else { - fprintf(stderr, "Unknown scale: %s\n", next); - exit(OPT_EXIT_BAD_ARGS); +static void opt_positional(struct opt_context * oc, char * value) { + oc->positional_count++; + struct opt_precomputed * opt = opt_find_item_shortopt(oc, (oc->positional_count > oc->positional_max ? 256 : oc->positional_count + 256)); + if (!opt) { + ASSERT(oc->positional_count > oc->positional_max); + opt_failure("Too many positional args."); } - return next + strlen(next); + + opt_parse_value(oc, opt, value, 2); } -static void teapot_temperature_dumper(struct fastbuf * fb, void * ptr) { - struct teapot_temperature * temp = ptr; - bprintf(fb, "%d%s", temp->value, temp_scale_str[temp->scale]); +static void opt_count_items(struct opt_context *oc, const struct opt_section *sec) +{ + for (const struct opt_item *item = sec->opt; item->cls != OPT_CL_END; item++) { + if (item->cls == OPT_CL_SECTION) + opt_count_items(oc, item->u.section); + else if (item->cls == OPT_CL_HOOK) { + if (item->flags & OPT_HOOK_BEFORE_ARG) + oc->hooks_before_arg_count++; + else if (item->flags & OPT_HOOK_BEFORE_VALUE) + oc->hooks_before_value_count++; + else if (item->flags & OPT_HOOK_AFTER_VALUE) + oc->hooks_after_value_count++; + else + ASSERT(0); + } else if (item->letter || item->name) { + oc->opt_count++; + if (item->letter > OPT_POSITIONAL_TAIL) + oc->positional_max++; + } + } } -static struct cf_user_type teapot_temperature_t = { - .size = sizeof(struct teapot_temperature), - .name = "teapot_temperature_t", - .parser = (cf_parser1*) teapot_temperature_parser, - .dumper = (cf_dumper1*) teapot_temperature_dumper -}; +static void opt_add_default_flags(struct opt_precomputed *opt) +{ + struct opt_item *item = opt->item; + uns flags = opt->flags; -static struct opt_section water_options = { - OPT_ITEMS { - OPT_INT('w', "water", water_amount, OPT_REQUIRED | OPT_REQUIRED_VALUE, "Amount of water (in mls)"), - OPT_BOOL('G', "with-gas", with_gas, OPT_NO_VALUE, "Use water with gas"), - OPT_END + if (item->letter >= 256) { + flags &= ~OPT_VALUE_FLAGS; + flags |= OPT_REQUIRED_VALUE; } -}; - -static struct opt_section help = { - OPT_ITEMS { - OPT_HELP("A simple tea boiling console."), - OPT_HELP("Usage: teapot [options] name-of-the-tea"), - OPT_HELP("Black, green or white tea supported as well as fruit or herbal tea."), - OPT_HELP("You may specify more kinds of tea, all of them will be boiled for you, in the given order."), - OPT_HELP(""), - OPT_HELP("Options:"), - OPT_HELP_OPTION, - OPT_CALL('V', "version", show_version, OPT_NO_VALUE, "Show the version"), - OPT_HELP(""), - OPT_BOOL('e', "english-style", english, 0, "English style (with milk)"), - OPT_INT('s', "sugar", sugar, OPT_REQUIRED_VALUE, "Amount of sugar (in teaspoons)"), - OPT_SWITCH(0, "standard-set", set, TEAPOT_STANDARD, 0, "Standard teapot"), - OPT_SWITCH('x', "exclusive-set", set, TEAPOT_EXCLUSIVE, 0, "Exclusive teapot"), - OPT_SWITCH('g', "glass-set", set, TEAPOT_GLASS, 0, "Transparent glass teapot"), - OPT_SWITCH('h', "hands", set, TEAPOT_HANDS, 0, "Use user's hands as a teapot (a bit dangerous)"), - OPT_USER('t', "temperature", temperature, teapot_temperature_t, OPT_REQUIRED_VALUE, - "Wanted final temperature of the tea to be served\n" - "\t\tSupported scales: \tCelsius [60C], Fahrenheit [140F]," - "\t\t\tKelvin [350K], Rankine [600R] and Reaumur [50Re]" - "\t\tOnly integer values allowed."), - OPT_INC('v', "verbose", verbose, 0, "Verbose (the more -v, the more verbose)"), - OPT_INC('q', "quiet", verbose, OPT_DECREMENT, "Quiet (the more -q, the more quiet)"), - OPT_INT('b', "black-magic", black_magic, 0, "Use black magic to make the tea extraordinary delicious"), - OPT_BOOL('p', "pray", pray, 0, "Pray before boiling"), - OPT_HELP(""), - OPT_HELP("Water options:"), - OPT_SECTION(water_options), - OPT_END + if (!(flags & OPT_VALUE_FLAGS)) { + ASSERT(item->cls != OPT_CL_CALL && item->cls != OPT_CL_USER); + flags |= opt_default_value_flags[item->cls]; } -}; -static void boil_tea(const char * name) { - printf("Boiling a tea: %s\n", name); + opt->flags = flags; } -int main(int argc, char ** argv) +static void opt_prepare_items(struct opt_context *oc, const struct opt_section *sec) { - char ** teas; - int teas_num; + for (struct opt_item *item = sec->opt; item->cls != OPT_CL_END; item++) { + if (item->cls == OPT_CL_SECTION) + opt_prepare_items(oc, item->u.section); + else if (item->cls == OPT_CL_HOOK) { + if (item->flags & OPT_HOOK_BEFORE_ARG) + oc->hooks_before_arg[oc->hooks_before_arg_count++] = item; + else if (item->flags & OPT_HOOK_BEFORE_VALUE) + oc->hooks_before_value[oc->hooks_before_value_count++] = item; + else if (item->flags & OPT_HOOK_AFTER_VALUE) + oc->hooks_after_value[oc->hooks_after_value_count++] = item; + else + ASSERT(0); + } else if (item->letter || item->name) { + struct opt_precomputed * opt = &oc->opts[oc->opt_count++]; + opt->item = item; + opt->flags = item->flags; + opt->count = 0; + opt->name = item->name; + if (item->letter) + oc->shortopt[(int) item->letter] = opt; + opt_add_default_flags(opt); + } + } +} + +void opt_parse(const struct opt_section * options, char ** argv) { + struct opt_context * oc = alloca(sizeof(*oc)); + memset(oc, 0, sizeof (*oc)); - opt_init(&help); - opt_parse(argv, NULL); + opt_count_items(oc, options); + oc->opts = alloca(sizeof(*oc->opts) * oc->opt_count); + oc->shortopt = alloca(sizeof(*oc->shortopt) * (oc->positional_max + 257)); + memset(oc->shortopt, 0, sizeof(*oc->shortopt) * (oc->positional_max + 257)); + oc->hooks_before_arg = alloca(sizeof (*oc->hooks_before_arg) * oc->hooks_before_arg_count); + oc->hooks_before_value = alloca(sizeof (*oc->hooks_before_value) * oc->hooks_before_value_count); + oc->hooks_after_value = alloca(sizeof (*oc->hooks_after_value) * oc->hooks_after_value_count); - for (int i=0; iopt_count = 0; + oc->hooks_before_arg_count = 0; + oc->hooks_before_value_count = 0; + oc->hooks_after_value_count = 0; + opt_prepare_items(oc, options); - printf("Everything OK. Bye.\n"); + int force_positional = 0; + for (int i=0;argv[i];i++) { + for (int j=0;jhooks_before_arg_count;j++) + oc->hooks_before_arg[j]->u.call(NULL, NULL, oc->hooks_before_arg[j]->ptr); + if (argv[i][0] != '-' || force_positional) { + opt_positional(oc, argv[i]); + } + else { + if (argv[i][1] == '-') { + if (argv[i][2] == '\0') + force_positional++; + else + i += opt_longopt(oc, argv, i); + } + else if (argv[i][1]) + i += opt_shortopt(oc, argv, i); + else + opt_positional(oc, argv[i]); + } + } + + for (int i=0;ipositional_max+257;i++) { + if (!oc->shortopt[i]) + continue; + if (!oc->shortopt[i]->count && (oc->shortopt[i]->flags & OPT_REQUIRED)) + if (i < 256) + if (oc->shortopt[i]->item->name) + opt_failure("Required option -%c/--%s not found.", oc->shortopt[i]->item->letter, oc->shortopt[i]->item->name); + else + opt_failure("Required option -%c not found.", oc->shortopt[i]->item->letter); + else + opt_failure("Required positional argument #%d not found.", (i > 256) ? oc->shortopt[i]->item->letter-256 : oc->positional_max+1); + } + + for (int i=0;iopt_count;i++) { + if (!oc->opts[i].count && (oc->opts[i].flags & OPT_REQUIRED)) + opt_failure("Required option --%s not found.", oc->opts[i].item->name); + } +} + +static void opt_conf_end_of_options(struct cf_context *cc) { + cf_load_default(cc); + if (cc->postpone_commit && cf_close_group()) + opt_failure("Loading of configuration failed"); } +void opt_conf_internal(struct opt_item * opt, const char * value, void * data UNUSED) { + struct cf_context *cc = cf_get_context(); + switch (opt->letter) { + case 'S': + cf_load_default(cc); + if (cf_set(value)) + opt_failure("Cannot set %s", value); + break; + case 'C': + if (cf_load(value)) + opt_failure("Cannot load config file %s", value); + break; +#ifdef CONFIG_UCW_DEBUG + case '0': + opt_conf_end_of_options(cc); + struct fastbuf *b = bfdopen(1, 4096); + cf_dump_sections(b); + bclose(b); + exit(0); + break; #endif + } +} + +void opt_conf_hook_internal(struct opt_item * opt, const char * value UNUSED, void * data UNUSED) { + static enum { + OPT_CONF_HOOK_BEGIN, + OPT_CONF_HOOK_CONFIG, + OPT_CONF_HOOK_OTHERS + } state = OPT_CONF_HOOK_BEGIN; + + int confopt = 0; + + if (opt->letter == 'S' || opt->letter == 'C' || (opt->name && !strcmp(opt->name, "dumpconfig"))) + confopt = 1; + + switch (state) { + case OPT_CONF_HOOK_BEGIN: + if (confopt) + state = OPT_CONF_HOOK_CONFIG; + else { + opt_conf_end_of_options(cf_get_context()); + state = OPT_CONF_HOOK_OTHERS; + } + break; + case OPT_CONF_HOOK_CONFIG: + if (!confopt) { + opt_conf_end_of_options(cf_get_context()); + state = OPT_CONF_HOOK_OTHERS; + } + break; + case OPT_CONF_HOOK_OTHERS: + if (confopt) + opt_failure("Config options (-C, -S) must stand before other options."); + break; + default: + ASSERT(0); + } +}