]> mj.ucw.cz Git - libucw.git/commitdiff
Opt: compilable, segfaulting.
authorJan 'Moskyt' Matejka <mq@ucw.cz>
Mon, 29 Apr 2013 15:30:40 +0000 (17:30 +0200)
committerJan 'Moskyt' Matejka <mq@ucw.cz>
Mon, 29 Apr 2013 15:33:41 +0000 (17:33 +0200)
ucw/opt.c
ucw/opt.h

index 0ae40a905284022cc637ed6d546b0aed6d621ccd..ebae85e4045499d790db5128e7caaf8e5b2b65da 100644 (file)
--- a/ucw/opt.c
+++ b/ucw/opt.c
@@ -43,7 +43,8 @@ void opt_help_noexit_internal(struct opt_section * help) {
     
     if (item->letter) { // will write sth like "-x, --exclusive"
       linelen = strlen("-x, --") + strlen(item->name);
-    } else { // will write sth like "--exclusive"
+    }
+    else { // will write sth like "--exclusive"
       linelen = strlen("--") + strlen(item->name);
     }
 
@@ -51,7 +52,8 @@ void opt_help_noexit_internal(struct opt_section * help) {
 
     if (item->flags & OPT_REQUIRED_VALUE) {
       linelen += strlen("=value");
-    } else if (item->flags & OPT_MAYBE_VALUE) {
+    }
+    else if (item->flags & OPT_MAYBE_VALUE) {
       linelen += strlen("(=value)");
     }
 
@@ -76,77 +78,67 @@ void opt_help_noexit_internal(struct opt_section * help) {
        fprintf(stderr, "\n");
       else
        fprintf(stderr, "%s %s\n", spaces + strlen(item->help), item->u.help2);
-    } else if (item->cls == OPT_CL_SECTION) {
+    }
+    else if (item->cls == OPT_CL_SECTION) {
       opt_help_noexit_internal(item->u.section);
-    } else if (item->letter) {
+    }
+    else if (item->letter) {
       sprintf(buf, "-%c, --%s%s", item->letter, item->name, VAL(item));
       fprintf(stderr, "%s%s %s\n", buf, spaces + strlen(buf), item->help);
-    } else {
+    }
+    else {
       sprintf(buf, "--%s%s", item->name, VAL(item));
       fprintf(stderr, "%s%s %s\n", buf, spaces + strlen(buf), item->help);
     }
   }
 }
 
-void opt_init(struct opt_section * options) {
-  for (struct opt_item * item = options->opt; item->cls != OPT_CL_END; item++) {
-    if (item->cls == OPT_CL_SECTION)
-      opt_init(item->u.section);
-    else 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);
-       ASSERT(0);
-      } else
-       item->flags |= opt_default_value_flags[item->cls];
-    }
-  }
-  opt_section_root = options;
+struct opt_precomputed {
+  struct opt_precomputed_option {
+    struct opt_item * item;
+    short flags;
+    short count;
+  } ** opts;
+  struct opt_precomputed_option * shortopt[256];
+  short opt_count;
+};
+
+static struct opt_item * opt_find_item_shortopt(int chr, struct opt_precomputed * opts) {
+  return (opts->shortopt[chr] ? opts->shortopt[chr]->item : NULL);
 }
 
-static struct opt_item * opt_find_item_longopt_section(char * str, struct opt_section * options) {
+static struct opt_item * opt_find_item_longopt(char * str, struct opt_precomputed * pre) {
   uns len = strlen(str);
   struct opt_item * candidate = NULL;
 
-  for (struct opt_item * item = options->opt; item->cls != OPT_CL_END; item++) {
-    if (item->cls == OPT_CL_SECTION) {
-      struct opt_item * out = opt_find_item_longopt_section(str, item->u.section);
-      if (out) {
-       if (candidate)
-         opt_failure("Ambiguous prefix %s: Found matching %s and %s.\n", str, candidate->name, item->name);
-       else
-         candidate = out;
-      }
-    } else if (!strncmp(item->name, str, len)) {
-      if (strlen(item->name) == len)
-       return item;
+  for (int i=0; i<pre->opt_count; i++) {
+    if (!strncmp(pre->opts[i]->item->name, str, len)) {
+      if (strlen(pre->opts[i]->item->name) == len) {
+       if (pre->opts[i]->count++ && (pre->opts[i]->flags & OPT_SINGLE))
+         opt_failure("Option %s appeared the second time.\n", pre->opts[i]->item->name);
 
+       return pre->opts[i]->item;
+      }
       if (candidate)
-       opt_failure("Ambiguous prefix %s: Found matching %s and %s.\n", str, candidate->name, item->name);
+       opt_failure("Ambiguous prefix %s: Found matching %s and %s.\n", str, candidate->name, pre->opts[i]->item->name);
       else
-       candidate = item;
+       candidate = pre->opts[i]->item;
     }
   }
 
   if (candidate)
     return candidate;
-  else {
-  }
-}
 
-static struct opt_item * opt_find_item_longopt(char * str) {
-  struct opt_item * out = opt_find_item_longopt_section(str, opt_section_root);
-  if (out == NULL)
-    opt_failure("Invalid argument: %s\n", str);
-  return out;
+  opt_failure("Invalid option %s.\n", str);
 }
 
 #define OPT_NAME (longopt ? stk_printf("--%s", item->name) : stk_printf("-%c", item->letter))
 static void opt_parse_value(struct opt_item * item, char * value, int longopt) {
   switch (item->cls) {
     case OPT_CL_BOOL:
-      if (!strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true"))
+      if (!strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "1"))
        *((int *) item->ptr) = 1;
-      else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false"))
+      else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "0"))
        *((int *) item->ptr) = 0;
       else
        opt_failure("Boolean argument for %s has a strange value. Supported (case insensitive): y/n, yes/no, true/false.\n", OPT_NAME);
@@ -190,12 +182,13 @@ static void opt_parse_value(struct opt_item * item, char * value, int longopt) {
        *((int *)item->ptr) = item->u.value;
       break;
     case OPT_CL_INC:
-      if (item->flags | OPT_DECREMENT)
+      if (item->flags & OPT_DECREMENT)
        (*((int *)item->ptr))--;
       else
        (*((int *)item->ptr))++;
     case OPT_CL_CALL:
-
+      item->u.call(item, value, item->ptr);
+      break;
     case OPT_CL_USER:
       {
        char * e = NULL;
@@ -204,17 +197,19 @@ static void opt_parse_value(struct opt_item * item, char * value, int longopt) {
          opt_failure("User defined type value parsing failed for argument %s: %s\n", OPT_NAME, e);
        break;
       }
+    default:
+      ASSERT(0);
   }
 }
 #undef OPT_NAME
 
-static int opt_longopt(char ** argv, int index) {
-  int eaten;
+static int opt_longopt(char ** argv, int index, struct opt_precomputed * pre) {
+  int eaten = 0;
   char * name_in = argv[index] + 2; // skipping the -- on the beginning
   uns pos = strchrnul(name_in, '=') - name_in;
-  struct opt_item * item = opt_find_item_longopt(strndupa(name_in, pos));
+  struct opt_item * item = opt_find_item_longopt(strndupa(name_in, pos), pre);
   char * value = NULL;
-  if (item->flags | OPT_REQUIRED_VALUE) {
+  if (item->flags & OPT_REQUIRED_VALUE) {
     if (pos < strlen(name_in))
       value = name_in + pos + 1;
     else {
@@ -222,7 +217,7 @@ static int opt_longopt(char ** argv, int index) {
       eaten++;
     }
   }
-  else if (item->flags | OPT_MAYBE_VALUE) {
+  else if (item->flags & OPT_MAYBE_VALUE) {
     if (pos < strlen(name_in))
       value = name_in + pos + 1;
   }
@@ -230,23 +225,129 @@ static int opt_longopt(char ** argv, int index) {
     if (pos < strlen(name_in))
       opt_failure("Argument %s must not have any value.", item->name);
   }
+  opt_parse_value(item, value, 1);
+  return eaten;
 }
 
-void opt_parse(char ** argv, opt_positional * callback) {
+static int opt_shortopt(char ** argv, int index, struct opt_precomputed * pre) {
+  int chr = 0;
+  struct opt_item * item;
+  while (argv[index][++chr] && (item = opt_find_item_shortopt(argv[index][chr], pre))) {
+    if (item->flags & OPT_NO_VALUE) {
+      opt_parse_value(item, NULL, 0);
+      continue;
+    }
+    if (chr == 1 && (item->flags & OPT_REQUIRED_VALUE)) {
+      if (argv[index][2]) {
+        opt_parse_value(item, argv[index] + 2, 0);
+       return 0;
+      }
+      else {
+       opt_parse_value(item, argv[index+1], 0);
+       return 1;
+      }
+    }
+    else if (chr == 1 && (item->flags & OPT_MAYBE_VALUE)) {
+      if (argv[index][2])
+        opt_parse_value(item, argv[index] + 2, 0);
+      else
+       opt_parse_value(item, NULL, 0);
+    }
+    else if (item->flags & (OPT_REQUIRED_VALUE | OPT_MAYBE_VALUE)) {
+      if (argv[index][chr+1] || (item->flags | OPT_MAYBE_VALUE))
+       opt_failure("Option -%c may or must have a value but found inside a bunch of short opts.", item->letter);
+      else {
+       opt_parse_value(item, argv[index+1], 0);
+       return 1;
+      }
+    }
+  }
+
+  if (argv[index][chr])
+    opt_failure("Unknown option -%c.", item->letter);
+  
+  return 0;
+}
+
+#define OPT_TRAVERSE_SECTIONS \
+  do { \
+    while (item->cls == OPT_CL_SECTION) { \
+      if (stk->next) \
+       stk = stk->next; \
+      else { \
+       struct opt_stack * new_stk = alloca(sizeof(*new_stk)); \
+       new_stk->prev = stk; \
+       stk->next = new_stk; \
+       stk = new_stk; \
+      } \
+      stk->this = item; \
+      item = item->u.section->opt; \
+    } \
+    if (item->cls == OPT_CL_END) { \
+      if (!stk) break; \
+      item = stk->this; \
+      stk = stk->prev; \
+      continue; \
+    } \
+  } while (0)
+
+void opt_parse(const struct opt_section * options, char ** argv, opt_positional * callback) {
+  struct opt_stack {
+    struct opt_item * this;
+    struct opt_stack * prev;
+    struct opt_stack * next;
+  } * stk = alloca(sizeof(*stk));
+  stk->this = NULL;
+  stk->prev = NULL;
+  stk->next = NULL;
+
+  struct opt_precomputed * pre = alloca(sizeof(*pre));
+
+  int count;
+
+  for (struct opt_item * item = options->opt; ; item++) {
+    OPT_TRAVERSE_SECTIONS;
+    if (item->letter || item->name)
+      count++;
+  }
+  
+  pre->opts = xmalloc(sizeof(*pre->opts) * count);
+  pre->opt_count = 0;
+
+  for (struct opt_item * item = options->opt; ; item++) {
+    OPT_TRAVERSE_SECTIONS;
+    if (item->letter || item->name) {
+      struct opt_precomputed_option * opt = xmalloc(sizeof(*opt));
+      opt->item = item;
+      opt->flags = item->flags;
+      opt->count = 0;
+      pre->opts[pre->opt_count++] = opt;
+      if (item->letter)
+       pre->shortopt[(int) item->letter] = opt;
+      if (!(opt->flags & OPT_VALUE_FLAGS) &&
+         (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);
+       ASSERT(0);
+      }
+      else
+       opt->flags |= opt_default_value_flags[item->cls];
+    }
+  }
 
   int force_positional = 0;
   for (int i=0;argv[i];i++) {
     if (argv[i][0] != '-' || force_positional) {
       callback(argv[i]);
-    } else {
+    }
+    else {
       if (argv[i][1] == '-') {
        if (argv[i][2] == '\0')
          force_positional++;
        else
-         i += opt_longopt(argv, i);
+         i += opt_longopt(argv, i, pre);
       }
       else if (argv[i][1])
-       i += opt_shortopt(argv, i);
+       i += opt_shortopt(argv, i, pre);
       else
        callback(argv[i]);
     }
@@ -256,7 +357,7 @@ void opt_parse(char ** argv, opt_positional * callback) {
 #ifdef TEST
 #include <ucw/fastbuf.h>
 
-static int show_version(const char ** param UNUSED) {
+static void show_version(struct opt_item * opt UNUSED, const char * value UNUSED, void * data UNUSED) {
   printf("This is a simple tea boiling console v0.1.\n");
   exit(EXIT_SUCCESS);
 }
@@ -343,7 +444,7 @@ static struct opt_section help = {
     OPT_HELP(""),
     OPT_HELP("Options:"),
     OPT_HELP_OPTION,
-    OPT_CALL('V', "version", show_version, OPT_NO_VALUE, "Show the version"),
+    OPT_CALL('V', "version", show_version, NULL, OPT_NO_VALUE, "Show the version"),
     OPT_HELP(""),
     OPT_BOOL('e', "english-style", english, 0, "English style (with milk)"),
     OPT_INT('s', "sugar", sugar, OPT_REQUIRED_VALUE, "Amount of sugar (in teaspoons)"),
@@ -353,7 +454,7 @@ static struct opt_section help = {
     OPT_SWITCH('h', "hands", set, TEAPOT_HANDS, 0, "Use user's hands as a teapot (a bit dangerous)"),
     OPT_USER('t', "temperature", temperature, teapot_temperature_t, OPT_REQUIRED_VALUE,
                  "Wanted final temperature of the tea to be served\n"
-             "\t\tSupported scales: \tCelsius [60C], Fahrenheit [140F],"
+             "\t\tSupported scales:\tCelsius [60C], Fahrenheit [140F],"
              "\t\t\tKelvin [350K], Rankine [600R] and Reaumur [50Re]"
              "\t\tOnly integer values allowed."),
     OPT_INC('v', "verbose", verbose, 0, "Verbose (the more -v, the more verbose)"),
@@ -367,20 +468,27 @@ static struct opt_section help = {
   }
 };
 
+#define MAX_TEA_COUNT 30
+static char * tea_list[MAX_TEA_COUNT];
+static int tea_num = 0;
+static void add_tea(const char * name) {
+  if (tea_num >= MAX_TEA_COUNT) {
+    fprintf(stderr, "Cannot boil more than %d teas.\n", MAX_TEA_COUNT);
+    exit(OPT_EXIT_BAD_ARGS);
+  }
+  tea_list[tea_num++] = strdup(name);
+}
+
 static void boil_tea(const char * name) {
   printf("Boiling a tea: %s\n", name);
 }
 
 int main(int argc, char ** argv)
 {
-  char ** teas;
-  int teas_num;
-
-  opt_init(&help);
-  opt_parse(argv, NULL);
+  opt_parse(&help, argv, add_tea);
 
-  for (int i=0; i<teas_num; i++)
-    boil_tea(teas[i]);
+  for (int i=0; i<tea_num; i++)
+    boil_tea(tea_list[i]);
 
   printf("Everything OK. Bye.\n");
 }
index 497bab641c28e593f3a581f1b5b81c5b836d0bf4..8e6994dfd81c3d708dee45bf6e886841570d1c63 100644 (file)
--- a/ucw/opt.h
+++ b/ucw/opt.h
@@ -34,8 +34,6 @@ enum opt_class {
   OPT_CL_HELP,   // help line
 };
 
-typedef void opt_custom_function(const char ** param);
-
 struct opt_section;
 struct opt_item {
   const char * name;                   // long-op
@@ -45,7 +43,7 @@ struct opt_item {
     struct opt_section * section;      // subsection for OPT_SECTION
     int value;                         // value for OPT_SWITCH
     const char * help2;                        // second value for OPT_HELP2
-    int (* call)(const char ** param); // function to call for OPT_CALL
+    void (* call)(struct opt_item * opt, const char * value, void * data);  // function to call for OPT_CALL
     struct cf_user_type * utype;       // specification of the user-defined type
   } u;
   const char letter;                   // short-op
@@ -64,23 +62,22 @@ struct opt_section {
  * Sub-items to be enclosed in OPT_ITEMS { } list
  * ----------------------------------------------
  *
- * OPT_HELP_OPTION declares --help and prints a line about that
- * OPT_HELP prints a line into help
- * OPT_HELP2 prints two strings onto a line using the same tab structure as the option listing
- * OPT_BOOL declares boolean option with an auto-negation (--sth and --no-sth); may be changed by OPT_BOOL_SET_PREFIXES
- * OPT_STRING, OPT_UNS, OPT_INT declare simple string/uns/int option
- * 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
- * OPT_SECTION declares a subsection
+ *  OPT_HELP_OPTION declares --help and prints a line about that
+ *  OPT_HELP prints a line into help
+ *  OPT_HELP2 prints two strings onto a line using the same tab structure as the option listing
+ *  OPT_BOOL declares boolean option with an auto-negation (--sth and --no-sth). It's also possible to write --sth=y/yes/true/1/n/no/false/0.
+ *  OPT_STRING, OPT_UNS, OPT_INT declare simple string/uns/int option
+ *  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 an argument, giving also the opt_item structure and some custom data.
+ *  OPT_USER declares a custom type of value defined by the given @cf_user_type in @ttype
+ *  OPT_INC declares an incremental value like -v/--verbose
+ *  OPT_SECTION declares a subsection
  *
  ***/
 
-#define OPT_HELP_OPTION OPT_CALL(0, "help", opt_show_help_internal, OPT_NO_VALUE, "Show this help")
+#define OPT_HELP_OPTION OPT_CALL(0, "help", opt_show_help_internal, NULL, 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 } // FIXME: remove this
 #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 }
@@ -90,7 +87,7 @@ struct opt_section {
 #define OPT_DOUBLE(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, double *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_DOUBLE }
 #define OPT_IP(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, u32 *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_IP }
 #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_CALL(shortopt, longopt, fn, data, fl, desc) { .letter = shortopt, .name = longopt, .ptr = data, .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 }
@@ -109,6 +106,7 @@ struct opt_section {
 #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 **/
+#define OPT_LAST_ARG       0x80        /** Stop processing argv after this line **/
 
 /***
  * Value flags defaults
@@ -142,7 +140,7 @@ static void opt_usage_noexit(void) {
   fprintf(stderr, "Run with argument --help for more information.\n");
 }
 
-static int opt_show_help_internal(const char ** param UNUSED) {
+static void opt_show_help_internal(struct opt_item * opt UNUSED, const char * value UNUSED, void * data UNUSED) {
   opt_help_noexit();
   exit(0);
 }
@@ -157,11 +155,6 @@ static void opt_usage(void) {
   exit(1);
 }
 
-/**
- * Init the opt engine.
- **/
-void opt_init(struct opt_section * options);
-
 /**
  * Positional argument handler to be given to opt_parse()
  **/
@@ -170,6 +163,6 @@ typedef void opt_positional(const char * str);
 /**
  * Parse all the arguments. Run the @callback for each of the positional argument.
  **/
-void opt_parse(char ** argv, opt_positional * callback);
+void opt_parse(const struct opt_section * options, char ** argv, opt_positional * callback);
 
 #endif