From: Jan 'Moskyt' Matejka Date: Thu, 31 Jan 2013 09:14:49 +0000 (+0100) Subject: Opt: defined user interface X-Git-Tag: v5.99~25^2~59 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=84d58e01664d047c47f8c95e663d7959aa40464b;p=libucw.git Opt: defined user interface --- diff --git a/ucw/Makefile b/ucw/Makefile index 9f1247fb..d1918273 100644 --- a/ucw/Makefile +++ b/ucw/Makefile @@ -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 index 00000000..7989ab84 --- /dev/null +++ b/ucw/opt.c @@ -0,0 +1,154 @@ +/* + * UCW Library -- Parsing of command line options + * + * (c) 2013 Jan Moskyto Matejka + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#include +#include +#include + +#include + +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;iflags | 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 index 00000000..5f68fea3 --- /dev/null +++ b/ucw/opt.h @@ -0,0 +1,131 @@ +/* + * UCW Library -- Parsing of command line options + * + * (c) 2013 Jan Moskyto Matejka + * + * 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 +#include + +/*** + * [[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 index 00000000..bac19921 --- /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