]> mj.ucw.cz Git - libucw.git/commitdiff
Opt: defined user interface
authorJan 'Moskyt' Matejka <mq@ucw.cz>
Thu, 31 Jan 2013 09:14:49 +0000 (10:14 +0100)
committerJan 'Moskyt' Matejka <mq@ucw.cz>
Mon, 15 Apr 2013 15:20:44 +0000 (17:20 +0200)
ucw/Makefile
ucw/opt.c [new file with mode: 0644]
ucw/opt.h [new file with mode: 0644]
ucw/opt.t [new file with mode: 0644]

index 9f1247fbabc01b2444fe2f2b837af44df84d45d7..d19182736404f3e564b1f051b99cef24d203ae16 100644 (file)
@@ -35,7 +35,8 @@ LIBUCW_MODS= \
        strtonum \
        resource trans res-fd res-mem res-subpool res-mempool res-eltpool \
        daemon daemon-ctrl \
-       signames
+       signames \
+       opt
 
 LIBUCW_MAIN_INCLUDES= \
        lib.h log.h threads.h time.h \
@@ -63,7 +64,8 @@ LIBUCW_MAIN_INCLUDES= \
        strtonum.h \
        resource.h trans.h \
        daemon.h \
-       signames.h
+       signames.h \
+       opt.h
 
 ifdef CONFIG_UCW_THREADS
 # Some modules require threading
@@ -119,7 +121,7 @@ TESTS+=$(addprefix $(o)/ucw/,varint.test regex.test unicode.test hash-test.test
     fb-socket.test trie-test.test string.test sha1.test asort-test.test binheap-test.test \
     redblack-test.test fb-file.test fb-grow.test fb-pool.test fb-atomic.test \
     fb-limfd.test fb-temp.test fb-mem.test fb-buffer.test fb-mmap.test fb-multi.test url.test strtonum-test.test \
-    gary.test time.test crc.test signames.test md5.test)
+    gary.test time.test crc.test signames.test md5.test opt.test)
 
 $(o)/ucw/varint.test: $(o)/ucw/varint-t
 $(o)/ucw/regex.test: $(o)/ucw/regex-t
@@ -150,6 +152,7 @@ $(o)/ucw/time.test: $(o)/ucw/time-conf-t
 $(o)/ucw/crc.test: $(o)/ucw/crc-t
 $(o)/ucw/signames.test: $(o)/ucw/signames-t
 $(o)/ucw/md5.test: $(o)/ucw/md5-t
+$(o)/ucw/opt.test: $(o)/ucw/opt-t
 
 ifdef CONFIG_UCW_THREADS
 TESTS+=$(addprefix $(o)/ucw/,asio.test)
diff --git a/ucw/opt.c b/ucw/opt.c
new file mode 100644 (file)
index 0000000..7989ab8
--- /dev/null
+++ b/ucw/opt.c
@@ -0,0 +1,154 @@
+/*
+ *     UCW Library -- Parsing of command line options
+ *
+ *     (c) 2013 Jan Moskyto Matejka <mq@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include <ucw/lib.h>
+#include <ucw/opt.h>
+#include <ucw/stkstring.h>
+
+#include <alloca.h>
+
+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->cls == OPT_CL_HELP && item->u.help2 == NULL) continue;
+    if (item->cls == OPT_CL_SECTION) continue;
+    
+    uns linelen = 0;
+    if (item->cls == OPT_CL_HELP) { // two-column help line
+      if (first_column < strlen(item->help))
+       first_column = strlen(item->help);
+      continue;
+    }
+    
+    if (item->letter) { // will write sth like "-x, --exclusive"
+      linelen = strlen("-x, --") + strlen(item->name);
+    } else { // will write sth like "--exclusive"
+      linelen = strlen("--") + strlen(item->name);
+    }
+
+    if (item->flags | OPT_REQUIRED_VALUE) {
+      linelen += strlen("=value");
+    } else if (!(item->flags | OPT_NO_VALUE)) {
+      linelen += strlen("(=value)");
+    }
+
+    if (linelen > first_column)
+      first_column = linelen;
+  }
+
+  char * spaces = alloca(first_column + 1);
+  char * buf = alloca(first_column + 1);
+  for (uns i=0;i<first_column;i++)
+    spaces[i] = ' ';
+
+  spaces[first_column] = 0;
+
+#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->cls == OPT_CL_HELP) {
+      fprintf(stderr, "%s", item->help);
+      if (item->u.help2 == NULL)
+       fprintf(stderr, "\n");
+      else
+       fprintf(stderr, "%s %s\n", spaces + strlen(item->help), item->u.help2);
+    } else if (item->cls == OPT_CL_SECTION) {
+      opt_help_noexit_internal(item->u.section);
+    } 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 {
+      sprintf(buf, "--%s%s", item->name, VAL(item));
+      fprintf(stderr, "%s%s %s\n", buf, spaces + strlen(buf), item->help);
+    }
+  }
+}
+
+void opt_parse(struct opt_section * options) {
+  opt_section_root = options;
+  opt_help();
+}
+
+#ifdef TEST
+
+static enum TEAPOT_TYPE {
+  TEAPOT_STANDARD = 0,
+  TEAPOT_EXCLUSIVE,
+  TEAPOT_GLASS,
+  TEAPOT_HANDS,
+  TEAPOT_UNDEFINED = -1
+} set = TEAPOT_UNDEFINED;
+
+static int english = 0;
+static char * name = NULL;
+static uns sugar = 0;
+static uns verbose = 1;
+static int with_gas = 0;
+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 int parse_temperature(const char * param, void * target) {
+  return 1;
+}
+
+static struct opt_section water_options = {
+  OPT_ITEMS {
+    OPT_UNS('w', "water", water_amount, OPT_REQUIRED | OPT_REQUIRED_VALUE, "Amount of water (in mls)"),
+    OPT_BOOL('G', "with-gas", with_gas, OPT_NO_VALUE, "Use water with gas"),
+    OPT_END
+  }
+};
+
+static struct opt_section help = {
+  OPT_ITEMS {
+    OPT_HELP("A simple tea boiling console."),
+    OPT_HELP("Options:"),
+    OPT_SHOW_HELP(0),
+    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_HELP2("", "Supported scales: Celsius [60C], Fahrenheit [140F],"),
+    OPT_HELP2("", "                  Kelvin [350K], Rankine [600R] and Reaumur [50Re]"),
+    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"),
+    OPT_BOOL('p', "pray", pray, 0, "Pray before boiling"),
+    OPT_HELP(""),
+    OPT_HELP("Water options:"),
+    OPT_SECTION(water_options),
+    OPT_END
+  }
+};
+
+int main(int argc, char ** argv)
+{
+  opt_parse(&help);
+}
+
+#endif
diff --git a/ucw/opt.h b/ucw/opt.h
new file mode 100644 (file)
index 0000000..5f68fea
--- /dev/null
+++ b/ucw/opt.h
@@ -0,0 +1,131 @@
+/*
+ *     UCW Library -- Parsing of command line options
+ *
+ *     (c) 2013 Jan Moskyto Matejka <mq@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#ifndef _UCW_OPT_H
+#define _UCW_OPT_H
+
+#include <stdlib.h>
+#include <stdio.h>
+
+/***
+ * [[opt]]
+ * Parsing of command line options
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ***/
+
+enum opt_class {
+  OPT_CL_END,    // end of list
+  OPT_CL_BOOL,   // boolean value
+  OPT_CL_STATIC,  // static value
+  OPT_CL_SWITCH,  // lookup/switch
+  OPT_CL_INC,    // incremental value
+  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);
+
+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
+  union opt_union {
+    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
+  } u;
+  short flags;
+  enum opt_class cls;
+  enum opt_type type;
+};
+
+struct opt_section {
+  struct opt_item *opt;
+};
+
+#define OPT_ITEMS      .opt = ( struct opt_item[] )  /** List of sub-items. **/
+
+/** Sub-items to be enclosed in OPT_ITEMS { } list
+ *
+ * OPT_SHOW_HELP 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_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
+ *
+ * **/
+
+#define OPT_SHOW_HELP(flags) OPT_USER(0, "help", *(NULL), opt_help_success2, flags, "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_SECTION(sec) { .cls = OPT_CL_SECTION, .u.section = &sec }
+#define OPT_END { .cls = OPT_CL_END }
+
+/** Flags for the preceeding calls **/
+#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
+
+extern struct opt_section * opt_section_root;
+void opt_help_noexit_internal(struct opt_section * help);
+
+static void opt_help_noexit(void) {
+  opt_help_noexit_internal(opt_section_root);
+}
+
+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) {
+  opt_help_noexit();
+  exit(0);
+}
+
+static void opt_help(void) {
+  opt_help_noexit();
+  exit(1);
+}
+
+static void opt_usage(void) {
+  opt_usage_noexit();
+  exit(1);
+}
+
+void opt_parse(struct opt_section * options);
+
+#endif
diff --git a/ucw/opt.t b/ucw/opt.t
new file mode 100644 (file)
index 0000000..bac1992
--- /dev/null
+++ b/ucw/opt.t
@@ -0,0 +1,5 @@
+# Tests of the command line option parser
+
+Name:  Opt-1
+Run:   ../obj/ucw/opt-t --help
+Out:   bagr kombajn