--- /dev/null
+/*
+ * Insane tester of reading configuration files
+ *
+ * (c) 2006 Robert Spalek <robert@ucw.cz>
+ */
+
+#include "lib/lib.h"
+#include "lib/conf2.h"
+#include "lib/clists.h"
+
+#include <stdlib.h>
+#include <time.h>
+
+struct sub_sect_1 {
+ byte *name;
+ byte *level;
+ int confidence;
+};
+
+static byte *
+init_sec_1(void *ptr, struct cf_section *sec UNUSED)
+{
+ struct sub_sect_1 *s = ptr;
+ s->name = "unknown";
+ s->level = "default";
+ s->confidence = 5;
+ return NULL;
+}
+
+static byte *
+commit_sec_1(void *ptr, struct cf_section *sec UNUSED)
+{
+ struct sub_sect_1 *s = ptr;
+ if (s->confidence < 0 || s->confidence > 10)
+ return "Well, this can't be";
+ return NULL;
+}
+
+static struct cf_section cf_sec_1 = {
+ .size = sizeof(struct sub_sect_1),
+ .init = init_sec_1,
+ .commit = commit_sec_1,
+ .cfg = (struct cf_item[]) {
+#define F(x) CF_FIELD(struct sub_sect_1, x)
+ CF_STRING("name", F(name)),
+ CF_STRING("level", F(level)),
+ CF_INT("confidence", F(confidence)),
+ CF_END
+#undef F
+ }
+};
+
+static int nr1 = 15;
+static int *nrs1 = DEFAULT_ARRAY(int, 5, 5, 4, 3, 2, 1);
+static int *nrs2;
+static byte *str1 = "no worries";
+static byte **str2 = DEFAULT_ARRAY(byte *, 2, "Alice", "Bob");
+static u64 u1 = 0xCafeBeefDeadC00ll;
+static double d1 = -1.1;
+static struct sub_sect_1 sec_1 = { "Charlie", "WBAFC", 0 };
+static struct cnode secs;
+static time_t t1, t2;
+
+static byte *
+commit_top(void *ptr UNUSED, struct cf_section *sec UNUSED)
+{
+ return NULL;
+}
+
+static byte *
+time_parser(uns nr_pars, byte **pars, void *sec_ptr, struct cf_section *sec, uns index)
+{
+ if (nr_pars != 0 && nr_pars != 1)
+ return "Either now or 1 parameter!";
+ ASSERT(!sec_ptr);
+ time_t t = nr_pars ? atoi(pars[0]) : time(NULL);
+ if (sec->cfg[index].name[0] == 'F')
+ t1 = t;
+ else
+ t2 = t;
+ return NULL;
+}
+
+static struct cf_section cf_top = {
+ .commit = commit_top,
+ .cfg = (struct cf_item []) {
+ CF_INT("nr1", &nr1),
+ CF_INT_AR("nrs1", &nrs1, 5),
+ CF_INT_AR("nrs2", &nrs2, -1000),
+ CF_STRING("str1", &str1),
+ CF_STRING_AR("str2", &str2, 2),
+ CF_U64("u1", &u1),
+ CF_DOUBLE("d1", &d1),
+ CF_FUNCTION("FirstTime", time_parser),
+ CF_FUNCTION("SecondTime", time_parser),
+ CF_SUB_SECTION("master", &sec_1, &cf_sec_1),
+ CF_LINK_LIST("slaves", &secs, &cf_sec_1),
+ CF_END
+ }
+};
+
+int
+main(void)
+{
+ return 0;
+}
--- /dev/null
+/*
+ * UCW Library -- Reading of configuration files
+ *
+ * (c) 2006 Robert Spalek <robert@ucw.cz>
+ *
+ * This software may be freely distributed and used according to the terms
+ * of the GNU Lesser General Public License.
+ */
+
+#ifndef _LIB_CONF2_H
+#define _LIB_CONF2_H
+
+enum cf_type {
+ CT_END, // end of list
+ CT_INT, CT_U64, CT_DOUBLE, // number types
+ CT_STRING, // string type
+ CT_FUNCTION, // arbitrary parser function
+ CT_SUB_SECTION, // sub-section appears exactly once
+ CT_LINK_LIST // link-list with 0..many nodes
+};
+
+struct cf_section;
+typedef byte *cf_hook(void *sec_ptr, struct cf_section *sec);
+ /* An init- or commit-hook gets a pointer to the sub-section or NULL if this
+ * is the global section, and a declaration of the section it is called on.
+ * It returns an error message or NULL if everything is all right. */
+typedef byte *cf_parser(uns nr_pars, byte **pars, void *sec_ptr, struct cf_section *sec, uns index);
+ /* A parser function gets an array of strings and stores it in any way it
+ * likes into its own data structures. It gets a pointer to the sub-section
+ * or NULL if this is the global section, a declaration of the section it is
+ * called in, and the index of the item it is called on. It returns an error
+ * message or NULL if everything is all right. */
+
+struct cf_item {
+ enum cf_type type;
+ byte *name;
+ int number; // number of values: k>0 means exactly k, k<0 means at most k
+ void *ptr; // pointer to a global variable or an offset in a sub-section
+ struct cf_section *sub; // declaration of a sub-section or link-list
+};
+
+struct cf_section {
+ uns size; // 0 for a global block, sizeof(struct) for a sub-section
+ cf_hook *init; // fills in default values
+ cf_hook *commit; // verifies parsed data and checks ranges (optional)
+ struct cf_item *cfg; // CT_END-terminated array of items
+};
+
+#define CHECK_VAR_TYPE(x,type) ((x)-(type)0 + (type)0)
+ // for a pointer x it returns x, and performs a compile-time check whether typeof(x)==type
+#define DEFAULT_ARRAY(type,len,val...) (type[]) { (type)len, ##val } + 1
+ // creates an array with an allocated space in the front for the (Pascal-like) length
+#define CF_FIELD(str,f) &((str*)0)->f
+ // returns a pointer to a field inside a structure suitable for passing as cf_item->ptr
+
+#define CF_END { .type = CT_END }
+ // please better put this at the end of each section
+#define CF_INT(n,p) { .type = CT_INT, .name = n, .number = 1, .ptr = CHECK_VAR_TYPE(p,int*) }
+#define CF_U64(n,p) { .type = CT_U64, .name = n, .number = 1, .ptr = CHECK_VAR_TYPE(p,u64*) }
+#define CF_DOUBLE(n,p) { .type = CT_DOUBLE, .name = n, .number = 1, .ptr = CHECK_VAR_TYPE(p,double*) }
+#define CF_STRING(n,p) { .type = CT_STRING, .name = n, .number = 1, .ptr = CHECK_VAR_TYPE(p,byte**) }
+#define CF_FUNCTION(n,p) { .type = CT_FUNCTION, .name = n, .number = 1, .ptr = CHECK_VAR_TYPE(p,cf_parser*) }
+#define CF_SUB_SECTION(n,p,s) { .type = CT_SUB_SECTION, .name = n, .number = 1, .ptr = p, .sub = s }
+#define CF_LINK_LIST(n,p,s) { .type = CT_LINK_LIST, .name = n, .number = 1, .ptr = CHECK_VAR_TYPE(p,struct cnode*), .sub = s }
+ // use the macros above to declare configuration items for single variables
+#define CF_INT_AR(n,p,c) { .type = CT_INT, .name = n, .number = c, .ptr = CHECK_VAR_TYPE(p,int**) }
+#define CF_U64_AR(n,p,c) { .type = CT_U64, .name = n, .number = c, .ptr = CHECK_VAR_TYPE(p,u64**) }
+#define CF_DOUBLE_AR(n,p,c) { .type = CT_DOUBLE, .name = n, .number = c, .ptr = CHECK_VAR_TYPE(p,double**) }
+#define CF_STRING_AR(n,p,c) { .type = CT_STRING, .name = n, .number = c, .ptr = CHECK_VAR_TYPE(p,byte***) }
+ // use the macros above to declare configuration items for arrays of variables
+
+#endif