You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
5.8 KiB
246 lines
5.8 KiB
/* Licensed under GPLv2+ - 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
|
|
#if HAVE_SYS_UNISTD_H
|
|
#include <sys/unistd.h> /* Required on Solaris for ioctl */
|
|
#endif
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdarg.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.
|
|
* start is set if we start a new line in the source description. */
|
|
static size_t consume_words(const char *words, size_t maxlen, size_t *prefix,
|
|
bool *start)
|
|
{
|
|
size_t oldlen, len;
|
|
|
|
/* Always swollow leading whitespace. */
|
|
*prefix = strspn(words, " \n");
|
|
words += *prefix;
|
|
|
|
/* Leading whitespace at start of line means literal. */
|
|
if (*start && *prefix) {
|
|
oldlen = strcspn(words, "\n");
|
|
} else {
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
*start = (words[oldlen - 1] == '\n');
|
|
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, start = true;
|
|
|
|
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, &start)) != 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;
|
|
}
|
|
|
|
void opt_usage_exit_fail(const char *msg, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (opt_argv0)
|
|
fprintf(stderr, "%s: ", opt_argv0);
|
|
va_start(ap, msg);
|
|
vfprintf(stderr, msg, ap);
|
|
va_end(ap);
|
|
fprintf(stderr, "\n%s",
|
|
opt_usage(opt_argv0 ? opt_argv0 : "<program>", NULL));
|
|
exit(1);
|
|
}
|
|
|