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