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