opt: fix reporting of arguments when multiple arguments exist.
[ccan] / ccan / opt / opt.c
1 #include <ccan/opt/opt.h>
2 #include <string.h>
3 #include <errno.h>
4 #include <getopt.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <err.h>
8 #include <assert.h>
9 #include <stdarg.h>
10 #include <stdint.h>
11 #include "private.h"
12
13 struct opt_table *opt_table;
14 unsigned int opt_count, opt_num_short, opt_num_short_arg, opt_num_long;
15 const char *opt_argv0;
16
17 static const char *first_name(const char *names, unsigned *len)
18 {
19         *len = strcspn(names + 1, "/");
20         return names + 1;
21 }
22
23 static const char *next_name(const char *names, unsigned *len)
24 {
25         names += *len;
26         if (!names[0])
27                 return NULL;
28         return first_name(names + 1, len);
29 }
30
31 /* FIXME: Combine with next_opt */
32 static const char *first_opt(bool want_long, unsigned *i, unsigned *len)
33 {
34         const char *p;
35         for (*i = 0; *i < opt_count; (*i)++) {
36                 if (opt_table[*i].flags == OPT_SUBTABLE)
37                         continue;
38                 for (p = first_name(opt_table[*i].names, len);
39                      p;
40                      p = next_name(p, len)) {
41                         if ((p[0] == '-') == want_long) {
42                                 if (want_long) {
43                                         /* Skip leading "-" */
44                                         (*len)--;
45                                         p++;
46                                 }
47                                 return p;
48                         }
49                 }
50         }
51         return NULL;
52 }
53
54 static const char *next_opt(const char *names, bool want_long,
55                             unsigned *i, unsigned *len)
56 {
57         const char *p = next_name(names, len);
58         for (;;) {
59                 while (p) {
60                         if ((p[0] == '-') == want_long) {
61                                 if (want_long) {
62                                         /* Skip leading "-" */
63                                         (*len)--;
64                                         p++;
65                                 }
66                                 return p;
67                         }
68                         p = next_name(p, len);
69                 }
70                 do {
71                         (*i)++;
72                 } while (*i < opt_count && opt_table[*i].flags == OPT_SUBTABLE);
73                 if (*i == opt_count)
74                         return NULL;
75                 p = first_name(opt_table[*i].names, len);
76         }
77 }
78
79 static const char *first_lopt(unsigned *i, unsigned *len)
80 {
81         return first_opt(true, i, len);
82 }
83
84 static const char *next_lopt(const char *names, unsigned *i, unsigned *len)
85 {
86         return next_opt(names, true, i, len);
87 }
88
89 const char *first_sopt(unsigned *i)
90 {
91         unsigned unused_len;
92         return first_opt(false, i, &unused_len);
93 }
94
95 const char *next_sopt(const char *names, unsigned *i)
96 {
97         unsigned unused_len = 1;
98
99         return next_opt(names, false, i, &unused_len);
100 }
101
102 static void check_opt(const struct opt_table *entry)
103 {
104         const char *p;
105         unsigned len;
106
107         assert(entry->flags == OPT_HASARG || entry->flags == OPT_NOARG);
108
109         assert(entry->names[0] == '-');
110         for (p = first_name(entry->names, &len); p; p = next_name(p, &len)) {
111                 if (*p == '-') {
112                         assert(len > 1);
113                         opt_num_long++;
114                 } else {
115                         assert(len == 1);
116                         assert(*p != ':');
117                         opt_num_short++;
118                         if (entry->flags == OPT_HASARG) {
119                                 opt_num_short_arg++;
120                                 /* FIXME: -? with ops breaks getopt_long */
121                                 assert(*p != '?');
122                         }
123                 }
124         }
125 }
126
127 static void add_opt(const struct opt_table *entry)
128 {
129         opt_table = realloc(opt_table, sizeof(opt_table[0]) * (opt_count+1));
130         opt_table[opt_count++] = *entry;
131 }
132
133 void _opt_register(const char *names, enum opt_flags flags,
134                    char *(*cb)(void *arg),
135                    char *(*cb_arg)(const char *optarg, void *arg),
136                    void (*show)(char buf[OPT_SHOW_LEN], const void *arg),
137                    void *arg, const char *desc)
138 {
139         struct opt_table opt;
140         opt.names = names;
141         opt.flags = flags;
142         opt.cb = cb;
143         opt.cb_arg = cb_arg;
144         opt.show = show;
145         opt.arg = arg;
146         opt.desc = desc;
147         check_opt(&opt);
148         add_opt(&opt);
149 }
150
151 void opt_register_table(const struct opt_table entry[], const char *desc)
152 {
153         unsigned int i, start = opt_count;
154
155         if (desc) {
156                 struct opt_table heading = OPT_SUBTABLE(NULL, desc);
157                 add_opt(&heading);
158         }
159         for (i = 0; entry[i].flags != OPT_END; i++) {
160                 if (entry[i].flags == OPT_SUBTABLE)
161                         opt_register_table(subtable_of(&entry[i]),
162                                            entry[i].desc);
163                 else {
164                         check_opt(&entry[i]);
165                         add_opt(&entry[i]);
166                 }
167         }
168         /* We store the table length in arg ptr. */
169         if (desc)
170                 opt_table[start].arg = (void *)(intptr_t)(opt_count - start);
171 }
172
173 static char *make_optstring(void)
174 {
175         char *str = malloc(1 + opt_num_short + opt_num_short_arg + 1);
176         const char *p;
177         unsigned int i, num = 0;
178
179         /* This tells getopt_long we want a ':' returned for missing arg. */
180         str[num++] = ':';
181         for (p = first_sopt(&i); p; p = next_sopt(p, &i)) {
182                 str[num++] = *p;
183                 if (opt_table[i].flags == OPT_HASARG)
184                         str[num++] = ':';
185         }
186         str[num++] = '\0';
187         assert(num == 1 + opt_num_short + opt_num_short_arg + 1);
188         return str;
189 }
190
191 static struct option *make_options(void)
192 {
193         struct option *options = malloc(sizeof(*options) * (opt_num_long + 1));
194         unsigned int i, num = 0, len;
195         const char *p;
196
197         for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) {
198                 char *buf = malloc(len + 1);
199                 memcpy(buf, p, len);
200                 buf[len] = 0;
201                 options[num].name = buf;
202                 options[num].has_arg = (opt_table[i].flags == OPT_HASARG);
203                 options[num].flag = NULL;
204                 options[num].val = 0;
205                 num++;
206         }
207         memset(&options[num], 0, sizeof(options[num]));
208         assert(num == opt_num_long);
209         return options;
210 }
211
212 static struct opt_table *find_short(char shortopt)
213 {
214         unsigned int i;
215         const char *p;
216
217         for (p = first_sopt(&i); p; p = next_sopt(p, &i)) {
218                 if (*p == shortopt)
219                         return &opt_table[i];
220         }
221         abort();
222 }
223
224 /* We want the index'th long entry. */
225 static struct opt_table *find_long(int index, const char **name)
226 {
227         unsigned int i, len;
228         const char *p;
229
230         for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) {
231                 if (index == 0) {
232                         *name = p;
233                         return &opt_table[i];
234                 }
235                 index--;
236         }
237         abort();
238 }
239
240 /* glibc does this as:
241 /tmp/opt-example: invalid option -- 'x'
242 /tmp/opt-example: unrecognized option '--long'
243 /tmp/opt-example: option '--someflag' doesn't allow an argument
244 /tmp/opt-example: option '--s' is ambiguous
245 /tmp/opt-example: option requires an argument -- 's'
246 */
247 static void parse_fail(void (*errlog)(const char *fmt, ...),
248                        char shortopt, const char *longopt, const char *problem)
249 {
250         if (shortopt)
251                 errlog("%s: -%c: %s", opt_argv0, shortopt, problem);
252         else
253                 errlog("%s: --%.*s: %s", opt_argv0,
254                        strcspn(longopt, "/"), longopt, problem);
255 }
256
257 void dump_optstate(void);
258 void dump_optstate(void)
259 {
260         printf("opterr = %i, optind = %i, optopt = %i, optarg = %s\n",
261                opterr, optind, optopt, optarg);
262 }
263
264 /* Parse your arguments. */
265 bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...))
266 {
267         char *optstring = make_optstring();
268         struct option *options = make_options();
269         int ret, longidx = 0;
270         struct opt_table *e;
271
272         /* We will do our own error reporting. */
273         opterr = 0;
274         opt_argv0 = argv[0];
275
276         /* Reset in case we're called more than once. */
277         optopt = 0;
278         optind = 0;
279         while ((ret = getopt_long(*argc, argv, optstring, options, &longidx))
280                != -1) {
281                 char *problem;
282                 const char *name;
283
284                 /* optopt is 0 if it's an unknown long option, *or* if
285                  * -? is a valid short option. */
286                 if (ret == '?') {
287                         if (optopt || strncmp(argv[optind-1], "--", 2) == 0) {
288                                 parse_fail(errlog, optopt, argv[optind-1]+2,
289                                            "unrecognized option");
290                                 break;
291                         }
292                 } else if (ret == ':') {
293                         /* Missing argument: longidx not updated :( */
294                         parse_fail(errlog, optopt, argv[optind-1]+2,
295                                    "option requires an argument");
296                         break;
297                 }
298
299                 if (ret != 0)
300                         e = find_short(ret);
301                 else
302                         e = find_long(longidx, &name);
303
304                 if (e->flags == OPT_HASARG)
305                         problem = e->cb_arg(optarg, e->arg);
306                 else
307                         problem = e->cb(e->arg);
308
309                 if (problem) {
310                         parse_fail(errlog, ret, name, problem);
311                         free(problem);
312                         break;
313                 }
314         }
315         free(optstring);
316         free(options);
317         if (ret != -1)
318                 return false;
319
320         /* We hide everything but remaining arguments. */
321         memmove(&argv[1], &argv[optind], sizeof(argv[1]) * (*argc-optind+1));
322         *argc -= optind - 1;
323
324         return ret == -1 ? true : false;
325 }
326
327 void opt_log_stderr(const char *fmt, ...)
328 {
329         va_list ap;
330
331         va_start(ap, fmt);
332         vfprintf(stderr, fmt, ap);
333         va_end(ap);
334 }
335
336 char *opt_invalid_argument(const char *arg)
337 {
338         char *str = malloc(sizeof("Invalid argument '%s'") + strlen(arg));
339         sprintf(str, "Invalid argument '%s'", arg);
340         return str;
341 }