]> mj.ucw.cz Git - libucw.git/blob - ucw/opt.c
Opt: User-defined types are specified as CT_USER, not OPT_CL_USER
[libucw.git] / ucw / opt.c
1 /*
2  *      UCW Library -- Parsing of command-line options
3  *
4  *      (c) 2013 Jan Moskyto Matejka <mq@ucw.cz>
5  *      (c) 2014 Martin Mares <mj@ucw.cz>
6  *
7  *      This software may be freely distributed and used according to the terms
8  *      of the GNU Lesser General Public License.
9  */
10
11 #include <ucw/lib.h>
12 #include <ucw/opt.h>
13 #include <ucw/opt-internal.h>
14 #include <ucw/stkstring.h>
15 #include <ucw/strtonum.h>
16
17 #include <alloca.h>
18 #include <math.h>
19
20 static uns opt_default_value_flags[] = {
21     [OPT_CL_BOOL] = OPT_NO_VALUE,
22     [OPT_CL_STATIC] = OPT_MAYBE_VALUE,
23     [OPT_CL_SWITCH] = OPT_NO_VALUE,
24     [OPT_CL_INC] = OPT_NO_VALUE,
25     [OPT_CL_CALL] = 0,
26     [OPT_CL_SECTION] = 0,
27     [OPT_CL_HELP] = 0
28 };
29
30 void opt_failure(const char * mesg, ...) {
31   va_list args;
32   va_start(args, mesg);
33   vfprintf(stderr, mesg, args);
34   fprintf(stderr, "\nRun with --help for more information.\n");
35   exit(OPT_EXIT_BAD_ARGS);
36 }
37
38 static char *opt_name(struct opt_context *oc, struct opt_precomputed *opt)
39 {
40   struct opt_item *item = opt->item;
41   char *res;
42   if (item->letter >= OPT_POSITIONAL_TAIL)
43     res = stk_printf("positional argument #%d", oc->positional_count);
44   else if (opt->flags & OPT_SEEN_AS_LONG)
45     res = stk_printf("--%s", opt->name);
46   else
47     res = stk_printf("-%c", item->letter);
48   return xstrdup(res);
49 }
50
51 #define THIS_OPT opt_name(oc, opt)
52
53 void opt_precompute(struct opt_precomputed *opt, struct opt_item *item)
54 {
55   opt->item = item;
56   opt->count = 0;
57   opt->name = item->name;
58   uns flags = item->flags;
59
60   if (item->letter >= OPT_POSITIONAL_TAIL) {
61     flags &= ~OPT_VALUE_FLAGS;
62     flags |= OPT_REQUIRED_VALUE;
63   }
64   if (!(flags & OPT_VALUE_FLAGS)) {
65     ASSERT(item->cls != OPT_CL_CALL);
66     flags |= opt_default_value_flags[item->cls];
67   }
68
69   opt->flags = flags;
70 }
71
72 static void opt_invoke_hooks(struct opt_context *oc, uns event, struct opt_item *item, char *value)
73 {
74   for (int i = 0; i < oc->hook_count; i++) {
75     struct opt_item *hook = oc->hooks[i];
76     if (hook->flags & event) {
77       void *data = (hook->flags & OPT_HOOK_INTERNAL) ? oc : hook->ptr;
78       hook->u.hook(item, event, value, data);
79     }
80   }
81 }
82
83 static struct opt_precomputed * opt_find_item_longopt(struct opt_context * oc, char * str) {
84   uns len = strlen(str);
85   struct opt_precomputed * candidate = NULL;
86
87   for (int i = 0; i < oc->opt_count; i++) {
88     struct opt_precomputed *opt = &oc->opts[i];
89     if (!opt->name)
90       continue;
91
92     if (!strncmp(opt->name, str, len)) {
93       if (strlen(opt->name) == len)
94         return opt;
95     } else if (opt->item->cls == OPT_CL_BOOL && !strncmp("no-", str, 3) && !strncmp(opt->name, str+3, len-3)) {
96       if (strlen(opt->name) == len-3)
97         return opt;
98     } else
99       continue;
100
101     if (candidate)
102       opt_failure("Ambiguous option --%s: matches both --%s and --%s.", str, candidate->name, opt->name);
103     else
104       candidate = opt;
105   }
106
107   if (candidate)
108     return candidate;
109
110   opt_failure("Invalid option --%s.", str);
111 }
112
113 // FIXME: Use simple-lists?
114 #define OPT_PTR(type) ({                        \
115   type * ptr;                                   \
116   if (item->flags & OPT_MULTIPLE) {             \
117     struct {                                    \
118       cnode n;                                  \
119       type v;                                   \
120     } * n = xmalloc(sizeof(*n));                \
121     clist_add_tail(item->ptr, &(n->n));         \
122     ptr = &(n->v);                              \
123   } else                                        \
124     ptr = item->ptr;                            \
125   ptr; })
126
127 static void opt_parse_value(struct opt_context * oc, struct opt_precomputed * opt, char * value) {
128   struct opt_item * item = opt->item;
129
130   if (opt->count++ && (opt->flags & OPT_SINGLE))
131     opt_failure("Option %s must be specified at most once.", THIS_OPT);
132
133   if (opt->flags & OPT_LAST_ARG)
134     oc->stop_parsing = 1;
135
136   opt_invoke_hooks(oc, OPT_HOOK_BEFORE_VALUE, item, value);
137
138   switch (item->cls) {
139     case OPT_CL_BOOL:
140       if (!value || !strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "1"))
141         *((int *) item->ptr) = 1 ^ (!!(opt->flags & OPT_NEGATIVE));
142       else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "0"))
143         *((int *) item->ptr) = 0 ^ (!!(opt->flags & OPT_NEGATIVE));
144       else
145         opt_failure("Boolean argument for %s has a strange value. Supported (case insensitive): 1/0, y/n, yes/no, true/false.", THIS_OPT);
146       break;
147     case OPT_CL_STATIC:
148       {
149         char * e = NULL;
150         switch (item->type) {
151           case CT_INT:
152             if (!value)
153               *OPT_PTR(int) = 0;
154             else
155               e = cf_parse_int(value, OPT_PTR(int));
156             if (e)
157               opt_failure("Integer value parsing failed for %s: %s", THIS_OPT, e);
158             break;
159           case CT_U64:
160             if (!value)
161               *OPT_PTR(u64) = 0;
162             else
163               e = cf_parse_u64(value, OPT_PTR(u64));
164             if (e)
165               opt_failure("Unsigned 64-bit value parsing failed for %s: %s", THIS_OPT, e);
166             break;
167           case CT_DOUBLE:
168             if (!value)
169               *OPT_PTR(double) = NAN;
170             else
171               e = cf_parse_double(value, OPT_PTR(double));
172             if (e)
173               opt_failure("Floating-point value parsing failed for %s: %s", THIS_OPT, e);
174             break;
175           case CT_IP:
176             if (!value)
177               *OPT_PTR(u32) = 0;
178             else
179               e = cf_parse_ip(value, OPT_PTR(u32));
180             if (e)
181               opt_failure("IP address parsing failed for %s: %s", THIS_OPT, e);
182             break;
183           case CT_STRING:
184             if (!value)
185               *OPT_PTR(const char *) = NULL;
186             else
187               *OPT_PTR(const char *) = xstrdup(value);
188             break;
189           case CT_USER:
190               {
191                 char * e = item->u.utype->parser(value, item->ptr);
192                 if (e)
193                   opt_failure("Cannot parse the value of %s: %s", THIS_OPT, e);
194                 break;
195               }
196           default:
197             ASSERT(0);
198         }
199         break;
200       }
201     case OPT_CL_SWITCH:
202       if ((opt->flags & OPT_SINGLE) && *((int *)item->ptr) != -1)
203         opt_failure("Multiple switches: %s", THIS_OPT);
204       else
205         *((int *)item->ptr) = item->u.value;
206       break;
207     case OPT_CL_INC:
208       if (opt->flags & OPT_NEGATIVE)
209         (*((int *)item->ptr))--;
210       else
211         (*((int *)item->ptr))++;
212       break;
213     case OPT_CL_CALL:
214       {
215         void *data = (opt->flags & OPT_INTERNAL) ? oc : item->ptr;
216         item->u.call(item, value, data);
217         break;
218       }
219     default:
220       ASSERT(0);
221   }
222
223   opt_invoke_hooks(oc, OPT_HOOK_AFTER_VALUE, item, value);
224 }
225
226 static int opt_longopt(struct opt_context * oc, char ** argv, int index) {
227   int eaten = 0;
228   char * name_in = argv[index] + 2; // skipping the -- on the beginning
229   uns pos = strchrnul(name_in, '=') - name_in;
230   struct opt_precomputed * opt = opt_find_item_longopt(oc, strndupa(name_in, pos));
231   char * value = NULL;
232
233   opt->flags |= OPT_SEEN_AS_LONG;
234
235   if (opt->item->cls == OPT_CL_BOOL && !strncmp(name_in, "no-", 3) && !strncmp(name_in+3, opt->item->name, pos-3)) {
236     if (name_in[pos])
237       opt_failure("Option --%s must not have any value.", name_in);
238     value = "n";
239   } else if (opt->flags & OPT_REQUIRED_VALUE) {
240     if (name_in[pos])
241       value = name_in + pos + 1;
242     else {
243       value = argv[index+1];
244       if (!value)
245         opt_failure("Option %s must have a value, but nothing supplied.", THIS_OPT);
246       eaten++;
247     }
248   } else if (opt->flags & OPT_MAYBE_VALUE) {
249     if (name_in[pos])
250       value = name_in + pos + 1;
251   } else {
252     if (name_in[pos])
253       opt_failure("Option %s must have no value.", THIS_OPT);
254   }
255   opt_parse_value(oc, opt, value);
256   return eaten;
257 }
258
259 static int opt_shortopt(struct opt_context * oc, char ** argv, int index) {
260   int chr = 0;
261   struct opt_precomputed * opt;
262   int o;
263
264   while (o = argv[index][++chr]) {
265     if (o < 0 || o >= 128)
266       opt_failure("Invalid character 0x%02x in option name. Only ASCII is allowed.", o & 0xff);
267     opt = oc->shortopt[o];
268
269     if (!opt)
270       opt_failure("Unknown option -%c.", o);
271
272     opt->flags &= ~OPT_SEEN_AS_LONG;
273
274     if (opt->flags & OPT_NO_VALUE)
275       opt_parse_value(oc, opt, NULL);
276     else if (opt->flags & OPT_REQUIRED_VALUE) {
277       if (argv[index][chr+1]) {
278         opt_parse_value(oc, opt, argv[index] + chr + 1);
279         return 0;
280       } else if (!argv[index+1])
281         opt_failure("Option -%c must have a value, but nothing supplied.", o);
282       else {
283         opt_parse_value(oc, opt, argv[index+1]);
284         return 1;
285       }
286     } else if (opt->flags & OPT_MAYBE_VALUE) {
287       if (argv[index][chr+1]) {
288         opt_parse_value(oc, opt, argv[index] + chr + 1);
289         return 0;
290       } else
291         opt_parse_value(oc, opt, NULL);
292     } else {
293       ASSERT(0);
294     }
295   }
296
297   return 0;
298 }
299
300 static void opt_positional(struct opt_context * oc, char * value) {
301   oc->positional_count++;
302   uns id = oc->positional_count > oc->positional_max ? OPT_POSITIONAL_TAIL : OPT_POSITIONAL(oc->positional_count);
303   struct opt_precomputed * opt = oc->shortopt[id];
304   if (!opt)
305     opt_failure("Too many positional arguments.");
306   else {
307     opt->flags &= OPT_SEEN_AS_LONG;
308     opt_parse_value(oc, opt, value);
309   }
310 }
311
312 static void opt_count_items(struct opt_context *oc, const struct opt_section *sec)
313 {
314   for (const struct opt_item *item = sec->opt; item->cls != OPT_CL_END; item++) {
315     if (item->cls == OPT_CL_SECTION)
316       opt_count_items(oc, item->u.section);
317     else if (item->cls == OPT_CL_HOOK)
318       oc->hook_count++;
319     else if (item->letter || item->name) {
320       oc->opt_count++;
321       if (item->letter > OPT_POSITIONAL_TAIL)
322         oc->positional_max++;
323     }
324   }
325 }
326
327 static void opt_prepare_items(struct opt_context *oc, const struct opt_section *sec)
328 {
329   for (struct opt_item *item = sec->opt; item->cls != OPT_CL_END; item++) {
330     if (item->cls == OPT_CL_SECTION)
331       opt_prepare_items(oc, item->u.section);
332     else if (item->cls == OPT_CL_HOOK)
333       oc->hooks[oc->hook_count++] = item;
334     else if (item->letter || item->name) {
335       struct opt_precomputed * opt = &oc->opts[oc->opt_count++];
336       opt_precompute(opt, item);
337       if (item->letter)
338         oc->shortopt[(int) item->letter] = opt;
339     }
340   }
341 }
342
343 static void opt_check_required(struct opt_context *oc)
344 {
345   for (int i = 0; i < oc->opt_count; i++) {
346     struct opt_precomputed *opt = &oc->opts[i];
347     if (!opt->count && (opt->flags & OPT_REQUIRED)) {
348       struct opt_item *item = opt->item;
349       if (item->letter > OPT_POSITIONAL_TAIL)
350         opt_failure("Required positional argument #%d not found.", item->letter - OPT_POSITIONAL_TAIL);
351       else if (item->letter == OPT_POSITIONAL_TAIL)
352         opt_failure("Required positional argument not found.");
353       else if (item->letter && item->name)
354         opt_failure("Required option -%c/--%s not found.", item->letter, item->name);
355       else if (item->letter)
356         opt_failure("Required option -%c not found.", item->letter);
357       else
358         opt_failure("Required option --%s not found.", item->name);
359     }
360   }
361 }
362
363 int opt_parse(const struct opt_section * options, char ** argv) {
364   struct opt_context * oc = alloca(sizeof(*oc));
365   memset(oc, 0, sizeof (*oc));
366   oc->options = options;
367
368   opt_count_items(oc, options);
369   oc->opts = alloca(sizeof(*oc->opts) * oc->opt_count);
370   oc->shortopt = alloca(sizeof(*oc->shortopt) * (oc->positional_max + 257));
371   memset(oc->shortopt, 0, sizeof(*oc->shortopt) * (oc->positional_max + 257));
372   oc->hooks = alloca(sizeof (*oc->hooks) * oc->hook_count);
373
374   oc->opt_count = 0;
375   oc->hook_count = 0;
376   opt_prepare_items(oc, options);
377
378   int force_positional = 0;
379   int i;
380   for (i=0; argv[i] && !oc->stop_parsing; i++) {
381     char *arg = argv[i];
382     opt_invoke_hooks(oc, OPT_HOOK_BEFORE_ARG, NULL, NULL);
383     if (arg[0] != '-' || force_positional)
384       opt_positional(oc, arg);
385     else {
386       if (arg[1] == '-') {
387         if (arg[2] == '\0')
388           force_positional++;
389         else
390           i += opt_longopt(oc, argv, i);
391       } else if (arg[1])
392         i += opt_shortopt(oc, argv, i);
393       else
394         opt_positional(oc, arg);
395     }
396   }
397
398   opt_check_required(oc);
399   opt_invoke_hooks(oc, OPT_HOOK_FINAL, NULL, NULL);
400   return i;
401 }