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