ref: 6c63e71880f8bcdf597548e8bbaba165c4fe7d38
dir: /ini.c/
#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);
int Aset(Array *a, int index, void *item);
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) {
werrstr("invalid config: %r");
return nil;
}
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)
{
while (*c != 0) {
*c = tolower(*c);
c++;
}
}
int
extractsection(char *line, char **section)
{
char *start, *end;
start = strchr(line, '[');
end = strchr(line, ']');
if (start == nil || end == nil)
return 0;
*section = malloc(end-start);
if (!section)
sysfatal("error allocating section string: %r");
if (!memcpy(*section, start+1, end-start-1))
sysfatal("error copying section string: %r");
(*section)[end-start-1] = 0;
return 1;
}
int
extractkeyvalue(char *line, char **key, char **value)
{
char *arg[2];
arg[0] = nil;
arg[1] = nil;
if (getfields(line, arg, 2, 1, "=") == 0)
return 0;
*key = arg[0] ? strdup(arg[0]) : nil;
*value = arg[1] ? strdup(arg[1]) : nil;
if (!(key && value))
sysfatal("error copying key/value strings: %r");
return 1;
}
int
parseini(char *file, void (*f)(char*,char*,char*,int), int forcelower, int config)
{
char *line;
int callback;
Biobuf *bio = Bopen(file, OREAD);
char *section = nil;
char *key = nil;
char *value = nil;
if (!bio)
return 0;
while (line = Brdstr(bio, '\n', 1)) {
if (!line)
return 1;
if (forcelower)
tolowercase(line);
callback = 0;
if (*line == ';')
goto endline;
if (extractsection(line, §ion))
goto endline;
if (callback = extractkeyvalue(line, &key, &value))
goto endline;
endline:
free(line);
if (callback && f)
f(section, key, value, config);
}
Bterm(bio);
return 1;
}
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));
if (!ini)
sysfatal("error: %r");
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
freeini(int config)
{
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);
free(p->key);
p->key = nil;
free(p->value);
p->value = nil;
free(p);
Aset(&c->properties, i, nil);
}
for (int i = 0; i < Anum(&c->arrayproperties); i++) {
ArrayProperty *p = (ArrayProperty*)Aget(&c->arrayproperties, i);
for (int j = 0; j < Anum(&p->values); j++) {
free(Aget(&p->values, j));
Aset(&p->values, j, nil);
}
free(p->key);
p->key = nil;
free(p);
Aset(&c->arrayproperties, i, nil);
}
for (int i = 0; i < Anum(&c->sections); i++) {
free(Aget(&c->sections, i));
Aset(&c->sections, i, nil);
}
free(c);
if (Aset(&configs, config, nil))
return 1;
werrstr("cannot free ini config: %r");
return 0;
}
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;
}
int
Aset(Array *a, int index, void *item)
{
if (index >= a->num) {
werrstr("bad index");
return 0;
}
a->items[index] = item;
return 1;
}
void
Ainit(Array *a)
{
a->num = 0;
a->maxnum = 0;
a->items = malloc(0);
if (!a->items)
sysfatal("error: %r");
}