opt: avoid using err.h.
[ccan] / ccan / opt / opt.c
1 /* Licensed under GPLv3+ - see LICENSE file for details */
2 #include <ccan/opt/opt.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <stdlib.h>
6 #include <stdio.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 /* Avoids dependency on err.h or ccan/err */
105 #ifndef failmsg
106 #define failmsg(fmt, ...) \
107         do { fprintf(stderr, fmt, __VA_ARGS__); exit(1); } while(0)
108 #endif
109
110 static void check_opt(const struct opt_table *entry)
111 {
112         const char *p;
113         unsigned len;
114
115         if (entry->type != OPT_HASARG && entry->type != OPT_NOARG
116             && entry->type != (OPT_EARLY|OPT_HASARG)
117             && entry->type != (OPT_EARLY|OPT_NOARG))
118                 failmsg("Option %s: unknown entry type %u",
119                         entry->names, entry->type);
120
121         if (!entry->desc)
122                 failmsg("Option %s: description cannot be NULL", entry->names);
123
124
125         if (entry->names[0] != '-')
126                 failmsg("Option %s: does not begin with '-'", entry->names);
127
128         for (p = first_name(entry->names, &len); p; p = next_name(p, &len)) {
129                 if (*p == '-') {
130                         if (len == 1)
131                                 failmsg("Option %s: invalid long option '--'",
132                                         entry->names);
133                         opt_num_long++;
134                 } else {
135                         if (len != 1)
136                                 failmsg("Option %s: invalid short option"
137                                         " '%.*s'", entry->names, len+1, p-1);
138                         opt_num_short++;
139                         if (entry->type == OPT_HASARG)
140                                 opt_num_short_arg++;
141                 }
142                 /* Don't document args unless there are some. */
143                 if (entry->type == OPT_NOARG) {
144                         if (p[len] == ' ' || p[len] == '=')
145                                 failmsg("Option %s: does not take arguments"
146                                         " '%s'", entry->names, p+len+1);
147                 }
148         }
149 }
150
151 static void add_opt(const struct opt_table *entry)
152 {
153         opt_table = realloc(opt_table, sizeof(opt_table[0]) * (opt_count+1));
154         opt_table[opt_count++] = *entry;
155 }
156
157 void _opt_register(const char *names, enum opt_type type,
158                    char *(*cb)(void *arg),
159                    char *(*cb_arg)(const char *optarg, void *arg),
160                    void (*show)(char buf[OPT_SHOW_LEN], const void *arg),
161                    const void *arg, const char *desc)
162 {
163         struct opt_table opt;
164         opt.names = names;
165         opt.type = type;
166         opt.cb = cb;
167         opt.cb_arg = cb_arg;
168         opt.show = show;
169         opt.u.carg = arg;
170         opt.desc = desc;
171         check_opt(&opt);
172         add_opt(&opt);
173 }
174
175 void opt_register_table(const struct opt_table entry[], const char *desc)
176 {
177         unsigned int i, start = opt_count;
178
179         if (desc) {
180                 struct opt_table heading = OPT_SUBTABLE(NULL, desc);
181                 add_opt(&heading);
182         }
183         for (i = 0; entry[i].type != OPT_END; i++) {
184                 if (entry[i].type == OPT_SUBTABLE)
185                         opt_register_table(subtable_of(&entry[i]),
186                                            entry[i].desc);
187                 else {
188                         check_opt(&entry[i]);
189                         add_opt(&entry[i]);
190                 }
191         }
192         /* We store the table length in arg ptr. */
193         if (desc)
194                 opt_table[start].u.tlen = (opt_count - start);
195 }
196
197 /* Parse your arguments. */
198 bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...))
199 {
200         int ret;
201         unsigned offset = 0;
202
203         /* This helps opt_usage. */
204         opt_argv0 = argv[0];
205
206         while ((ret = parse_one(argc, argv, 0, &offset, errlog)) == 1);
207
208         /* parse_one returns 0 on finish, -1 on error */
209         return (ret == 0);
210 }
211
212 bool opt_early_parse(int argc, char *argv[],
213                      void (*errlog)(const char *fmt, ...))
214 {
215         int ret;
216         unsigned off = 0;
217         char **tmpargv = malloc(sizeof(argv[0]) * (argc + 1));
218
219         /* We could avoid a copy and skip instead, but this is simple. */
220         memcpy(tmpargv, argv, sizeof(argv[0]) * (argc + 1));
221
222         /* This helps opt_usage. */
223         opt_argv0 = argv[0];
224
225         while ((ret = parse_one(&argc, tmpargv, OPT_EARLY, &off, errlog)) == 1);
226
227         free(tmpargv);
228
229         /* parse_one returns 0 on finish, -1 on error */
230         return (ret == 0);
231 }
232
233 void opt_free_table(void)
234 {
235         free(opt_table);
236         opt_table = NULL;
237         opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0;
238 }
239
240 void opt_log_stderr(const char *fmt, ...)
241 {
242         va_list ap;
243
244         va_start(ap, fmt);
245         vfprintf(stderr, fmt, ap);
246         fprintf(stderr, "\n");
247         va_end(ap);
248 }
249
250 void opt_log_stderr_exit(const char *fmt, ...)
251 {
252         va_list ap;
253
254         va_start(ap, fmt);
255         vfprintf(stderr, fmt, ap);
256         fprintf(stderr, "\n");
257         va_end(ap);
258         exit(1);
259 }
260
261 char *opt_invalid_argument(const char *arg)
262 {
263         char *str = malloc(sizeof("Invalid argument '%s'") + strlen(arg));
264         sprintf(str, "Invalid argument '%s'", arg);
265         return str;
266 }