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);
}