# HG changeset patch # User John Tsiombikas # Date 1447451311 -7200 # Node ID 10b89befcaa9367d114ac94d923f5d35106a1256 initial commit diff -r 000000000000 -r 10b89befcaa9 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Fri Nov 13 23:48:31 2015 +0200 @@ -0,0 +1,6 @@ +\.o$ +\.swp$ +\.d$ +\.a$ +\.so\. +\.dylib$ diff -r 000000000000 -r 10b89befcaa9 LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Fri Nov 13 23:48:31 2015 +0200 @@ -0,0 +1,3 @@ +liboptcfg, written by John Tsiombikas is public domain +software. Feel free to use it any way you like. Attribution appreciated but not +required. diff -r 000000000000 -r 10b89befcaa9 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Fri Nov 13 23:48:31 2015 +0200 @@ -0,0 +1,54 @@ +src = $(wildcard src/*.c) +obj = $(src:.c=.o) +name = optcfg + +so_major = 0 +so_minor = 1 + +alib = lib$(name).a + +CFLAGS = -pedantic -Wall -g + +sys := $(shell uname -s) +ifeq ($(sys), Darwin) + solib = lib$(name).dylib + shared = -dynamiclib +else + ldname = lib$(name).so + soname = lib$(name).so.$(so_major) + solib = lib$(name).so.$(so_major).$(so_minor) + shared = -shared -Wl,-soname=$(soname) + CFLAGS += -fPIC +endif + +.PHONY: all +all: $(solib) $(alib) + +$(solib): $(obj) + $(CC) -o $@ $(shared) $(obj) $(LDFLAGS) + +$(alib): $(obj) + $(AR) rcs $@ $(obj) + +.PHONY: clean +clean: + rm -f $(obj) $(alib) $(solib) + +.PHONY: install +install: + mkdir -p $(DESTDIR)$(PREFIX)/include $(DESTDIR)$(PREFIX)/lib + cp src/optcfg.h $(DESTDIR)$(PREFIX)/include/optcfg.h + cp $(alib) $(DESTDIR)$(PREFIX)/lib/$(alib) + cp $(solib) $(DESTDIR)$(PREFIX)/lib/$(solib) + [ -n "$(soname)" ] && cd $(DESTDIR)$(PREFIX)/lib && \ + ln -s $(solib) $(soname) && \ + ln -s $(soname) $(ldname) || true + +.PHONY: uninstall +uninstall: + rm -f $(DESTDIR)$(PREFIX)/include/optcfg.h + rm -f $(DESTDIR)$(PREFIX)/lib/$(alib) + rm -f $(DESTDIR)$(PREFIX)/lib/$(solib) + [ -n "$(soname)" ] && \ + rm -f $(DESTDIR)$(PREFIX)/lib/$(soname) && \ + rm -f $(DESTDIR)$(PREFIX)/lib/$(ldname) || true diff -r 000000000000 -r 10b89befcaa9 src/optcfg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/optcfg.c Fri Nov 13 23:48:31 2015 +0200 @@ -0,0 +1,317 @@ +#include +#include +#include +#include +#ifdef _MSC_VER +#include +#else +#include +#endif +#include "optcfg.h" + +struct optcfg { + struct optcfg_option *optlist; + + optcfg_opt_callback opt_func; + void *opt_cls; + optcfg_arg_callback arg_func; + void *arg_cls; + + int err_abort; + + /* argument parsing state */ + char **argv; + int argidx; + + /* config file parsing state */ + const char *cfg_fname; + int cfg_nline; + char *cfg_value; +}; + +static int get_opt(struct optcfg *oc, const char *s); +static char *skip_spaces(char *s); +static void strip_comments(char *s); +static void strip_trailing_spaces(char *s); +static char *parse_keyval(char *line); + + +struct optcfg *optcfg_init(struct optcfg_option *optv) +{ + struct optcfg *oc; + + if(!(oc = calloc(1, sizeof *oc))) { + return 0; + } + oc->optlist = optv; + oc->err_abort = 1; + return oc; +} + +void optcfg_destroy(struct optcfg *oc) +{ + memset(oc, 0, sizeof *oc); + free(oc); +} + +void optcfg_set_opt_callback(struct optcfg *oc, optcfg_opt_callback func, void *cls) +{ + oc->opt_func = func; + oc->opt_cls = cls; +} + +void optcfg_set_arg_callback(struct optcfg *oc, optcfg_arg_callback func, void *cls) +{ + oc->arg_func = func; + oc->arg_cls = cls; +} + +void optcfg_set_error_action(struct optcfg *oc, int act) +{ + if(act == OPTCFG_ERROR_FAIL) { + oc->err_abort = 1; + } else if(act == OPTCFG_ERROR_IGNORE) { + oc->err_abort = 0; + } +} + +int optcfg_parse_args(struct optcfg *oc, int argc, char **argv) +{ + int i; + + oc->argv = argv; + + for(i=1; iargidx = i; + + if(argv[i][0] == '-') { + if(oc->opt_func) { + int o = get_opt(oc, argv[i]); + if(o == -1 || oc->opt_func(oc, o, oc->opt_cls) == -1) { + if(oc->err_abort) { + return -1; + } + } + } else { + fprintf(stderr, "unexpected option: %s\n", argv[i]); + if(oc->err_abort) { + return -1; + } + } + } else { + if(oc->arg_func) { + if(oc->arg_func(oc, argv[i], oc->arg_cls) == -1) { + if(oc->err_abort) { + return -1; + } + } + } else { + fprintf(stderr, "unexpected argument: %s\n", argv[i]); + if(oc->err_abort) { + return -1; + } + } + } + + i = oc->argidx; + } + + oc->argidx = 0; /* done parsing args */ + return 0; +} + +int optcfg_parse_config_file(struct optcfg *oc, const char *fname) +{ + int res; + FILE *fp = fopen(fname, "rb"); + if(!fp) { + return -1; + } + + oc->cfg_fname = fname; + res = optcfg_parse_config_stream(oc, fp); + oc->cfg_fname = 0; + fclose(fp); + return res; +} + +int optcfg_parse_config_stream(struct optcfg *oc, FILE *fp) +{ + char buf[512]; + + oc->cfg_nline = 0; + while(fgets(buf, sizeof buf, fp)) { + ++oc->cfg_nline; + + if(optcfg_parse_config_line(oc, buf) == -1) { + if(oc->err_abort) { + return -1; + } + } + } + return 0; +} + +int optcfg_parse_config_line(struct optcfg *oc, const char *line) +{ + int opt, len; + char *start, *val, *buf; + + len = strlen(line); + buf = alloca(len + 1); + memcpy(buf, line, len); + + start = skip_spaces(buf); + strip_comments(start); + strip_trailing_spaces(start); + if(!*start) { + return 0; + } + + if(!(val = parse_keyval(start))) { + fprintf(stderr, "error parsing %s line %d: invalid syntax\n", oc->cfg_fname ? oc->cfg_fname : "", oc->cfg_nline); + return -1; + } + oc->cfg_value = val; + if((opt = get_opt(oc, start)) == -1) { + fprintf(stderr, "error parsing %s line %d: unknown option: %s\n", oc->cfg_fname ? oc->cfg_fname : "", + oc->cfg_nline, start); + return -1; + } + + if(oc->opt_func) { + if(oc->opt_func(oc, opt, oc->opt_cls) == -1) { + return -1; + } + } + oc->cfg_value = 0; + return 0; +} + +char *optcfg_next_value(struct optcfg *oc) +{ + if(oc->argidx) { /* we're in the middle of parsing arguments, so get the next one */ + return oc->argv[++oc->argidx]; + } + if(oc->cfg_value) { + char *val = oc->cfg_value; + oc->cfg_value = 0; + return val; + } + return 0; +} + +void optcfg_print_options(struct optcfg *oc) +{ + int i; + for(i=0; oc->optlist[i].opt != -1; i++) { + struct optcfg_option *opt = oc->optlist + i; + + if(opt->c) { + printf(" -%c", opt->c); + } else { + printf(" "); + } + if(opt->s) { + printf("%c-%s: ", opt->c ? ',' : ' ', opt->s); + } else { + printf(": "); + } + printf("%s\n", opt->desc ? opt->desc : "undocumented"); + } +} + +int optcfg_bool_value(char *s, int *valret) +{ + if(strcasecmp(s, "yes") == 0 || strcasecmp(s, "true") == 0 || strcmp(s, "1") == 0) { + *valret = 1; + return 0; + } + if(strcasecmp(s, "no") == 0 || strcasecmp(s, "false") == 0 || strcmp(s, "0") == 0) { + *valret = 0; + return 0; + } + return -1; +} + +int optcfg_int_value(char *str, int *valret) +{ + char *endp; + *valret = strtol(str, &endp, 0); + if(endp == str) { + return -1; + } + return 0; +} + +int optcfg_float_value(char *str, float *valret) +{ + char *endp; + *valret = strtod(str, &endp); + if(endp == str) { + return -1; + } + return 0; +} + + + +static int get_opt(struct optcfg *oc, const char *arg) +{ + int i; + + if(arg[0] != '-' || !arg[1]) { + return -1; + } + + if(arg[2]) { /* match long options */ + for(i=0; oc->optlist[i].opt != -1; i++) { + if(strcmp(arg + 1, oc->optlist[i].s) == 0) { + return i; + } + } + } else { + for(i=0; oc->optlist[i].opt != -1; i++) { + if(arg[1] == oc->optlist[i].c) { + return i; + } + } + } + return -1; +} + +static char *skip_spaces(char *s) +{ + while(*s && isspace(*s)) ++s; + return s; +} + +static void strip_comments(char *s) +{ + while(*s && *s != '#') ++s; + if(*s == '#') *s = 0; +} + +static void strip_trailing_spaces(char *s) +{ + char *end = s + strlen(s) - 1; + while(end >= s && isspace(*end)) { + *end-- = 0; + } +} + +static char *parse_keyval(char *line) +{ + char *val; + char *eq = strchr(line, '='); + if(!eq) return 0; + + *eq = 0; + strip_trailing_spaces(line); + val = skip_spaces(eq + 1); + + if(!*line || !*val) { + return 0; + } + return val; +} diff -r 000000000000 -r 10b89befcaa9 src/optcfg.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/optcfg.h Fri Nov 13 23:48:31 2015 +0200 @@ -0,0 +1,70 @@ +/* generic unified commandline option and config file parsing library */ +#ifndef LIBOPTCFG_H_ +#define LIBOPTCFG_H_ + +struct optcfg; + +struct optcfg_option { + char c; /* short (optional): used only for argument parsing */ + char *s; /* long: used for long options and config files */ + int opt; /* the corresponding option enumeration */ + char *desc; /* text description for printing usage information */ +}; + +typedef int (*optcfg_opt_callback)(struct optcfg *oc, int opt, void *cls); +typedef int (*optcfg_arg_callback)(struct optcfg *oc, const char *arg, void *cls); + +/* initialize the optcfg object with a valid option vector terminated by an + * entry with an opt value of -1 (other fields ignored for termination purposes) + * + * Example: + * struct optcfg_option options[] = { + * {'f', "foo", OPT_FOO, "Makes sure the foo is bar"}, + * {'h', "help", OPT_HELP, "Print usage information and exit"}, + * {0, 0, -1, 0} + * }; + * struct optcfg *oc = optcfg_init(options); + */ +struct optcfg *optcfg_init(struct optcfg_option *optv); +void optcfg_destroy(struct optcfg *oc); + +/* The parse_* functions call the option callback for each option. + * + * The option callback can then call optcfg_next_value to retrieve any + * values attached to this option. When optcfg_next_value returns 0, there + * are no more values available. + * The option callback must return 0 for success, and -1 to abort parsing. + */ +void optcfg_set_opt_callback(struct optcfg *oc, optcfg_opt_callback func, void *cls); +/* the argument callback is only called from optcfg_parse_args(), when a non-option + * argument is encountered (an argument not starting with a dash) + */ +void optcfg_set_arg_callback(struct optcfg *oc, optcfg_arg_callback func, void *cls); + +enum { OPTCFG_ERROR_FAIL, OPTCFG_ERROR_IGNORE }; +void optcfg_set_error_action(struct optcfg *oc, int act); + +int optcfg_parse_args(struct optcfg *oc, int argc, char **argv); +int optcfg_parse_config_file(struct optcfg *oc, const char *fname); +int optcfg_parse_config_stream(struct optcfg *oc, FILE *fp); +int optcfg_parse_config_line(struct optcfg *oc, const char *line); +/* TODO custom I/O callback version of config file parsing */ + +/* call optcfg_next_value in the option callback to retrieve the next value + * of the current option. returns 0 if there is no next value. + */ +char *optcfg_next_value(struct optcfg *oc); + +/* helper function which can be used to print the available options */ +void optcfg_print_options(struct optcfg *oc); + +/* helper functions to convert value strings to typed values + * returns 0 for success and value is returned through the valret pointer, + * otherwise it returns -1 for type mismatch, and valret contents are undefined + */ +int optcfg_bool_value(char *str, int *valret); /* accepts yes/no, true/false, 1/0 */ +int optcfg_int_value(char *str, int *valret); +int optcfg_float_value(char *str, float *valret); + + +#endif /* LIBOPTCFG_H_ */