]> mj.ucw.cz Git - libucw.git/commitdiff
Opt: interface ready for review
authorJan 'Moskyt' Matejka <mq@ucw.cz>
Mon, 22 Apr 2013 10:57:11 +0000 (12:57 +0200)
committerJan 'Moskyt' Matejka <mq@ucw.cz>
Mon, 22 Apr 2013 10:57:11 +0000 (12:57 +0200)
ucw/opt.c
ucw/opt.h

index 7989ab84a0cd4072b65a04ca8504d72448fb63a2..0bd8df2585ed1b35041f68ebde3a755217933743 100644 (file)
--- a/ucw/opt.c
+++ b/ucw/opt.c
@@ -10,6 +10,7 @@
 #include <ucw/lib.h>
 #include <ucw/opt.h>
 #include <ucw/stkstring.h>
+#include <ucw/strtonum.h>
 
 #include <alloca.h>
 
@@ -17,8 +18,9 @@ struct opt_section * opt_section_root;
 
 void opt_help_noexit_internal(struct opt_section * help) {
   uns first_column = 0;
+
   for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
-    if (item->flags | OPT_NO_HELP) continue;
+    if (item->flags & OPT_NO_HELP) continue;
     if (item->cls == OPT_CL_HELP && item->u.help2 == NULL) continue;
     if (item->cls == OPT_CL_SECTION) continue;
     
@@ -35,9 +37,11 @@ void opt_help_noexit_internal(struct opt_section * help) {
       linelen = strlen("--") + strlen(item->name);
     }
 
-    if (item->flags | OPT_REQUIRED_VALUE) {
+    ASSERT(item->flags & OPT_VALUE_FLAGS);
+
+    if (item->flags & OPT_REQUIRED_VALUE) {
       linelen += strlen("=value");
-    } else if (!(item->flags | OPT_NO_VALUE)) {
+    } else if (item->flags & OPT_MAYBE_VALUE) {
       linelen += strlen("(=value)");
     }
 
@@ -52,9 +56,9 @@ void opt_help_noexit_internal(struct opt_section * help) {
 
   spaces[first_column] = 0;
 
-#define VAL(it) ((it->flags | OPT_REQUIRED_VALUE) ? "=value" : ((it->flags | OPT_NO_VALUE) ? "" : "(=value)"))
+#define VAL(it) ((it->flags & OPT_REQUIRED_VALUE) ? "=value" : ((it->flags & OPT_NO_VALUE) ? "" : "(=value)"))
   for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
-    if (item->flags | OPT_NO_HELP) continue;
+    if (item->flags & OPT_NO_HELP) continue;
     
     if (item->cls == OPT_CL_HELP) {
       fprintf(stderr, "%s", item->help);
@@ -74,12 +78,48 @@ void opt_help_noexit_internal(struct opt_section * help) {
   }
 }
 
-void opt_parse(struct opt_section * options) {
+void opt_init(struct opt_section * options) {
   opt_section_root = options;
-  opt_help();
+  for (struct opt_item * item = options->opt; item->cls != OPT_CL_END; item++)
+    if (!(item->flags & OPT_VALUE_FLAGS)) {
+      if (item->cls == OPT_CL_CALL || item->cls == OPT_CL_USER) {
+       fprintf(stderr, "You MUST specify some of the value flags for the %c/%s item.\n", item->letter, item->name);
+       exit(2);
+      } else
+       item->flags |= opt_default_value_flags[item->cls];
+    }
+}
+
+int opt_parse(char ** argv, char *** posargs) {
+  // Temporary. To be fixed.
+  static char ** d;
+  d = xmalloc(sizeof (*d) * 2);
+  d[0] = "darjeeling";
+  d[1] = "puerh";
+  *posargs = d;
+  return 2;
 }
 
 #ifdef TEST
+#include <ucw/fastbuf.h>
+
+static int show_version(const char ** param UNUSED) {
+  printf("This is a simple tea boiling console v0.1.\n");
+  exit(0);
+}
+
+struct teapot_temperature {
+  enum {
+    TEMP_CELSIUS = 0,
+    TEMP_FAHRENHEIT,
+    TEMP_KELVIN,
+    TEMP_REAUMUR,
+    TEMP_RANKINE
+  } scale;
+  int value;
+} temperature;
+
+static char * temp_scale_str[] = { "C", "F", "K", "Re", "R" };
 
 static enum TEAPOT_TYPE {
   TEAPOT_STANDARD = 0,
@@ -98,20 +138,41 @@ static uns black_magic = 0;
 static int pray = 0;
 static uns water_amount = 0;
 
-static struct teapot_temperature {
-  enum {
-    TEMP_CELSIUS,
-    TEMP_FAHRENHEIT,
-    TEMP_KELVIN,
-    TEMP_REAUMUR
-  } scale;
-  int value;
-} temperature;
+static const char * teapot_temperature_parser(char * in, void * ptr) {
+  struct teapot_temperature * temp = ptr;
+  const char * next;
+  const char * err = str_to_int(&temp->value, in, &next, 0);
+  if (err)
+    return err;
+  if (!strcmp("C", next))
+    temp->scale = TEMP_CELSIUS;
+  else if (!strcmp("F", next))
+    temp->scale = TEMP_FAHRENHEIT;
+  else if (!strcmp("K", next))
+    temp->scale = TEMP_KELVIN;
+  else if (!strcmp("R", next))
+    temp->scale = TEMP_RANKINE;
+  else if (!strcmp("Re", next))
+    temp->scale = TEMP_REAUMUR;
+  else {
+    fprintf(stderr, "Unknown scale: %s\n", next);
+    exit(1);
+  }
+  return next + strlen(next);
+}
 
-static int parse_temperature(const char * param, void * target) {
-  return 1;
+static void teapot_temperature_dumper(struct fastbuf * fb, void * ptr) {
+  struct teapot_temperature * temp = ptr;
+  bprintf(fb, "%d%s", temp->value, temp_scale_str[temp->scale]);
 }
 
+static struct cf_user_type teapot_temperature_t = {
+  .size = 2*sizeof(int),
+  .name = "teapot_temperature_t",
+  .parser = (cf_parser1*) teapot_temperature_parser,
+  .dumper = (cf_dumper1*) teapot_temperature_dumper
+};
+
 static struct opt_section water_options = {
   OPT_ITEMS {
     OPT_UNS('w', "water", water_amount, OPT_REQUIRED | OPT_REQUIRED_VALUE, "Amount of water (in mls)"),
@@ -123,18 +184,24 @@ static struct opt_section water_options = {
 static struct opt_section help = {
   OPT_ITEMS {
     OPT_HELP("A simple tea boiling console."),
+    OPT_HELP("Usage: teapot [options] name-of-the-tea"),
+    OPT_HELP("Black, green or white tea supported as well as fruit or herbal tea."),
+    OPT_HELP("You may specify more kinds of tea, all of them will be boiled for you, in the given order."),
+    OPT_HELP(""),
     OPT_HELP("Options:"),
-    OPT_SHOW_HELP(0),
+    OPT_SHOW_HELP,
+    OPT_CALL('V', "version", show_version, OPT_NO_VALUE, "Show the version"),
+    OPT_HELP(""),
     OPT_BOOL('e', "english-style", english, 0, "English style (with milk)"),
-    OPT_STRING('n', "name", name, OPT_REQUIRED | OPT_REQUIRED_VALUE, "Name of the tea to be prepared"),
     OPT_UNS('s', "sugar", sugar, OPT_REQUIRED_VALUE, "Amount of sugar (in teaspoons)"),
     OPT_SWITCH(0, "standard-set", set, TEAPOT_STANDARD, 0, "Standard teapot"),
     OPT_SWITCH('x', "exclusive-set", set, TEAPOT_EXCLUSIVE, 0, "Exclusive teapot"),
     OPT_SWITCH('g', "glass-set", set, TEAPOT_GLASS, 0, "Transparent glass teapot"),
     OPT_SWITCH('h', "hands", set, TEAPOT_HANDS, 0, "Use user's hands as a teapot (a bit dangerous)"),
-    OPT_USER('t', "temperature", temperature, parse_temperature, OPT_REQUIRED_VALUE, "Wanted final temperature of the tea to be served"),
+    OPT_USER('t', "temperature", temperature, teapot_temperature_t, OPT_REQUIRED_VALUE, "Wanted final temperature of the tea to be served"),
     OPT_HELP2("", "Supported scales: Celsius [60C], Fahrenheit [140F],"),
     OPT_HELP2("", "                  Kelvin [350K], Rankine [600R] and Reaumur [50Re]"),
+    OPT_HELP2("", "Only integer values allowed."),
     OPT_INC('v', "verbose", verbose, 0, "Verbose (the more -v, the more verbose)"),
     OPT_INC('q', "quiet", verbose, OPT_DECREMENT, "Quiet (the more -q, the more quiet)"),
     OPT_UNS('b', "black-magic", black_magic, 0, "Use black magic to make the tea extraordinary delicious"),
@@ -146,9 +213,22 @@ static struct opt_section help = {
   }
 };
 
+static void boil_tea(const char * name) {
+  printf("Boiling a tea: %s\n", name);
+}
+
 int main(int argc, char ** argv)
 {
-  opt_parse(&help);
+  char ** teas;
+  int teas_num;
+
+  opt_init(&help);
+  teas_num = opt_parse(argv, &teas);
+
+  for (int i=0; i<teas_num; i++)
+    boil_tea(teas[i]);
+
+  printf("Everything OK. Bye.\n");
 }
 
 #endif
index 5f68fea39f5199b638ff4e287d8ab1aa6b0247d6..00a98ce9e6e4d40b5390946d01767e10159cac5e 100644 (file)
--- a/ucw/opt.h
+++ b/ucw/opt.h
@@ -10,6 +10,9 @@
 #ifndef _UCW_OPT_H
 #define _UCW_OPT_H
 
+#include <ucw/lib.h>
+#include <ucw/conf.h>
+
 #include <stdlib.h>
 #include <stdio.h>
 
@@ -25,39 +28,34 @@ enum opt_class {
   OPT_CL_STATIC,  // static value
   OPT_CL_SWITCH,  // lookup/switch
   OPT_CL_INC,    // incremental value
+  OPT_CL_CALL,   // call a function
   OPT_CL_USER,   // user defined value
   OPT_CL_SECTION, // subsection
   OPT_CL_HELP,   // help line
 };
 
-enum opt_type {
-  OPT_CT_INT, OPT_CT_64, OPT_CT_DOUBLE,        // number
-  OPT_CT_STRING,                       // string
-  OPT_CT_LOOKUP,                       // lookup/switch
-  OPT_CT_USER,                         // user defined
-};
-
-typedef int opt_custom_parser(const char * param, void * target);
+typedef void opt_custom_function(const char ** param);
 
 struct opt_section;
 struct opt_item {
   const char letter;                   // short-op
-  const char *name;                    // long-op
-  void *ptr;                           // where to save
-  const char *help;                    // description in --help
+  const char * name;                   // long-op
+  void * ptr;                          // where to save
+  const char * help;                   // description in --help
   union opt_union {
-    struct opt_section *section;       // subsection for OPT_SECTION
+    struct opt_section * section;      // subsection for OPT_SECTION
     int value;                         // value for OPT_SWITCH
-    opt_custom_parser *parser;         // parser for OPT_USER
-    const char *help2;                 // second value for OPT_HELP2
+    const char * help2;                        // second value for OPT_HELP2
+    int (* call)(const char ** param); // function to call for OPT_CALL
+    struct cf_user_type * utype;               // specification of the user-defined type
   } u;
   short flags;
   enum opt_class cls;
-  enum opt_type type;
+  enum cf_type type;
 };
 
 struct opt_section {
-  struct opt_item *opt;
+  struct opt_item * opt;
 };
 
 #define OPT_ITEMS      .opt = ( struct opt_item[] )  /** List of sub-items. **/
@@ -72,6 +70,7 @@ struct opt_section {
  * OPT_SWITCH declares one choice of a switch statement; these have common target and different `value`s; last wins unless OPT_SINGLE is set;
  *           parser fails if it matches an OPT_SWITCH with OPT_SINGLE set and also target set.
  *           Target must be of signed integer type; it is set to -1 if no switch appears at the command-line.
+ * OPT_CALL calls the given function with all the remaining command line, it returns the number of arguments to be skipped.
  * OPT_USER declares a custom type of value; parser is of type opt_custom_parser
  *                                          and returns 1 on success and 0 on failure
  * OPT_INC declares an incremental value like -v/--verbose
@@ -79,16 +78,17 @@ struct opt_section {
  *
  * **/
 
-#define OPT_SHOW_HELP(flags) OPT_USER(0, "help", *(NULL), opt_help_success2, flags, "Show this help")
+#define OPT_SHOW_HELP OPT_CALL(0, "help", opt_show_help_internal, OPT_NO_VALUE, "Show this help")
 #define OPT_HELP(line) OPT_HELP2(line, NULL)
 #define OPT_HELP2(first, second) { .help = first, .cls = OPT_CL_HELP, .u.help2 = second }
-#define OPT_BOOL(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .help = desc, .flags = fl, .cls = OPT_CL_BOOL, .type = OPT_CT_INT }
-#define OPT_STRING(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, char**), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = OPT_CT_STRING }
-#define OPT_UNS(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, uns*), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = OPT_CT_INT }
-#define OPT_INT(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int*), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = OPT_CT_INT }
-#define OPT_SWITCH(shortopt, longopt, target, val, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int*), .help = desc, .flags = fl, .cls = OPT_CL_SWITCH, .type = OPT_CT_INT, .u.value = val }
-#define OPT_USER(shortopt, longopt, target, pa, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .u.parser = pa, .flags = fl, .help = desc, .cls = OPT_CL_USER, .type = OPT_CT_USER }
-#define OPT_INC(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .flags = fl, .help = desc, .cls = OPT_CL_INC, .type = OPT_CT_INT }
+#define OPT_BOOL(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .help = desc, .flags = fl, .cls = OPT_CL_BOOL, .type = CT_INT }
+#define OPT_STRING(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, char **), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_STRING }
+#define OPT_UNS(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, uns *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_INT }
+#define OPT_INT(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_INT }
+#define OPT_SWITCH(shortopt, longopt, target, val, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int *), .help = desc, .flags = fl, .cls = OPT_CL_SWITCH, .type = CT_LOOKUP, .u.value = val }
+#define OPT_CALL(shortopt, longopt, fn, fl, desc) { .letter = shortopt, .name = longopt, .ptr = NULL, .help = desc, .u.call = fn, .flags = fl, .cls = OPT_CL_CALL, .type = CT_USER }
+#define OPT_USER(shortopt, longopt, target, ttype, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .u.utype = &ttype, .flags = fl, .help = desc, .cls = OPT_CL_USER, .type = CT_USER }
+#define OPT_INC(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .flags = fl, .help = desc, .cls = OPT_CL_INC, .type = CT_INT }
 #define OPT_SECTION(sec) { .cls = OPT_CL_SECTION, .u.section = &sec }
 #define OPT_END { .cls = OPT_CL_END }
 
@@ -96,9 +96,28 @@ struct opt_section {
 #define OPT_REQUIRED       0x1         // Argument must appear at the command line
 #define OPT_REQUIRED_VALUE  0x2                // Argument must have a value
 #define OPT_NO_VALUE       0x4         // Argument must have no value
-#define OPT_DECREMENT      0x8         // Reversing the effect of OPT_INC
-#define OPT_SINGLE         0x10        // Argument must appear at most once
-#define OPT_NO_HELP        0x20        // Omit this line from help
+#define OPT_MAYBE_VALUE            0x8         // Argument may have a value
+#define OPT_VALUE_FLAGS            (OPT_REQUIRED_VALUE | OPT_NO_VALUE | OPT_MAYBE_VALUE)
+#define OPT_DECREMENT      0x10        // Reversing the effect of OPT_INC
+#define OPT_SINGLE         0x20        // Argument must appear at most once
+#define OPT_NO_HELP        0x40        // Omit this line from help
+
+/** Value flags defaults:
+ * OPT_NO_VALUE for OPT_BOOL, OPT_SWITCH and OPT_INC
+ * OPT_MAYBE_VALUE for OPT_STRING, OPT_UNS, OPT_INT
+ * Some of the value flags (OPT_NO_VALUE, OPT_MAYBE_VALUE, OPT_REQUIRED_VALUE)
+ * must be specified for OPT_CALL and OPT_USER.
+ */
+static uns opt_default_value_flags[] = {
+  [OPT_CL_BOOL] = OPT_NO_VALUE,
+  [OPT_CL_STATIC] = OPT_MAYBE_VALUE,
+  [OPT_CL_SWITCH] = OPT_NO_VALUE,
+  [OPT_CL_INC] = OPT_NO_VALUE,
+  [OPT_CL_CALL] = 0,
+  [OPT_CL_USER] = 0,
+  [OPT_CL_SECTION] = 0,
+  [OPT_CL_HELP] = 0
+};
 
 extern struct opt_section * opt_section_root;
 void opt_help_noexit_internal(struct opt_section * help);
@@ -111,7 +130,7 @@ static void opt_usage_noexit(void) {
   fprintf(stderr, "Run with argument --help for more information.\n");
 }
 
-static int opt_help_success2(const char * param UNUSED, void * target UNUSED) {
+static int opt_show_help_internal(const char ** param UNUSED) {
   opt_help_noexit();
   exit(0);
 }
@@ -126,6 +145,23 @@ static void opt_usage(void) {
   exit(1);
 }
 
-void opt_parse(struct opt_section * options);
+/**
+ * Init the opt engine.
+ */
+void opt_init(struct opt_section * options);
+
+/**
+ * Parse all the arguments.
+ * Returns the number of positional arguments and an array of them in @posargs.
+ */
+int opt_parse(char ** argv, char *** posargs);
+
+/**
+ * Parse all the arguments until first positional argument is found.
+ * Returns the position of that argument in argv.
+ * On next call of this function, start from the next item in argv.
+ * Iterate this function to get all the positional arguments.
+ */
+int opt_get(char ** argv);
 
 #endif