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