opt: new module to parse commandline options.
[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;
15 const char *opt_argv0;
16
17 static void check_opt(const struct opt_table *entry)
18 {
19         assert(entry->flags == OPT_HASARG || entry->flags == OPT_NOARG);
20         assert(entry->shortopt || entry->longopt);
21         assert(entry->shortopt != ':');
22         assert(entry->shortopt != '?' || entry->flags == OPT_NOARG);
23 }
24
25 static void add_opt(const struct opt_table *entry)
26 {
27         opt_table = realloc(opt_table, sizeof(opt_table[0]) * (opt_count+1));
28         opt_table[opt_count++] = *entry;
29 }
30
31 void _opt_register(const char *longopt, char shortopt, enum opt_flags flags,
32                    char *(*cb)(void *arg),
33                    char *(*cb_arg)(const char *optarg, void *arg),
34                    void *arg, const char *desc)
35 {
36         struct opt_table opt;
37         opt.longopt = longopt;
38         opt.shortopt = shortopt;
39         opt.flags = flags;
40         opt.cb = cb;
41         opt.cb_arg = cb_arg;
42         opt.arg = arg;
43         opt.desc = desc;
44         check_opt(&opt);
45         add_opt(&opt);
46 }
47
48 void opt_register_table(const struct opt_table entry[], const char *desc)
49 {
50         unsigned int i, start = opt_count;
51
52         if (desc) {
53                 struct opt_table heading = OPT_SUBTABLE(NULL, desc);
54                 add_opt(&heading);
55         }
56         for (i = 0; entry[i].flags != OPT_END; i++) {
57                 if (entry[i].flags == OPT_SUBTABLE)
58                         opt_register_table(subtable_of(&entry[i]),
59                                            entry[i].desc);
60                 else {
61                         check_opt(&entry[i]);
62                         add_opt(&entry[i]);
63                 }
64         }
65         /* We store the table length in arg ptr. */
66         if (desc)
67                 opt_table[start].arg = (void *)(intptr_t)(opt_count - start);
68 }
69
70 static char *make_optstring(void)
71 {
72         /* Worst case, each one is ":x:", plus nul term. */
73         char *str = malloc(1 + opt_count * 2 + 1);
74         unsigned int num, i;
75
76         /* This tells getopt_long we want a ':' returned for missing arg. */
77         str[0] = ':';
78         num = 1;
79         for (i = 0; i < opt_count; i++) {
80                 if (!opt_table[i].shortopt)
81                         continue;
82                 str[num++] = opt_table[i].shortopt;
83                 if (opt_table[i].flags == OPT_HASARG)
84                         str[num++] = ':';
85         }
86         str[num] = '\0';
87         return str;
88 }
89
90 static struct option *make_options(void)
91 {
92         struct option *options = malloc(sizeof(*options) * (opt_count + 1));
93         unsigned int i, num;
94
95         for (num = i = 0; i < opt_count; i++) {
96                 if (!opt_table[i].longopt)
97                         continue;
98                 options[num].name = opt_table[i].longopt;
99                 options[num].has_arg = (opt_table[i].flags == OPT_HASARG);
100                 options[num].flag = NULL;
101                 options[num].val = 0;
102                 num++;
103         }
104         memset(&options[num], 0, sizeof(options[num]));
105         return options;
106 }
107
108 static struct opt_table *find_short(char shortopt)
109 {
110         unsigned int i;
111         for (i = 0; i < opt_count; i++) {
112                 if (opt_table[i].shortopt == shortopt)
113                         return &opt_table[i];
114         }
115         abort();
116 }
117
118 /* We want the index'th long entry. */
119 static struct opt_table *find_long(int index)
120 {
121         unsigned int i;
122         for (i = 0; i < opt_count; i++) {
123                 if (!opt_table[i].longopt)
124                         continue;
125                 if (index == 0)
126                         return &opt_table[i];
127                 index--;
128         }
129         abort();
130 }
131
132 /* glibc does this as:
133 /tmp/opt-example: invalid option -- 'x'
134 /tmp/opt-example: unrecognized option '--long'
135 /tmp/opt-example: option '--someflag' doesn't allow an argument
136 /tmp/opt-example: option '--s' is ambiguous
137 /tmp/opt-example: option requires an argument -- 's'
138 */
139 static void parse_fail(void (*errlog)(const char *fmt, ...),
140                        char shortopt, const char *longopt, const char *problem)
141 {
142         if (shortopt)
143                 errlog("%s: -%c: %s", opt_argv0, shortopt, problem);
144         else
145                 errlog("%s: --%s: %s", opt_argv0, longopt, problem);
146 }
147
148 void dump_optstate(void);
149 void dump_optstate(void)
150 {
151         printf("opterr = %i, optind = %i, optopt = %i, optarg = %s\n",
152                opterr, optind, optopt, optarg);
153 }
154
155 /* Parse your arguments. */
156 bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...))
157 {
158         char *optstring = make_optstring();
159         struct option *options = make_options();
160         int ret, longidx = 0;
161         struct opt_table *e;
162
163         /* We will do our own error reporting. */
164         opterr = 0;
165         opt_argv0 = argv[0];
166
167         /* Reset in case we're called more than once. */
168         optopt = 0;
169         optind = 1;
170         while ((ret = getopt_long(*argc, argv, optstring, options, &longidx))
171                != -1) {
172                 char *problem;
173                 bool missing = false;
174
175                 /* optopt is 0 if it's an unknown long option, *or* if
176                  * -? is a valid short option. */
177                 if (ret == '?') {
178                         if (optopt || strncmp(argv[optind-1], "--", 2) == 0) {
179                                 parse_fail(errlog, optopt, argv[optind-1]+2,
180                                            "unrecognized option");
181                                 break;
182                         }
183                 } else if (ret == ':') {
184                         missing = true;
185                         ret = optopt;
186                 }
187
188                 if (ret != 0)
189                         e = find_short(ret);
190                 else
191                         e = find_long(longidx);
192
193                 /* Missing argument */
194                 if (missing) {
195                         parse_fail(errlog, e->shortopt, e->longopt,
196                                    "option requires an argument");
197                         break;
198                 }
199
200                 if (e->flags == OPT_HASARG)
201                         problem = e->cb_arg(optarg, e->arg);
202                 else
203                         problem = e->cb(e->arg);
204
205                 if (problem) {
206                         parse_fail(errlog, e->shortopt, e->longopt,
207                                    problem);
208                         free(problem);
209                         break;
210                 }
211         }
212         free(optstring);
213         free(options);
214         if (ret != -1)
215                 return false;
216
217         /* We hide everything but remaining arguments. */
218         memmove(&argv[1], &argv[optind], sizeof(argv[1]) * (*argc-optind+1));
219         *argc -= optind - 1;
220
221         return ret == -1 ? true : false;
222 }
223
224 void opt_log_stderr(const char *fmt, ...)
225 {
226         va_list ap;
227
228         va_start(ap, fmt);
229         vfprintf(stderr, fmt, ap);
230         va_end(ap);
231 }
232
233 char *opt_invalid_argument(const char *arg)
234 {
235         char *str = malloc(sizeof("Invalid argument '%s'") + strlen(arg));
236         sprintf(str, "Invalid argument '%s'", arg);
237         return str;
238 }