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