comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:bd61a80a6c54
1 /*
2 * Very simple configuration handling functions
3 * Programmed and designed by Matti 'ccr' Hamalainen
4 * (C) Copyright 2004-2007 Tecnic Software productions (TNSP)
5 *
6 * Please read file 'COPYING' for information on license and distribution.
7 */
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif
11 #include <stdio.h>
12 #include <stdarg.h>
13 #include "th_config.h"
14 #include "th_util.h"
15 #include "th_string.h"
16
17 #define LPREV (pNode->pPrev)
18 #define LNEXT (pNode->pNext)
19
20
21 void th_config_error(char_t * pcFilename, size_t lineNum,
22 const char_t * pcFormat, ...)
23 {
24 va_list ap;
25 va_start(ap, pcFormat);
26 fprintf(stderr, "%s: '%s', line #%d: ", th_prog_name, pcFilename, lineNum);
27 vfprintf(stderr, pcFormat, ap);
28 va_end(ap);
29 }
30
31
32 /* Create a new configuration
33 */
34 t_config *th_config_new(void)
35 {
36 t_config *cfg;
37
38 cfg = (t_config *) th_calloc(1, sizeof(t_config));
39 if (!cfg)
40 return NULL;
41
42 return cfg;
43 }
44
45
46 /* Free a given configuration (the values are not free'd)
47 */
48 void th_config_free(t_config * cfg)
49 {
50 t_config_item *pCurr, *pNext;
51
52 if (!cfg)
53 return;
54
55 pCurr = cfg->pItems;
56 while (pCurr) {
57 pNext = pCurr->pNext;
58 th_free(pCurr->itemName);
59 th_free(pCurr);
60 pCurr = pNext;
61 }
62 }
63
64
65 /* Allocate and add new item to configuration
66 */
67 t_config_item *th_config_add(t_config * cfg, char_t * itemName, int itemType,
68 BOOL(*itemValidate) (t_config_item *), void *itemData)
69 {
70 t_config_item *pNode;
71
72 if (!cfg)
73 return NULL;
74
75 /* Allocate new item */
76 pNode = (t_config_item *) th_calloc(1, sizeof(t_config_item));
77 if (!pNode)
78 return NULL;
79
80 /* Set values */
81 pNode->itemType = itemType;
82 pNode->itemData = itemData;
83 pNode->itemValidate = itemValidate;
84 th_pstrcpy(&pNode->itemName, itemName);
85
86 /* Insert into linked list */
87 if (cfg->pItems) {
88 /* The first node's pPrev points to last node */
89 LPREV = cfg->pItems->pPrev; /* New node's prev = Previous last node */
90 cfg->pItems->pPrev->pNext = pNode; /* Previous last node's next = New node */
91 cfg->pItems->pPrev = pNode; /* New last node = New node */
92 LNEXT = NULL; /* But next is NULL! */
93 } else {
94 cfg->pItems = pNode; /* First node ... */
95 LPREV = pNode; /* ... it's also last */
96 LNEXT = NULL; /* But next is NULL! */
97 }
98
99 return pNode;
100 }
101
102
103 /* Add integer type setting into give configuration
104 */
105 int th_config_add_int(t_config * cfg, char_t * itemName, BOOL(*itemValidate) (t_config_item *),
106 int *itemData, int itemDef)
107 {
108 t_config_item *pNode;
109
110 pNode = th_config_add(cfg, itemName, ITEM_INT, itemValidate, (void *) itemData);
111 if (!pNode)
112 return -1;
113
114 *itemData = itemDef;
115
116 return 0;
117 }
118
119
120 /* Add unsigned integer type setting into give configuration
121 */
122 int th_config_add_uint(t_config * cfg, char_t * itemName, BOOL(*itemValidate) (t_config_item *),
123 unsigned int * itemData, unsigned int itemDef)
124 {
125 t_config_item *pNode;
126
127 pNode = th_config_add(cfg, itemName, ITEM_UINT, itemValidate, (void *) itemData);
128 if (!pNode)
129 return -1;
130
131 *itemData = itemDef;
132
133 return 0;
134 }
135
136
137 /* Add strint type setting into given configuration
138 */
139 int th_config_add_str(t_config * cfg, char_t * itemName, BOOL(*itemValidate) (t_config_item *),
140 char_t ** itemData, char_t * itemDef)
141 {
142 t_config_item *pNode;
143
144 pNode = th_config_add(cfg, itemName, ITEM_STRING, itemValidate, (void *) itemData);
145 if (!pNode)
146 return -1;
147
148 if (itemDef != NULL)
149 *itemData = th_strdup(itemDef);
150 else
151 *itemData = NULL;
152
153 return 0;
154 }
155
156
157 /* Add boolean type setting into given configuration
158 */
159 int th_config_add_bool(t_config * cfg, char_t * itemName, BOOL(*itemValidate) (t_config_item *),
160 BOOL * itemData, BOOL itemDef)
161 {
162 t_config_item *pNode;
163
164 pNode = th_config_add(cfg, itemName, ITEM_BOOL, itemValidate, (void *) itemData);
165 if (!pNode)
166 return -1;
167
168 *itemData = itemDef;
169
170 return 0;
171 }
172
173
174 /* Read a given file into configuration structure and variables
175 */
176 enum
177 {
178 PM_EOF,
179 PM_ERROR,
180 PM_NORMAL,
181 PM_COMMENT,
182 PM_NEXT,
183 PM_KEYNAME,
184 PM_KEYSET,
185 PM_STRING,
186 PM_INT,
187 PM_BOOL
188 };
189
190 #define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; }
191 #define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c))
192
193 int th_config_read(char_t * pcFilename, t_config * cfg)
194 {
195 FILE *inFile;
196 t_config_item *pItem;
197 char_t tmpStr[SET_MAX_BUF + 1];
198 size_t lineNum, strPos;
199 int c, parseMode, prevMode, nextMode, tmpCh;
200 BOOL isFound, isStart, tmpBool, isError, validError;
201
202 assert(cfg);
203
204 /* Open the file */
205 if ((inFile = fopen(pcFilename, "rb")) == NULL)
206 return -1;
207
208 /* Initialize values */
209 pItem = NULL;
210 tmpCh = 0;
211 strPos = 0;
212 lineNum = 1;
213 c = -1;
214 isFound = isStart = tmpBool = isError = validError = FALSE;
215 nextMode = prevMode = parseMode = PM_NORMAL;
216
217 /* Parse the configuration */
218 while ((parseMode != PM_EOF) && (parseMode != PM_ERROR)) {
219 if (c == -1) {
220 /* Get next character */
221 switch (c = fgetc(inFile)) {
222 case EOF:
223 if (parseMode != PM_NORMAL) {
224 th_config_error(pcFilename, lineNum,
225 "Unexpected end of file.\n");
226 parseMode = PM_ERROR;
227 } else
228 parseMode = PM_EOF;
229 break;
230
231 case '\n':
232 lineNum++;
233 }
234 }
235
236 switch (parseMode) {
237 case PM_COMMENT:
238 /* Comment parsing mode */
239 if (c == '\n') {
240 /* End of line, end of comment */
241 parseMode = prevMode;
242 prevMode = PM_COMMENT;
243 }
244 c = -1;
245 break;
246
247 case PM_NORMAL:
248 /* Normal parsing mode */
249 if (c == '#') {
250 prevMode = parseMode;
251 parseMode = PM_COMMENT;
252 c = -1;
253 } else if (VISEND(c)) {
254 c = -1;
255 } else if (th_isalpha(c)) {
256 /* Start of key name found */
257 prevMode = parseMode;
258 parseMode = PM_KEYNAME;
259 strPos = 0;
260 } else {
261 /* Error! Invalid character found */
262 th_config_error(pcFilename, lineNum,
263 "Unexpected character '%c'\n", c);
264 parseMode = PM_ERROR;
265 }
266 break;
267
268 case PM_KEYNAME:
269 /* Configuration KEY name parsing mode */
270 if (c == '#') {
271 /* Start of comment */
272 prevMode = parseMode;
273 parseMode = PM_COMMENT;
274 c = -1;
275 } else if (th_iscrlf(c) || th_isspace(c) || c == '=') {
276 /* End of key name */
277 prevMode = parseMode;
278 parseMode = PM_NEXT;
279 nextMode = PM_KEYSET;
280 } else if (th_isalnum(c) || (c == '_')) {
281 /* Add to key name string */
282 VADDCH(c)
283 else
284 {
285 /* Error! Key name string too long! */
286 th_config_error(pcFilename, lineNum,
287 "Config key name too long!");
288 parseMode = PM_ERROR;
289 }
290 c = -1;
291 } else {
292 /* Error! Invalid character found */
293 th_config_error(pcFilename, lineNum,
294 "Unexpected character '%c'\n", c);
295 parseMode = PM_ERROR;
296 }
297 break;
298
299 case PM_KEYSET:
300 if (c == '=') {
301 /* Find key from configuration */
302 tmpStr[strPos] = 0;
303 isFound = FALSE;
304 pItem = cfg->pItems;
305 while (pItem && !isFound) {
306 if (strcmp(pItem->itemName, tmpStr) == 0)
307 isFound = TRUE;
308 else
309 pItem = pItem->pNext;
310 }
311
312 /* Check if key was found */
313 if (isFound) {
314 /* Okay, set next mode */
315 switch (pItem->itemType) {
316 case ITEM_STRING:
317 nextMode = PM_STRING;
318 break;
319
320 case ITEM_INT:
321 case ITEM_UINT:
322 nextMode = PM_INT;
323 break;
324
325 case ITEM_BOOL:
326 nextMode = PM_BOOL;
327 break;
328 }
329
330 prevMode = parseMode;
331 parseMode = PM_NEXT;
332 isStart = TRUE;
333 strPos = 0;
334 } else {
335 /* Error! No configuration key by this name found */
336 th_config_error(pcFilename, lineNum,
337 "No such configuration setting ('%s')\n",
338 tmpStr);
339 parseMode = PM_ERROR;
340 }
341
342 c = -1;
343 } else {
344 /* Error! '=' expected! */
345 th_config_error(pcFilename, lineNum,
346 "Unexpected character '%c', '=' expected.\n", c);
347 parseMode = PM_ERROR;
348 }
349 break;
350
351 case PM_NEXT:
352 /* Search next item parsing mode */
353 if (c == '#') {
354 /* Start of comment */
355 prevMode = parseMode;
356 parseMode = PM_COMMENT;
357 } else if (th_isspace(c) || th_iscrlf(c)) {
358 /* Ignore whitespaces and linechanges */
359 c = -1;
360 } else {
361 /* Next item found */
362 prevMode = parseMode;
363 parseMode = nextMode;
364 }
365 break;
366
367 case PM_STRING:
368 /* String parsing mode */
369 if (isStart) {
370 /* Start of string, get delimiter */
371 tmpCh = c;
372 isStart = FALSE;
373 strPos = 0;
374 } else if (c == tmpCh) {
375 /* End of string, set the value */
376 tmpStr[strPos] = 0;
377 th_pstrcpy((char_t **) pItem->itemData, tmpStr);
378 if (pItem->itemValidate) {
379 if (!pItem->itemValidate(pItem))
380 validError = TRUE;
381 }
382
383 prevMode = parseMode;
384 parseMode = PM_NORMAL;
385 } else {
386 /* Add character to string */
387 VADDCH(c)
388 else
389 {
390 /* Error! String too long! */
391 th_config_error(pcFilename, lineNum,
392 "String too long! Maximum is %d characters.",
393 SET_MAX_BUF);
394 parseMode = PM_ERROR;
395 }
396 }
397
398 c = -1;
399 break;
400
401 case PM_INT:
402 /* Integer parsing mode */
403 if (isStart && (pItem->itemType == ITEM_UINT) && (c == '-')) {
404 /* Error! Negative values not allowed for unsigned ints */
405 th_config_error(pcFilename, lineNum,
406 "Negative value specified, unsigned value expected.");
407 parseMode = PM_ERROR;
408 } else if (isStart && (c == '-' || c == '+')) {
409 VADDCH(c)
410 else
411 isError = TRUE;
412 } else if (th_isdigit(c)) {
413 VADDCH(c)
414 else
415 isError = TRUE;
416 } else if (VISEND(c)) {
417 /* End of integer parsing mode */
418 tmpStr[strPos] = 0;
419 switch (pItem->itemType) {
420 case ITEM_INT:
421 *((int *) pItem->itemData) = atoi(tmpStr);
422 break;
423
424 case ITEM_UINT:
425 *((unsigned int *) pItem->itemData) = atol(tmpStr);
426 break;
427 }
428 if (pItem->itemValidate) {
429 if (!pItem->itemValidate(pItem))
430 validError = TRUE;
431 }
432
433 prevMode = parseMode;
434 parseMode = PM_NORMAL;
435 } else {
436 /* Error! Unexpected character. */
437 th_config_error(pcFilename, lineNum,
438 "Unexpected character, ", SET_MAX_BUF);
439 parseMode = PM_ERROR;
440 }
441
442 if (isError) {
443 /* Error! String too long! */
444 th_config_error(pcFilename, lineNum,
445 "String too long! Maximum is %d characters.",
446 SET_MAX_BUF);
447 parseMode = PM_ERROR;
448 }
449
450 isStart = FALSE;
451 c = -1;
452 break;
453
454 case PM_BOOL:
455 /* Boolean parsing mode */
456 if (isStart) {
457 tmpCh = c;
458 isStart = FALSE;
459 } else if (VISEND(c)) {
460 /* End of boolean parsing */
461 switch (tmpCh) {
462 case 'Y':
463 case 'y':
464 case 'T':
465 case 't':
466 case '1':
467 tmpBool = TRUE;
468 break;
469
470 default:
471 tmpBool = FALSE;
472 break;
473 }
474
475 /* Set the value */
476 *((BOOL *) pItem->itemData) = tmpBool;
477 if (pItem->itemValidate) {
478 if (!pItem->itemValidate(pItem))
479 validError = TRUE;
480 }
481
482 prevMode = parseMode;
483 parseMode = PM_NORMAL;
484 }
485
486 c = -1;
487 break;
488 }
489 }
490
491
492 /* Close files */
493 fclose(inFile);
494
495 /* Check for validation errors */
496 if (validError)
497 return 1;
498
499 /* Return result */
500 if (parseMode == PM_ERROR)
501 return -2;
502 else
503 return 0;
504 }
505
506
507 /* Write a configuration into file
508 */
509 int th_config_write(FILE * outFile, t_config * cfg)
510 {
511 t_config_item *pItem;
512
513 if (!cfg)
514 return -1;
515
516 fprintf(outFile, "# Configuration written by %s %s\n\n",
517 th_prog_fullname, th_prog_version);
518
519 pItem = cfg->pItems;
520 while (pItem) {
521 if (!pItem->itemData || ((pItem->itemType == ITEM_STRING) &&
522 *(char_t **) pItem->itemData == NULL)) {
523
524 fprintf(outFile, "#%s = ", pItem->itemName);
525
526 switch (pItem->itemType) {
527 case ITEM_STRING:
528 fprintf(outFile, "\"string\"");
529 break;
530
531 case ITEM_INT:
532 fprintf(outFile, "int");
533 break;
534
535 case ITEM_UINT:
536 fprintf(outFile, "uint");
537 break;
538
539 case ITEM_BOOL:
540 fprintf(outFile, "boolean");
541 break;
542 }
543
544 } else {
545 fprintf(outFile, "%s = ", pItem->itemName);
546
547 switch (pItem->itemType) {
548 case ITEM_STRING:
549 fprintf(outFile, "\"%s\"",
550 *((char_t **) pItem->itemData));
551 break;
552
553 case ITEM_INT:
554 fprintf(outFile, "%i",
555 *((int *) pItem->itemData));
556 break;
557
558 case ITEM_UINT:
559 fprintf(outFile, "%d",
560 *((unsigned int *) pItem->itemData));
561 break;
562
563 case ITEM_BOOL:
564 fprintf(outFile, "%s",
565 *((BOOL *) pItem->itemData) ? "yes" : "no");
566 break;
567 }
568 }
569
570 fprintf(outFile, "\n\n");
571 pItem = pItem->pNext;
572 }
573
574 return 0;
575 }