X-Git-Url: http://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Fopt%2Fusage.c;h=ff0545591ed4050a5153aa5c8d6db2041a1295e4;hp=873ee5db1856cf0bf326a8ed4ba9f19aa22c2db6;hb=e4fbff960f8d384ee793259ff6e13bb798114c98;hpb=ac9d55d8c5af9697be8c4dd4f27de61e3cb8bf95 diff --git a/ccan/opt/usage.c b/ccan/opt/usage.c index 873ee5db..ff054559 100644 --- a/ccan/opt/usage.c +++ b/ccan/opt/usage.c @@ -1,5 +1,10 @@ /* Licensed under GPLv3+ - see LICENSE file for details */ #include +#if HAVE_SYS_TERMIOS_H +#include +#include /* Required on Solaris for struct winsize */ +#endif +#include /* Required on Solaris for ioctl */ #include #include #include @@ -9,26 +14,182 @@ /* We only use this for pointer comparisons. */ const char opt_hidden[1]; -static unsigned write_short_options(char *str) +#define MIN_DESC_WIDTH 40 +#define MIN_TOTAL_WIDTH 50 + +static unsigned int get_columns(void) { - unsigned int i, num = 0; - const char *p; + int ws_col = 0; + const char *env = getenv("COLUMNS"); + + if (env) + ws_col = atoi(env); + +#ifdef TIOCGWINSZ + if (!ws_col) + { + struct winsize w; + if (ioctl(0, TIOCGWINSZ, &w) != -1) + ws_col = w.ws_col; + } +#endif + if (!ws_col) + ws_col = 80; + + return ws_col; +} + +/* Return number of chars of words to put on this line. + * Prefix is set to number to skip at start, maxlen is max width, returns + * length (after prefix) to put on this line. */ +static size_t consume_words(const char *words, size_t maxlen, size_t *prefix) +{ + size_t oldlen, len; + + /* Swallow leading whitespace. */ + *prefix = strspn(words, " "); + words += *prefix; - for (p = first_sopt(&i); p; p = next_sopt(p, &i)) { - if (opt_table[i].desc != opt_hidden) - str[num++] = *p; + /* Use at least one word, even if it takes us over maxlen. */ + oldlen = len = strcspn(words, " "); + while (len <= maxlen) { + oldlen = len; + len += strspn(words+len, " "); + len += strcspn(words+len, " "); + if (len == oldlen) + break; } - return num; + + return oldlen; +} + +static char *add_str_len(char *base, size_t *len, size_t *max, + const char *str, size_t slen) +{ + if (slen >= *max - *len) + base = opt_alloc.realloc(base, *max = (*max * 2 + slen + 1)); + memcpy(base + *len, str, slen); + *len += slen; + return base; +} + +static char *add_str(char *base, size_t *len, size_t *max, const char *str) +{ + return add_str_len(base, len, max, str, strlen(str)); +} + +static char *add_indent(char *base, size_t *len, size_t *max, size_t indent) +{ + if (indent >= *max - *len) + base = opt_alloc.realloc(base, *max = (*max * 2 + indent + 1)); + memset(base + *len, ' ', indent); + *len += indent; + return base; } -#define OPT_SPACE_PAD " " +static char *add_desc(char *base, size_t *len, size_t *max, + unsigned int indent, unsigned int width, + const struct opt_table *opt) +{ + size_t off, prefix, l; + const char *p; + bool same_line = false; + + base = add_str(base, len, max, opt->names); + off = strlen(opt->names); + if (opt->type == OPT_HASARG + && !strchr(opt->names, ' ') + && !strchr(opt->names, '=')) { + base = add_str(base, len, max, " "); + off += strlen(" "); + } + + /* Do we start description on next line? */ + if (off + 2 > indent) { + base = add_str(base, len, max, "\n"); + off = 0; + } else { + base = add_indent(base, len, max, indent - off); + off = indent; + same_line = true; + } + + /* Indent description. */ + p = opt->desc; + while ((l = consume_words(p, width - indent, &prefix)) != 0) { + if (!same_line) + base = add_indent(base, len, max, indent); + p += prefix; + base = add_str_len(base, len, max, p, l); + base = add_str(base, len, max, "\n"); + off = indent + l; + p += l; + same_line = false; + } + + /* Empty description? Make it match normal case. */ + if (same_line) + base = add_str(base, len, max, "\n"); + + if (opt->show) { + char buf[OPT_SHOW_LEN + sizeof("...")]; + strcpy(buf + OPT_SHOW_LEN, "..."); + opt->show(buf, opt->u.arg); + + /* If it doesn't fit on this line, indent. */ + if (off + strlen(" (default: ") + strlen(buf) + strlen(")") + > width) { + base = add_indent(base, len, max, indent); + } else { + /* Remove \n. */ + (*len)--; + } + + base = add_str(base, len, max, " (default: "); + base = add_str(base, len, max, buf); + base = add_str(base, len, max, ")\n"); + } + return base; +} -/* FIXME: Get all purdy. */ char *opt_usage(const char *argv0, const char *extra) { - unsigned int i, num, len; - char *ret, *p; + unsigned int i; + size_t max, len, width, indent; + char *ret; + + width = get_columns(); + if (width < MIN_TOTAL_WIDTH) + width = MIN_TOTAL_WIDTH; + + /* Figure out longest option. */ + indent = 0; + for (i = 0; i < opt_count; i++) { + size_t l; + if (opt_table[i].desc == opt_hidden) + continue; + if (opt_table[i].type == OPT_SUBTABLE) + continue; + l = strlen(opt_table[i].names); + if (opt_table[i].type == OPT_HASARG + && !strchr(opt_table[i].names, ' ') + && !strchr(opt_table[i].names, '=')) + l += strlen(" "); + if (l + 2 > indent) + indent = l + 2; + } + + /* Now we know how much to indent */ + if (indent + MIN_DESC_WIDTH > width) + indent = width - MIN_DESC_WIDTH; + + len = max = 0; + ret = NULL; + ret = add_str(ret, &len, &max, "Usage: "); + ret = add_str(ret, &len, &max, argv0); + + /* Find usage message from among registered options if necessary. */ if (!extra) { extra = ""; for (i = 0; i < opt_count; i++) { @@ -39,71 +200,20 @@ char *opt_usage(const char *argv0, const char *extra) } } } - - /* An overestimate of our length. */ - len = strlen("Usage: %s ") + strlen(argv0) - + strlen("[-%.*s]") + opt_num_short + 1 - + strlen(" ") + strlen(extra) - + strlen("\n"); - - for (i = 0; i < opt_count; i++) { - if (opt_table[i].type == OPT_SUBTABLE) { - len += strlen("\n") + strlen(opt_table[i].desc) - + strlen(":\n"); - } else if (opt_table[i].desc != opt_hidden) { - len += strlen(opt_table[i].names) + strlen(" "); - len += strlen(OPT_SPACE_PAD) - + strlen(opt_table[i].desc) + 1; - if (opt_table[i].show) { - len += strlen("(default: %s)") - + OPT_SHOW_LEN + sizeof("..."); - } - len += strlen("\n"); - } - } - - p = ret = malloc(len); - p += sprintf(p, "Usage: %s", argv0); - p += sprintf(p, " [-"); - num = write_short_options(p); - if (num) { - p += num; - p += sprintf(p, "]"); - } else { - /* Remove start of single-entry options */ - p -= 3; - } - if (extra) - p += sprintf(p, " %s", extra); - p += sprintf(p, "\n"); + ret = add_str(ret, &len, &max, " "); + ret = add_str(ret, &len, &max, extra); + ret = add_str(ret, &len, &max, "\n"); for (i = 0; i < opt_count; i++) { if (opt_table[i].desc == opt_hidden) continue; if (opt_table[i].type == OPT_SUBTABLE) { - p += sprintf(p, "%s:\n", opt_table[i].desc); + ret = add_str(ret, &len, &max, opt_table[i].desc); + ret = add_str(ret, &len, &max, ":\n"); continue; } - len = sprintf(p, "%s", opt_table[i].names); - if (opt_table[i].type == OPT_HASARG - && !strchr(opt_table[i].names, ' ') - && !strchr(opt_table[i].names, '=')) - len += sprintf(p + len, " "); - len += sprintf(p + len, "%.*s", - len < strlen(OPT_SPACE_PAD) - ? (unsigned)strlen(OPT_SPACE_PAD) - len : 1, - OPT_SPACE_PAD); - - len += sprintf(p + len, "%s", opt_table[i].desc); - if (opt_table[i].show) { - char buf[OPT_SHOW_LEN + sizeof("...")]; - strcpy(buf + OPT_SHOW_LEN, "..."); - opt_table[i].show(buf, opt_table[i].u.arg); - len += sprintf(p + len, " (default: %s)", buf); - } - p += len; - p += sprintf(p, "\n"); + ret = add_desc(ret, &len, &max, indent, width, &opt_table[i]); } - *p = '\0'; + ret[len] = '\0'; return ret; }