| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- /* Licensed under GPLv3+ - see LICENSE file for details */
- #include <ccan/opt/opt.h>
- #if HAVE_SYS_TERMIOS_H
- #include <sys/ioctl.h>
- #include <sys/termios.h> /* Required on Solaris for struct winsize */
- #endif
- #include <sys/unistd.h> /* Required on Solaris for ioctl */
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <stdint.h>
- #include "private.h"
- /* We only use this for pointer comparisons. */
- const char opt_hidden[1];
- #define MIN_DESC_WIDTH 40
- #define MIN_TOTAL_WIDTH 50
- static unsigned int get_columns(void)
- {
- 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, " \n");
- 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, " ");
- if (words[len] == '\n')
- break;
- len += strcspn(words+len, " \n");
- 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)
- {
- 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;
- }
- 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;
- }
- 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)
- 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++) {
- 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;
- }
- ret = add_desc(ret, &len, &max, indent, width, &opt_table[i]);
- }
- ret[len] = '\0';
- return ret;
- }
|