]> mj.ucw.cz Git - libucw.git/blob - ucw/opt.c
5e8429ec3766d79378a431fc32219f0c02674a82
[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/conf-internal.h>
14 #include <ucw/fastbuf.h>
15 #include <ucw/stkstring.h>
16 #include <ucw/strtonum.h>
17
18 #include <alloca.h>
19 #include <math.h>
20
21 int opt_parsed_count = 0;
22 int opt_conf_parsed_count = 0;
23
24 static void opt_failure(const char * mesg, ...) FORMAT_CHECK(printf,1,2) NONRET;
25 static void opt_failure(const char * mesg, ...) {
26   va_list args;
27   va_start(args, mesg);
28   vfprintf(stderr, mesg, args);
29   fprintf(stderr, "\n");
30   opt_usage();
31   exit(OPT_EXIT_BAD_ARGS);
32   va_end(args);
33 }
34
35 #define OPT_ADD_DEFAULT_ITEM_FLAGS(item, flags) \
36   do { \
37     if (item->letter >= 256) { \
38       if (flags & OPT_VALUE_FLAGS) \
39         flags &= ~OPT_VALUE_FLAGS; \
40       flags |= OPT_REQUIRED_VALUE; \
41     } \
42     if (!(flags & OPT_VALUE_FLAGS) && \
43         (item->cls == OPT_CL_CALL || item->cls == OPT_CL_USER)) { \
44       fprintf(stderr, "You MUST specify some of the value flags for the %c/%s item.\n", item->letter, item->name); \
45       ASSERT(0); \
46     } \
47     else if (!(flags & OPT_VALUE_FLAGS)) \
48       flags |= opt_default_value_flags[item->cls]; \
49   } while (0)
50 #define OPT_ITEM_FLAGS(item) ((item->flags & OPT_VALUE_FLAGS) ? item->flags : item->flags | opt_default_value_flags[item->cls])
51
52 const struct opt_section * opt_section_root;
53
54 #define FOREACHLINE(text) for (const char * begin = (text), * end = (text); (*end) && (end = strchrnul(begin, '\n')); begin = end+1)
55
56 void opt_help_internal(const struct opt_section * help) {
57   int sections_cnt = 0;
58   int lines_cnt = 0;
59
60   for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
61     if (item->flags & OPT_NO_HELP) continue;
62     if (item->cls == OPT_CL_SECTION) {
63       sections_cnt++;
64       continue;
65     }
66     if (!*(item->help)) {
67       lines_cnt++;
68       continue;
69     }
70     FOREACHLINE(item->help)
71       lines_cnt++;
72   }
73
74   struct opt_sectlist {
75     int pos;
76     struct opt_section * sect;
77   } sections[sections_cnt];
78   int s = 0;
79
80   const char *lines[lines_cnt][3];
81   memset(lines, 0, sizeof(lines));
82   int line = 0;
83
84   int linelengths[3] = { -1, -1, -1 };
85
86   for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
87     if (item->flags & OPT_NO_HELP) continue;
88
89     if (item->cls == OPT_CL_HELP) {
90       if (!*(item->help)) {
91         line++;
92         continue;
93       }
94 #define SPLITLINES(text) do { \
95       FOREACHLINE(text) { \
96         int cell = 0; \
97         for (const char * b = begin, * e = begin; (e < end) && (e = strchrnul(b, '\t')) && (e > end ? (e = end) : end); b = e+1) { \
98           lines[line][cell] = b; \
99           if (cell >= 2) \
100             break; \
101           else \
102             if (*e == '\t' && linelengths[cell] < (e - b)) \
103               linelengths[cell] = e-b; \
104           cell++; \
105         } \
106         line++; \
107       } } while (0)
108       SPLITLINES(item->help);
109       continue;
110     }
111
112     if (item->cls == OPT_CL_SECTION) {
113       sections[s++] = (struct opt_sectlist) { .pos = line, .sect = item->u.section };
114       continue;
115     }
116
117     uns valoff = strchrnul(item->help, '\t') - item->help;
118     uns eol = strchrnul(item->help, '\n') - item->help;
119     if (valoff > eol)
120       valoff = eol;
121 #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)))
122     if (item->name) {
123       lines[line][1] = stk_printf("--%s%s", item->name, VAL(item));
124       if (linelengths[1] < (int) strlen(lines[line][1]))
125         linelengths[1] = strlen(lines[line][1]);
126       lines[line][0] = "";
127       if (linelengths[0] < 0)
128         linelengths[0] = 0;
129     }
130     if (item->letter) {
131       lines[line][0] = stk_printf("-%c,", item->letter);
132       if (linelengths[0] < (int) strlen(lines[line][0]))
133         linelengths[0] = strlen(lines[line][0]);
134     }
135 #undef VAL
136
137     if (eol > valoff) {
138       lines[line][2] = item->help + valoff + 1;
139     }
140
141     line++;
142
143     if (*(item->help + eol))
144       SPLITLINES(item->help + eol + 1);
145   }
146 #undef SPLITLINES
147
148   s = 0;
149 #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]
150 #define LASTFIELD(k) MIN(strchrnul(lines[i][k], '\t')-lines[i][k],strchrnul(lines[i][k], '\n')-lines[i][k]), lines[i][k]
151   for (int i=0;i<line;i++) {
152     while (s < sections_cnt && sections[s].pos == i) {
153       opt_help_internal(sections[s].sect);
154       s++;
155     }
156     if (lines[i][0] == NULL)
157       printf("\n");
158     else if (linelengths[0] == -1 || lines[i][1] == NULL)
159       printf("%.*s\n", LASTFIELD(0));
160     else if (linelengths[1] == -1 || lines[i][2] == NULL)
161       printf("%-*.*s  %.*s\n", FIELD(0), LASTFIELD(1));
162     else
163       printf("%-*.*s  %-*.*s  %.*s\n", FIELD(0), FIELD(1), LASTFIELD(2));
164   }
165   while (s < sections_cnt && sections[s].pos == line) {
166     opt_help_internal(sections[s].sect);
167     s++;
168   }
169 }
170
171 static int opt_positional_max = 0;
172 static int opt_positional_count = 0;
173
174 struct opt_precomputed {
175   struct opt_precomputed_option {
176     struct opt_precomputed *pre;
177     struct opt_item * item;
178     const char * name;
179     short flags;
180     short count;
181   } ** opts;
182   struct opt_precomputed_option ** shortopt;
183   struct opt_item ** hooks_before_arg;
184   struct opt_item ** hooks_before_value;
185   struct opt_item ** hooks_after_value;
186   short opt_count;
187   short hooks_before_arg_count;
188   short hooks_before_value_count;
189   short hooks_after_value_count;
190 };
191
192 static struct opt_precomputed_option * opt_find_item_shortopt(int chr, struct opt_precomputed * pre) {
193   struct opt_precomputed_option * candidate = pre->shortopt[chr];
194   if (!candidate)
195     opt_failure("Invalid option -%c", chr);
196   if (candidate->count++ && (candidate->flags & OPT_SINGLE))
197     opt_failure("Option -%c appeared the second time.", candidate->item->letter);
198   return candidate;
199 }
200
201 static struct opt_precomputed_option * opt_find_item_longopt(char * str, struct opt_precomputed * pre) {
202   uns len = strlen(str);
203   struct opt_precomputed_option * candidate = NULL;
204
205   for (int i=0; i<pre->opt_count; i++) {
206     if (!pre->opts[i]->name)
207       continue;
208     if (!strncmp(pre->opts[i]->name, str, len)) {
209       if (strlen(pre->opts[i]->name) == len) {
210         if (pre->opts[i]->count++ && (pre->opts[i]->flags & OPT_SINGLE))
211           opt_failure("Option %s appeared the second time.", pre->opts[i]->name);
212
213         return pre->opts[i];
214       }
215       if (candidate)
216         opt_failure("Ambiguous prefix %s: Found matching %s and %s.", str, candidate->name, pre->opts[i]->name);
217       else
218         candidate = pre->opts[i];
219     }
220     if (!strncmp("no-", str, 3) && !strncmp(pre->opts[i]->name, str+3, len-3)) {
221       if (strlen(pre->opts[i]->name) == len-3) {
222         if (pre->opts[i]->count++ && (pre->opts[i]->flags & OPT_SINGLE))
223           opt_failure("Option %s appeared the second time.", pre->opts[i]->name);
224
225         return pre->opts[i];
226       }
227       if (candidate)
228         opt_failure("Ambiguous prefix %s: Found matching %s and %s.", str, candidate->name, pre->opts[i]->name);
229       else
230         candidate = pre->opts[i];
231     }
232   }
233
234   if (candidate)
235     return candidate;
236
237   opt_failure("Invalid option %s.", str);
238 }
239
240 #define OPT_PTR(type) ({ \
241   type * ptr; \
242   if (item->flags & OPT_MULTIPLE) { \
243     struct { \
244       cnode n; \
245       type v; \
246     } * n = xmalloc(sizeof(*n)); \
247     clist_add_tail(item->ptr, &(n->n)); \
248     ptr = &(n->v); \
249   } else \
250     ptr = item->ptr; \
251   ptr; })
252
253 #define OPT_NAME (longopt == 2 ? stk_printf("positional arg #%d", opt_positional_count) : (longopt == 1 ? stk_printf("--%s", opt->name) : stk_printf("-%c", item->letter)))
254 static void opt_parse_value(struct opt_precomputed_option * opt, char * value, int longopt) {
255   struct opt_item * item = opt->item;
256   struct opt_precomputed * pre = opt->pre;
257   for (int i=0;i<pre->hooks_before_value_count;i++)
258     pre->hooks_before_value[i]->u.call(item, value, pre->hooks_before_value[i]->ptr);
259
260   switch (item->cls) {
261     case OPT_CL_BOOL:
262       if (!value || !strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "1"))
263         *((int *) item->ptr) = 1 ^ (!!(opt->flags & OPT_NEGATIVE));
264       else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "0"))
265         *((int *) item->ptr) = 0 ^ (!!(opt->flags & OPT_NEGATIVE));
266       else
267         opt_failure("Boolean argument for %s has a strange value. Supported (case insensitive): y/n, yes/no, true/false.", OPT_NAME);
268       break;
269     case OPT_CL_STATIC:
270       {
271         char * e = NULL;
272         switch (item->type) {
273           case CT_INT:
274             if (!value)
275               *OPT_PTR(int) = 0;
276             else
277               e = cf_parse_int(value, OPT_PTR(int));
278             if (e)
279               opt_failure("Integer value parsing failed for %s: %s", OPT_NAME, e);
280             break;
281           case CT_U64:
282             if (!value)
283               *OPT_PTR(u64) = 0;
284             else
285               e = cf_parse_u64(value, OPT_PTR(u64));
286             if (e)
287               opt_failure("Unsigned 64-bit value parsing failed for %s: %s", OPT_NAME, e);
288             break;
289           case CT_DOUBLE:
290             if (!value)
291               *OPT_PTR(double) = NAN;
292             else
293               e = cf_parse_double(value, OPT_PTR(double));
294             if (e)
295               opt_failure("Double value parsing failed for %s: %s", OPT_NAME, e);
296             break;
297           case CT_IP:
298             if (!value)
299               e = cf_parse_ip("0.0.0.0", OPT_PTR(u32));
300             else
301               e = cf_parse_ip(value, OPT_PTR(u32));
302             if (e)
303               opt_failure("IP parsing failed for %s: %s", OPT_NAME, e);
304             break;
305           case CT_STRING:
306             if (!value)
307               *OPT_PTR(const char *) = NULL;
308             else
309               *OPT_PTR(const char *) = xstrdup(value);
310             break;
311           default:
312             ASSERT(0);
313         }
314         break;
315       }
316     case OPT_CL_SWITCH:
317       if (*((int *)item->ptr) != -1)
318         opt_failure("Multiple switches: %s", OPT_NAME);
319       else
320         *((int *)item->ptr) = item->u.value;
321       break;
322     case OPT_CL_INC:
323       if (opt->flags & OPT_NEGATIVE)
324         (*((int *)item->ptr))--;
325       else
326         (*((int *)item->ptr))++;
327       break;
328     case OPT_CL_CALL:
329       item->u.call(item, value, item->ptr);
330       break;
331     case OPT_CL_USER:
332       {
333         char * e = NULL;
334         e = item->u.utype->parser(value, OPT_PTR(void*));
335         if (e)
336           opt_failure("User defined type value parsing failed for %s: %s", OPT_NAME, e);
337         break;
338       }
339     default:
340       ASSERT(0);
341   }
342   opt_parsed_count++;
343
344   for (int i=0;i<pre->hooks_after_value_count;i++)
345     pre->hooks_after_value[i]->u.call(item, value, pre->hooks_after_value[i]->ptr);
346 }
347 #undef OPT_NAME
348
349 static int opt_longopt(char ** argv, int index, struct opt_precomputed * pre) {
350   int eaten = 0;
351   char * name_in = argv[index] + 2; // skipping the -- on the beginning
352   uns pos = strchrnul(name_in, '=') - name_in;
353   struct opt_precomputed_option * opt = opt_find_item_longopt(strndupa(name_in, pos), pre);
354   char * value = NULL;
355
356   if (opt->item->cls == OPT_CL_BOOL && !strncmp(name_in, "no-", 3) && !strncmp(name_in+3, opt->item->name, pos-3))
357     value = "n";
358   else if (opt->flags & OPT_REQUIRED_VALUE) {
359     if (pos < strlen(name_in))
360       value = name_in + pos + 1;
361     else {
362       value = argv[index+1];
363       if (!value)
364         opt_failure("Argument --%s must have a value but nothing supplied.", opt->name);
365       eaten++;
366     }
367   }
368   else if (opt->flags & OPT_MAYBE_VALUE) {
369     if (pos < strlen(name_in))
370       value = name_in + pos + 1;
371   }
372   else {
373     if (pos < strlen(name_in))
374       opt_failure("Argument --%s must not have any value.", opt->name);
375   }
376   opt_parse_value(opt, value, 1);
377   return eaten;
378 }
379
380 static int opt_shortopt(char ** argv, int index, struct opt_precomputed * pre) {
381   int chr = 0;
382   struct opt_precomputed_option * opt;
383   while (argv[index][++chr] && (opt = opt_find_item_shortopt(argv[index][chr], pre))) {
384     if (opt->flags & OPT_NO_VALUE) {
385       opt_parse_value(opt, NULL, 0);
386     }
387     else if (opt->flags & OPT_REQUIRED_VALUE) {
388       if (chr == 1 && argv[index][2]) {
389         opt_parse_value(opt, argv[index] + 2, 0);
390         return 0;
391       }
392       else if (argv[index][chr+1])
393         opt_failure("Option -%c must have a value but found inside a bunch of short opts.", opt->item->letter);
394       else if (!argv[index+1])
395         opt_failure("Option -%c must have a value but nothing supplied.", opt->item->letter);
396       else {
397         opt_parse_value(opt, argv[index+1], 0);
398         return 1;
399       }
400     }
401     else if (opt->flags & OPT_MAYBE_VALUE) {
402       if (chr == 1 && argv[index][2]) {
403         opt_parse_value(opt, argv[index] + 2, 0);
404         return 0;
405       }
406       else
407         opt_parse_value(opt, NULL, 0);
408     }
409     else {
410       ASSERT(0);
411     }
412   }
413
414   if (argv[index][chr])
415     opt_failure("Unknown option -%c.", argv[index][chr]);
416   
417   return 0;
418 }
419
420 static void opt_positional(char * value, struct opt_precomputed * pre) {
421   opt_positional_count++;
422   struct opt_precomputed_option * opt = opt_find_item_shortopt((opt_positional_count > opt_positional_max ? 256 : opt_positional_count + 256), pre);
423   if (!opt) {
424     ASSERT(opt_positional_count > opt_positional_max);
425     opt_failure("Too many positional args.");
426   }
427
428   opt_parse_value(opt, value, 2);
429 }
430
431 #define OPT_TRAVERSE_SECTIONS \
432     while (item->cls == OPT_CL_SECTION) { \
433       if (stk->next) \
434         stk = stk->next; \
435       else { \
436         struct opt_stack * new_stk = alloca(sizeof(*new_stk)); \
437         new_stk->prev = stk; \
438         stk->next = new_stk; \
439         stk = new_stk; \
440       } \
441       stk->this = item; \
442       item = item->u.section->opt; \
443     } \
444     if (item->cls == OPT_CL_END) { \
445       if (!stk->prev) break; \
446       item = stk->this; \
447       stk = stk->prev; \
448       continue; \
449     }
450
451 void opt_parse(const struct opt_section * options, char ** argv) {
452   opt_section_root = options;
453
454   struct opt_stack {
455     struct opt_item * this;
456     struct opt_stack * prev;
457     struct opt_stack * next;
458   } * stk = alloca(sizeof(*stk));
459   stk->this = NULL;
460   stk->prev = NULL;
461   stk->next = NULL;
462
463   struct opt_precomputed * pre = alloca(sizeof(*pre));
464   memset(pre, 0, sizeof (*pre));
465
466   int count = 0;
467   int hooks = 0;
468
469   for (struct opt_item * item = options->opt; ; item++) {
470     OPT_TRAVERSE_SECTIONS;
471     if (item->letter || item->name)
472       count++;
473     if (item->cls == OPT_CL_BOOL)
474       count++;
475     if (item->letter > 256)
476       opt_positional_max++;
477     if (item->cls == OPT_CL_HOOK)
478       hooks++;
479   }
480   
481   pre->opts = alloca(sizeof(*pre->opts) * count);
482   pre->shortopt = alloca(sizeof(*pre->shortopt) * (opt_positional_max + 257));
483   memset(pre->shortopt, 0, sizeof(*pre->shortopt) * (opt_positional_max + 257));
484   pre->hooks_before_arg = alloca(sizeof (*pre->hooks_before_arg) * hooks);
485   pre->hooks_before_value = alloca(sizeof (*pre->hooks_before_value) * hooks);
486   pre->hooks_after_value = alloca(sizeof (*pre->hooks_after_value) * hooks);
487   
488   pre->hooks_before_arg_count = 0;
489   pre->hooks_before_value_count = 0;
490   pre->hooks_after_value_count = 0;
491   
492   pre->opt_count = 0;
493
494   for (struct opt_item * item = options->opt; ; item++) {
495     OPT_TRAVERSE_SECTIONS;
496     if (item->letter || item->name) {
497       struct opt_precomputed_option * opt = xmalloc(sizeof(*opt));
498       opt->pre = pre;
499       opt->item = item;
500       opt->flags = item->flags;
501       opt->count = 0;
502       opt->name = item->name;
503       pre->opts[pre->opt_count++] = opt;
504       if (item->letter)
505         pre->shortopt[(int) item->letter] = opt;
506       OPT_ADD_DEFAULT_ITEM_FLAGS(item, opt->flags);
507     }
508     if (item->cls == OPT_CL_HOOK) {
509       if (item->flags & OPT_HOOK_BEFORE_ARG)
510         pre->hooks_before_arg[pre->hooks_before_arg_count++] = item;
511       else if (item->flags & OPT_HOOK_BEFORE_VALUE)
512         pre->hooks_before_value[pre->hooks_before_value_count++] = item;
513       else if (item->flags & OPT_HOOK_AFTER_VALUE)
514         pre->hooks_after_value[pre->hooks_after_value_count++] = item;
515       else
516         ASSERT(0);
517     }
518   }
519
520   int force_positional = 0;
521   for (int i=0;argv[i];i++) {
522     for (int j=0;j<pre->hooks_before_arg_count;j++)
523       pre->hooks_before_arg[j]->u.call(NULL, NULL, pre->hooks_before_arg[j]->ptr);
524     if (argv[i][0] != '-' || force_positional) {
525       opt_positional(argv[i], pre);
526     }
527     else {
528       if (argv[i][1] == '-') {
529         if (argv[i][2] == '\0')
530           force_positional++;
531         else
532           i += opt_longopt(argv, i, pre);
533       }
534       else if (argv[i][1])
535         i += opt_shortopt(argv, i, pre);
536       else
537         opt_positional(argv[i], pre);
538     }
539   }
540
541   for (int i=0;i<opt_positional_max+257;i++) {
542     if (!pre->shortopt[i])
543       continue;
544     if (!pre->shortopt[i]->count && (pre->shortopt[i]->flags & OPT_REQUIRED))
545       if (i < 256)
546         opt_failure("Required option -%c not found.\n", pre->shortopt[i]->item->letter);
547       else
548         opt_failure("Required positional argument #%d not found.\n", (i > 256) ? pre->shortopt[i]->item->letter-256 : opt_positional_max+1);
549   }
550
551   for (int i=0;i<pre->opt_count;i++) {
552     if (!pre->opts[i])
553       continue;
554     if (!pre->opts[i]->count && (pre->opts[i]->flags & OPT_REQUIRED))
555       opt_failure("Required option --%s not found.\n", pre->opts[i]->item->name);
556   }
557 }
558
559 static void opt_conf_end_of_options(struct cf_context *cc) {
560   cf_load_default(cc);
561   if (cc->postpone_commit && cf_close_group())
562     opt_failure("Loading of configuration failed");
563 }
564
565 void opt_conf_internal(struct opt_item * opt, const char * value, void * data UNUSED) {
566   struct cf_context *cc = cf_get_context();
567   switch (opt->letter) {
568     case 'S':
569       cf_load_default(cc);
570       if (cf_set(value))
571         opt_failure("Cannot set %s", value);
572       break;
573     case 'C':
574       if (cf_load(value))
575         opt_failure("Cannot load config file %s", value);
576       break;
577 #ifdef CONFIG_UCW_DEBUG
578     case '0':
579       opt_conf_end_of_options(cc);
580       struct fastbuf *b = bfdopen(1, 4096);
581       cf_dump_sections(b);
582       bclose(b);
583       exit(0);
584       break;
585 #endif
586   }
587
588   opt_conf_parsed_count++;
589 }
590
591 void opt_conf_hook_internal(struct opt_item * opt, const char * value UNUSED, void * data UNUSED) {
592   static enum {
593     OPT_CONF_HOOK_BEGIN,
594     OPT_CONF_HOOK_CONFIG,
595     OPT_CONF_HOOK_OTHERS
596   } state = OPT_CONF_HOOK_BEGIN;
597
598   int confopt = 0;
599
600   if (opt->letter == 'S' || opt->letter == 'C' || (opt->name && !strcmp(opt->name, "dumpconfig")))
601     confopt = 1;
602
603   switch (state) {
604     case OPT_CONF_HOOK_BEGIN:
605       if (confopt)
606         state = OPT_CONF_HOOK_CONFIG;
607       else {
608         opt_conf_end_of_options(cf_get_context());
609         state = OPT_CONF_HOOK_OTHERS;
610       }
611       break;
612     case OPT_CONF_HOOK_CONFIG:
613       if (!confopt) {
614         opt_conf_end_of_options(cf_get_context());
615         state = OPT_CONF_HOOK_OTHERS;
616       }
617       break;
618     case OPT_CONF_HOOK_OTHERS:
619       if (confopt)
620         opt_failure("Config options (-C, -S) must stand before other options.");
621       break;
622     default:
623       ASSERT(0);
624   }
625 }
626
627 #ifdef TEST
628 #include <ucw/fastbuf.h>
629
630 static void show_version(struct opt_item * opt UNUSED, const char * value UNUSED, void * data UNUSED) {
631   printf("This is a simple tea boiling console v0.1.\n");
632   exit(EXIT_SUCCESS);
633 }
634
635 struct teapot_temperature {
636   enum {
637     TEMP_CELSIUS = 0,
638     TEMP_FAHRENHEIT,
639     TEMP_KELVIN,
640     TEMP_REAUMUR,
641     TEMP_RANKINE
642   } scale;
643   int value;
644 } temperature;
645
646 static char * temp_scale_str[] = { "C", "F", "K", "Re", "R" };
647
648 static enum TEAPOT_TYPE {
649   TEAPOT_STANDARD = 0,
650   TEAPOT_EXCLUSIVE,
651   TEAPOT_GLASS,
652   TEAPOT_HANDS,
653   TEAPOT_UNDEFINED = -1
654 } set = TEAPOT_UNDEFINED;
655
656 static char * teapot_type_str[] = { "standard", "exclusive", "glass", "hands" };
657
658 static int show_hooks = 0;
659 static int english = 0;
660 static int sugar = 0;
661 static int verbose = 1;
662 static int with_gas = 0;
663 static clist black_magic;
664 static int pray = 0;
665 static int water_amount = 0;
666 static char * first_tea = NULL;
667
668 #define MAX_TEA_COUNT 30
669 static char * tea_list[MAX_TEA_COUNT];
670 static int tea_num = 0;
671 static void add_tea(struct opt_item * opt UNUSED, const char * name, void * data) {
672   char ** tea_list = data;
673   if (tea_num >= MAX_TEA_COUNT) {
674     fprintf(stderr, "Cannot boil more than %d teas.\n", MAX_TEA_COUNT);
675     exit(OPT_EXIT_BAD_ARGS);
676   }
677   tea_list[tea_num++] = xstrdup(name);
678 }
679
680 static const char * teapot_temperature_parser(char * in, void * ptr) {
681   struct teapot_temperature * temp = ptr;
682   const char * next;
683   const char * err = str_to_int(&temp->value, in, &next, 10);
684   if (err)
685     return err;
686   if (!strcmp("C", next))
687     temp->scale = TEMP_CELSIUS;
688   else if (!strcmp("F", next))
689     temp->scale = TEMP_FAHRENHEIT;
690   else if (!strcmp("K", next))
691     temp->scale = TEMP_KELVIN;
692   else if (!strcmp("R", next))
693     temp->scale = TEMP_RANKINE;
694   else if (!strcmp("Re", next))
695     temp->scale = TEMP_REAUMUR;
696   else {
697     fprintf(stderr, "Unknown scale: %s\n", next);
698     exit(OPT_EXIT_BAD_ARGS);
699   }
700   return NULL;
701 }
702
703 static void teapot_temperature_dumper(struct fastbuf * fb, void * ptr) {
704   struct teapot_temperature * temp = ptr;
705   bprintf(fb, "%d%s", temp->value, temp_scale_str[temp->scale]);
706 }
707
708 static struct cf_user_type teapot_temperature_t = {
709   .size = sizeof(struct teapot_temperature),
710   .name = "teapot_temperature_t",
711   .parser = (cf_parser1*) teapot_temperature_parser,
712   .dumper = (cf_dumper1*) teapot_temperature_dumper
713 };
714
715 static void opt_test_hook(struct opt_item * opt, const char * value, void * data) {
716   if (!show_hooks)
717     return;
718   if (opt)
719     printf("[HOOK-%s:%c/%s=%s] ", (char *) data, opt->letter, opt->name, value);
720   else
721     printf("[HOOK-%s] ", (char *) data);
722 }
723
724 static struct opt_section water_options = {
725   OPT_ITEMS {
726     OPT_INT('w', "water", water_amount, OPT_REQUIRED | OPT_REQUIRED_VALUE, "<volume>\tAmount of water (in mls; required)"),
727     OPT_BOOL('G', "with-gas", with_gas, OPT_NO_VALUE, "\tUse water with gas"),
728     OPT_END
729   }
730 };
731
732 static struct opt_section help = {
733   OPT_ITEMS {
734     OPT_HELP("A simple tea boiling console."),
735     OPT_HELP("Usage: teapot [options] name-of-the-tea"),
736     OPT_HELP("Black, green or white tea supported as well as fruit or herbal tea."),
737     OPT_HELP("You may specify more kinds of tea, all of them will be boiled for you, in the given order."),
738     OPT_HELP("At least one kind of tea must be specified."),
739     OPT_HELP(""),
740     OPT_HELP("Options:"),
741     OPT_HELP_OPTION,
742     OPT_CALL('V', "version", show_version, NULL, OPT_NO_VALUE, "\tShow the version"),
743     OPT_HELP(""),
744     OPT_BOOL('e', "english-style", english, 0, "\tEnglish style (with milk)"),
745     OPT_INT('s', "sugar", sugar, OPT_REQUIRED_VALUE, "<spoons>\tAmount of sugar (in teaspoons)"),
746     OPT_SWITCH(0, "standard-set", set, TEAPOT_STANDARD, 0, "\tStandard teapot"),
747     OPT_SWITCH('x', "exclusive-set", set, TEAPOT_EXCLUSIVE, 0, "\tExclusive teapot"),
748     OPT_SWITCH('g', "glass-set", set, TEAPOT_GLASS, 0, "\tTransparent glass teapot"),
749     OPT_SWITCH('h', "hands", set, TEAPOT_HANDS, 0, "\tUse user's hands as a teapot (a bit dangerous)"),
750     OPT_USER('t', "temperature", temperature, teapot_temperature_t, OPT_REQUIRED_VALUE | OPT_REQUIRED,
751                   "<value>\tWanted final temperature of the tea to be served (required)\n"
752               "\t\tSupported scales:  Celsius [60C], Fahrenheit [140F],\n"
753               "\t\t                   Kelvin [350K], Rankine [600R] and Reaumur [50Re]\n"
754               "\t\tOnly integer values allowed."),
755     OPT_INC('v', "verbose", verbose, 0, "\tVerbose (the more -v, the more verbose)"),
756     OPT_INC('q', "quiet", verbose, OPT_NEGATIVE, "\tQuiet (the more -q, the more quiet)"),
757     OPT_INT('b', "black-magic", black_magic, OPT_MULTIPLE, "<strength>\tUse black magic to make the tea extraordinary delicious.\n\t\tMay be specified more than once to describe the amounts of black magic to be invoked in each step of tea boiling."),
758     OPT_BOOL('p', "pray", pray, OPT_SINGLE, "\tPray before boiling"),
759     OPT_STRING(OPT_POSITIONAL(1), NULL, first_tea, OPT_REQUIRED | OPT_NO_HELP, ""),
760     OPT_CALL(OPT_POSITIONAL_TAIL, NULL, add_tea, &tea_list, OPT_NO_HELP, ""),
761     OPT_HELP(""),
762     OPT_HELP("Water options:"),
763     OPT_SECTION(water_options),
764     OPT_HOOK(opt_test_hook, "prearg", OPT_HOOK_BEFORE_ARG),
765     OPT_HOOK(opt_test_hook, "preval", OPT_HOOK_BEFORE_VALUE),
766     OPT_HOOK(opt_test_hook, "postval", OPT_HOOK_AFTER_VALUE),
767     OPT_BOOL('H', "show-hooks", show_hooks, 0, "Demonstrate the hooks."),
768     OPT_CONF_OPTIONS,
769     OPT_END
770   }
771 };
772
773 struct intnode {
774   cnode n;
775   int x;
776 };
777
778 int main(int argc UNUSED, char ** argv)
779 {
780   clist_init(&black_magic);
781   opt_parse(&help, argv+1);
782
783   printf("English style: %s|", english ? "yes" : "no");
784   if (sugar)
785     printf("Sugar: %d teaspoons|", sugar);
786   if (set != -1)
787     printf("Chosen teapot: %s|", teapot_type_str[set]);
788   printf("Temperature: %d%s|", temperature.value, temp_scale_str[temperature.scale]);
789   printf("Verbosity: %d|", verbose);
790   CLIST_FOR_EACH(struct intnode *, n, black_magic)
791     printf("Black magic: %d|", n->x);
792   printf("Prayer: %s|", pray ? "yes" : "no");
793   printf("Water amount: %d|", water_amount);
794   printf("Gas: %s|", with_gas ? "yes" : "no");
795   printf("First tea: %s|", first_tea);
796   for (int i=0; i<tea_num; i++)
797     printf("Boiling a tea: %s|", tea_list[i]);
798
799   printf("Everything OK. Bye.\n");
800 }
801
802 #endif