X-Git-Url: https://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Fopt%2Fusage.c;h=1142fb85c9c91ca091172d8971fe58101d0beca6;hp=d76c3b03ed317e3306f4ccad739ccdcac2fbdd9d;hb=4f09cf20ca00fe38b0702e0556bbad2341595ed0;hpb=f8b1841d26dabd23c053f5fc61dbd1536cdad43c;ds=sidebyside diff --git a/ccan/opt/usage.c b/ccan/opt/usage.c index d76c3b03..1142fb85 100644 --- a/ccan/opt/usage.c +++ b/ccan/opt/usage.c @@ -1,4 +1,8 @@ +/* Licensed under GPLv3+ - see LICENSE file for details */ #include +#include +#include /* Required on Solaris for struct winsize */ +#include /* Required on Solaris for ioctl */ #include #include #include @@ -6,115 +10,203 @@ #include "private.h" /* We only use this for pointer comparisons. */ -const char opt_table_hidden[1]; +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; + struct winsize w; + const char *env = getenv("COLUMNS"); - for (i = 0; i < opt_count; i++) { - if (opt_table[i].flags == OPT_SUBTABLE) { - if (opt_table[i].desc == opt_table_hidden) { - /* Skip these options. */ - i += (intptr_t)opt_table[i].arg - 1; - continue; - } - } else if (opt_table[i].shortopt) - str[num++] = opt_table[i].shortopt; + w.ws_col = 0; + if (env) + w.ws_col = atoi(env); + if (!w.ws_col) + if (ioctl(0, TIOCGWINSZ, &w) == -1) + w.ws_col = 0; + if (!w.ws_col) + w.ws_col = 80; + + return w.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; + + /* 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; } -#define OPT_SPACE_PAD " " +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; +} -/* FIXME: Get all purdy. */ -char *opt_usage(const char *argv0, const char *extra) +static char *add_str(char *base, size_t *len, size_t *max, const char *str) { - unsigned int i, num, len; - char *ret, *p; + 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; +} - /* An overestimate of our length. */ - len = strlen("Usage: %s ") + strlen(argv0) - + strlen("[-%.*s]") + opt_count + 1 - + strlen(" ") + strlen(extra) - + strlen("\n"); +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; - for (i = 0; i < opt_count; i++) { - if (opt_table[i].flags == OPT_SUBTABLE) { - len += strlen("\n") + strlen(opt_table[i].desc) - + strlen(":\n"); + 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 { - len += strlen("--%s/-%c") + strlen(" "); - if (opt_table[i].longopt) - len += strlen(opt_table[i].longopt); - if (opt_table[i].desc) { - 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"); + /* 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; +} - p = ret = malloc(len); - if (!ret) - return NULL; +char *opt_usage(const char *argv0, const char *extra) +{ + unsigned int i; + size_t max, len, width, indent; + char *ret; - 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"); + 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++) { - if (opt_table[i].flags == OPT_SUBTABLE) { - if (opt_table[i].desc == opt_table_hidden) { - /* Skip these options. */ - i += (intptr_t)opt_table[i].arg - 1; - continue; - } - p += sprintf(p, "%s:\n", opt_table[i].desc); + 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++) { + if (opt_table[i].cb == (void *)opt_usage_and_exit + && opt_table[i].u.carg) { + extra = opt_table[i].u.carg; + break; + } } - if (opt_table[i].shortopt && opt_table[i].longopt) - len = sprintf(p, "--%s/-%c", - opt_table[i].longopt, - opt_table[i].shortopt); - else if (opt_table[i].shortopt) - len = sprintf(p, "-%c", opt_table[i].shortopt); - else - len = sprintf(p, "--%s", opt_table[i].longopt); - if (opt_table[i].flags == OPT_HASARG) - len += sprintf(p + len, " "); - if (opt_table[i].desc || opt_table[i].show) - len += sprintf(p + len, "%.*s", - len < strlen(OPT_SPACE_PAD) - ? strlen(OPT_SPACE_PAD) - len : 1, - OPT_SPACE_PAD); - - if (opt_table[i].desc) - 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, "%s(default: %s)", - opt_table[i].desc ? " " : "", buf); + } + 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; }