cfparser: API reorg, regtests
authorMarko Kreen <markokr@gmail.com>
Thu, 18 Nov 2010 14:10:36 +0000 (16:10 +0200)
committerMarko Kreen <markokr@gmail.com>
Mon, 22 Nov 2010 22:37:54 +0000 (00:37 +0200)
Support both dynamic sections and dynamic keys.

Cleanup of both internal and external APIs.

test/Makefile
test/test_cfparser.c [new file with mode: 0644]
test/test_cfparser.ini [new file with mode: 0644]
test/test_common.c
test/test_common.h
usual/cfparser.c
usual/cfparser.h

index 22662c78505c21041bbd9823fef6e9cf1e235618..0d5f692a0643a0767bf9ce6e8d491fa6819d21c7 100644 (file)
@@ -9,10 +9,12 @@ override CPPFLAGS = -I. -I..
 override USUAL_DIR = ..
 
 override DEFS = -DUSUAL_TEST_CONFIG
-OBJS = test_string.o test_crypto.o test_aatree.o test_heap.o \
-       test_common.o test_list.o tinytest.o test_cbtree.o \
-       test_utf8.o test_strpool.o test_pgutil.o test_regex.o \
-       test_cxalloc.o test_bits.o test_base.o test_netdb.o
+SRCS = test_string.c test_crypto.c test_aatree.c test_heap.c \
+       test_common.c test_list.c tinytest.c test_cbtree.c \
+       test_utf8.c test_strpool.c test_pgutil.c test_regex.c \
+       test_cxalloc.c test_bits.c test_base.c test_netdb.c \
+       test_cfparser.c
+OBJS = $(SRCS:.c=.o)
 
 test-all: regtest.compat
 
@@ -41,14 +43,14 @@ clean-test:
 config.mak: ../config.mak
        cp ../config.mak .
 
-regtest.compat: ../usual/config.h force_compat.sed
+regtest.compat: ../usual/config.h force_compat.sed $(SRCS)
        sed -f force_compat.sed $< > test_config.h
        make regtest
        mv regtest regtest.compat
        mv libusual.a libusual_compat.a
        rm test_config.h
 
-regtest.system: ../usual/config.h
+regtest.system: ../usual/config.h $(SRCS)
        cp $< test_config.h
        make regtest
        mv regtest regtest.system
diff --git a/test/test_cfparser.c b/test/test_cfparser.c
new file mode 100644 (file)
index 0000000..ca68b24
--- /dev/null
@@ -0,0 +1,148 @@
+#include <usual/cfparser.h>
+#include <usual/time.h>
+#include <usual/string.h>
+#include <usual/logging.h>
+
+#include "test_common.h"
+
+struct Config1 {
+       const char *str1;
+       const char *def1;
+       int int1;
+       int bool1;
+};
+
+struct Config2 {
+       const char *str2;
+       const char *def2;
+       double time_double;
+       usec_t time_usec;
+};
+
+static struct Config1 cf1;
+static struct Config2 cf2;
+
+static void cleanup(void)
+{
+       free(cf1.str1);
+       free(cf1.def1);
+       free(cf2.str2);
+       free(cf2.def2);
+       memset(&cf1, 0, sizeof(cf1));
+       memset(&cf2, 0, sizeof(cf2));
+}
+
+static const struct CfKey keys1 [] = {
+       CF_ABS("str1", CF_STR, cf1.str1, 0, NULL),
+       CF_ABS("def1", CF_STR, cf1.def1, 0, NULL),
+       CF_ABS("int", CF_INT, cf1.int1, 0, NULL),
+       CF_ABS("bool", CF_BOOL, cf1.bool1, 0, NULL),
+       { NULL },
+};
+
+static const struct CfKey keys2 [] = {
+       CF_ABS("str2", CF_STR, cf2.str2, 0, NULL),
+       CF_ABS("def2", CF_STR, cf2.def2, 0, "somedefault"),
+       CF_ABS("time1", CF_TIME_USEC, cf2.time_usec, 0, NULL),
+       CF_ABS("time2", CF_TIME_DOUBLE, cf2.time_double, 0, NULL),
+       { NULL },
+};
+
+static const struct CfSect sects [] = {
+       { "one", keys1 },
+       { "two", keys2 },
+       { NULL },
+};
+
+static struct CfContext cfdesc1 = { sects, NULL };
+
+static void test_abs(void *ptr)
+{
+       char buf[128];
+
+       int_check(1, cf_load_file(&cfdesc1, "test_cfparser.ini"));
+
+       str_check(cf1.str1, "val1");
+       tt_assert(cf1.def1 == NULL);
+       str_check(cf2.str2, "val2");
+       str_check(cf2.def2, "somedefault");
+
+       tt_assert(cf2.time_usec == (3 * USEC / 2));
+       tt_assert(cf2.time_double == 2.5);
+
+       str_check("val1", cf_get(&cfdesc1, "one", "str1", buf, sizeof(buf)));
+       int_check(1, cf_set(&cfdesc1, "one", "str1", "val2"));
+       str_check("val2", cf_get(&cfdesc1, "one", "str1", buf, sizeof(buf)));
+end:
+       cleanup();
+}
+
+/*
+ * relative addressing.
+ */
+
+#define CF_REL_BASE struct Config1
+static const struct CfKey rkeys1 [] = {
+       CF_REL("str1", CF_STR, str1, 0, NULL),
+       CF_REL("def1", CF_STR, def1, 0, NULL),
+       CF_REL("int", CF_INT, int1, 0, NULL),
+       CF_REL("bool", CF_BOOL, bool1, 0, NULL),
+       { NULL },
+};
+#undef CF_REL_BASE
+
+#define CF_REL_BASE struct Config2
+static const struct CfKey rkeys2 [] = {
+       CF_REL("str2", CF_STR, str2, 0, NULL),
+       CF_REL("def2", CF_STR, def2, 0, "somedefault"),
+       CF_REL("time1", CF_TIME_USEC, time_usec, 0, NULL),
+       CF_REL("time2", CF_TIME_DOUBLE, time_double, 0, NULL),
+       { NULL },
+};
+#undef CF_REL_BASE
+
+static void *get_two(void *top_arg, const char *sect_name)
+{
+       return &cf2;
+}
+
+static const struct CfSect rsects [] = {
+       { "one", rkeys1 },
+       { "two", rkeys2, .base_lookup = get_two, },
+       { NULL },
+};
+
+static struct CfContext cfdesc2 = { rsects, &cf1 };
+
+static void test_rel(void *ptr)
+{
+       char buf[128];
+
+       cleanup();
+
+       int_check(1, cf_load_file(&cfdesc2, "test_cfparser.ini"));
+
+       str_check(cf1.str1, "val1");
+       tt_assert(cf1.def1 == NULL);
+       str_check(cf2.str2, "val2");
+       str_check(cf2.def2, "somedefault");
+
+       tt_assert(cf2.time_usec == (3 * USEC / 2));
+       tt_assert(cf2.time_double == 2.5);
+
+       str_check("val1", cf_get(&cfdesc2, "one", "str1", buf, sizeof(buf)));
+       int_check(1, cf_set(&cfdesc2, "one", "str1", "val2"));
+       str_check("val2", cf_get(&cfdesc2, "one", "str1", buf, sizeof(buf)));
+end:
+       cleanup();
+}
+/*
+ * Describe
+ */
+
+struct testcase_t cfparser_tests[] = {
+       { "abs", test_abs },
+       { "rel", test_rel },
+       END_OF_TESTCASES
+};
+
diff --git a/test/test_cfparser.ini b/test/test_cfparser.ini
new file mode 100644 (file)
index 0000000..e746f6f
--- /dev/null
@@ -0,0 +1,15 @@
+[one]
+
+str1 = val1
+
+int = 5
+
+bool = 1
+
+[two]
+
+str2 = val2
+
+time1 = 1.5
+time2 = 2.5
+
index c0be1bd24d4db8428b6a7053e1cabbe884c06817..77674617c866453dff367bd3141f5bae8e9791aa 100644 (file)
@@ -17,6 +17,7 @@ struct testgroup_t groups[] = {
        { "pgutil/", pgutil_tests },
        { "regex/", regex_tests },
        { "netdb/", netdb_tests },
+       { "cfparser/", cfparser_tests },
        END_OF_GROUPS
 };
 
index 8eae52221a64d5f2531b91f884ee188f46092b12..16ece15ddf7dbbe1ec7255fdaf73db04cad208ac 100644 (file)
@@ -22,4 +22,5 @@ extern struct testcase_t cxalloc_tests[];
 extern struct testcase_t bits_tests[];
 extern struct testcase_t base_tests[];
 extern struct testcase_t netdb_tests[];
+extern struct testcase_t cfparser_tests[];
 
index fdbd2fe0d4f533640299d2a409a3a88b9c235fa9..113c4d808ca6d4c25380878634454db1e79ed3e1 100644 (file)
@@ -72,7 +72,7 @@ bool parse_ini_file(const char *fn, cf_handler_f user_handler, void *arg)
                        *p = 0;
 
                        log_debug("parse_ini_file: [%s]", key);
-                       ok = user_handler(arg, CF_SECT, key, NULL);
+                       ok = user_handler(arg, true, key, NULL);
                        *p++ = o1;
                        if (!ok)
                                goto failed;
@@ -116,7 +116,9 @@ bool parse_ini_file(const char *fn, cf_handler_f user_handler, void *arg)
 
                log_debug("parse_ini_file: '%s' = '%s'", key, val);
 
-               ok = user_handler(arg, CF_KEY, key, val);
+               ok = user_handler(arg, false, key, val);
+
+               log_debug("parse_ini_file: '%s' = '%s' ok:%d", key, val, ok);
 
                /* restore data, to keep count_lines() working */
                key[klen] = o1;
@@ -136,127 +138,229 @@ failed:
        return false;
 }
 
-struct LoaderCtx {
-       const struct CfSect *sect_list;
-       const struct CfSect *cur_sect;
-       void *target;
-       void *top_arg;
-};
+/*
+ * Config framework.
+ */
 
-static void *get_dest(struct LoaderCtx *ctx, const struct CfKey *k)
+static void *get_dest(void *base, const struct CfKey *k)
 {
        char *dst;
        if (k->flags & CF_VAL_REL) {
-               if (!ctx->target)
+               /* relative address requires base */
+               if (!base)
                        return NULL;
-               dst = (char *)ctx->target + k->key_ofs;
+               dst = (char *)base + k->key_ofs;
        } else
                dst = (char *)k->key_ofs;
        return dst;
 }
 
+static const struct CfSect *find_sect(const struct CfContext *cf, const char *name)
+{
+       const struct CfSect *s;
+       for (s = cf->sect_list; s->sect_name; s++) {
+               if (strcmp(s->sect_name, name) == 0)
+                       return s;
+               if (strcmp(s->sect_name, "*") == 0)
+                       return s;
+       }
+       return NULL;
+}
+
+static const struct CfKey *find_key(const struct CfSect *s, const char *key)
+{
+       const struct CfKey *k;
+       for (k = s->key_list; k->key_name; k++) {
+               if (strcmp(k->key_name, key) == 0)
+                       return k;
+       }
+       return k;
+}
+
+const char *cf_get(const struct CfContext *cf, const char *sect, const char *key,
+                  char *buf, int buflen)
+{
+       const struct CfSect *s;
+       const struct CfKey *k;
+       void *base, *p;
+       struct CfValue cv;
+
+       /* find section */
+       s = find_sect(cf, sect);
+       if (!s)
+               return NULL;
+
+       /* find section base */
+       base = cf->base;
+       if (s->base_lookup)
+           base = s->base_lookup(base, sect);
+
+       /* handle dynamic keys */
+       if (s->set_key) {
+               if (!s->get_key)
+                       return NULL;
+               return s->get_key(base, key, buf, buflen);
+       }
+
+       /* get fixed key */
+       k = find_key(s, key);
+       if (!k || !k->op.getter)
+               return NULL;
+       p = get_dest(base, k);
+       if (!p)
+               return NULL;
+       cv.key_name = k->key_name;
+       cv.extra = k->op.op_extra;
+       cv.value_p = p;
+       cv.buf = buf;
+       cv.buflen = buflen;
+       return k->op.getter(&cv);
+}
+
+bool cf_set(const struct CfContext *cf, const char *sect, const char *key, const char *val)
+{
+       const struct CfSect *s;
+       const struct CfKey *k;
+       void *base, *p;
+       struct CfValue cv;
+
+       /* find section */
+       s = find_sect(cf, sect);
+       if (!s)
+               return false;
+
+       /* find section base */
+       base = cf->base;
+       if (s->base_lookup)
+           base = s->base_lookup(base, sect);
+
+       /* handle dynamic keys */
+       if (s->set_key)
+               return s->set_key(base, key, val);
+
+       /* set fixed key */
+       k = find_key(s, key);
+       if (!k || !k->op.setter)
+               return false;
+       if (k->flags & CF_READONLY)
+               return false;
+       if ((k->flags & CF_NO_RELOAD) && cf->loaded)
+               return false;
+       p = get_dest(base, k);
+       if (!p)
+               return false;
+       cv.key_name = k->key_name;
+       cv.extra = k->op.op_extra;
+       cv.value_p = p;
+       cv.buf = NULL;
+       cv.buflen = 0;
+       return k->op.setter(&cv, val);
+}
+
+/*
+ * File loader
+ */
+
+struct LoaderCtx {
+       const struct CfContext *cf;
+       const char *cur_sect;
+       void *top_base;
+};
+
 static bool fill_defaults(struct LoaderCtx *ctx)
 {
        const struct CfKey *k;
-       void *dst;
-       for (k = ctx->cur_sect->key_list; k->key_name; k++) {
-               if (!k->def_value)
+       const struct CfSect *s;
+
+       s = find_sect(ctx->cf, ctx->cur_sect);
+       if (!s)
+               goto fail;
+
+       if (s->set_key)
+               return true;
+
+       for (k = s->key_list; k->key_name; k++) {
+               if (!k->def_value || k->flags & CF_READONLY)
                        continue;
-               dst = get_dest(ctx, k);
-               if (!dst)
-                       return false;
-               if (!k->set_fn(dst, k->def_value))
-                       return false;
+               if (k->flags & CF_NO_RELOAD && ctx->cf->loaded)
+                       continue;
+               if (!cf_set(ctx->cf, ctx->cur_sect, k->key_name, k->def_value))
+                       goto fail;
        }
        return true;
+fail:
+       log_error("fill_defaults fail");
+       return false;
 }
 
-static bool load_handler(void *arg, enum CfKeyType ktype, const char *key, const char *val)
+static bool load_handler(void *arg, bool is_sect, const char *key, const char *val)
 {
        struct LoaderCtx *ctx = arg;
-       const struct CfSect *s;
-       const struct CfKey *k;
-       void *dst;
-
-       if (ktype == CF_SECT) {
-               for (s = ctx->sect_list; s->sect_name; s++) {
-                       if (strcmp(s->sect_name, key) != 0
-                           && strcmp(s->sect_name, "*") != 0)
-                               continue;
-                       ctx->cur_sect = s;
-                       if (s->create_target_fn)
-                               ctx->target = s->create_target_fn(ctx->top_arg, key);
-                       else
-                               ctx->target = ctx->top_arg;
-                       return fill_defaults(ctx);
-               }
-               log_error("load_init_file: unknown section: %s", key);
-               return false;
+
+       if (is_sect) {
+               if (ctx->cur_sect)
+                       free(ctx->cur_sect);
+               ctx->cur_sect = strdup(key);
+               if (!ctx->cur_sect)
+                       return false;
+               return fill_defaults(ctx);
        } else if (!ctx->cur_sect) {
                log_error("load_init_file: value without section: %s", key);
                return false;
        } else {
-               for (k = ctx->cur_sect->key_list; k->key_name; k++) {
-                       if (strcmp(k->key_name, key) != 0)
-                               continue;
-                       dst = get_dest(ctx, k);
-                       if (!dst)
-                               return false;
-                       return k->set_fn(dst, val);
-               }
-               log_error("load_init_file: unknown key: %s", key);
-               return false;
+               return cf_set(ctx->cf, ctx->cur_sect, key, val);
        }
-
-       return true;
 }
 
-bool load_ini_file(const char *fn, const struct CfSect *sect_list, void *top_arg)
+bool cf_load_file(const struct CfContext *cf, const char *fn)
 {
        struct LoaderCtx ctx = {
-               .top_arg = top_arg,
-               .sect_list = sect_list,
+               .cf = cf,
                .cur_sect = NULL,
-               .target = NULL,
        };
 
-       return parse_ini_file(fn, load_handler, &ctx);
+       bool ok = parse_ini_file(fn, load_handler, &ctx);
+       if (ctx.cur_sect)
+               free(ctx.cur_sect);
+       return ok;
 }
 
 /*
  * Various value parsers.
  */
 
-bool cf_set_int(void *dst, const char *value)
+bool cf_set_int(struct CfValue *cv, const char *value)
 {
-       int *ptr = dst;
+       int *ptr = cv->value_p;
        *ptr = atoi(value);
        return true;
 }
 
-bool cf_set_str(void *dst, const char *value)
+bool cf_set_str(struct CfValue *cv, const char *value)
 {
-       char **dst_p = dst;
+       char **dst_p = cv->value_p;
 
        char *tmp = strdup(value);
-       if (!tmp)
+       if (!tmp) {
+               log_error("cf_set_str: no mem");
                return false;
+       }
        if (*dst_p)
                free(*dst_p);
        *dst_p = tmp;
        return true;
 }
 
-bool cf_set_filename(void *dst, const char *value)
+bool cf_set_filename(struct CfValue *cv, const char *value)
 {
-       char **dst_p = dst;
+       char **dst_p = cv->value_p;
        char *tmp, *home, *p;
        int v_len, usr_len, home_len;
        struct passwd *pw;
 
        /* do we need to do tilde expansion */
        if (value[0] != '~')
-               return cf_set_str(dst, value);
+               return cf_set_str(cv, value);
 
        /* find username end */
        v_len = strlen(value);
@@ -307,18 +411,78 @@ fail:
        return false;
 }
 
-bool cf_set_time_usec(void *dst, const char *value)
+bool cf_set_time_usec(struct CfValue *cv, const char *value)
 {
-       usec_t *ptr = dst;
+       usec_t *ptr = cv->value_p;
        *ptr = USEC * atof(value);
        return true;
 }
 
-bool cf_set_time_double(void *dst, const char *value)
+bool cf_set_time_double(struct CfValue *cv, const char *value)
 {
-       double *ptr = dst;
+       double *ptr = cv->value_p;
        *ptr = atof(value);
        return true;
 }
 
+/*
+ * Various value formatters.
+ */
+
+const char *cf_get_str(struct CfValue *cv)
+{
+       char **p = cv->value_p;
+       return *p;
+}
+
+const char *cf_get_int(struct CfValue *cv)
+{
+       int *p = cv->value_p;
+       snprintf(cv->buf, cv->buflen, "%d", *p);
+       return cv->buf;
+}
+
+const char *cf_get_time_double(struct CfValue *cv)
+{
+       double *p = cv->value_p;
+       snprintf(cv->buf, cv->buflen, "%g", *p);
+       return cv->buf;
+}
+
+const char *cf_get_time_usec(struct CfValue *cv)
+{
+       struct CfValue tmp = *cv;
+       usec_t *p = cv->value_p;
+       double d = (double)(*p) / USEC;
+       tmp.value_p = &d;
+       return cf_get_time_double(&tmp);
+}
+
+/*
+ * str->int mapping
+ */
+
+const char *cf_get_lookup(struct CfValue *cv)
+{
+       int *p = cv->value_p;
+       const struct CfLookup *lk = cv->extra;
+       for (; lk->name; lk++) {
+               if (lk->value == *p)
+                       return lk->name;
+       }
+       return "INVALID";
+}
+
+bool cf_set_lookup(struct CfValue *cv, const char *value)
+{
+       int *p = cv->value_p;
+       const struct CfLookup *lk = cv->extra;
+       for (; lk->name; lk++) {
+               if (strcasecmp(lk->name, value) == 0) {
+                       *p = lk->value;
+                       return true;
+               }
+       }
+       return false;
+}
 
index 1c1631b0e87fb51e1b39cbcb335e0cb56132a2e5..14a39b043b7a11e933fd5c187b5e1852e979f101 100644 (file)
  * @{
  */
 
-/**
- * Parsed line type.
- */
-enum CfKeyType {
-       CF_SECT,        /**< Section */
-       CF_KEY          /**< Parameter */
-};
-
 /** Callback signarure for @ref parse_ini_file() */
-typedef bool (*cf_handler_f)(void *arg, enum CfKeyType, const char *key, const char *val);
+typedef bool (*cf_handler_f)(void *arg, bool is_sect, const char *key, const char *val);
 
 /**
  * Simple parser, launches callback for each line
@@ -50,15 +42,46 @@ bool parse_ini_file(const char *fn, cf_handler_f user_handler, void *arg) _MUSTC
  * @{
  */
 
+/** @name Per-key flags
+ * @{
+ */
+
+/** The pointer is absolute */
+#define CF_VAL_ABS 0
+
+/** The pointer is relative to base */
+#define CF_VAL_REL (1<<1)
+
+/** Value must not be changed on reload */
+#define CF_NO_RELOAD (1<<2)
+
+/** Value can only be read */
+#define CF_READONLY (1<<3)
+
+/** @} */
+
 /**
- * Callback for section initialization
+ * Helper structure for passing key info to CfOps
  */
-typedef void *(*cf_create_target_f)(void *top_arg, const char *sect_name);
+struct CfValue {
+       void *value_p;
+       const void *extra;
+       const char *key_name;
+       char *buf;
+       int buflen;
+};
 
 /**
- * Callback for setting a variable
+ * Callbacks for setting and getting a variable value.
+ *
+ * Getter requires temp buf, returns string pointer, which
+ * may or may not point to temp buf.  Getter is optional.
  */
-typedef bool (*cf_setter_f)(void *dst_p, const char *value);
+struct CfOps {
+       bool (*setter)(struct CfValue *cv, const char *value);
+       const char *(*getter)(struct CfValue *cv);
+       const void *op_extra;
+};
 
 /**
  * Parameter description
@@ -66,8 +89,8 @@ typedef bool (*cf_setter_f)(void *dst_p, const char *value);
 struct CfKey {
        /** Parameter name */
        const char *key_name;
-       /** Setter function, called wit absolute pointer */
-       cf_setter_f set_fn;
+       /** Type-specific functions, called with absolute pointer */
+       struct CfOps op;
        /** Flags: CF_VAL_ABS, CF_VAL_REL */
        int flags;
        /** Absolute or relative offset */
@@ -76,77 +99,131 @@ struct CfKey {
        const char *def_value;
 };
 
-/** The pointer is relative to base */
-#define CF_VAL_REL 1
-/** The pointer is absolute */
-#define CF_VAL_ABS 2
-
 /**
  * Section description
  */
 struct CfSect {
        /** Section name */
        const char *sect_name;
-       /** Section creation function (optional) */
-       cf_create_target_f create_target_fn;
+
        /** Key list */
        const struct CfKey *key_list;
+
+       /** Get base pointer to dynamic sections (optional) */
+       void *(*base_lookup)(void *top_base, const char *sect_name);
+
+       /** Set dynamic keys (optional) */
+       bool (*set_key)(void *base, const char *key, const char *val);
+
+       /** Get dynamic keys (optional) */
+       const char *(*get_key)(void *base, const char *key, char *buf, int buflen);
+};
+
+/**
+ * Top-level config information
+ */
+struct CfContext {
+       /** Section list */
+       const struct CfSect *sect_list;
+       /** Top-level base pointer, needed for relative addressing */
+       void *base;
+       /** If set, then CF_NO_RELOAD keys cannot be changed anymore */
+       bool loaded;
 };
 
+/**
+ * @name Type-specific helpers
+ * @{
+ */
+
 /** Setter for string */
-bool cf_set_str(void *dst, const char *value);
+bool cf_set_str(struct CfValue *cv, const char *value);
 /** Setter for filename */
-bool cf_set_filename(void *dst, const char *value);
+bool cf_set_filename(struct CfValue *cv, const char *value);
 /** Setter for int */
-bool cf_set_int(void *dst, const char *value);
+bool cf_set_int(struct CfValue *cv, const char *value);
 /** Setter for time-usec */
-bool cf_set_time_usec(void *dst, const char *value);
+bool cf_set_time_usec(struct CfValue *cv, const char *value);
 /** Setter for time-double */
-bool cf_set_time_double(void *dst, const char *value);
-
-/* before using them do: #define CF_REL_BASE struct Foo */
-/* later: #undef CF_REL_BASE */
-
-/** Integer with offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_INT(x) cf_set_int, CF_VAL_REL, offsetof(CF_REL_BASE, x)
+bool cf_set_time_double(struct CfValue *cv, const char *value);
+/** Setter for lookup */
+bool cf_set_lookup(struct CfValue *cv, const char *value);
+
+/** Getter for string */
+const char *cf_get_str(struct CfValue *cv);
+/** Getter for int */
+const char *cf_get_int(struct CfValue *cv);
+/** Getter for time-usec */
+const char *cf_get_time_usec(struct CfValue *cv);
+/** Getter for time-double */
+const char *cf_get_time_double(struct CfValue *cv);
+/** Getter for int lookup */
+const char *cf_get_lookup(struct CfValue *cv);
+
+/** @} */
 
-/** String with offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_STR(x) cf_set_str, CF_VAL_REL, offsetof(CF_REL_BASE, x)
-
-/** Filename with offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_FILENAME(x) cf_set_filename, CF_VAL_REL, offsetof(CF_REL_BASE, x)
-
-/** Integer offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_BOOL(x) cf_set_int, CF_VAL_REL, offsetof(CF_REL_BASE, x)
-
-/** Integer offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_TIME_USEC(x) cf_set_time_usec, CF_VAL_REL, offsetof(CF_REL_BASE, x)
-
-/** Integer offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_TIME_DOUBLE(x) cf_set_time_double, CF_VAL_REL, offsetof(CF_REL_BASE, x)
+/**
+ * @name Shortcut CfOps for well-known types
+ * @{
+ */
 
-/** Integer with absolute pointer */
-#define CF_ABS_INT(x) cf_set_int, CF_VAL_ABS, (uintptr_t)&(x)
+/** Ops for string */
+#define CF_STR { cf_set_str, cf_get_str }
+/** Ops for filename */
+#define CF_FILE        { cf_set_filename, cf_get_str }
+/** Ops for integer */
+#define CF_INT { cf_set_int, cf_get_int }
+/** Ops for boolean */
+#define CF_BOOL        { cf_set_int, cf_get_int }
+/** Ops for time as usec */
+#define CF_TIME_USEC   { cf_set_time_usec, cf_get_time_usec }
+/** Ops for time as double */
+#define CF_TIME_DOUBLE { cf_set_time_double, cf_get_time_double }
+/** Ops for lookup, takes table as argument */
+#define CF_LOOKUP(t)   { cf_set_lookup, cf_get_lookup, t }
+
+/** @} */
 
-/** String with absolute pointer */
-#define CF_ABS_STR(x) cf_set_str, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Lookup entry for CF_LOOKUP table.
+ */
+struct CfLookup {
+       const char *name;
+       int value;
+};
 
-/** Filename with absolute pointer */
-#define CF_ABS_FILENAME(x) cf_set_filename, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Helper to describe CfKey with absolute addressing
+ */
+#define CF_ABS(name, ops, var, flags, def) \
+       { name, ops, flags | CF_VAL_ABS, (uintptr_t)&(var), def }
 
-/** Bool with absolute pointer */
-#define CF_ABS_BOOL(x) cf_set_int, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Helper to describe CfKey with relative addressing.
+ *
+ * Before using it do: #define CF_REL_BASE struct Foo
+ *
+ * The var should be field in that struct.
+ *
+ * Later: #undef CF_REL_BASE
+ */
+#define CF_REL(name, ops, var, flags, def) \
+       { name, ops, flags | CF_VAL_REL, offsetof(CF_REL_BASE, var), def }
 
-/** Time in seconds stored in usec_t */
-#define CF_ABS_TIME_USEC(x) cf_set_time_usec, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Load config from file.
+ */
+bool cf_load_file(const struct CfContext *cf, const char *fn) _MUSTCHECK;
 
-/** Time in second as double */
-#define CF_ABS_TIME_DOUBLE(x) cf_set_time_double, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Get single value.
+ */
+const char *cf_get(const struct CfContext *cf, const char *sect, const char *var, char *buf, int buflen);
 
 /**
- * Main entry point
+ * Set single value.
  */
-bool load_ini_file(const char *fn, const struct CfSect *sect_list, void *top_arg) _MUSTCHECK;
+bool cf_set(const struct CfContext *cf, const char *sect, const char *var, const char *val);
 
 /* @} */