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