]> mj.ucw.cz Git - libucw.git/blob - ucw/opt.c
3bd51d32e225e7b348cfbc2110aef126bbbee9be
[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  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU Lesser General Public License.
8  */
9
10 #include <ucw/lib.h>
11 #include <ucw/opt.h>
12 #include <ucw/conf.h>
13 #include <ucw/stkstring.h>
14 #include <ucw/strtonum.h>
15
16 #include <alloca.h>
17
18 static void opt_failure(const char * mesg, ...) FORMAT_CHECK(printf,1,2);
19 static void opt_failure(const char * mesg, ...) {
20   va_list args;
21   va_start(args, mesg);
22   vfprintf(stderr, mesg, args);
23   fprintf(stderr, "\n");
24   exit(OPT_EXIT_BAD_ARGS);
25   va_end(args);
26 }
27
28 #define OPT_ADD_DEFAULT_ITEM_FLAGS(item, flags) \
29   do { \
30     if (!(flags & OPT_VALUE_FLAGS) && \
31         (item->cls == OPT_CL_CALL || item->cls == OPT_CL_USER)) { \
32       fprintf(stderr, "You MUST specify some of the value flags for the %c/%s item.\n", item->letter, item->name); \
33       ASSERT(0); \
34     } \
35     else if (!(flags & OPT_VALUE_FLAGS)) \
36       flags |= opt_default_value_flags[item->cls]; \
37   } while (0)
38 #define OPT_ITEM_FLAGS(item) ((item->flags & OPT_VALUE_FLAGS) ? item->flags : item->flags | opt_default_value_flags[item->cls])
39
40 const struct opt_section * opt_section_root;
41
42 #define FOREACHLINE(text) for (const char * begin = (text), * end = (text); (*end) && (end = strchrnul(begin, '\n')); begin = end+1)
43
44 void opt_help_noexit_internal(const struct opt_section * help) {
45   int sections_cnt = 0;
46   int lines_cnt = 0;
47
48   for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
49     if (item->flags & OPT_NO_HELP) continue;
50     if (item->cls == OPT_CL_SECTION) {
51       sections_cnt++;
52       continue;
53     }
54     if (!*(item->help)) {
55       lines_cnt++;
56       continue;
57     }
58     FOREACHLINE(item->help)
59       lines_cnt++;
60   }
61
62   struct opt_sectlist {
63     int pos;
64     struct opt_section * sect;
65   } sections[sections_cnt];
66   int s = 0;
67
68   const char *lines[lines_cnt][3];
69   memset(lines, 0, sizeof(lines));
70   int line = 0;
71
72   int linelengths[3] = { -1, -1, -1 };
73
74   for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
75     if (item->flags & OPT_NO_HELP) continue;
76
77     if (item->cls == OPT_CL_HELP) {
78       if (!*(item->help)) {
79         line++;
80         continue;
81       }
82 #define SPLITLINES(text) do { \
83       FOREACHLINE(text) { \
84         int cell = 0; \
85         for (const char * b = begin, * e = begin; (e < end) && (e = strchrnul(b, '\t')) && (e > end ? (e = end) : end); b = e+1) { \
86           lines[line][cell] = b; \
87           if (cell >= 2) \
88             break; \
89           else \
90             if (*e == '\t' && linelengths[cell] < (e - b)) \
91               linelengths[cell] = e-b; \
92           cell++; \
93         } \
94         line++; \
95       } } while (0)
96       SPLITLINES(item->help);
97       continue;
98     }
99
100     if (item->cls == OPT_CL_SECTION) {
101       sections[s++] = (struct opt_sectlist) { .pos = line, .sect = item->u.section };
102       continue;
103     }
104
105     uns valoff = strchrnul(item->help, '\t') - item->help;
106     uns eol = strchrnul(item->help, '\n') - item->help;
107     if (valoff > eol)
108       valoff = eol;
109 #define VAL(it) ((OPT_ITEM_FLAGS(it) & OPT_REQUIRED_VALUE) ? stk_printf("=%.*s", valoff, item->help)  : ((OPT_ITEM_FLAGS(it) & OPT_NO_VALUE) ? "" : stk_printf("(=%.*s)", valoff, item->help)))
110     if (item->name) {
111       lines[line][1] = stk_printf("--%s%s", item->name, VAL(item));
112       if (linelengths[1] < (int) strlen(lines[line][1]))
113         linelengths[1] = strlen(lines[line][1]);
114       lines[line][0] = "";
115       if (linelengths[0] < 0)
116         linelengths[0] = 0;
117     }
118     if (item->letter) {
119       lines[line][0] = stk_printf("-%c,", item->letter);
120       if (linelengths[0] < (int) strlen(lines[line][0]))
121         linelengths[0] = strlen(lines[line][0]);
122     }
123 #undef VAL
124
125     if (eol > valoff) {
126       lines[line][2] = item->help + valoff + 1;
127     }
128
129     line++;
130
131     if (*(item->help + eol))
132       SPLITLINES(item->help + eol + 1);
133   }
134 #undef SPLITLINES
135
136   s = 0;
137 #define FIELD(k) linelengths[k], MIN(strchrnul(lines[i][k], '\t')-lines[i][k],strchrnul(lines[i][k], '\n')-lines[i][k]), lines[i][k]
138 #define LASTFIELD(k) MIN(strchrnul(lines[i][k], '\t')-lines[i][k],strchrnul(lines[i][k], '\n')-lines[i][k]), lines[i][k]
139   for (int i=0;i<line;i++) {
140     while (s < sections_cnt && sections[s].pos == i) {
141       opt_help_noexit_internal(sections[s].sect);
142       s++;
143     }
144     if (lines[i][0] == NULL)
145       printf("\n");
146     else if (linelengths[0] == -1 || lines[i][1] == NULL)
147       printf("%.*s\n", LASTFIELD(0));
148     else if (linelengths[1] == -1 || lines[i][2] == NULL)
149       printf("%-*.*s  %.*s\n", FIELD(0), LASTFIELD(1));
150     else
151       printf("%-*.*s  %-*.*s  %.*s\n", FIELD(0), FIELD(1), LASTFIELD(2));
152   }
153   while (s < sections_cnt && sections[s].pos == line) {
154     opt_help_noexit_internal(sections[s].sect);
155     s++;
156   }
157 }
158
159 struct opt_precomputed {
160   struct opt_precomputed_option {
161     struct opt_item * item;
162     short flags;
163     short count;
164   } ** opts;
165   struct opt_precomputed_option * shortopt[256];
166   short opt_count;
167 };
168
169 static struct opt_item * opt_find_item_shortopt(int chr, struct opt_precomputed * opts) {
170   return (opts->shortopt[chr] ? opts->shortopt[chr]->item : NULL);
171 }
172
173 static struct opt_item * opt_find_item_longopt(char * str, struct opt_precomputed * pre) {
174   uns len = strlen(str);
175   struct opt_item * candidate = NULL;
176
177   for (int i=0; i<pre->opt_count; i++) {
178     if (!strncmp(pre->opts[i]->item->name, str, len)) {
179       if (strlen(pre->opts[i]->item->name) == len) {
180         if (pre->opts[i]->count++ && (pre->opts[i]->flags & OPT_SINGLE))
181           opt_failure("Option %s appeared the second time.", pre->opts[i]->item->name);
182
183         return pre->opts[i]->item;
184       }
185       if (candidate)
186         opt_failure("Ambiguous prefix %s: Found matching %s and %s.", str, candidate->name, pre->opts[i]->item->name);
187       else
188         candidate = pre->opts[i]->item;
189     }
190   }
191
192   if (candidate)
193     return candidate;
194
195   opt_failure("Invalid option %s.", str);
196 }
197
198 #define OPT_NAME (longopt ? stk_printf("--%s", item->name) : stk_printf("-%c", item->letter))
199 static void opt_parse_value(struct opt_item * item, char * value, int longopt) {
200   switch (item->cls) {
201     case OPT_CL_BOOL:
202       if (!strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "1"))
203         *((int *) item->ptr) = 1;
204       else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "0"))
205         *((int *) item->ptr) = 0;
206       else
207         opt_failure("Boolean argument for %s has a strange value. Supported (case insensitive): y/n, yes/no, true/false.", OPT_NAME);
208       break;
209     case OPT_CL_STATIC:
210       {
211         char * e = NULL;
212         switch (item->type) {
213           case CT_INT:
214             e = cf_parse_int(value, item->ptr);
215             if (e)
216               opt_failure("Integer value parsing failed for argument %s: %s", OPT_NAME, e);
217             break;
218           case CT_U64:
219             e = cf_parse_u64(value, item->ptr);
220             if (e)
221               opt_failure("Unsigned 64-bit value parsing failed for argument %s: %s", OPT_NAME, e);
222             break;
223           case CT_DOUBLE:
224             e = cf_parse_double(value, item->ptr);
225             if (e)
226               opt_failure("Double value parsing failed for argument %s: %s", OPT_NAME, e);
227             break;
228           case CT_IP:
229             e = cf_parse_ip(value, item->ptr);
230             if (e)
231               opt_failure("IP parsing failed for argument %s: %s", OPT_NAME, e);
232             break;
233           case CT_STRING:
234             item->ptr = strdup(value);
235             break;
236           default:
237             ASSERT(0);
238         }
239         break;
240       }
241     case OPT_CL_SWITCH:
242       if (*((int *)item->ptr) != -1)
243         opt_failure("Multiple switches: %s", OPT_NAME);
244       else
245         *((int *)item->ptr) = item->u.value;
246       break;
247     case OPT_CL_INC:
248       if (item->flags & OPT_DECREMENT)
249         (*((int *)item->ptr))--;
250       else
251         (*((int *)item->ptr))++;
252     case OPT_CL_CALL:
253       item->u.call(item, value, item->ptr);
254       break;
255     case OPT_CL_USER:
256       {
257         char * e = NULL;
258         e = item->u.utype->parser(value, item->ptr);
259         if (e)
260           opt_failure("User defined type value parsing failed for argument %s: %s", OPT_NAME, e);
261         break;
262       }
263     default:
264       ASSERT(0);
265   }
266 }
267 #undef OPT_NAME
268
269 static int opt_longopt(char ** argv, int index, struct opt_precomputed * pre) {
270   int eaten = 0;
271   char * name_in = argv[index] + 2; // skipping the -- on the beginning
272   uns pos = strchrnul(name_in, '=') - name_in;
273   struct opt_item * item = opt_find_item_longopt(strndupa(name_in, pos), pre);
274   char * value = NULL;
275   if (item->flags & OPT_REQUIRED_VALUE) {
276     if (pos < strlen(name_in))
277       value = name_in + pos + 1;
278     else {
279       value = argv[index+1];
280       eaten++;
281     }
282   }
283   else if (item->flags & OPT_MAYBE_VALUE) {
284     if (pos < strlen(name_in))
285       value = name_in + pos + 1;
286   }
287   else {
288     if (pos < strlen(name_in))
289       opt_failure("Argument %s must not have any value.", item->name);
290   }
291   opt_parse_value(item, value, 1);
292   return eaten;
293 }
294
295 static int opt_shortopt(char ** argv, int index, struct opt_precomputed * pre) {
296   int chr = 0;
297   struct opt_item * item;
298   while (argv[index][++chr] && (item = opt_find_item_shortopt(argv[index][chr], pre))) {
299     if (item->flags & OPT_NO_VALUE) {
300       opt_parse_value(item, NULL, 0);
301       continue;
302     }
303     if (chr == 1 && (item->flags & OPT_REQUIRED_VALUE)) {
304       if (argv[index][2]) {
305         opt_parse_value(item, argv[index] + 2, 0);
306         return 0;
307       }
308       else {
309         opt_parse_value(item, argv[index+1], 0);
310         return 1;
311       }
312     }
313     else if (chr == 1 && (item->flags & OPT_MAYBE_VALUE)) {
314       if (argv[index][2])
315         opt_parse_value(item, argv[index] + 2, 0);
316       else
317         opt_parse_value(item, NULL, 0);
318     }
319     else if (item->flags & (OPT_REQUIRED_VALUE | OPT_MAYBE_VALUE)) {
320       if (argv[index][chr+1] || (item->flags | OPT_MAYBE_VALUE))
321         opt_failure("Option -%c may or must have a value but found inside a bunch of short opts.", item->letter);
322       else {
323         opt_parse_value(item, argv[index+1], 0);
324         return 1;
325       }
326     }
327   }
328
329   if (argv[index][chr])
330     opt_failure("Unknown option -%c.", argv[index][chr]);
331   
332   return 0;
333 }
334
335 #define OPT_TRAVERSE_SECTIONS \
336     while (item->cls == OPT_CL_SECTION) { \
337       if (stk->next) \
338         stk = stk->next; \
339       else { \
340         struct opt_stack * new_stk = alloca(sizeof(*new_stk)); \
341         new_stk->prev = stk; \
342         stk->next = new_stk; \
343         stk = new_stk; \
344       } \
345       stk->this = item; \
346       item = item->u.section->opt; \
347     } \
348     if (item->cls == OPT_CL_END) { \
349       if (!stk->prev) break; \
350       item = stk->this; \
351       stk = stk->prev; \
352       continue; \
353     }
354
355 void opt_parse(const struct opt_section * options, char ** argv, opt_positional * callback) {
356   opt_section_root = options;
357
358   struct opt_stack {
359     struct opt_item * this;
360     struct opt_stack * prev;
361     struct opt_stack * next;
362   } * stk = alloca(sizeof(*stk));
363   stk->this = NULL;
364   stk->prev = NULL;
365   stk->next = NULL;
366
367   struct opt_precomputed * pre = alloca(sizeof(*pre));
368   memset(pre, 0, sizeof (*pre));
369
370   int count = 0;
371
372   for (struct opt_item * item = options->opt; ; item++) {
373     OPT_TRAVERSE_SECTIONS;
374     if (item->letter || item->name)
375       count++;
376   }
377   
378   pre->opts = xmalloc(sizeof(*pre->opts) * count);
379   pre->opt_count = 0;
380
381   for (struct opt_item * item = options->opt; ; item++) {
382     OPT_TRAVERSE_SECTIONS;
383     if (item->letter || item->name) {
384       struct opt_precomputed_option * opt = xmalloc(sizeof(*opt));
385       opt->item = item;
386       opt->flags = item->flags;
387       opt->count = 0;
388       pre->opts[pre->opt_count++] = opt;
389       if (item->letter)
390         pre->shortopt[(int) item->letter] = opt;
391       OPT_ADD_DEFAULT_ITEM_FLAGS(item, opt->flags);
392     }
393   }
394
395   int force_positional = 0;
396   for (int i=0;argv[i];i++) {
397     if (argv[i][0] != '-' || force_positional) {
398       callback(argv[i]);
399     }
400     else {
401       if (argv[i][1] == '-') {
402         if (argv[i][2] == '\0')
403           force_positional++;
404         else
405           i += opt_longopt(argv, i, pre);
406       }
407       else if (argv[i][1])
408         i += opt_shortopt(argv, i, pre);
409       else
410         callback(argv[i]);
411     }
412   }
413 }
414
415 #ifdef TEST
416 #include <ucw/fastbuf.h>
417
418 static void show_version(struct opt_item * opt UNUSED, const char * value UNUSED, void * data UNUSED) {
419   printf("This is a simple tea boiling console v0.1.\n");
420   exit(EXIT_SUCCESS);
421 }
422
423 struct teapot_temperature {
424   enum {
425     TEMP_CELSIUS = 0,
426     TEMP_FAHRENHEIT,
427     TEMP_KELVIN,
428     TEMP_REAUMUR,
429     TEMP_RANKINE
430   } scale;
431   int value;
432 } temperature;
433
434 static char * temp_scale_str[] = { "C", "F", "K", "Re", "R" };
435
436 static enum TEAPOT_TYPE {
437   TEAPOT_STANDARD = 0,
438   TEAPOT_EXCLUSIVE,
439   TEAPOT_GLASS,
440   TEAPOT_HANDS,
441   TEAPOT_UNDEFINED = -1
442 } set = TEAPOT_UNDEFINED;
443
444 static int english = 0;
445 static char * name = NULL;
446 static int sugar = 0;
447 static int verbose = 1;
448 static int with_gas = 0;
449 static int black_magic = 0;
450 static int pray = 0;
451 static int water_amount = 0;
452
453 static const char * teapot_temperature_parser(char * in, void * ptr) {
454   struct teapot_temperature * temp = ptr;
455   const char * next;
456   const char * err = str_to_int(&temp->value, in, &next, 0);
457   if (err)
458     return err;
459   if (!strcmp("C", next))
460     temp->scale = TEMP_CELSIUS;
461   else if (!strcmp("F", next))
462     temp->scale = TEMP_FAHRENHEIT;
463   else if (!strcmp("K", next))
464     temp->scale = TEMP_KELVIN;
465   else if (!strcmp("R", next))
466     temp->scale = TEMP_RANKINE;
467   else if (!strcmp("Re", next))
468     temp->scale = TEMP_REAUMUR;
469   else {
470     fprintf(stderr, "Unknown scale: %s\n", next);
471     exit(OPT_EXIT_BAD_ARGS);
472   }
473   return next + strlen(next);
474 }
475
476 static void teapot_temperature_dumper(struct fastbuf * fb, void * ptr) {
477   struct teapot_temperature * temp = ptr;
478   bprintf(fb, "%d%s", temp->value, temp_scale_str[temp->scale]);
479 }
480
481 static struct cf_user_type teapot_temperature_t = {
482   .size = sizeof(struct teapot_temperature),
483   .name = "teapot_temperature_t",
484   .parser = (cf_parser1*) teapot_temperature_parser,
485   .dumper = (cf_dumper1*) teapot_temperature_dumper
486 };
487
488 static struct opt_section water_options = {
489   OPT_ITEMS {
490     OPT_INT('w', "water", water_amount, OPT_REQUIRED | OPT_REQUIRED_VALUE, "<volume>\tAmount of water (in mls)"),
491     OPT_BOOL('G', "with-gas", with_gas, OPT_NO_VALUE, "\tUse water with gas"),
492     OPT_END
493   }
494 };
495
496 static struct opt_section help = {
497   OPT_ITEMS {
498     OPT_HELP("A simple tea boiling console."),
499     OPT_HELP("Usage: teapot [options] name-of-the-tea"),
500     OPT_HELP("Black, green or white tea supported as well as fruit or herbal tea."),
501     OPT_HELP("You may specify more kinds of tea, all of them will be boiled for you, in the given order."),
502     OPT_HELP(""),
503     OPT_HELP("Options:"),
504     OPT_HELP_OPTION,
505     OPT_CALL('V', "version", show_version, NULL, OPT_NO_VALUE, "\tShow the version"),
506     OPT_HELP(""),
507     OPT_BOOL('e', "english-style", english, 0, "\tEnglish style (with milk)"),
508     OPT_INT('s', "sugar", sugar, OPT_REQUIRED_VALUE, "<spoons>\tAmount of sugar (in teaspoons)"),
509     OPT_SWITCH(0, "standard-set", set, TEAPOT_STANDARD, 0, "\tStandard teapot"),
510     OPT_SWITCH('x', "exclusive-set", set, TEAPOT_EXCLUSIVE, 0, "\tExclusive teapot"),
511     OPT_SWITCH('g', "glass-set", set, TEAPOT_GLASS, 0, "\tTransparent glass teapot"),
512     OPT_SWITCH('h', "hands", set, TEAPOT_HANDS, 0, "\tUse user's hands as a teapot (a bit dangerous)"),
513     OPT_USER('t', "temperature", temperature, teapot_temperature_t, OPT_REQUIRED_VALUE,
514                   "<value>\tWanted final temperature of the tea to be served\n"
515               "\t\tSupported scales:  Celsius [60C], Fahrenheit [140F],\n"
516               "\t\t                   Kelvin [350K], Rankine [600R] and Reaumur [50Re]\n"
517               "\t\tOnly integer values allowed."),
518     OPT_INC('v', "verbose", verbose, 0, "\tVerbose (the more -v, the more verbose)"),
519     OPT_INC('q', "quiet", verbose, OPT_DECREMENT, "\tQuiet (the more -q, the more quiet)"),
520     OPT_INT('b', "black-magic", black_magic, 0, "<strength>\tUse black magic to make the tea extraordinary delicious"),
521     OPT_BOOL('p', "pray", pray, 0, "\tPray before boiling"),
522     OPT_HELP(""),
523     OPT_HELP("Water options:"),
524     OPT_SECTION(water_options),
525     OPT_END
526   }
527 };
528
529 #define MAX_TEA_COUNT 30
530 static char * tea_list[MAX_TEA_COUNT];
531 static int tea_num = 0;
532 static void add_tea(const char * name) {
533   if (tea_num >= MAX_TEA_COUNT) {
534     fprintf(stderr, "Cannot boil more than %d teas.\n", MAX_TEA_COUNT);
535     exit(OPT_EXIT_BAD_ARGS);
536   }
537   tea_list[tea_num++] = strdup(name);
538 }
539
540 static void boil_tea(const char * name) {
541   printf("Boiling a tea: %s\n", name);
542 }
543
544 int main(int argc, char ** argv)
545 {
546   opt_parse(&help, argv, add_tea);
547
548   for (int i=0; i<tea_num; i++)
549     boil_tea(tea_list[i]);
550
551   printf("Everything OK. Bye.\n");
552 }
553
554 #endif