1 /* Licensed under GPLv3+ - see LICENSE file for details */
2 #include <ccan/opt/opt.h>
4 #include <sys/termios.h> /* Required on Solaris for struct winsize */
5 #include <sys/unistd.h> /* Required on Solaris for ioctl */
12 /* We only use this for pointer comparisons. */
13 const char opt_hidden[1];
15 #define MIN_DESC_WIDTH 40
16 #define MIN_TOTAL_WIDTH 50
18 static unsigned int get_columns(void)
21 const char *env = getenv("COLUMNS");
27 if (ioctl(0, TIOCGWINSZ, &w) == -1)
35 /* Return number of chars of words to put on this line.
36 * Prefix is set to number to skip at start, maxlen is max width, returns
37 * length (after prefix) to put on this line. */
38 static size_t consume_words(const char *words, size_t maxlen, size_t *prefix)
42 /* Swallow leading whitespace. */
43 *prefix = strspn(words, " ");
46 /* Use at least one word, even if it takes us over maxlen. */
47 oldlen = len = strcspn(words, " ");
48 while (len <= maxlen) {
50 len += strspn(words+len, " ");
51 len += strcspn(words+len, " ");
59 static char *add_str_len(char *base, size_t *len, size_t *max,
60 const char *str, size_t slen)
62 if (slen >= *max - *len)
63 base = realloc(base, *max = (*max * 2 + slen + 1));
64 memcpy(base + *len, str, slen);
69 static char *add_str(char *base, size_t *len, size_t *max, const char *str)
71 return add_str_len(base, len, max, str, strlen(str));
74 static char *add_indent(char *base, size_t *len, size_t *max, size_t indent)
76 if (indent >= *max - *len)
77 base = realloc(base, *max = (*max * 2 + indent + 1));
78 memset(base + *len, ' ', indent);
83 static char *add_desc(char *base, size_t *len, size_t *max,
84 unsigned int indent, unsigned int width,
85 const struct opt_table *opt)
87 size_t off, prefix, l;
89 bool same_line = false;
91 base = add_str(base, len, max, opt->names);
92 off = strlen(opt->names);
93 if (opt->type == OPT_HASARG
94 && !strchr(opt->names, ' ')
95 && !strchr(opt->names, '=')) {
96 base = add_str(base, len, max, " <arg>");
97 off += strlen(" <arg>");
100 /* Do we start description on next line? */
101 if (off + 2 > indent) {
102 base = add_str(base, len, max, "\n");
105 base = add_indent(base, len, max, indent - off);
110 /* Indent description. */
112 while ((l = consume_words(p, width - indent, &prefix)) != 0) {
114 base = add_indent(base, len, max, indent);
116 base = add_str_len(base, len, max, p, l);
117 base = add_str(base, len, max, "\n");
123 /* Empty description? Make it match normal case. */
125 base = add_str(base, len, max, "\n");
128 char buf[OPT_SHOW_LEN + sizeof("...")];
129 strcpy(buf + OPT_SHOW_LEN, "...");
130 opt->show(buf, opt->u.arg);
132 /* If it doesn't fit on this line, indent. */
133 if (off + strlen(" (default: ") + strlen(buf) + strlen(")")
135 base = add_indent(base, len, max, indent);
141 base = add_str(base, len, max, " (default: ");
142 base = add_str(base, len, max, buf);
143 base = add_str(base, len, max, ")\n");
148 char *opt_usage(const char *argv0, const char *extra)
151 size_t max, len, width, indent;
154 width = get_columns();
155 if (width < MIN_TOTAL_WIDTH)
156 width = MIN_TOTAL_WIDTH;
158 /* Figure out longest option. */
160 for (i = 0; i < opt_count; i++) {
162 if (opt_table[i].desc == opt_hidden)
164 if (opt_table[i].type == OPT_SUBTABLE)
166 l = strlen(opt_table[i].names);
167 if (opt_table[i].type == OPT_HASARG
168 && !strchr(opt_table[i].names, ' ')
169 && !strchr(opt_table[i].names, '='))
170 l += strlen(" <arg>");
175 /* Now we know how much to indent */
176 if (indent + MIN_DESC_WIDTH > width)
177 indent = width - MIN_DESC_WIDTH;
182 ret = add_str(ret, &len, &max, "Usage: ");
183 ret = add_str(ret, &len, &max, argv0);
185 /* Find usage message from among registered options if necessary. */
188 for (i = 0; i < opt_count; i++) {
189 if (opt_table[i].cb == (void *)opt_usage_and_exit
190 && opt_table[i].u.carg) {
191 extra = opt_table[i].u.carg;
196 ret = add_str(ret, &len, &max, " ");
197 ret = add_str(ret, &len, &max, extra);
198 ret = add_str(ret, &len, &max, "\n");
200 for (i = 0; i < opt_count; i++) {
201 if (opt_table[i].desc == opt_hidden)
203 if (opt_table[i].type == OPT_SUBTABLE) {
204 ret = add_str(ret, &len, &max, opt_table[i].desc);
205 ret = add_str(ret, &len, &max, ":\n");
208 ret = add_desc(ret, &len, &max, indent, width, &opt_table[i]);