]> git.ozlabs.org Git - ccan/blob - ccan/opt/usage.c
opt: allow show callbacks to return false.
[ccan] / ccan / opt / usage.c
1 /* Licensed under GPLv2+ - see LICENSE file for details */
2 #include <ccan/opt/opt.h>
3 #if HAVE_SYS_TERMIOS_H
4 #include <sys/ioctl.h>
5 #include <sys/termios.h> /* Required on Solaris for struct winsize */
6 #endif
7 #if HAVE_SYS_UNISTD_H
8 #include <sys/unistd.h> /* Required on Solaris for ioctl */
9 #endif
10 #include <string.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <stdint.h>
14 #include <stdarg.h>
15 #include "private.h"
16
17 /* We only use this for pointer comparisons. */
18 const char opt_hidden[1];
19
20 #define MIN_DESC_WIDTH 40
21 #define MIN_TOTAL_WIDTH 50
22
23 /* Maximum length of arg to show in opt_usage */
24 #define OPT_SHOW_LEN 80
25
26 static unsigned int get_columns(void)
27 {
28         int ws_col = 0;
29         const char *env = getenv("COLUMNS");
30
31         if (env)
32                 ws_col = atoi(env);
33
34 #ifdef TIOCGWINSZ
35         if (!ws_col)
36         {
37                 struct winsize w;
38                 if (ioctl(0, TIOCGWINSZ, &w) != -1)
39                         ws_col = w.ws_col;
40         }
41 #endif
42         if (!ws_col)
43                 ws_col = 80;
44
45         return ws_col;
46 }
47
48 /* Return number of chars of words to put on this line.
49  * Prefix is set to number to skip at start, maxlen is max width, returns
50  * length (after prefix) to put on this line.
51  * start is set if we start a new line in the source description. */
52 static size_t consume_words(const char *words, size_t maxlen, size_t *prefix,
53                             bool *start)
54 {
55         size_t oldlen, len;
56
57         /* Always swollow leading whitespace. */
58         *prefix = strspn(words, " \n");
59         words += *prefix;
60
61         /* Leading whitespace at start of line means literal. */
62         if (*start && *prefix) {
63                 oldlen = strcspn(words, "\n");
64         } else {
65                 /* Use at least one word, even if it takes us over maxlen. */
66                 oldlen = len = strcspn(words, " ");
67                 while (len <= maxlen) {
68                         oldlen = len;
69                         len += strspn(words+len, " ");
70                         if (words[len] == '\n')
71                                 break;
72                         len += strcspn(words+len, " \n");
73                         if (len == oldlen)
74                                 break;
75                 }
76         }
77
78         if (oldlen != 0)
79                 *start = (words[oldlen - 1] == '\n');
80         return oldlen;
81 }
82
83 static char *add_str_len(char *base, size_t *len, size_t *max,
84                          const char *str, size_t slen)
85 {
86         if (slen >= *max - *len)
87                 base = opt_alloc.realloc(base, *max = (*max * 2 + slen + 1));
88         memcpy(base + *len, str, slen);
89         *len += slen;
90         return base;
91 }
92
93 static char *add_str(char *base, size_t *len, size_t *max, const char *str)
94 {
95         return add_str_len(base, len, max, str, strlen(str));
96 }
97
98 static char *add_indent(char *base, size_t *len, size_t *max, size_t indent)
99 {
100         if (indent >= *max - *len)
101                 base = opt_alloc.realloc(base, *max = (*max * 2 + indent + 1));
102         memset(base + *len, ' ', indent);
103         *len += indent;
104         return base;
105 }
106
107 static char *add_desc(char *base, size_t *len, size_t *max,
108                       unsigned int indent, unsigned int width,
109                       const struct opt_table *opt)
110 {
111         size_t off, prefix, l;
112         const char *p;
113         bool same_line = false, start = true;
114
115         base = add_str(base, len, max, opt->names);
116         off = strlen(opt->names);
117         if ((opt->type & OPT_HASARG)
118             && !strchr(opt->names, ' ')
119             && !strchr(opt->names, '=')) {
120                 base = add_str(base, len, max, " <arg>");
121                 off += strlen(" <arg>");
122         }
123
124         /* Do we start description on next line? */
125         if (off + 2 > indent) {
126                 base = add_str(base, len, max, "\n");
127                 off = 0;
128         } else {
129                 base = add_indent(base, len, max, indent - off);
130                 off = indent;
131                 same_line = true;
132         }
133
134         /* Indent description. */
135         p = opt->desc;
136         while ((l = consume_words(p, width - indent, &prefix, &start)) != 0) {
137                 if (!same_line)
138                         base = add_indent(base, len, max, indent);
139                 p += prefix;
140                 base = add_str_len(base, len, max, p, l);
141                 base = add_str(base, len, max, "\n");
142                 off = indent + l;
143                 p += l;
144                 same_line = false;
145         }
146
147         /* Empty description?  Make it match normal case. */
148         if (same_line)
149                 base = add_str(base, len, max, "\n");
150
151         if (opt->show) {
152                 char buf[OPT_SHOW_LEN + sizeof("...")];
153                 strcpy(buf + OPT_SHOW_LEN, "...");
154                 if (opt->show(buf, OPT_SHOW_LEN, opt->u.arg)) {
155                         /* If it doesn't fit on this line, indent. */
156                         if (off + strlen(" (default: ") + strlen(buf) + strlen(")")
157                             > width) {
158                                 base = add_indent(base, len, max, indent);
159                         } else {
160                                 /* Remove \n. */
161                                 (*len)--;
162                         }
163
164                         base = add_str(base, len, max, " (default: ");
165                         base = add_str(base, len, max, buf);
166                         base = add_str(base, len, max, ")\n");
167                 }
168         }
169         return base;
170 }
171
172 char *opt_usage(const char *argv0, const char *extra)
173 {
174         unsigned int i;
175         size_t max, len, width, indent;
176         char *ret;
177
178         width = get_columns();
179         if (width < MIN_TOTAL_WIDTH)
180                 width = MIN_TOTAL_WIDTH;
181
182         /* Figure out longest option. */
183         indent = 0;
184         for (i = 0; i < opt_count; i++) {
185                 size_t l;
186                 if (opt_table[i].desc == opt_hidden)
187                         continue;
188                 if (opt_table[i].type & OPT_SUBTABLE)
189                         continue;
190                 l = strlen(opt_table[i].names);
191                 if ((opt_table[i].type & OPT_HASARG)
192                     && !strchr(opt_table[i].names, ' ')
193                     && !strchr(opt_table[i].names, '='))
194                         l += strlen(" <arg>");
195                 if (l + 2 > indent)
196                         indent = l + 2;
197         }
198
199         /* Now we know how much to indent */
200         if (indent + MIN_DESC_WIDTH > width)
201                 indent = width - MIN_DESC_WIDTH;
202
203         len = max = 0;
204         ret = NULL;
205
206         ret = add_str(ret, &len, &max, "Usage: ");
207         ret = add_str(ret, &len, &max, argv0);
208
209         /* Find usage message from among registered options if necessary. */
210         if (!extra) {
211                 extra = "";
212                 for (i = 0; i < opt_count; i++) {
213                         if (opt_table[i].cb == (void *)opt_usage_and_exit
214                             && opt_table[i].u.carg) {
215                                 extra = opt_table[i].u.carg;
216                                 break;
217                         }
218                 }
219         }
220         ret = add_str(ret, &len, &max, " ");
221         ret = add_str(ret, &len, &max, extra);
222         ret = add_str(ret, &len, &max, "\n");
223
224         for (i = 0; i < opt_count; i++) {
225                 if (opt_table[i].desc == opt_hidden)
226                         continue;
227                 if (opt_table[i].type & OPT_SUBTABLE) {
228                         ret = add_str(ret, &len, &max, opt_table[i].desc);
229                         ret = add_str(ret, &len, &max, ":\n");
230                         continue;
231                 }
232                 ret = add_desc(ret, &len, &max, indent, width, &opt_table[i]);
233         }
234         ret[len] = '\0';
235         return ret;
236 }
237
238 void opt_usage_exit_fail(const char *msg, ...)
239 {
240         va_list ap;
241
242         if (opt_argv0)
243                 fprintf(stderr, "%s: ", opt_argv0);
244         va_start(ap, msg);
245         vfprintf(stderr, msg, ap);
246         va_end(ap);
247         fprintf(stderr, "\n%s",
248                 opt_usage(opt_argv0 ? opt_argv0 : "<program>", NULL));
249         exit(1);
250 }