/* * UCW Library -- Formatting of command-line option help * * (c) 2013 Maria Matejka * (c) 2014 Martin Mares * * This software may be freely distributed and used according to the terms * of the GNU Lesser General Public License. */ #include #include #include #include #include #include struct help { struct mempool *pool; struct help_line *lines; // A growing array of lines }; struct help_line { const char *extra; char *fields[3]; }; static void opt_help_scan_item(struct help *h, struct opt_precomputed *opt) { const struct opt_item *item = opt->item; if (!item->help) return; bool force_col1 = 0; if (item->cls == OPT_CL_HELP) { if (item->flags & OPT_HELP_COL) { force_col1 = 1; } else { struct help_line *l = GARY_PUSH(h->lines); l->extra = item->help; return; } } if (item->letter >= OPT_POSITIONAL_TAIL) return; struct help_line *first = GARY_PUSH(h->lines); 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 && !force_col1 ? 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; } text = eol; if (text) l = GARY_PUSH(h->lines); } 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 (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; } } } static void opt_help_scan(struct help *h, 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_help_scan(h, item->u.section); else if (item->cls == OPT_CL_HOOK) ; else { struct opt_precomputed opt; opt_precompute(&opt, item); opt_help_scan_item(h, &opt); } } } void opt_help(const struct opt_section * sec) { // Prepare help text struct help h; h.pool = mp_new(4096); GARY_INIT_ZERO(h.lines, 0); opt_help_scan(&h, sec); // Calculate natural width of each column uint n = GARY_SIZE(h.lines); uint widths[3] = { 0, 0, 0 }; for (uint i=0; ifields[f]) l->fields[f] = ""; uint w = strlen(l->fields[f]); widths[f] = MAX(widths[f], w); } } 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 (uint i=0; iextra) puts(l->extra); else { int t = 0; for (uint 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); } void opt_handle_help(const struct opt_item * opt UNUSED, const char * value UNUSED, void * data) { struct opt_context *oc = data; opt_help(oc->options); exit(0); }