Mercurial > hg > th-libs
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 } |