X-Git-Url: http://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Fopt%2Fusage.c;h=ff0545591ed4050a5153aa5c8d6db2041a1295e4;hp=eedaae7eb821d0dab8114de49222b14496887aa0;hb=e4fbff960f8d384ee793259ff6e13bb798114c98;hpb=0191b7a74144ea8193961235f45715e715d4d8eb diff --git a/ccan/opt/usage.c b/ccan/opt/usage.c index eedaae7e..ff054559 100644 --- a/ccan/opt/usage.c +++ b/ccan/opt/usage.c @@ -1,4 +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 @@ -8,104 +14,206 @@ /* 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"); - for (p = first_sopt(&i); p; p = next_sopt(p, &i)) { - if (opt_table[i].desc != opt_hidden) - str[num++] = *p; + 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; } - return num; +#endif + if (!ws_col) + ws_col = 80; + + return ws_col; } -#define OPT_SPACE_PAD " " +/* 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; -/* FIXME: Get all purdy. */ -char *opt_usage(const char *argv0, const char *extra) + /* Swallow leading whitespace. */ + *prefix = strspn(words, " "); + words += *prefix; + + /* 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 oldlen; +} + +static char *add_str_len(char *base, size_t *len, size_t *max, + const char *str, size_t slen) { - unsigned int i, num, len; - char *ret, *p; + if (slen >= *max - *len) + base = opt_alloc.realloc(base, *max = (*max * 2 + slen + 1)); + memcpy(base + *len, str, slen); + *len += slen; + return base; +} - if (!extra) { - extra = ""; - for (i = 0; i < opt_count; i++) { - if (opt_table[i].cb == (void *)opt_usage_and_exit - && opt_table[i].arg) { - extra = opt_table[i].arg; - break; - } - } +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; +} + +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(" "); } - /* An overestimate of our length. */ - len = strlen("Usage: %s ") + strlen(argv0) - + strlen("[-%.*s]") + opt_num_short + 1 - + strlen(" ") + strlen(extra) - + strlen("\n"); + /* 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; + } - 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"); - } + /* 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; } - p = ret = malloc(len); - if (!ret) - return NULL; + /* Empty description? Make it match normal case. */ + if (same_line) + base = add_str(base, len, max, "\n"); - 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 (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"); } - if (extra) - p += sprintf(p, " %s", extra); - p += sprintf(p, "\n"); + return base; +} + +char *opt_usage(const char *argv0, const char *extra) +{ + 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) { - p += sprintf(p, "%s:\n", opt_table[i].desc); + if (opt_table[i].type == OPT_SUBTABLE) continue; - } - len = sprintf(p, "%s", opt_table[i].names); + l = strlen(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) - ? 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].arg); - len += sprintf(p + len, " (default: %s)", buf); + 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++) { + if (opt_table[i].cb == (void *)opt_usage_and_exit + && opt_table[i].u.carg) { + extra = opt_table[i].u.carg; + break; + } + } + } + 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) { + ret = add_str(ret, &len, &max, opt_table[i].desc); + ret = add_str(ret, &len, &max, ":\n"); + continue; } - p += len; - p += sprintf(p, "\n"); + ret = add_desc(ret, &len, &max, indent, width, &opt_table[i]); } - *p = '\0'; + ret[len] = '\0'; return ret; }