]> mj.ucw.cz Git - libucw.git/commitdiff
Opt: Rewritten formatting of help
authorMartin Mares <mj@ucw.cz>
Mon, 27 Jan 2014 17:42:01 +0000 (18:42 +0100)
committerMartin Mares <mj@ucw.cz>
Mon, 27 Jan 2014 17:42:01 +0000 (18:42 +0100)
Previous implementation of opt_help() had several drawbacks:
First, it formatted each section separately, so the columns
did not aligh. Second, it did produced incorrect output for
options which possess only the short form.

I hope that the new implementation is cleaner and easier to extend.

ucw/opt-test.c
ucw/opt.c
ucw/opt.h

index cd7acc38f5f27009dd537c3cfcebe1debfdc1987..f7e7fa024a4e7bb51838ebe053e7d47f29673694 100644 (file)
@@ -118,6 +118,7 @@ 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(""),
     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("At least one kind of tea must be specified."),
@@ -139,17 +140,17 @@ static struct opt_section help = {
              "\t\tOnly integer values allowed."),
     OPT_INC('v', "verbose", verbose, 0, "\tVerbose (the more -v, the more verbose)"),
     OPT_INC('q', "quiet", verbose, OPT_NEGATIVE, "\tQuiet (the more -q, the more quiet)"),
-    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."),
+    OPT_INT('b', NULL, black_magic, OPT_MULTIPLE, "<strength>\tUse black magic to make the tea extraordinarily 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."),
     OPT_BOOL('p', "pray", pray, OPT_SINGLE, "\tPray before boiling"),
-    OPT_STRING(OPT_POSITIONAL(1), NULL, first_tea, OPT_REQUIRED | OPT_NO_HELP, ""),
-    OPT_CALL(OPT_POSITIONAL_TAIL, NULL, add_tea, &tea_list, OPT_NO_HELP, ""),
+    OPT_STRING(OPT_POSITIONAL(1), NULL, first_tea, OPT_REQUIRED, ""),
+    OPT_CALL(OPT_POSITIONAL_TAIL, NULL, add_tea, &tea_list, 0, ""),
     OPT_HELP(""),
     OPT_HELP("Water options:"),
     OPT_SECTION(water_options),
     OPT_HOOK(opt_test_hook, "prearg", OPT_HOOK_BEFORE_ARG),
     OPT_HOOK(opt_test_hook, "preval", OPT_HOOK_BEFORE_VALUE),
     OPT_HOOK(opt_test_hook, "postval", OPT_HOOK_AFTER_VALUE),
-    OPT_BOOL('H', "show-hooks", show_hooks, 0, "Demonstrate the hooks."),
+    OPT_BOOL('H', "show-hooks", show_hooks, 0, "\tDemonstrate the hooks."),
     OPT_CONF_OPTIONS,
     OPT_END
   }
index 5a529d7ff2ba0438293a7b5e50aa5e8b5487d9c5..59bcb26088ce8aeda7aab0fbd2ac481eea695e40 100644 (file)
--- a/ucw/opt.c
+++ b/ucw/opt.c
@@ -15,6 +15,8 @@
 #include <ucw/fastbuf.h>
 #include <ucw/stkstring.h>
 #include <ucw/strtonum.h>
+#include <ucw/mempool.h>
+#include <ucw/gary.h>
 
 #include <alloca.h>
 #include <math.h>
@@ -85,126 +87,165 @@ static char *opt_name(struct opt_context *oc, struct opt_precomputed *opt)
 
 #define THIS_OPT opt_name(oc, opt)
 
-#define FOREACHLINE(text) for (const char * begin = (text), * end = (text); (*end) && (end = strchrnul(begin, '\n')); begin = end+1)
-
-static inline uns uns_min(uns x, uns y)
+static void opt_precompute(struct opt_precomputed *opt, struct opt_item *item)
 {
-  return MIN(x, y);
+  opt->item = item;
+  opt->count = 0;
+  opt->name = item->name;
+  uns flags = item->flags;
+
+  if (item->letter >= OPT_POSITIONAL_TAIL) {
+    flags &= ~OPT_VALUE_FLAGS;
+    flags |= OPT_REQUIRED_VALUE;
+  }
+  if (!(flags & OPT_VALUE_FLAGS)) {
+    ASSERT(item->cls != OPT_CL_CALL && item->cls != OPT_CL_USER);
+    flags |= opt_default_value_flags[item->cls];
+  }
+
+  opt->flags = flags;
 }
 
-void opt_help(const struct opt_section * help) {
-  int sections_cnt = 0;
-  int lines_cnt = 0;
+#define FOREACHLINE(text) for (const char * begin = (text), * end = (text); (*end) && (end = strchrnul(begin, '\n')); begin = end+1)
 
-  for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
-    if (item->flags & OPT_NO_HELP) continue;
-    if (item->cls == OPT_CL_SECTION) {
-      sections_cnt++;
-      continue;
-    }
-    if (!*(item->help)) {
-      lines_cnt++;
-      continue;
-    }
-    FOREACHLINE(item->help)
-      lines_cnt++;
-  }
+struct help {
+  struct mempool *pool;
+  struct help_line *lines;                     // A growing array of lines
+};
 
-  struct opt_sectlist {
-    int pos;
-    struct opt_section * sect;
-  } sections[sections_cnt];
-  int s = 0;
+struct help_line {
+  const char *extra;
+  char *fields[3];
+};
 
-  const char *lines[lines_cnt][3];
-  memset(lines, 0, sizeof(lines));
-  int line = 0;
+static void opt_help_scan_item(struct help *h, struct opt_precomputed *opt)
+{
+  struct opt_item *item = opt->item;
 
-  int linelengths[3] = { -1, -1, -1 };
+  if (opt->flags & OPT_NO_HELP)
+    return;
 
-  for (struct opt_item * item = help->opt; item->cls != OPT_CL_END; item++) {
-    if (item->flags & OPT_NO_HELP) continue;
+  if (item->cls == OPT_CL_HELP) {
+    struct help_line *l = GARY_PUSH(h->lines, 1);
+    l->extra = item->help ? : "";
+    return;
+  }
 
-    if (item->cls == OPT_CL_HELP) {
-      if (!*(item->help)) {
-       line++;
-       continue;
+  if (item->letter >= OPT_POSITIONAL_TAIL)
+    return;
+
+  struct help_line *first = GARY_PUSH(h->lines, 1);
+  if (item->help) {
+    char *text = mp_strdup(h->pool, item->help);
+    struct help_line *l = first;
+    while (text) {
+      char *eol = strchr(text, '\n');
+      if (eol)
+       *eol++ = 0;
+
+      int field = (l == first ? 1 : 0);
+      char *f = text;
+      while (f) {
+       char *tab = strchr(f, '\t');
+       if (tab)
+         *tab++ = 0;
+       if (field < 3)
+         l->fields[field++] = f;
+       f = tab;
       }
-#define SPLITLINES(text) do { \
-      FOREACHLINE(text) { \
-       int cell = 0; \
-       for (const char * b = begin, * e = begin; (e < end) && (e = strchrnul(b, '\t')) && (e > end ? (e = end) : end); b = e+1) { \
-         lines[line][cell] = b; \
-         if (cell >= 2) \
-           break; \
-         else \
-           if (*e == '\t' && linelengths[cell] < (e - b)) \
-             linelengths[cell] = e-b; \
-         cell++; \
-       } \
-       line++; \
-      } } while (0)
-      SPLITLINES(item->help);
-      continue;
-    }
 
-    if (item->cls == OPT_CL_SECTION) {
-      sections[s++] = (struct opt_sectlist) { .pos = line, .sect = item->u.section };
-      continue;
+      text = eol;
+      if (text)
+       l = GARY_PUSH(h->lines, 1);
     }
+  }
 
-    uns valoff = strchrnul(item->help, '\t') - item->help;
-    uns eol = strchrnul(item->help, '\n') - item->help;
-    if (valoff > eol)
-      valoff = eol;
-#define VAL(it) ((it->flags & OPT_REQUIRED_VALUE) ? stk_printf("=%.*s", valoff, item->help)  : ((it->flags & OPT_NO_VALUE) ? "" : stk_printf("(=%.*s)", valoff, item->help)))
-    if (item->name) {
-      lines[line][1] = stk_printf("--%s%s", item->name, VAL(item));
-      if (linelengths[1] < (int) strlen(lines[line][1]))
-       linelengths[1] = strlen(lines[line][1]);
-      lines[line][0] = "";
-      if (linelengths[0] < 0)
-       linelengths[0] = 0;
-    }
-    if (item->letter) {
-      lines[line][0] = stk_printf("-%c,", item->letter);
-      if (linelengths[0] < (int) strlen(lines[line][0]))
-       linelengths[0] = strlen(lines[line][0]);
-    }
-#undef VAL
+  if (item->name) {
+    char *val = first->fields[1] ? : "";
+    if (opt->flags & OPT_REQUIRED_VALUE)
+      val = mp_printf(h->pool, "=%s", val);
+    else if (!(opt->flags & OPT_NO_VALUE))
+      val = mp_printf(h->pool, "[=%s]", val);
+    first->fields[1] = mp_printf(h->pool, "--%s%s", item->name, val);
+  }
 
-    if (eol > valoff) {
-      lines[line][2] = item->help + valoff + 1;
+  if (item->letter) {
+    if (item->name)
+      first->fields[0] = mp_printf(h->pool, "-%c, ", item->letter);
+    else {
+      char *val = first->fields[1] ? : "";
+      if (!(opt->flags & OPT_REQUIRED_VALUE) && !(opt->flags & OPT_NO_VALUE))
+       val = mp_printf(h->pool, "[%s]", val);
+      first->fields[0] = mp_printf(h->pool, "-%c%s", item->letter, val);
+      first->fields[1] = NULL;
     }
+  }
+}
 
-    line++;
-
-    if (*(item->help + eol))
-      SPLITLINES(item->help + eol + 1);
+static void opt_help_scan(struct help *h, const struct opt_section *sec)
+{
+  for (struct opt_item * item = sec->opt; item->cls != OPT_CL_END; item++) {
+    if (item->cls == OPT_CL_SECTION)
+      opt_help_scan(h, item->u.section);
+    else {
+      struct opt_precomputed opt;
+      opt_precompute(&opt, item);
+      opt_help_scan_item(h, &opt);
+    }
   }
-#undef SPLITLINES
-
-  s = 0;
-#define FIELD(k) linelengths[k], uns_min(strchrnul(lines[i][k], '\t') - lines[i][k], strchrnul(lines[i][k], '\n') - lines[i][k]), lines[i][k]
-#define LASTFIELD(k) uns_min(strchrnul(lines[i][k], '\t') - lines[i][k], strchrnul(lines[i][k], '\n') - lines[i][k]), lines[i][k]
-  for (int i=0;i<line;i++) {
-    while (s < sections_cnt && sections[s].pos == i) {
-      opt_help(sections[s].sect);
-      s++;
+}
+
+void opt_help(const struct opt_section * sec) {
+  // Prepare help text
+  struct help h;
+  h.pool = mp_new(4096);
+  GARY_INIT_ZERO(h.lines, 0);
+  opt_help_scan(&h, sec);
+
+  // Calculate natural width of each column
+  uns n = GARY_SIZE(h.lines);
+  uns widths[3] = { 0, 0, 0 };
+  for (uns i=0; i<n; i++) {
+    struct help_line *l = &h.lines[i];
+    for (uns f=0; f<3; f++) {
+      if (!l->fields[f])
+       l->fields[f] = "";
+      uns w = strlen(l->fields[f]);
+      widths[f] = MAX(widths[f], w);
     }
-    if (lines[i][0] == NULL)
-      printf("\n");
-    else if (linelengths[0] == -1 || lines[i][1] == NULL)
-      printf("%.*s\n", LASTFIELD(0));
-    else if (linelengths[1] == -1 || lines[i][2] == NULL)
-      printf("%-*.*s  %.*s\n", FIELD(0), LASTFIELD(1));
-    else
-      printf("%-*.*s  %-*.*s  %.*s\n", FIELD(0), FIELD(1), LASTFIELD(2));
   }
-  while (s < sections_cnt && sections[s].pos == line) {
-    opt_help(sections[s].sect);
-    s++;
+  if (widths[0] > 4) {
+    /*
+     *  This is tricky: if there are short options, which have an argument,
+     *  but no long variant, we are willing to let column 0 overflow to column 1.
+     */
+    widths[1] = MAX(widths[1], widths[0] - 4);
+    widths[0] = 4;
+  }
+  widths[1] += 4;
+
+  // Print columns
+  for (uns i=0; i<n; i++) {
+    struct help_line *l = &h.lines[i];
+    if (l->extra)
+      puts(l->extra);
+    else {
+      int t = 0;
+      for (uns f=0; f<3; f++) {
+       t += widths[f];
+       t -= printf("%s", l->fields[f]);
+       while (t > 0) {
+         putchar(' ');
+         t--;
+       }
+      }
+      putchar('\n');
+    }
   }
+
+  // Clean up
+  GARY_FREE(h.lines);
+  mp_delete(h.pool);
 }
 
 static struct opt_precomputed * opt_find_item_longopt(struct opt_context * oc, char * str) {
@@ -455,23 +496,6 @@ static void opt_count_items(struct opt_context *oc, const struct opt_section *se
   }
 }
 
-static void opt_add_default_flags(struct opt_precomputed *opt)
-{
-  struct opt_item *item = opt->item;
-  uns flags = opt->flags;
-
-  if (item->letter >= OPT_POSITIONAL_TAIL) {
-    flags &= ~OPT_VALUE_FLAGS;
-    flags |= OPT_REQUIRED_VALUE;
-  }
-  if (!(flags & OPT_VALUE_FLAGS)) {
-    ASSERT(item->cls != OPT_CL_CALL && item->cls != OPT_CL_USER);
-    flags |= opt_default_value_flags[item->cls];
-  }
-
-  opt->flags = flags;
-}
-
 static void opt_prepare_items(struct opt_context *oc, const struct opt_section *sec)
 {
   for (struct opt_item *item = sec->opt; item->cls != OPT_CL_END; item++) {
@@ -488,13 +512,9 @@ static void opt_prepare_items(struct opt_context *oc, const struct opt_section *
        ASSERT(0);
     } else if (item->letter || item->name) {
       struct opt_precomputed * opt = &oc->opts[oc->opt_count++];
-      opt->item = item;
-      opt->flags = item->flags;
-      opt->count = 0;
-      opt->name = item->name;
+      opt_precompute(opt, item);
       if (item->letter)
        oc->shortopt[(int) item->letter] = opt;
-      opt_add_default_flags(opt);
     }
   }
 }
index cda8022f5642d2acff1a7459da1f05eacc887560..c50bac009e509379bb1804c5f9087932c7649c39 100644 (file)
--- a/ucw/opt.h
+++ b/ucw/opt.h
@@ -73,7 +73,6 @@ struct opt_section {
  *
  *  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;
@@ -90,7 +89,7 @@ struct opt_section {
  *
  ***/
 
-#define OPT_HELP_OPTION(help) OPT_CALL(0, "help", opt_show_help_internal, &help, OPT_NO_VALUE, "Show this help")
+#define OPT_HELP_OPTION(help) OPT_CALL(0, "help", opt_show_help_internal, &help, OPT_NO_VALUE, "\tShow this help")
 #define OPT_HELP(line) { .help = line, .cls = OPT_CL_HELP }
 #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 = &target, .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_STRING }
@@ -124,9 +123,9 @@ struct opt_section {
 #define OPT_CONF_OPTIONS    OPT_CONF_CONFIG, OPT_CONF_SET, OPT_CONF_HOOK
 #endif
 
-#define OPT_CONF_CONFIG            OPT_CALL('C', "config", opt_conf_internal, NULL, OPT_REQUIRED_VALUE, "Override the default configuration file")
-#define OPT_CONF_SET       OPT_CALL('S', "set", opt_conf_internal, NULL, OPT_REQUIRED_VALUE, "Manual setting of a configuration item")
-#define OPT_CONF_DUMPCONFIG OPT_CALL(0, "dumpconfig", opt_conf_internal, NULL, OPT_NO_VALUE, "Dump program configuration")
+#define OPT_CONF_CONFIG            OPT_CALL('C', "config", opt_conf_internal, NULL, OPT_REQUIRED_VALUE, "\tOverride the default configuration file")
+#define OPT_CONF_SET       OPT_CALL('S', "set", opt_conf_internal, NULL, OPT_REQUIRED_VALUE, "\tManual setting of a configuration item")
+#define OPT_CONF_DUMPCONFIG OPT_CALL(0, "dumpconfig", opt_conf_internal, NULL, OPT_NO_VALUE, "\tDump program configuration")
 #define OPT_CONF_HOOK      OPT_HOOK(opt_conf_hook_internal, NULL, OPT_HOOK_BEFORE_VALUE)
 
 void opt_conf_internal(struct opt_item * opt, const char * value, void * data);
@@ -172,7 +171,7 @@ void opt_conf_hook_internal(struct opt_item * opt, const char * value, void * da
 #define OPT_HOOK_AFTER_VALUE   0x4000  /** Call after value parsing **/
 
 extern const struct opt_section * opt_section_root;
-void opt_help(const struct opt_section * help);
+void opt_help(const struct opt_section * sec);
 
 static inline void opt_usage(void) {
   fprintf(stderr, "Run with argument --help for more information.\n");