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