|
|
@@ -1,5 +1,6 @@
|
|
|
/* Licensed under GPLv3+ - see LICENSE file for details */
|
|
|
#include <ccan/opt/opt.h>
|
|
|
+#include <sys/ioctl.h>
|
|
|
#include <string.h>
|
|
|
#include <stdlib.h>
|
|
|
#include <stdio.h>
|
|
|
@@ -9,26 +10,177 @@
|
|
|
/* 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;
|
|
|
+ struct winsize w;
|
|
|
+ const char *env = getenv("COLUMNS");
|
|
|
+
|
|
|
+ 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;
|
|
|
|
|
|
- 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 = realloc(base, *max = (*max * 2 + slen + 1));
|
|
|
+ memcpy(base + *len, str, slen);
|
|
|
+ *len += slen;
|
|
|
+ return base;
|
|
|
}
|
|
|
|
|
|
-#define OPT_SPACE_PAD " "
|
|
|
+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 = 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, " <arg>");
|
|
|
+ off += strlen(" <arg>");
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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(" <arg>");
|
|
|
+ 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 +191,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(" <arg>");
|
|
|
- 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, " <arg>");
|
|
|
- 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;
|
|
|
}
|