view 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 source

/*
 * 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;
}