From 4a2604f821344eaa3fc9bde1c8380577d93d7b1c Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Mon, 27 Jan 2014 18:42:01 +0100 Subject: [PATCH] Opt: Rewritten formatting of help Previous implementation of opt_help() had several drawbacks: First, it formatted each section separately, so the columns did not aligh. Second, it did produced incorrect output for options which possess only the short form. I hope that the new implementation is cleaner and easier to extend. --- ucw/opt-test.c | 9 +- ucw/opt.c | 264 ++++++++++++++++++++++++++----------------------- ucw/opt.h | 11 +-- 3 files changed, 152 insertions(+), 132 deletions(-) diff --git a/ucw/opt-test.c b/ucw/opt-test.c index cd7acc38..f7e7fa02 100644 --- a/ucw/opt-test.c +++ b/ucw/opt-test.c @@ -118,6 +118,7 @@ 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(""), 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("At least one kind of tea must be specified."), @@ -139,17 +140,17 @@ static struct opt_section help = { "\t\tOnly integer values allowed."), OPT_INC('v', "verbose", verbose, 0, "\tVerbose (the more -v, the more verbose)"), OPT_INC('q', "quiet", verbose, OPT_NEGATIVE, "\tQuiet (the more -q, the more quiet)"), - OPT_INT('b', "black-magic", black_magic, OPT_MULTIPLE, "\tUse black magic to make the tea extraordinary delicious.\n\t\tMay be specified more than once to describe the amounts of black magic to be invoked in each step of tea boiling."), + OPT_INT('b', NULL, black_magic, OPT_MULTIPLE, "\tUse black magic to make the tea extraordinarily delicious.\n\t\tMay be specified more than once to describe the amounts of black magic to be invoked in each step of tea boiling."), OPT_BOOL('p', "pray", pray, OPT_SINGLE, "\tPray before boiling"), - OPT_STRING(OPT_POSITIONAL(1), NULL, first_tea, OPT_REQUIRED | OPT_NO_HELP, ""), - OPT_CALL(OPT_POSITIONAL_TAIL, NULL, add_tea, &tea_list, OPT_NO_HELP, ""), + OPT_STRING(OPT_POSITIONAL(1), NULL, first_tea, OPT_REQUIRED, ""), + OPT_CALL(OPT_POSITIONAL_TAIL, NULL, add_tea, &tea_list, 0, ""), OPT_HELP(""), OPT_HELP("Water options:"), OPT_SECTION(water_options), OPT_HOOK(opt_test_hook, "prearg", OPT_HOOK_BEFORE_ARG), OPT_HOOK(opt_test_hook, "preval", OPT_HOOK_BEFORE_VALUE), OPT_HOOK(opt_test_hook, "postval", OPT_HOOK_AFTER_VALUE), - OPT_BOOL('H', "show-hooks", show_hooks, 0, "Demonstrate the hooks."), + OPT_BOOL('H', "show-hooks", show_hooks, 0, "\tDemonstrate the hooks."), OPT_CONF_OPTIONS, OPT_END } diff --git a/ucw/opt.c b/ucw/opt.c index 5a529d7f..59bcb260 100644 --- a/ucw/opt.c +++ b/ucw/opt.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include @@ -85,126 +87,165 @@ static char *opt_name(struct opt_context *oc, struct opt_precomputed *opt) #define THIS_OPT opt_name(oc, opt) -#define FOREACHLINE(text) for (const char * begin = (text), * end = (text); (*end) && (end = strchrnul(begin, '\n')); begin = end+1) - -static inline uns uns_min(uns x, uns y) +static void opt_precompute(struct opt_precomputed *opt, struct opt_item *item) { - return MIN(x, y); + opt->item = item; + opt->count = 0; + opt->name = item->name; + uns flags = item->flags; + + if (item->letter >= OPT_POSITIONAL_TAIL) { + flags &= ~OPT_VALUE_FLAGS; + flags |= OPT_REQUIRED_VALUE; + } + if (!(flags & OPT_VALUE_FLAGS)) { + ASSERT(item->cls != OPT_CL_CALL && item->cls != OPT_CL_USER); + flags |= opt_default_value_flags[item->cls]; + } + + opt->flags = flags; } -void opt_help(const struct opt_section * help) { - int sections_cnt = 0; - int lines_cnt = 0; +#define FOREACHLINE(text) for (const char * begin = (text), * end = (text); (*end) && (end = strchrnul(begin, '\n')); begin = end+1) - 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_SECTION) { - sections_cnt++; - continue; - } - if (!*(item->help)) { - lines_cnt++; - continue; - } - FOREACHLINE(item->help) - lines_cnt++; - } +struct help { + struct mempool *pool; + struct help_line *lines; // A growing array of lines +}; - struct opt_sectlist { - int pos; - struct opt_section * sect; - } sections[sections_cnt]; - int s = 0; +struct help_line { + const char *extra; + char *fields[3]; +}; - const char *lines[lines_cnt][3]; - memset(lines, 0, sizeof(lines)); - int line = 0; +static void opt_help_scan_item(struct help *h, struct opt_precomputed *opt) +{ + struct opt_item *item = opt->item; - int linelengths[3] = { -1, -1, -1 }; + if (opt->flags & OPT_NO_HELP) + return; - 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) { + struct help_line *l = GARY_PUSH(h->lines, 1); + l->extra = item->help ? : ""; + return; + } - if (item->cls == OPT_CL_HELP) { - if (!*(item->help)) { - line++; - continue; + if (item->letter >= OPT_POSITIONAL_TAIL) + return; + + struct help_line *first = GARY_PUSH(h->lines, 1); + if (item->help) { + char *text = mp_strdup(h->pool, item->help); + struct help_line *l = first; + while (text) { + char *eol = strchr(text, '\n'); + if (eol) + *eol++ = 0; + + int field = (l == first ? 1 : 0); + char *f = text; + while (f) { + char *tab = strchr(f, '\t'); + if (tab) + *tab++ = 0; + if (field < 3) + l->fields[field++] = f; + f = tab; } -#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; - } - if (item->cls == OPT_CL_SECTION) { - sections[s++] = (struct opt_sectlist) { .pos = line, .sect = item->u.section }; - continue; + text = eol; + if (text) + l = GARY_PUSH(h->lines, 1); } + } - 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 (item->name) { + char *val = first->fields[1] ? : ""; + if (opt->flags & OPT_REQUIRED_VALUE) + val = mp_printf(h->pool, "=%s", val); + else if (!(opt->flags & OPT_NO_VALUE)) + val = mp_printf(h->pool, "[=%s]", val); + first->fields[1] = mp_printf(h->pool, "--%s%s", item->name, val); + } - if (eol > valoff) { - lines[line][2] = item->help + valoff + 1; + if (item->letter) { + if (item->name) + first->fields[0] = mp_printf(h->pool, "-%c, ", item->letter); + else { + char *val = first->fields[1] ? : ""; + if (!(opt->flags & OPT_REQUIRED_VALUE) && !(opt->flags & OPT_NO_VALUE)) + val = mp_printf(h->pool, "[%s]", val); + first->fields[0] = mp_printf(h->pool, "-%c%s", item->letter, val); + first->fields[1] = NULL; } + } +} - line++; - - if (*(item->help + eol)) - SPLITLINES(item->help + eol + 1); +static void opt_help_scan(struct help *h, const struct opt_section *sec) +{ + for (struct opt_item * item = sec->opt; item->cls != OPT_CL_END; item++) { + if (item->cls == OPT_CL_SECTION) + opt_help_scan(h, item->u.section); + else { + struct opt_precomputed opt; + opt_precompute(&opt, item); + opt_help_scan_item(h, &opt); + } } -#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;ifields[f]) + l->fields[f] = ""; + uns w = strlen(l->fields[f]); + widths[f] = MAX(widths[f], w); } - if (lines[i][0] == NULL) - printf("\n"); - else if (linelengths[0] == -1 || lines[i][1] == NULL) - printf("%.*s\n", LASTFIELD(0)); - else if (linelengths[1] == -1 || lines[i][2] == NULL) - printf("%-*.*s %.*s\n", FIELD(0), LASTFIELD(1)); - else - printf("%-*.*s %-*.*s %.*s\n", FIELD(0), FIELD(1), LASTFIELD(2)); } - while (s < sections_cnt && sections[s].pos == line) { - opt_help(sections[s].sect); - s++; + if (widths[0] > 4) { + /* + * This is tricky: if there are short options, which have an argument, + * but no long variant, we are willing to let column 0 overflow to column 1. + */ + widths[1] = MAX(widths[1], widths[0] - 4); + widths[0] = 4; + } + widths[1] += 4; + + // Print columns + for (uns i=0; iextra) + puts(l->extra); + else { + int t = 0; + for (uns f=0; f<3; f++) { + t += widths[f]; + t -= printf("%s", l->fields[f]); + while (t > 0) { + putchar(' '); + t--; + } + } + putchar('\n'); + } } + + // Clean up + GARY_FREE(h.lines); + mp_delete(h.pool); } static struct opt_precomputed * opt_find_item_longopt(struct opt_context * oc, char * str) { @@ -455,23 +496,6 @@ static void opt_count_items(struct opt_context *oc, const struct opt_section *se } } -static void opt_add_default_flags(struct opt_precomputed *opt) -{ - struct opt_item *item = opt->item; - uns flags = opt->flags; - - if (item->letter >= OPT_POSITIONAL_TAIL) { - flags &= ~OPT_VALUE_FLAGS; - flags |= OPT_REQUIRED_VALUE; - } - if (!(flags & OPT_VALUE_FLAGS)) { - ASSERT(item->cls != OPT_CL_CALL && item->cls != OPT_CL_USER); - flags |= opt_default_value_flags[item->cls]; - } - - opt->flags = flags; -} - static void opt_prepare_items(struct opt_context *oc, const struct opt_section *sec) { for (struct opt_item *item = sec->opt; item->cls != OPT_CL_END; item++) { @@ -488,13 +512,9 @@ static void opt_prepare_items(struct opt_context *oc, const struct opt_section * 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; + opt_precompute(opt, item); if (item->letter) oc->shortopt[(int) item->letter] = opt; - opt_add_default_flags(opt); } } } diff --git a/ucw/opt.h b/ucw/opt.h index cda8022f..c50bac00 100644 --- a/ucw/opt.h +++ b/ucw/opt.h @@ -73,7 +73,6 @@ struct opt_section { * * OPT_HELP_OPTION declares --help and prints a line about that * OPT_HELP prints a line into help - * OPT_HELP2 prints two strings onto a line using the same tab structure as the option listing * OPT_BOOL declares boolean option with an auto-negation (--sth and --no-sth). It's also possible to write --sth=y/yes/true/1/n/no/false/0. * OPT_STRING, OPT_UNS, OPT_INT declare simple string/uns/int option * OPT_SWITCH declares one choice of a switch statement; these have common target and different `value`s; last wins unless OPT_SINGLE is set; @@ -90,7 +89,7 @@ struct opt_section { * ***/ -#define OPT_HELP_OPTION(help) OPT_CALL(0, "help", opt_show_help_internal, &help, OPT_NO_VALUE, "Show this help") +#define OPT_HELP_OPTION(help) OPT_CALL(0, "help", opt_show_help_internal, &help, OPT_NO_VALUE, "\tShow this help") #define OPT_HELP(line) { .help = line, .cls = OPT_CL_HELP } #define OPT_BOOL(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .help = desc, .flags = fl, .cls = OPT_CL_BOOL, .type = CT_INT } #define OPT_STRING(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_STRING } @@ -124,9 +123,9 @@ struct opt_section { #define OPT_CONF_OPTIONS OPT_CONF_CONFIG, OPT_CONF_SET, OPT_CONF_HOOK #endif -#define OPT_CONF_CONFIG OPT_CALL('C', "config", opt_conf_internal, NULL, OPT_REQUIRED_VALUE, "Override the default configuration file") -#define OPT_CONF_SET OPT_CALL('S', "set", opt_conf_internal, NULL, OPT_REQUIRED_VALUE, "Manual setting of a configuration item") -#define OPT_CONF_DUMPCONFIG OPT_CALL(0, "dumpconfig", opt_conf_internal, NULL, OPT_NO_VALUE, "Dump program configuration") +#define OPT_CONF_CONFIG OPT_CALL('C', "config", opt_conf_internal, NULL, OPT_REQUIRED_VALUE, "\tOverride the default configuration file") +#define OPT_CONF_SET OPT_CALL('S', "set", opt_conf_internal, NULL, OPT_REQUIRED_VALUE, "\tManual setting of a configuration item") +#define OPT_CONF_DUMPCONFIG OPT_CALL(0, "dumpconfig", opt_conf_internal, NULL, OPT_NO_VALUE, "\tDump program configuration") #define OPT_CONF_HOOK OPT_HOOK(opt_conf_hook_internal, NULL, OPT_HOOK_BEFORE_VALUE) void opt_conf_internal(struct opt_item * opt, const char * value, void * data); @@ -172,7 +171,7 @@ void opt_conf_hook_internal(struct opt_item * opt, const char * value, void * da #define OPT_HOOK_AFTER_VALUE 0x4000 /** Call after value parsing **/ extern const struct opt_section * opt_section_root; -void opt_help(const struct opt_section * help); +void opt_help(const struct opt_section * sec); static inline void opt_usage(void) { fprintf(stderr, "Run with argument --help for more information.\n"); -- 2.39.2