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