shithub: libini

ref: 6c63e71880f8bcdf597548e8bbaba165c4fe7d38
dir: /ini.c/

View raw version
#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, &section))
			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");
}