opt: wean off getopt_long, beef up tests.
[ccan] / ccan / opt / opt.c
1 #include <ccan/opt/opt.h>
2 #include <string.h>
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <err.h>
7 #include <assert.h>
8 #include <stdarg.h>
9 #include <stdint.h>
10 #include "private.h"
11
12 struct opt_table *opt_table;
13 unsigned int opt_count, opt_num_short, opt_num_short_arg, opt_num_long;
14 const char *opt_argv0;
15
16 /* Returns string after first '-'. */
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] == ' ' || names[0] == '=' || names[0] == '\0')
27                 return NULL;
28         return first_name(names + 1, len);
29 }
30
31 static const char *first_opt(unsigned *i, unsigned *len)
32 {
33         for (*i = 0; *i < opt_count; (*i)++) {
34                 if (opt_table[*i].type == OPT_SUBTABLE)
35                         continue;
36                 return first_name(opt_table[*i].names, len);
37         }
38         return NULL;
39 }
40
41 static const char *next_opt(const char *p, unsigned *i, unsigned *len)
42 {
43         for (; *i < opt_count; (*i)++) {
44                 if (opt_table[*i].type == OPT_SUBTABLE)
45                         continue;
46                 if (!p)
47                         return first_name(opt_table[*i].names, len);
48                 p = next_name(p, len);
49                 if (p)
50                         return p;
51         }
52         return NULL;
53 }
54
55 const char *first_lopt(unsigned *i, unsigned *len)
56 {
57         const char *p;
58         for (p = first_opt(i, len); p; p = next_opt(p, i, len)) {
59                 if (p[0] == '-') {
60                         /* Skip leading "-" */
61                         (*len)--;
62                         p++;
63                         break;
64                 }
65         }
66         return p;
67 }
68
69 const char *next_lopt(const char *p, unsigned *i, unsigned *len)
70 {
71         for (p = next_opt(p, i, len); p; p = next_opt(p, i, len)) {
72                 if (p[0] == '-') {
73                         /* Skip leading "-" */
74                         (*len)--;
75                         p++;
76                         break;
77                 }
78         }
79         return p;
80 }
81
82 const char *first_sopt(unsigned *i)
83 {
84         const char *p;
85         unsigned int len = 0 /* GCC bogus warning */;
86
87         for (p = first_opt(i, &len); p; p = next_opt(p, i, &len)) {
88                 if (p[0] != '-')
89                         break;
90         }
91         return p;
92 }
93
94 const char *next_sopt(const char *p, unsigned *i)
95 {
96         unsigned int len = 1;
97         for (p = next_opt(p, i, &len); p; p = next_opt(p, i, &len)) {
98                 if (p[0] != '-')
99                         break;
100         }
101         return p;
102 }
103
104 static void check_opt(const struct opt_table *entry)
105 {
106         const char *p;
107         unsigned len;
108
109         if (entry->type != OPT_HASARG && entry->type != OPT_NOARG)
110                 errx(1, "Option %s: unknown entry type %u",
111                      entry->names, entry->type);
112
113         if (!entry->desc)
114                 errx(1, "Option %s: description cannot be NULL", entry->names);
115
116
117         if (entry->names[0] != '-')
118                 errx(1, "Option %s: does not begin with '-'", entry->names);
119
120         for (p = first_name(entry->names, &len); p; p = next_name(p, &len)) {
121                 if (*p == '-') {
122                         if (len == 1)
123                                 errx(1, "Option %s: invalid long option '--'",
124                                      entry->names);
125                         opt_num_long++;
126                 } else {
127                         if (len != 1)
128                                 errx(1, "Option %s: invalid short option"
129                                      " '%.*s'", entry->names, len+1, p-1);
130                         opt_num_short++;
131                         if (entry->type == OPT_HASARG)
132                                 opt_num_short_arg++;
133                 }
134                 /* Don't document args unless there are some. */
135                 if (entry->type == OPT_NOARG) {
136                         if (p[len] == ' ' || p[len] == '=')
137                                 errx(1, "Option %s: does not take arguments"
138                                      " '%s'", entry->names, p+len+1);
139                 }
140         }
141 }
142
143 static void add_opt(const struct opt_table *entry)
144 {
145         opt_table = realloc(opt_table, sizeof(opt_table[0]) * (opt_count+1));
146         opt_table[opt_count++] = *entry;
147 }
148
149 void _opt_register(const char *names, enum opt_type type,
150                    char *(*cb)(void *arg),
151                    char *(*cb_arg)(const char *optarg, void *arg),
152                    void (*show)(char buf[OPT_SHOW_LEN], const void *arg),
153                    void *arg, const char *desc)
154 {
155         struct opt_table opt;
156         opt.names = names;
157         opt.type = type;
158         opt.cb = cb;
159         opt.cb_arg = cb_arg;
160         opt.show = show;
161         opt.arg = arg;
162         opt.desc = desc;
163         check_opt(&opt);
164         add_opt(&opt);
165 }
166
167 void opt_register_table(const struct opt_table entry[], const char *desc)
168 {
169         unsigned int i, start = opt_count;
170
171         if (desc) {
172                 struct opt_table heading = OPT_SUBTABLE(NULL, desc);
173                 add_opt(&heading);
174         }
175         for (i = 0; entry[i].type != OPT_END; i++) {
176                 if (entry[i].type == OPT_SUBTABLE)
177                         opt_register_table(subtable_of(&entry[i]),
178                                            entry[i].desc);
179                 else {
180                         check_opt(&entry[i]);
181                         add_opt(&entry[i]);
182                 }
183         }
184         /* We store the table length in arg ptr. */
185         if (desc)
186                 opt_table[start].arg = (void *)(intptr_t)(opt_count - start);
187 }
188
189 /* Parse your arguments. */
190 bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...))
191 {
192         int ret;
193         unsigned offset = 0;
194
195         /* This helps opt_usage. */
196         opt_argv0 = argv[0];
197
198         while ((ret = parse_one(argc, argv, &offset, errlog)) == 1);
199
200         /* parse_one returns 0 on finish, -1 on error */
201         return (ret == 0);
202 }
203
204 void opt_log_stderr(const char *fmt, ...)
205 {
206         va_list ap;
207
208         va_start(ap, fmt);
209         vfprintf(stderr, fmt, ap);
210         fprintf(stderr, "\n");
211         va_end(ap);
212 }
213
214 void opt_log_stderr_exit(const char *fmt, ...)
215 {
216         va_list ap;
217
218         va_start(ap, fmt);
219         vfprintf(stderr, fmt, ap);
220         fprintf(stderr, "\n");
221         va_end(ap);
222         exit(1);
223 }
224
225 char *opt_invalid_argument(const char *arg)
226 {
227         char *str = malloc(sizeof("Invalid argument '%s'") + strlen(arg));
228         sprintf(str, "Invalid argument '%s'", arg);
229         return str;
230 }