diff th_config.c @ 0:bd61a80a6c54

Initial import into Mercurial repository. Discarding old cvs/svn history here, because it's cluttered and commit messages are mostly crap.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 26 Mar 2008 04:41:58 +0200
parents
children 5a327a2988fa
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/th_config.c	Wed Mar 26 04:41:58 2008 +0200
@@ -0,0 +1,575 @@
+/*
+ * Very simple configuration handling functions
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2004-2007 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdio.h>
+#include <stdarg.h>
+#include "th_config.h"
+#include "th_util.h"
+#include "th_string.h"
+
+#define LPREV (pNode->pPrev)
+#define LNEXT (pNode->pNext)
+
+
+void th_config_error(char_t * pcFilename, size_t lineNum,
+	const char_t * pcFormat, ...)
+{
+	va_list ap;
+	va_start(ap, pcFormat);
+	fprintf(stderr, "%s: '%s', line #%d: ", th_prog_name, pcFilename, lineNum);
+	vfprintf(stderr, pcFormat, ap);
+	va_end(ap);
+}
+
+
+/* Create a new configuration
+ */
+t_config *th_config_new(void)
+{
+	t_config *cfg;
+
+	cfg = (t_config *) th_calloc(1, sizeof(t_config));
+	if (!cfg)
+		return NULL;
+
+	return cfg;
+}
+
+
+/* Free a given configuration (the values are not free'd)
+ */
+void th_config_free(t_config * cfg)
+{
+	t_config_item *pCurr, *pNext;
+
+	if (!cfg)
+		return;
+
+	pCurr = cfg->pItems;
+	while (pCurr) {
+		pNext = pCurr->pNext;
+		th_free(pCurr->itemName);
+		th_free(pCurr);
+		pCurr = pNext;
+	}
+}
+
+
+/* Allocate and add new item to configuration
+ */
+t_config_item *th_config_add(t_config * cfg, char_t * itemName, int itemType,
+			     BOOL(*itemValidate) (t_config_item *), void *itemData)
+{
+	t_config_item *pNode;
+
+	if (!cfg)
+		return NULL;
+
+	/* Allocate new item */
+	pNode = (t_config_item *) th_calloc(1, sizeof(t_config_item));
+	if (!pNode)
+		return NULL;
+
+	/* Set values */
+	pNode->itemType = itemType;
+	pNode->itemData = itemData;
+	pNode->itemValidate = itemValidate;
+	th_pstrcpy(&pNode->itemName, itemName);
+
+	/* Insert into linked list */
+	if (cfg->pItems) {
+		/* The first node's pPrev points to last node */
+		LPREV = cfg->pItems->pPrev;	/* New node's prev = Previous last node */
+		cfg->pItems->pPrev->pNext = pNode;	/* Previous last node's next = New node */
+		cfg->pItems->pPrev = pNode;	/* New last node = New node */
+		LNEXT = NULL;	/* But next is NULL! */
+	} else {
+		cfg->pItems = pNode;	/* First node ... */
+		LPREV = pNode;	/* ... it's also last */
+		LNEXT = NULL;	/* But next is NULL! */
+	}
+
+	return pNode;
+}
+
+
+/* Add integer type setting into give configuration
+ */
+int th_config_add_int(t_config * cfg, char_t * itemName, BOOL(*itemValidate) (t_config_item *),
+		      int *itemData, int itemDef)
+{
+	t_config_item *pNode;
+
+	pNode = th_config_add(cfg, itemName, ITEM_INT, itemValidate, (void *) itemData);
+	if (!pNode)
+		return -1;
+
+	*itemData = itemDef;
+
+	return 0;
+}
+
+
+/* Add unsigned integer type setting into give configuration
+ */
+int th_config_add_uint(t_config * cfg, char_t * itemName, BOOL(*itemValidate) (t_config_item *),
+		       unsigned int * itemData, unsigned int itemDef)
+{
+	t_config_item *pNode;
+
+	pNode = th_config_add(cfg, itemName, ITEM_UINT, itemValidate, (void *) itemData);
+	if (!pNode)
+		return -1;
+
+	*itemData = itemDef;
+
+	return 0;
+}
+
+
+/* Add strint type setting into given configuration
+ */
+int th_config_add_str(t_config * cfg, char_t * itemName, BOOL(*itemValidate) (t_config_item *),
+		      char_t ** itemData, char_t * itemDef)
+{
+	t_config_item *pNode;
+
+	pNode = th_config_add(cfg, itemName, ITEM_STRING, itemValidate, (void *) itemData);
+	if (!pNode)
+		return -1;
+
+	if (itemDef != NULL)
+		*itemData = th_strdup(itemDef);
+	else
+		*itemData = NULL;
+
+	return 0;
+}
+
+
+/* Add boolean type setting into given configuration
+ */
+int th_config_add_bool(t_config * cfg, char_t * itemName, BOOL(*itemValidate) (t_config_item *),
+		       BOOL * itemData, BOOL itemDef)
+{
+	t_config_item *pNode;
+
+	pNode = th_config_add(cfg, itemName, ITEM_BOOL, itemValidate, (void *) itemData);
+	if (!pNode)
+		return -1;
+
+	*itemData = itemDef;
+
+	return 0;
+}
+
+
+/* Read a given file into configuration structure and variables
+ */
+enum
+{
+	PM_EOF,
+	PM_ERROR,
+	PM_NORMAL,
+	PM_COMMENT,
+	PM_NEXT,
+	PM_KEYNAME,
+	PM_KEYSET,
+	PM_STRING,
+	PM_INT,
+	PM_BOOL
+};
+
+#define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; }
+#define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c))
+
+int th_config_read(char_t * pcFilename, t_config * cfg)
+{
+	FILE *inFile;
+	t_config_item *pItem;
+	char_t tmpStr[SET_MAX_BUF + 1];
+	size_t lineNum, strPos;
+	int c, parseMode, prevMode, nextMode, tmpCh;
+	BOOL isFound, isStart, tmpBool, isError, validError;
+
+	assert(cfg);
+
+	/* Open the file */
+	if ((inFile = fopen(pcFilename, "rb")) == NULL)
+		return -1;
+
+	/* Initialize values */
+	pItem = NULL;
+	tmpCh = 0;
+	strPos = 0;
+	lineNum = 1;
+	c = -1;
+	isFound = isStart = tmpBool = isError = validError = FALSE;
+	nextMode = prevMode = parseMode = PM_NORMAL;
+
+	/* Parse the configuration */
+	while ((parseMode != PM_EOF) && (parseMode != PM_ERROR)) {
+		if (c == -1) {
+			/* Get next character */
+			switch (c = fgetc(inFile)) {
+			case EOF:
+				if (parseMode != PM_NORMAL) {
+					th_config_error(pcFilename, lineNum,
+					"Unexpected end of file.\n");
+					parseMode = PM_ERROR;
+				} else
+					parseMode = PM_EOF;
+				break;
+
+			case '\n':
+				lineNum++;
+			}
+		}
+
+		switch (parseMode) {
+		case PM_COMMENT:
+			/* Comment parsing mode */
+			if (c == '\n') {
+				/* End of line, end of comment */
+				parseMode = prevMode;
+				prevMode = PM_COMMENT;
+			}
+			c = -1;
+			break;
+
+		case PM_NORMAL:
+			/* Normal parsing mode */
+			if (c == '#') {
+				prevMode = parseMode;
+				parseMode = PM_COMMENT;
+				c = -1;
+			} else if (VISEND(c)) {
+				c = -1;
+			} else if (th_isalpha(c)) {
+				/* Start of key name found */
+				prevMode = parseMode;
+				parseMode = PM_KEYNAME;
+				strPos = 0;
+			} else {
+				/* Error! Invalid character found */
+				th_config_error(pcFilename, lineNum,
+					"Unexpected character '%c'\n", c);
+				parseMode = PM_ERROR;
+			}
+			break;
+
+		case PM_KEYNAME:
+			/* Configuration KEY name parsing mode */
+			if (c == '#') {
+				/* Start of comment */
+				prevMode = parseMode;
+				parseMode = PM_COMMENT;
+				c = -1;
+			} else if (th_iscrlf(c) || th_isspace(c) || c == '=') {
+				/* End of key name */
+				prevMode = parseMode;
+				parseMode = PM_NEXT;
+				nextMode = PM_KEYSET;
+			} else if (th_isalnum(c) || (c == '_')) {
+				/* Add to key name string */
+				VADDCH(c)
+				else
+				{
+					/* Error! Key name string too long! */
+					th_config_error(pcFilename, lineNum,
+						"Config key name too long!");
+					parseMode = PM_ERROR;
+				}
+				c = -1;
+			} else {
+				/* Error! Invalid character found */
+				th_config_error(pcFilename, lineNum,
+					"Unexpected character '%c'\n", c);
+				parseMode = PM_ERROR;
+			}
+			break;
+
+		case PM_KEYSET:
+			if (c == '=') {
+				/* Find key from configuration */
+				tmpStr[strPos] = 0;
+				isFound = FALSE;
+				pItem = cfg->pItems;
+				while (pItem && !isFound) {
+					if (strcmp(pItem->itemName, tmpStr) == 0)
+						isFound = TRUE;
+					else
+						pItem = pItem->pNext;
+				}
+
+				/* Check if key was found */
+				if (isFound) {
+					/* Okay, set next mode */
+					switch (pItem->itemType) {
+					case ITEM_STRING:
+						nextMode = PM_STRING;
+						break;
+
+					case ITEM_INT:
+					case ITEM_UINT:
+						nextMode = PM_INT;
+						break;
+
+					case ITEM_BOOL:
+						nextMode = PM_BOOL;
+						break;
+					}
+
+					prevMode = parseMode;
+					parseMode = PM_NEXT;
+					isStart = TRUE;
+					strPos = 0;
+				} else {
+					/* Error! No configuration key by this name found */
+					th_config_error(pcFilename, lineNum,
+						"No such configuration setting ('%s')\n",
+						tmpStr);
+					parseMode = PM_ERROR;
+				}
+
+				c = -1;
+			} else {
+				/* Error! '=' expected! */
+				th_config_error(pcFilename, lineNum,
+					"Unexpected character '%c', '=' expected.\n", c);
+				parseMode = PM_ERROR;
+			}
+			break;
+
+		case PM_NEXT:
+			/* Search next item parsing mode */
+			if (c == '#') {
+				/* Start of comment */
+				prevMode = parseMode;
+				parseMode = PM_COMMENT;
+			} else if (th_isspace(c) || th_iscrlf(c)) {
+				/* Ignore whitespaces and linechanges */
+				c = -1;
+			} else {
+				/* Next item found */
+				prevMode = parseMode;
+				parseMode = nextMode;
+			}
+			break;
+
+		case PM_STRING:
+			/* String parsing mode */
+			if (isStart) {
+				/* Start of string, get delimiter */
+				tmpCh = c;
+				isStart = FALSE;
+				strPos = 0;
+			} else if (c == tmpCh) {
+				/* End of string, set the value */
+				tmpStr[strPos] = 0;
+				th_pstrcpy((char_t **) pItem->itemData, tmpStr);
+				if (pItem->itemValidate) {
+					if (!pItem->itemValidate(pItem))
+						validError = TRUE;
+				}
+
+				prevMode = parseMode;
+				parseMode = PM_NORMAL;
+			} else {
+				/* Add character to string */
+				VADDCH(c)
+				else
+				{
+					/* Error! String too long! */
+					th_config_error(pcFilename, lineNum,
+						"String too long! Maximum is %d characters.",
+						SET_MAX_BUF);
+					parseMode = PM_ERROR;
+				}
+			}
+
+			c = -1;
+			break;
+
+		case PM_INT:
+			/* Integer parsing mode */
+			if (isStart && (pItem->itemType == ITEM_UINT) && (c == '-')) {
+				/* Error! Negative values not allowed for unsigned ints */
+				th_config_error(pcFilename, lineNum,
+						"Negative value specified, unsigned value expected.");
+				parseMode = PM_ERROR;
+			} else if (isStart && (c == '-' || c == '+')) {
+				VADDCH(c)
+				else
+				isError = TRUE;
+			} else if (th_isdigit(c)) {
+				VADDCH(c)
+				else
+				isError = TRUE;
+			} else if (VISEND(c)) {
+				/* End of integer parsing mode */
+				tmpStr[strPos] = 0;
+				switch (pItem->itemType) {
+				case ITEM_INT:
+					*((int *) pItem->itemData) = atoi(tmpStr);
+					break;
+
+				case ITEM_UINT:
+					*((unsigned int *) pItem->itemData) = atol(tmpStr);
+					break;
+				}
+				if (pItem->itemValidate) {
+					if (!pItem->itemValidate(pItem))
+						validError = TRUE;
+				}
+
+				prevMode = parseMode;
+				parseMode = PM_NORMAL;
+			} else {
+				/* Error! Unexpected character. */
+				th_config_error(pcFilename, lineNum,
+						"Unexpected character, ", SET_MAX_BUF);
+				parseMode = PM_ERROR;
+			}
+
+			if (isError) {
+				/* Error! String too long! */
+				th_config_error(pcFilename, lineNum,
+						"String too long! Maximum is %d characters.",
+						SET_MAX_BUF);
+				parseMode = PM_ERROR;
+			}
+
+			isStart = FALSE;
+			c = -1;
+			break;
+
+		case PM_BOOL:
+			/* Boolean parsing mode */
+			if (isStart) {
+				tmpCh = c;
+				isStart = FALSE;
+			} else if (VISEND(c)) {
+				/* End of boolean parsing */
+				switch (tmpCh) {
+				case 'Y':
+				case 'y':
+				case 'T':
+				case 't':
+				case '1':
+					tmpBool = TRUE;
+					break;
+
+				default:
+					tmpBool = FALSE;
+					break;
+				}
+
+				/* Set the value */
+				*((BOOL *) pItem->itemData) = tmpBool;
+				if (pItem->itemValidate) {
+					if (!pItem->itemValidate(pItem))
+						validError = TRUE;
+				}
+
+				prevMode = parseMode;
+				parseMode = PM_NORMAL;
+			}
+
+			c = -1;
+			break;
+		}
+	}
+
+
+	/* Close files */
+	fclose(inFile);
+
+	/* Check for validation errors */
+	if (validError)
+		return 1;
+
+	/* Return result */
+	if (parseMode == PM_ERROR)
+		return -2;
+	else
+		return 0;
+}
+
+
+/* Write a configuration into file
+ */
+int th_config_write(FILE * outFile, t_config * cfg)
+{
+	t_config_item *pItem;
+
+	if (!cfg)
+		return -1;
+
+	fprintf(outFile, "# Configuration written by %s %s\n\n", 
+		th_prog_fullname, th_prog_version);
+
+	pItem = cfg->pItems;
+	while (pItem) {
+		if (!pItem->itemData || ((pItem->itemType == ITEM_STRING) &&
+			*(char_t **) pItem->itemData == NULL)) {
+			
+			fprintf(outFile, "#%s = ", pItem->itemName);
+			
+			switch (pItem->itemType) {
+			case ITEM_STRING:
+				fprintf(outFile, "\"string\"");
+				break;
+
+			case ITEM_INT:
+				fprintf(outFile, "int");
+				break;
+
+			case ITEM_UINT:
+				fprintf(outFile, "uint");
+				break;
+
+			case ITEM_BOOL:
+				fprintf(outFile, "boolean");
+				break;
+			}
+			
+		} else {
+			fprintf(outFile, "%s = ", pItem->itemName);
+			
+			switch (pItem->itemType) {
+			case ITEM_STRING:
+				fprintf(outFile, "\"%s\"",
+					*((char_t **) pItem->itemData));
+				break;
+
+			case ITEM_INT:
+				fprintf(outFile, "%i",
+					*((int *) pItem->itemData));
+				break;
+
+			case ITEM_UINT:
+				fprintf(outFile, "%d",
+					*((unsigned int *) pItem->itemData));
+				break;
+
+			case ITEM_BOOL:
+				fprintf(outFile, "%s",
+					*((BOOL *) pItem->itemData) ? "yes" : "no");
+				break;
+			}
+		}
+
+		fprintf(outFile, "\n\n");
+		pItem = pItem->pNext;
+	}
+
+	return 0;
+}