shithub: libini

Download patch

ref: 61a400cc2f9b50b4a39537eb9d0981df77347929
parent: e922ef10cc79b48a7085ec76f343bee5859be94b
author: sirjofri <sirjofri@sirjofri.de>
date: Sun Feb 19 11:37:40 EST 2023

adds iniconfig/getinivalue/getiniarray configuration stuff, adds man-readme functionality.

--- a/README
+++ b/README
@@ -1,14 +1,60 @@
-Parse INI files
 
+     INI(2)                                                     INI(2)
 
-Usage:
+     NAME
+          parseini - parse ini files
 
-    void
-    parseline(char *section, char *key, char *value) {
-        print("%s: %s -> %s\n", section, key, value);
-    }
-    
-    parseini("file.ini", parseline, 0);
+     SYNOPSIS
+          #include <ini.h>
 
+          int    parseini(char *file, void (*f)(char*, char*, char*),
+          int forcelower)
 
-See also the ini.man file for more details.
\ No newline at end of file
+          int    iniconfig(char *file, int forcelower)
+
+          char*  getinivalue(int config, char *section, char *key)
+          char** getiniarray(int config, char *section, char *key, int
+          *num)
+
+     DESCRIPTION
+          This library function provides simple parsing of INI files
+          using bio(2). For each key-value pair within the INI file it
+          calls the provided callback function f.
+
+          The callback function gets the section, key and value as
+          parameters.
+
+          The forcelower flag tells the function to return all strings
+          as lowercase strings, which can be helpful for some cases.
+          Otherwise, all strings are returned as entered in the INI
+          file.
+
+          Iniconfig parses the given file into a convenient config
+          database.  It returns the id of the parsed config file
+          that's needed for future lookups.  It is possible to parse
+          multiple INI files within one program this way.
+
+          The functions getinivalue and getiniarray can be used to
+          fetch the parsed config values.  Both functions expect the
+          configuration id received by the previous iniconfig call, as
+          well as strings for section and key.
+
+          Getinivalue returns the string value for the given combina-
+          tion.
+
+          Getiniarray returns an array of all value strings for the
+          given combination.  It sets num to the number of elements
+          within this list.
+
+     SOURCE
+          /sys/src/libini
+
+     SEE ALSO
+          bio(2)
+
+     DIAGNOSTICS
+          Parseini returns 0 on failure and sets errstr.
+
+     BUGS
+          Sure.
+
--- a/ini.c
+++ b/ini.c
@@ -1,7 +1,107 @@
 #include <u.h>
 #include <libc.h>
 #include <bio.h>
+#include "ini.h"
 
+#define REALLOC_SLACK 32
+// #define DUMPINPUT
+
+typedef struct Array Array;
+struct Array {
+	int num;
+	void **items;
+	int maxnum;
+};
+void Ainit(Array *a);
+int Aaddunique(Array *a, void *item);
+int Aaddstrunique(Array *a, char *item);
+int Aappenditem(Array *a, void *item);
+int Afind(Array *a, void *item);
+char* Afindstr(Array *a, char *item);
+void* Aget(Array *a, int i);
+void** Aarr(Array *a);
+int Anum(Array *a);
+void Ashrink(Array *a);
+
+typedef struct Property Property;
+struct Property {
+	char *key;
+	char *value;
+	char *section;
+};
+typedef struct ArrayProperty ArrayProperty;
+struct ArrayProperty {
+	char *key;
+	Array values;
+	char *section;
+};
+typedef struct IniConfig IniConfig;
+struct IniConfig {
+	Array sections;
+	Array properties;
+	Array arrayproperties;
+};
+
+Array configs;
+
+int
+seceq(char* left, char* right)
+{
+	if (left == nil && right == nil)
+		return 1;
+	
+	if (left == right)
+		return 1;
+	
+	if (left != nil && right != nil)
+		return strcmp(left, right) == 0;
+	return 0;
+}
+
+char*
+getinivalue(int config, char *section, char *key)
+{
+	if (!key)
+		return nil;
+	
+	IniConfig *c = (IniConfig*)Aget(&configs, config);
+	if (!c)
+		sysfatal("invalid config: %r");
+	
+	for (int i = 0; i < Anum(&c->properties); i++) {
+		Property *p = (Property*)Aget(&c->properties, i);
+		
+		if (seceq(p->section, section) && strcmp(p->key, key) == 0)
+			return p->value;
+	}
+	werrstr("unable to find key value pair within section");
+	return nil;
+}
+
+char**
+getiniarray(int config, char *section, char *key, int *num)
+{
+	if (!key) {
+		*num = 0;
+		return nil;
+	}
+	
+	IniConfig *c = (IniConfig*)Aget(&configs, config);
+	if (!c)
+		sysfatal("invalid config: %r");
+	
+	for (int i = 0; i < Anum(&c->arrayproperties); i++) {
+		ArrayProperty *p = (ArrayProperty*)Aget(&c->arrayproperties, i);
+		if (seceq(p->section, section) && strcmp(p->key+1, key) == 0) {
+			*num = Anum(&p->values);
+			return (char**)Aarr(&p->values);
+		}
+	}
+	
+	*num = 0;
+	return nil;
+}
+
 void
 tolowercase(char *c)
 {
@@ -53,7 +153,7 @@
 }
 
 int
-parseini(char *file, void (*f)(char*,char*,char*), int forcelower)
+parseini(char *file, void (*f)(char*,char*,char*,int), int forcelower, int config)
 {
 	char *line;
 	int callback;
@@ -86,9 +186,225 @@
 endline:
 		free(line);
 		if (callback && f)
-			f(section, key, value);
+			f(section, key, value, config);
 	}
 	Bterm(bio);
 	
 	return 1;
-}
\ No newline at end of file
+}
+
+void
+configparseline(char *section, char *key, char *value, int config)
+{
+	char *sec;
+	Property *prop;
+	ArrayProperty *arr;
+	IniConfig *c = (IniConfig*)Aget(&configs, config);
+	
+	sec = nil;
+	if (section != nil) {
+		int sid = Aaddstrunique(&c->sections, section);
+		sec = (char*)Aget(&c->sections, sid);
+	}
+	
+	if (*key == '+') {
+#ifdef DUMPINPUT
+		fprint(2, "]]array key: %s\n", key);
+#endif
+		arr = nil;
+		for (int i = 0; i < Anum(&c->arrayproperties); i++) {
+			ArrayProperty *p = (ArrayProperty*)Aget(&c->arrayproperties, i);
+			if (seceq(p->section, sec) && strcmp(p->key, key) == 0) {
+				arr = p;
+#ifdef DUMPINPUT
+				fprint(2, "]]  key exists\n");
+#endif
+				break;
+			}
+		}
+		if (!arr) {
+			arr = malloc(sizeof(ArrayProperty));
+			if (!arr)
+				sysfatal("error: %r");
+			arr->key = key;
+			Ainit(&arr->values);
+			arr->section = sec;
+			Aaddunique(&c->arrayproperties, arr);
+#ifdef DUMPINPUT
+			fprint(2, "]]  new key\n");
+#endif
+		}
+		Aaddunique(&arr->values, value);
+#ifdef DUMPINPUT
+		fprint(2, "]]    %s | %s | num: %d\n", arr->section, arr->key, Anum(&arr->values));
+#endif
+	} else {
+#ifdef DUMPINPUT
+		fprint(2, "]]prop key: %s\n", key);
+#endif
+		prop = nil;
+		for (int i = 0; i < Anum(&c->properties); i++) {
+			Property *p = (Property*)Aget(&c->properties, i);
+			if (seceq(p->section, sec) && strcmp(p->key, key) == 0) {
+				prop = p;
+#ifdef DUMPINPUT
+				fprint(2, "]]  key exists\n");
+#endif
+				break;
+			}
+		}
+		if (!prop) {
+			prop = malloc(sizeof(Property));
+			if (!prop)
+				sysfatal("error: %r");
+			prop->key = key;
+			prop->section = sec;
+			Aaddunique(&c->properties, prop);
+#ifdef DUMPINPUT
+			fprint(2, "]]  new key\n");
+#endif
+		}
+		prop->value = value;
+#ifdef DUMPINPUT
+		fprint(2, "]]    %s | %s | %s\n", prop->section, prop->key, prop->value);
+#endif
+	}
+}
+
+void
+shrinkconfig(int id)
+{
+	IniConfig *c = (IniConfig*)Aget(&configs, id);
+	if (!c)
+		return;
+	
+	Ashrink(&c->sections);
+	Ashrink(&c->properties);
+	Ashrink(&c->arrayproperties);
+}
+
+int
+iniconfig(char *file, int forcelower) // returns ID of config entry
+{
+	int id;
+	
+	if (!configs.items)
+		Ainit(&configs);
+	
+	IniConfig *ini = malloc(sizeof(IniConfig));
+	Ainit(&ini->sections);
+	Ainit(&ini->properties);
+	Ainit(&ini->arrayproperties);
+	
+	id = Aappenditem(&configs, ini);
+	
+	if (parseini(file, configparseline, forcelower, id)) {
+		//shrinkconfig(id);
+		return id;
+	}
+	return -1;
+}
+
+int
+Afindstrid(Array *a, char *item)
+{
+	for (int i = 0; i < a->num; i++) {
+		if (strcmp((char*)a->items[i], item) == 0)
+			return i;
+	}
+	return -1;
+}
+
+char*
+Afindstr(Array *a, char *item)
+{
+	int i = Afindstrid(a, item);
+	return Aget(a, i);
+}
+
+int
+Afind(Array *a, void *item)
+{
+	for (int i = 0; i < a->num; i++) {
+		if (item == a->items[i])
+			return i;
+	}
+	return -1;
+}
+
+int
+Aappenditem(Array *a, void *item)
+{
+	if (a->num + 1 > a->maxnum) {
+		a->maxnum += REALLOC_SLACK;
+		a->items = realloc(a->items, a->maxnum);
+		if (!a->items)
+			sysfatal("error: %r");
+	}
+	
+	a->items[a->num] = item;
+	a->num++;
+	return a->num - 1;
+}
+
+int
+Aaddunique(Array *a, void *item)
+{
+	int it = Afind(a, item);
+	if (it >= 0)
+		return it;
+	
+	return Aappenditem(a, item);
+}
+
+int
+Aaddstrunique(Array *a, char *item)
+{
+	int it = Afindstrid(a, item);
+	if (it >= 0)
+		return it;
+	
+	return Aappenditem(a, item);
+}
+
+void*
+Aget(Array *a, int i)
+{
+	if (i < 0)
+		return nil;
+	if (i < a->num)
+		return a->items[i];
+	werrstr("array index out of bounds");
+	return nil;
+}
+
+void**
+Aarr(Array *a)
+{
+	return a->items;
+}
+
+int
+Anum(Array *a)
+{
+	return a->num;
+}
+
+void
+Ashrink(Array *a)
+{
+	a->items = realloc(a->items, a->num);
+	if (!a->items)
+		sysfatal("error: %r");
+	a->maxnum = a->num;
+}
+
+void
+Ainit(Array *a)
+{
+	a->num = 0;
+	a->maxnum = 0;
+	a->items = malloc(0);
+	if (!a->items)
+		sysfatal("error: %r");
+}
--- a/ini.h
+++ b/ini.h
@@ -1,1 +1,9 @@
-int parseini(char *file, void (*f)(char*,char*,char*), int forcelower);
\ No newline at end of file
+#pragma src "/sys/src/libini"
+#pragma lib "libini.a"
+
+int parseini(char *file, void (*f)(char*,char*,char*,int), int forcelower, int config);
+
+int iniconfig(char *file, int forcelower);
+
+char* getinivalue(int config, char *section, char *key);
+char** getiniarray(int config, char *section, char *key, int *num);
--- a/ini.man
+++ b/ini.man
@@ -5,8 +5,20 @@
 .B #include <ini.h>
 .PP
 .B
-int parseini(char *file, void (*f)(char*, char*, char*), int forcelower)
+.ta \w'char** 'u
+int	parseini(char *file, void (*f)(char*, char*, char*), int forcelower)
 .PP
+.B
+.ta \w'char** 'u
+int	iniconfig(char *file, int forcelower)
+.PP
+.B
+.ta \w'char** 'u
+char*	getinivalue(int config, char *section, char *key)
+.br
+.B
+char**	getiniarray(int config, char *section, char *key, int *num)
+.PP
 .SH DESCRIPTION
 This library function provides simple parsing of INI files using
 .IR bio (2).
@@ -17,7 +29,36 @@
 .PP
 The
 .I forcelower
-flag tells the function to return all strings as lowercase strings, which can be helpful for some cases. Otherwise, all strings are returned as entered in the INI file.
+flag tells the function to return all strings as lowercase strings, which can be helpful for some cases.
+Otherwise, all strings are returned as entered in the INI file.
+.PP
+.I Iniconfig
+parses the given file into a convenient config database.
+It returns the
+.I id
+of the parsed config file that's needed for future lookups.
+It is possible to parse multiple INI files within one program this way.
+.PP
+The functions
+.I getinivalue
+and
+.I getiniarray
+can be used to fetch the parsed config values.
+Both functions expect the configuration id received by the previous
+.I iniconfig
+call, as well as strings for
+.I section
+and
+.IR key .
+.PP
+.I Getinivalue
+returns the string value for the given combination.
+.PP
+.I Getiniarray
+returns an array of all value strings for the given combination.
+It sets
+.I num
+to the number of elements within this list.
 .SH SOURCE
 .B /sys/src/libini
 .SH SEE ALSO
--- a/mkfile
+++ b/mkfile
@@ -1,6 +1,6 @@
 </$objtype/mkfile
 
-LIB=$objtype-libini.a
+LIB=/$objtype/lib/libini.a
 OFILES=ini.$O
 HFILES=/sys/include/ini.h
 
@@ -12,21 +12,34 @@
 test.$O: test.c ini.h
 	$CC $CFLAGS test.c
 
-test:V: $O.out
+test:QV: $O.out
 	cat <<EOF >test.ini
+	nocat=novalue
+	+nocatarr=valA
+	+nocatarr=valB
+	
 	[Category A]
 	keyA=valueA
-	keyA=valueB
-	keyB=valueA
 	keyB=valueB
 	
 	; Comment
 	
 	[Category B]
-	keyC=valueA
-	keyC=valueB
-	keyD=valueA
-	keyD=valueB
+	keyC=valueC
+	keyD=valueD
+	
+	[Category C]
+	keyx=nope
+	+keyF=abc
+	+keyF=def
 	EOF
 	$O.out test.ini
 	rm test.ini
+
+README: ini.man
+	nroff -rL1000i -man $prereq | sed '
+	${
+	/^$/p
+	}
+	//N
+	/^\n$/D' > $target
--- a/test.c
+++ b/test.c
@@ -3,7 +3,7 @@
 #include "ini.h"
 
 void
-parseline(char *section, char *key, char *value)
+parseline(char *section, char *key, char *value, int)
 {
 	print("section: '%s', key: '%s', value: '%s'\n", section, key, value);
 }
@@ -15,18 +15,43 @@
 	char *status = nil;
 	char *err = "error";
 	
-	print("Testing forcelower=0\n");
+	print("Testing forcelower=0\n\n");
 	
-	if (!parseini(file, parseline, 0)) {
+	if (!parseini(file, parseline, 0, 0)) {
 		fprint(2, "error: %r\n");
 		status = err;
 	}
 	
-	print("Testing forcelower=1\n");
+	print("\nTesting forcelower=1\n\n");
 	
-	if (!parseini(file, parseline, 1)) {
+	if (!parseini(file, parseline, 1, 0)) {
 		fprint(2, "error: %r\n");
 		status = err;
 	}
+	
+	print("\nTesting iniconfig\n\n");
+	int conf = iniconfig(file, 0);
+	
+	char* v = getinivalue(conf, "Category A", "keyB");
+	print("Category A, keyB: %s\n", v);
+	
+	int num;
+	char** values = getiniarray(conf, "Category C", "keyF", &num);
+	if (values) {
+		for (int i = 0; i < num; i++) {
+			print("array: %s\n", values[i]);
+		}
+	}
+	
+	v = getinivalue(conf, nil, "nocat");
+	print("no category, nocat: %s\n", v);
+	
+	values = getiniarray(conf, nil, "nocatarr", &num);
+	if (values) {
+		for (int i = 0; i < num; i++) {
+			print("nocatarr: %s\n", values[i]);
+		}
+	}
+	
 	exits(status);
 }