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