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