]> mj.ucw.cz Git - libucw.git/blob - ucw/opt.c
92c7a7372c851db809beccfd7f050cd3e05f0c8e
[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   stk_vprintf(mesg, args);
23   exit(OPT_EXIT_BAD_ARGS);
24   va_end(args);
25 }
26
27 struct opt_section * opt_section_root;
28
29 void opt_help_noexit_internal(struct opt_section * help) {
30   uns first_column = 0;
31
32   for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
33     if (item->flags & OPT_NO_HELP) continue;
34     if (item->cls == OPT_CL_HELP && item->u.help2 == NULL) continue;
35     if (item->cls == OPT_CL_SECTION) continue;
36     
37     uns linelen = 0;
38     if (item->cls == OPT_CL_HELP) { // two-column help line
39       if (first_column < strlen(item->help))
40         first_column = strlen(item->help);
41       continue;
42     }
43     
44     if (item->letter) { // will write sth like "-x, --exclusive"
45       linelen = strlen("-x, --") + strlen(item->name);
46     } else { // will write sth like "--exclusive"
47       linelen = strlen("--") + strlen(item->name);
48     }
49
50     ASSERT(item->flags & OPT_VALUE_FLAGS);
51
52     if (item->flags & OPT_REQUIRED_VALUE) {
53       linelen += strlen("=value");
54     } else if (item->flags & OPT_MAYBE_VALUE) {
55       linelen += strlen("(=value)");
56     }
57
58     if (linelen > first_column)
59       first_column = linelen;
60   }
61
62   char * spaces = alloca(first_column + 1);
63   char * buf = alloca(first_column + 1);
64   for (uns i=0;i<first_column;i++)
65     spaces[i] = ' ';
66
67   spaces[first_column] = 0;
68
69 #define VAL(it) ((it->flags & OPT_REQUIRED_VALUE) ? "=value" : ((it->flags & OPT_NO_VALUE) ? "" : "(=value)"))
70   for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
71     if (item->flags & OPT_NO_HELP) continue;
72     
73     if (item->cls == OPT_CL_HELP) {
74       fprintf(stderr, "%s", item->help);
75       if (item->u.help2 == NULL)
76         fprintf(stderr, "\n");
77       else
78         fprintf(stderr, "%s %s\n", spaces + strlen(item->help), item->u.help2);
79     } else if (item->cls == OPT_CL_SECTION) {
80       opt_help_noexit_internal(item->u.section);
81     } else if (item->letter) {
82       sprintf(buf, "-%c, --%s%s", item->letter, item->name, VAL(item));
83       fprintf(stderr, "%s%s %s\n", buf, spaces + strlen(buf), item->help);
84     } else {
85       sprintf(buf, "--%s%s", item->name, VAL(item));
86       fprintf(stderr, "%s%s %s\n", buf, spaces + strlen(buf), item->help);
87     }
88   }
89 }
90
91 void opt_init(struct opt_section * options) {
92   for (struct opt_item * item = options->opt; item->cls != OPT_CL_END; item++) {
93     if (item->cls == OPT_CL_SECTION)
94       opt_init(item->u.section);
95     else if (!(item->flags & OPT_VALUE_FLAGS)) {
96       if (item->cls == OPT_CL_CALL || item->cls == OPT_CL_USER) {
97         fprintf(stderr, "You MUST specify some of the value flags for the %c/%s item.\n", item->letter, item->name);
98         ASSERT(0);
99       } else
100         item->flags |= opt_default_value_flags[item->cls];
101     }
102   }
103   opt_section_root = options;
104 }
105
106 static struct opt_item * opt_find_item_longopt_section(char * str, struct opt_section * options) {
107   uns len = strlen(str);
108   struct opt_item * candidate = NULL;
109
110   for (struct opt_item * item = options->opt; item->cls != OPT_CL_END; item++) {
111     if (item->cls == OPT_CL_SECTION) {
112       struct opt_item * out = opt_find_item_longopt_section(str, item->u.section);
113       if (out) {
114         if (candidate)
115           opt_failure("Ambiguos prefix %s: Found matching %s and %s.\n", str, candidate->name, item->name);
116         else
117           candidate = out;
118       }
119     } else if (!strncmp(item->name, str, len)) {
120       if (strlen(item->name) == len)
121         return item;
122
123       if (candidate)
124         opt_failure("Ambiguos prefix %s: Found matching %s and %s.\n", str, candidate->name, item->name);
125       else
126         candidate = item;
127     }
128   }
129
130   if (candidate)
131     return candidate;
132   else {
133   }
134 }
135
136 static struct opt_item * opt_find_item_longopt(char * str) {
137   struct opt_item * out = opt_find_item_longopt_section(str, opt_section_root);
138   if (out == NULL)
139     opt_failure("Invalid argument: %s\n", str);
140   return out;
141 }
142
143 #define OPT_NAME (longopt ? stk_printf("--%s", item->name) : stk_printf("-%c", item->letter))
144 static void opt_parse_value(struct opt_item * item, char * value, int longopt) {
145   switch (item->cls) {
146     case OPT_CL_BOOL:
147       if (!strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true"))
148         *((int *) item->ptr) = 1;
149       else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false"))
150         *((int *) item->ptr) = 0;
151       else
152         opt_failure("Boolean argument for %s has a strange value. Supported (case insensitive): y/n, yes/no, true/false.\n", OPT_NAME);
153       break;
154     case OPT_CL_STATIC:
155       {
156         char * e = NULL;
157         switch (item->type) {
158           case CT_INT:
159             e = cf_parse_int(value, item->ptr);
160             if (e)
161               opt_failure("Integer value parsing failed for argument %s: %s\n", OPT_NAME, e);
162             break;
163           case CT_U64:
164             e = cf_parse_u64(value, item->ptr);
165             if (e)
166               opt_failure("Unsigned 64-bit value parsing failed for argument %s: %s\n", OPT_NAME, e);
167             break;
168           case CT_DOUBLE:
169             e = cf_parse_double(value, item->ptr);
170             if (e)
171               opt_failure("Double value parsing failed for argument %s: %s\n", OPT_NAME, e);
172             break;
173           case CT_IP:
174             e = cf_parse_ip(value, item->ptr);
175             if (e)
176               opt_failure("IP parsing failed for argument %s: %s\n", OPT_NAME, e);
177             break;
178           case CT_STRING:
179             item->ptr = strdup(value);
180             break;
181           default:
182             ASSERT(0);
183         }
184         break;
185       }
186     case OPT_CL_SWITCH:
187       if (*((int *)item->ptr) != -1)
188         opt_failure("Multiple switches: %s", OPT_NAME);
189       else
190         *((int *)item->ptr) = item->u.value;
191       break;
192     case OPT_CL_INC:
193       if (item->flags | OPT_DECREMENT)
194         (*((int *)item->ptr))--;
195       else
196         (*((int *)item->ptr))++;
197     case OPT_CL_CALL:
198
199     case OPT_CL_USER:
200       {
201         char * e = NULL;
202         e = item->u.utype->parser(value, item->ptr);
203         if (e)
204           opt_failure("User defined type value parsing failed for argument %s: %s\n", OPT_NAME, e);
205         break;
206       }
207   }
208 }
209 #undef OPT_NAME
210
211 static int opt_longopt(char ** argv, int index) {
212   int eaten;
213   char * name_in = argv[index] + 2; // skipping the -- on the beginning
214   uns pos = strchrnul(name_in, '=') - name_in;
215   struct opt_item * item = opt_find_item_longopt(strndupa(name_in, pos));
216   char * value = NULL;
217   if (item->flags | OPT_REQUIRED_VALUE) {
218     if (pos < strlen(name_in))
219       value = name_in + pos + 1;
220     else {
221       value = argv[index+1];
222       eaten++;
223     }
224   }
225   else if (item->flags | OPT_MAYBE_VALUE) {
226     if (pos < strlen(name_in))
227       value = name_in + pos + 1;
228   }
229   else {
230     if (pos < strlen(name_in))
231       opt_failure("Argument %s must not have any value.", item->name);
232   }
233 }
234
235 void opt_parse(char ** argv, opt_positional * callback) {
236
237   int force_positional = 0;
238   for (int i=0;argv[i];i++) {
239     if (argv[i][0] != '-' || force_positional) {
240       callback(argv[i]);
241     } else {
242       if (argv[i][1] == '-') {
243         if (argv[i][2] == '\0')
244           force_positional++;
245         else
246           i += opt_longopt(argv, i);
247       }
248       else if (argv[i][1])
249         i += opt_shortopt(argv, i);
250       else
251         callback(argv[i]);
252     }
253   }
254 }
255
256 #ifdef TEST
257 #include <ucw/fastbuf.h>
258
259 static int show_version(const char ** param UNUSED) {
260   printf("This is a simple tea boiling console v0.1.\n");
261   exit(EXIT_SUCCESS);
262 }
263
264 struct teapot_temperature {
265   enum {
266     TEMP_CELSIUS = 0,
267     TEMP_FAHRENHEIT,
268     TEMP_KELVIN,
269     TEMP_REAUMUR,
270     TEMP_RANKINE
271   } scale;
272   int value;
273 } temperature;
274
275 static char * temp_scale_str[] = { "C", "F", "K", "Re", "R" };
276
277 static enum TEAPOT_TYPE {
278   TEAPOT_STANDARD = 0,
279   TEAPOT_EXCLUSIVE,
280   TEAPOT_GLASS,
281   TEAPOT_HANDS,
282   TEAPOT_UNDEFINED = -1
283 } set = TEAPOT_UNDEFINED;
284
285 static int english = 0;
286 static char * name = NULL;
287 static int sugar = 0;
288 static int verbose = 1;
289 static int with_gas = 0;
290 static int black_magic = 0;
291 static int pray = 0;
292 static int water_amount = 0;
293
294 static const char * teapot_temperature_parser(char * in, void * ptr) {
295   struct teapot_temperature * temp = ptr;
296   const char * next;
297   const char * err = str_to_int(&temp->value, in, &next, 0);
298   if (err)
299     return err;
300   if (!strcmp("C", next))
301     temp->scale = TEMP_CELSIUS;
302   else if (!strcmp("F", next))
303     temp->scale = TEMP_FAHRENHEIT;
304   else if (!strcmp("K", next))
305     temp->scale = TEMP_KELVIN;
306   else if (!strcmp("R", next))
307     temp->scale = TEMP_RANKINE;
308   else if (!strcmp("Re", next))
309     temp->scale = TEMP_REAUMUR;
310   else {
311     fprintf(stderr, "Unknown scale: %s\n", next);
312     exit(OPT_EXIT_BAD_ARGS);
313   }
314   return next + strlen(next);
315 }
316
317 static void teapot_temperature_dumper(struct fastbuf * fb, void * ptr) {
318   struct teapot_temperature * temp = ptr;
319   bprintf(fb, "%d%s", temp->value, temp_scale_str[temp->scale]);
320 }
321
322 static struct cf_user_type teapot_temperature_t = {
323   .size = sizeof(struct teapot_temperature),
324   .name = "teapot_temperature_t",
325   .parser = (cf_parser1*) teapot_temperature_parser,
326   .dumper = (cf_dumper1*) teapot_temperature_dumper
327 };
328
329 static struct opt_section water_options = {
330   OPT_ITEMS {
331     OPT_INT('w', "water", water_amount, OPT_REQUIRED | OPT_REQUIRED_VALUE, "Amount of water (in mls)"),
332     OPT_BOOL('G', "with-gas", with_gas, OPT_NO_VALUE, "Use water with gas"),
333     OPT_END
334   }
335 };
336
337 static struct opt_section help = {
338   OPT_ITEMS {
339     OPT_HELP("A simple tea boiling console."),
340     OPT_HELP("Usage: teapot [options] name-of-the-tea"),
341     OPT_HELP("Black, green or white tea supported as well as fruit or herbal tea."),
342     OPT_HELP("You may specify more kinds of tea, all of them will be boiled for you, in the given order."),
343     OPT_HELP(""),
344     OPT_HELP("Options:"),
345     OPT_HELP_OPTION,
346     OPT_CALL('V', "version", show_version, OPT_NO_VALUE, "Show the version"),
347     OPT_HELP(""),
348     OPT_BOOL('e', "english-style", english, 0, "English style (with milk)"),
349     OPT_INT('s', "sugar", sugar, OPT_REQUIRED_VALUE, "Amount of sugar (in teaspoons)"),
350     OPT_SWITCH(0, "standard-set", set, TEAPOT_STANDARD, 0, "Standard teapot"),
351     OPT_SWITCH('x', "exclusive-set", set, TEAPOT_EXCLUSIVE, 0, "Exclusive teapot"),
352     OPT_SWITCH('g', "glass-set", set, TEAPOT_GLASS, 0, "Transparent glass teapot"),
353     OPT_SWITCH('h', "hands", set, TEAPOT_HANDS, 0, "Use user's hands as a teapot (a bit dangerous)"),
354     OPT_USER('t', "temperature", temperature, teapot_temperature_t, OPT_REQUIRED_VALUE,
355                   "Wanted final temperature of the tea to be served\n"
356               "\t\tSupported scales: \tCelsius [60C], Fahrenheit [140F],"
357               "\t\t\tKelvin [350K], Rankine [600R] and Reaumur [50Re]"
358               "\t\tOnly integer values allowed."),
359     OPT_INC('v', "verbose", verbose, 0, "Verbose (the more -v, the more verbose)"),
360     OPT_INC('q', "quiet", verbose, OPT_DECREMENT, "Quiet (the more -q, the more quiet)"),
361     OPT_INT('b', "black-magic", black_magic, 0, "Use black magic to make the tea extraordinary delicious"),
362     OPT_BOOL('p', "pray", pray, 0, "Pray before boiling"),
363     OPT_HELP(""),
364     OPT_HELP("Water options:"),
365     OPT_SECTION(water_options),
366     OPT_END
367   }
368 };
369
370 static void boil_tea(const char * name) {
371   printf("Boiling a tea: %s\n", name);
372 }
373
374 int main(int argc, char ** argv)
375 {
376   char ** teas;
377   int teas_num;
378
379   opt_init(&help);
380   opt_parse(argv, NULL);
381
382   for (int i=0; i<teas_num; i++)
383     boil_tea(teas[i]);
384
385   printf("Everything OK. Bye.\n");
386 }
387
388 #endif