Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
9169 | turbocat | 1 | /* |
2 | * OpenTyrian: A modern cross-platform port of Tyrian |
||
3 | * Copyright (C) 2015 The OpenTyrian Development Team |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or |
||
6 | * modify it under the terms of the GNU General Public License |
||
7 | * as published by the Free Software Foundation; either version 2 |
||
8 | * of the License, or (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License |
||
16 | * along with this program; if not, write to the Free Software |
||
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | */ |
||
19 | /*! |
||
20 | * \file config_file.c |
||
21 | * \author Carl Reinke |
||
22 | * \date 2015 |
||
23 | * \copyright GNU General Public License v2+ or Mozilla Public License 2.0 |
||
24 | */ |
||
25 | #include "config_file.h" |
||
26 | |||
27 | #include |
||
28 | #include |
||
29 | #include |
||
30 | #include |
||
31 | #include |
||
32 | |||
33 | /* potential size of decimal representation of type */ |
||
34 | #define udecsizeof(t) ((CHAR_BIT * sizeof(t) / 3) + 1) |
||
35 | #define sdecsizeof(t) (udecsizeof(t) + 1) |
||
36 | |||
37 | extern void config_oom( void ); |
||
38 | |||
39 | void config_oom( void ) |
||
40 | { |
||
41 | fprintf(stderr, "out of memory\n"); |
||
42 | exit(EXIT_FAILURE); |
||
43 | } |
||
44 | |||
45 | /* string manipulators */ |
||
46 | |||
47 | static ConfigString string_init_len( const char *s, size_t n ) |
||
48 | { |
||
49 | ConfigString string; |
||
50 | |||
51 | if (s == NULL) |
||
52 | { |
||
53 | CONFIG_STRING_LONG_TAG(string) = true; |
||
54 | |||
55 | string.long_buf = NULL; |
||
56 | } |
||
57 | else |
||
58 | { |
||
59 | char is_long = n >= COUNTOF(string.short_buf); |
||
60 | |||
61 | CONFIG_STRING_LONG_TAG(string) = is_long; |
||
62 | |||
63 | char *buffer = is_long ? |
||
64 | string.long_buf = malloc((n + 1) * sizeof(char)) : |
||
65 | string.short_buf; |
||
66 | if (buffer == NULL) |
||
67 | config_oom(); |
||
68 | |||
69 | memcpy(buffer, s, n * sizeof(char)); |
||
70 | buffer[n] = '\0'; |
||
71 | } |
||
72 | |||
73 | return string; |
||
74 | } |
||
75 | |||
76 | static void string_deinit( ConfigString *string ) |
||
77 | { |
||
78 | char is_long = CONFIG_STRING_LONG_TAG(*string); |
||
79 | |||
80 | if (is_long) |
||
81 | { |
||
82 | free(string->long_buf); |
||
83 | string->long_buf = NULL; |
||
84 | } |
||
85 | } |
||
86 | |||
87 | static bool string_equal_len( ConfigString *string, const char *s, size_t n ) |
||
88 | { |
||
89 | const char *cstr = config_string_to_cstr(string); |
||
90 | return strncmp(cstr, s, n) == 0 && cstr[n] == '\0'; |
||
91 | } |
||
92 | |||
93 | /* config manipulators */ |
||
94 | |||
95 | static void deinit_section( ConfigSection *section ); |
||
96 | static void deinit_option( ConfigOption *option ); |
||
97 | |||
98 | void config_init( Config *config ) |
||
99 | { |
||
100 | assert(config != NULL); |
||
101 | |||
102 | config->sections_count = 0; |
||
103 | config->sections = NULL; |
||
104 | } |
||
105 | |||
106 | void config_deinit( Config *config ) |
||
107 | { |
||
108 | assert(config != NULL); |
||
109 | |||
110 | for (unsigned int s = 0; s < config->sections_count; ++s) |
||
111 | { |
||
112 | ConfigSection *section = &config->sections[s]; |
||
113 | |||
114 | deinit_section(section); |
||
115 | } |
||
116 | |||
117 | free(config->sections); |
||
118 | config->sections = NULL; |
||
119 | } |
||
120 | |||
121 | /* config section manipulators -- internal */ |
||
122 | |||
123 | static void init_section( ConfigSection *section, const char *type, size_t type_len, const char *name, size_t name_len ) |
||
124 | { |
||
125 | section->type = string_init_len(type, type_len); |
||
126 | section->name = string_init_len(name, name_len); |
||
127 | section->options_count = 0; |
||
128 | section->options = NULL; |
||
129 | } |
||
130 | |||
131 | static void deinit_section( ConfigSection *section ) |
||
132 | { |
||
133 | for (unsigned int o = 0; o < section->options_count; ++o) |
||
134 | { |
||
135 | ConfigOption *option = §ion->options[o]; |
||
136 | |||
137 | deinit_option(option); |
||
138 | } |
||
139 | |||
140 | string_deinit(§ion->type); |
||
141 | string_deinit(§ion->name); |
||
142 | |||
143 | free(section->options); |
||
144 | section->options = NULL; |
||
145 | } |
||
146 | |||
147 | /* config section accessors/manipulators -- by type, name */ |
||
148 | |||
149 | ConfigSection *config_add_section_len( Config *config, const char *type, size_t type_len, const char *name, size_t name_len ) |
||
150 | { |
||
151 | assert(config != NULL); |
||
152 | assert(type != NULL); |
||
153 | |||
154 | ConfigSection *sections = realloc(config->sections, (config->sections_count + 1) * sizeof(ConfigSection)); |
||
155 | if (sections == NULL) |
||
156 | return NULL; |
||
157 | |||
158 | ConfigSection *section = §ions[config->sections_count]; |
||
159 | |||
160 | config->sections_count += 1; |
||
161 | config->sections = sections; |
||
162 | |||
163 | init_section(section, type, type_len, name, name_len); |
||
164 | |||
165 | return section; |
||
166 | } |
||
167 | |||
168 | ConfigSection *config_find_sections( Config *config, const char *type, ConfigSection **save ) |
||
169 | { |
||
170 | assert(config != NULL); |
||
171 | assert(type != NULL); |
||
172 | |||
173 | ConfigSection *sections_end = &config->sections[config->sections_count]; |
||
174 | |||
175 | ConfigSection *section = save != NULL && *save != NULL ? |
||
176 | *save : |
||
177 | &config->sections[0]; |
||
178 | |||
179 | for (; section < sections_end; ++section) |
||
180 | if (strcmp(config_string_to_cstr(§ion->type), type) == 0) |
||
181 | break; |
||
182 | |||
183 | if (save != NULL) |
||
184 | *save = section; |
||
185 | |||
186 | return section < sections_end ? section : NULL; |
||
187 | } |
||
188 | |||
189 | ConfigSection *config_find_section( Config *config, const char *type, const char *name ) |
||
190 | { |
||
191 | assert(config != NULL); |
||
192 | assert(type != NULL); |
||
193 | |||
194 | ConfigSection *sections_end = &config->sections[config->sections_count]; |
||
195 | |||
196 | for (ConfigSection *section = &config->sections[0]; section < sections_end; ++section) |
||
197 | { |
||
198 | if (strcmp(config_string_to_cstr(§ion->type), type) == 0) |
||
199 | { |
||
200 | const char *section_name = config_string_to_cstr(§ion->name); |
||
201 | if ((section_name == NULL || name == NULL) ? section_name == name : strcmp(config_string_to_cstr(§ion->name), name) == 0) |
||
202 | return section; |
||
203 | } |
||
204 | } |
||
205 | |||
206 | return NULL; |
||
207 | } |
||
208 | |||
209 | ConfigSection *config_find_or_add_section( Config *config, const char *type, const char *name ) |
||
210 | { |
||
211 | assert(config != NULL); |
||
212 | assert(type != NULL); |
||
213 | |||
214 | ConfigSection *section = config_find_section(config, type, name); |
||
215 | |||
216 | if (section != NULL) |
||
217 | return section; |
||
218 | |||
219 | return config_add_section(config, type, name); |
||
220 | } |
||
221 | |||
222 | /* config option manipulators -- internal */ |
||
223 | |||
224 | static void init_option_value( ConfigOption *option, const char *value, size_t value_len ) |
||
225 | { |
||
226 | option->values_count = 0; |
||
227 | option->v.value = string_init_len(value, value_len); |
||
228 | } |
||
229 | |||
230 | static void deinit_option_value( ConfigOption *option ) |
||
231 | { |
||
232 | if (option->values_count != 0) |
||
233 | { |
||
234 | ConfigString *values_end = &option->v.values[option->values_count]; |
||
235 | for (ConfigString *value = &option->v.values[0]; value < values_end; ++value) |
||
236 | string_deinit(value); |
||
237 | |||
238 | free(option->v.values); |
||
239 | option->v.values = NULL; |
||
240 | } |
||
241 | else |
||
242 | { |
||
243 | string_deinit(&option->v.value); |
||
244 | } |
||
245 | } |
||
246 | |||
247 | static void init_option( ConfigOption *option, const char *key, size_t key_len, const char *value, size_t value_len ) |
||
248 | { |
||
249 | option->key = string_init_len(key, key_len); |
||
250 | init_option_value(option, value, value_len); |
||
251 | } |
||
252 | |||
253 | static void deinit_option( ConfigOption *option ) |
||
254 | { |
||
255 | string_deinit(&option->key); |
||
256 | deinit_option_value(option); |
||
257 | } |
||
258 | |||
259 | static ConfigOption *append_option( ConfigSection *section, const char *key, size_t key_len, const char *value, size_t value_len ) |
||
260 | { |
||
261 | ConfigOption *options = realloc(section->options, (section->options_count + 1) * sizeof(ConfigSection)); |
||
262 | if (options == NULL) |
||
263 | return NULL; |
||
264 | |||
265 | ConfigOption *option = &options[section->options_count]; |
||
266 | |||
267 | section->options_count += 1; |
||
268 | section->options = options; |
||
269 | |||
270 | init_option(option, key, key_len, value, value_len); |
||
271 | |||
272 | return option; |
||
273 | } |
||
274 | |||
275 | static ConfigOption *get_option_len( ConfigSection *section, const char *key, size_t key_len ) |
||
276 | { |
||
277 | assert(section != NULL); |
||
278 | assert(key != NULL); |
||
279 | |||
280 | ConfigOption *options_end = §ion->options[section->options_count]; |
||
281 | for (ConfigOption *option = §ion->options[0]; option < options_end; ++option) |
||
282 | if (string_equal_len(&option->key, key, key_len)) |
||
283 | return option; |
||
284 | |||
285 | return NULL; |
||
286 | } |
||
287 | |||
288 | /* config option accessors/manipulators -- by key */ |
||
289 | |||
290 | ConfigOption *config_set_option_len( ConfigSection *section, const char *key, size_t key_len, const char *value, size_t value_len ) |
||
291 | { |
||
292 | assert(section != NULL); |
||
293 | assert(key != NULL); |
||
294 | |||
295 | ConfigOption *option = get_option_len(section, key, key_len); |
||
296 | |||
297 | if (option != NULL) |
||
298 | return config_set_value_len(option, value, value_len); |
||
299 | |||
300 | return append_option(section, key, key_len, value, value_len); |
||
301 | } |
||
302 | |||
303 | ConfigOption *config_get_option( const ConfigSection *section, const char *key ) |
||
304 | { |
||
305 | assert(section != NULL); |
||
306 | assert(key != NULL); |
||
307 | |||
308 | ConfigOption *options_end = §ion->options[section->options_count]; |
||
309 | for (ConfigOption *option = §ion->options[0]; option < options_end; ++option) |
||
310 | if (strcmp(config_string_to_cstr(&option->key), key) == 0) |
||
311 | return option; |
||
312 | |||
313 | return NULL; |
||
314 | } |
||
315 | |||
316 | ConfigOption *config_get_or_set_option_len( ConfigSection *section, const char *key, size_t key_len, const char *value, size_t value_len ) |
||
317 | { |
||
318 | assert(section != NULL); |
||
319 | assert(key != NULL); |
||
320 | |||
321 | ConfigOption *option = get_option_len(section, key, key_len); |
||
322 | |||
323 | if (option != NULL) |
||
324 | return option; |
||
325 | |||
326 | return append_option(section, key, key_len, value, value_len); |
||
327 | } |
||
328 | |||
329 | void config_set_string_option_len( ConfigSection *section, const char *key, size_t key_len, const char *value, size_t value_len ) |
||
330 | { |
||
331 | if (config_set_option_len(section, key, key_len, value, value_len) == NULL) |
||
332 | config_oom(); |
||
333 | } |
||
334 | |||
335 | bool config_get_string_option( const ConfigSection *section, const char *key, const char **out_value ) |
||
336 | { |
||
337 | assert(section != NULL); |
||
338 | assert(key != NULL); |
||
339 | |||
340 | ConfigOption *option = config_get_option(section, key); |
||
341 | if (option != NULL) |
||
342 | { |
||
343 | const char *value = config_get_value(option); |
||
344 | if (value != NULL) |
||
345 | { |
||
346 | *out_value = value; |
||
347 | return true; |
||
348 | } |
||
349 | } |
||
350 | |||
351 | return false; |
||
352 | } |
||
353 | |||
354 | const char *config_get_or_set_string_option( ConfigSection *section, const char *key, const char *value ) |
||
355 | { |
||
356 | if (!config_get_string_option(section, key, &value)) |
||
357 | config_set_string_option_len(section, key, strlen(key), value, value == NULL ? 0 : strlen(value)); |
||
358 | return value; |
||
359 | } |
||
360 | |||
361 | static const char *bool_values[][2] = |
||
362 | { |
||
363 | { "0", "1" }, |
||
364 | { "no", "yes" }, |
||
365 | { "off", "on" }, |
||
366 | { "false", "true" }, |
||
367 | }; |
||
368 | |||
369 | void config_set_bool_option( ConfigSection *section, const char *key, bool value, ConfigBoolStyle style ) |
||
370 | { |
||
371 | if (config_set_option(section, key, bool_values[style][value ? 1 : 0]) == NULL) |
||
372 | config_oom(); |
||
373 | } |
||
374 | |||
375 | bool config_get_bool_option( const ConfigSection *section, const char *key, bool *out_value ) |
||
376 | { |
||
377 | assert(section != NULL); |
||
378 | assert(key != NULL); |
||
379 | assert(out_value != NULL); |
||
380 | |||
381 | const char *value; |
||
382 | if (config_get_string_option(section, key, &value)) |
||
383 | { |
||
384 | for (size_t i = 0; i < COUNTOF(bool_values); ++i) |
||
385 | { |
||
386 | for (size_t j = 0; j < COUNTOF(bool_values[i]); ++j) |
||
387 | { |
||
388 | if (strcmp(value, bool_values[i][j]) == 0) |
||
389 | { |
||
390 | *out_value = j == 0 ? false : true; |
||
391 | return true; |
||
392 | } |
||
393 | } |
||
394 | } |
||
395 | } |
||
396 | |||
397 | return false; |
||
398 | } |
||
399 | |||
400 | bool config_get_or_set_bool_option( ConfigSection *section, const char *key, bool value, ConfigBoolStyle style ) |
||
401 | { |
||
402 | if (!config_get_bool_option(section, key, &value)) |
||
403 | config_set_bool_option(section, key, value, style); |
||
404 | return value; |
||
405 | } |
||
406 | |||
407 | void config_set_int_option( ConfigSection *section, const char *key, int value ) |
||
408 | { |
||
409 | assert(key != NULL); |
||
410 | |||
411 | char buffer[sdecsizeof(int) + 1]; |
||
412 | int buffer_len = snprintf(buffer, sizeof(buffer), "%i", value); |
||
413 | |||
414 | if (config_set_option_len(section, key, strlen(key), buffer, buffer_len) == NULL) |
||
415 | config_oom(); |
||
416 | } |
||
417 | |||
418 | bool config_get_int_option( const ConfigSection *section, const char *key, int *out_value ) |
||
419 | { |
||
420 | assert(section != NULL); |
||
421 | assert(key != NULL); |
||
422 | assert(out_value != NULL); |
||
423 | |||
424 | const char *value; |
||
425 | if (config_get_string_option(section, key, &value)) |
||
426 | { |
||
427 | int i; |
||
428 | int n; |
||
429 | if (sscanf(value, "%i%n", &i, &n) > 0 && value[n] == '\0') /* must be entire string */ |
||
430 | { |
||
431 | *out_value = i; |
||
432 | return true; |
||
433 | } |
||
434 | } |
||
435 | |||
436 | return false; |
||
437 | } |
||
438 | |||
439 | int config_get_or_set_int_option( ConfigSection *section, const char *key, int value ) |
||
440 | { |
||
441 | if (!config_get_int_option(section, key, &value)) |
||
442 | config_set_int_option(section, key, value); |
||
443 | return value; |
||
444 | } |
||
445 | |||
446 | void config_set_uint_option( ConfigSection *section, const char *key, unsigned int value ) |
||
447 | { |
||
448 | assert(key != NULL); |
||
449 | |||
450 | char buffer[udecsizeof(unsigned int) + 1]; |
||
451 | int buffer_len = snprintf(buffer, sizeof(buffer), "%u", value); |
||
452 | |||
453 | if (config_set_option_len(section, key, strlen(key), buffer, buffer_len) == NULL) |
||
454 | config_oom(); |
||
455 | } |
||
456 | |||
457 | bool config_get_uint_option( const ConfigSection *section, const char *key, unsigned int *out_value ) |
||
458 | { |
||
459 | assert(section != NULL); |
||
460 | assert(key != NULL); |
||
461 | assert(out_value != NULL); |
||
462 | |||
463 | const char *value; |
||
464 | if (config_get_string_option(section, key, &value)) |
||
465 | { |
||
466 | unsigned int u; |
||
467 | int n; |
||
468 | if (sscanf(value, "%u%n", &u, &n) > 0 && value[n] == '\0') /* must be entire string */ |
||
469 | { |
||
470 | *out_value = u; |
||
471 | return true; |
||
472 | } |
||
473 | } |
||
474 | |||
475 | return false; |
||
476 | } |
||
477 | |||
478 | unsigned int config_get_or_set_uint_option( ConfigSection *section, const char *key, unsigned int value ) |
||
479 | { |
||
480 | if (!config_get_uint_option(section, key, &value)) |
||
481 | config_set_uint_option(section, key, value); |
||
482 | return value; |
||
483 | } |
||
484 | |||
485 | /* config option accessors/manipulators -- by reference */ |
||
486 | |||
487 | ConfigOption *config_set_value_len( ConfigOption *option, const char *value, size_t value_len ) |
||
488 | { |
||
489 | assert(option != NULL); |
||
490 | |||
491 | deinit_option_value(option); |
||
492 | |||
493 | init_option_value(option, value, value_len); |
||
494 | |||
495 | return option; |
||
496 | } |
||
497 | |||
498 | ConfigOption *config_add_value_len( ConfigOption *option, const char *value, size_t value_len ) |
||
499 | { |
||
500 | assert(option != NULL); |
||
501 | assert(value != NULL); |
||
502 | |||
503 | /* convert 'item' to 'list' */ |
||
504 | if (option->values_count == 0 && config_string_to_cstr(&option->v.value) != NULL) |
||
505 | { |
||
506 | ConfigString option_value = option->v.value; |
||
507 | |||
508 | ConfigString *values = malloc(2 * sizeof(ConfigString)); |
||
509 | if (values == NULL) |
||
510 | return NULL; |
||
511 | |||
512 | option->v.values = values; |
||
513 | option->v.values[0] = option_value; |
||
514 | option->v.values[1] = string_init_len(value, value_len); |
||
515 | option->values_count = 2; |
||
516 | } |
||
517 | else |
||
518 | { |
||
519 | ConfigString *values = realloc(option->v.values, (option->values_count + 1) * sizeof(ConfigString)); |
||
520 | if (values == NULL) |
||
521 | return NULL; |
||
522 | |||
523 | option->v.values = values; |
||
524 | option->v.values[option->values_count] = string_init_len(value, value_len); |
||
525 | option->values_count += 1; |
||
526 | } |
||
527 | |||
528 | return option; |
||
529 | } |
||
530 | |||
531 | ConfigOption *config_remove_value( ConfigOption *option, unsigned int i ) |
||
532 | { |
||
533 | assert(option != NULL); |
||
534 | |||
535 | if (!config_is_value_list(option)) |
||
536 | { |
||
537 | if (i > 0) |
||
538 | return NULL; |
||
539 | |||
540 | config_set_value_len(option, NULL, 0); |
||
541 | } |
||
542 | else |
||
543 | { |
||
544 | if (i >= option->values_count) |
||
545 | return NULL; |
||
546 | |||
547 | string_deinit(&option->v.values[i]); |
||
548 | memmove(&option->v.values[i], &option->v.values[i + 1], (option->values_count - i - 1) * sizeof(ConfigString)); |
||
549 | |||
550 | if (option->values_count - 1 == 0) |
||
551 | { |
||
552 | option->v.value = string_init_len(NULL, 0); |
||
553 | option->values_count = 0; |
||
554 | } |
||
555 | else |
||
556 | { |
||
557 | ConfigString *values = realloc(option->v.values, (option->values_count - 1) * sizeof(ConfigString)); |
||
558 | if (values == NULL) |
||
559 | return NULL; |
||
560 | |||
561 | option->v.values = values; |
||
562 | option->values_count -= 1; |
||
563 | } |
||
564 | } |
||
565 | |||
566 | return option; |
||
567 | } |
||
568 | |||
569 | const char *config_get_value( const ConfigOption *option ) |
||
570 | { |
||
571 | if (option == NULL || option->values_count != 0) |
||
572 | return NULL; |
||
573 | |||
574 | return config_string_to_cstr(&option->v.value); |
||
575 | } |
||
576 | |||
577 | /* config parser */ |
||
578 | |||
579 | static bool is_whitespace( char c ) |
||
580 | { |
||
581 | return c == '\t' || c == ' '; |
||
582 | } |
||
583 | |||
584 | static bool is_end( char c ) |
||
585 | { |
||
586 | return c == '\0' || c == '\n' || c == '\r'; |
||
587 | } |
||
588 | |||
589 | static bool is_whitespace_or_end( char c ) |
||
590 | { |
||
591 | return is_whitespace(c) || is_end(c); |
||
592 | } |
||
593 | |||
594 | typedef enum |
||
595 | { |
||
596 | INVALID_DIRECTIVE = 0, |
||
597 | SECTION_DIRECTIVE, |
||
598 | ITEM_DIRECTIVE, |
||
599 | LIST_DIRECTIVE, |
||
600 | } Directive; |
||
601 | |||
602 | static Directive match_directive( const char *buffer, size_t *index ) |
||
603 | { |
||
604 | size_t i = *index; |
||
605 | |||
606 | while (is_whitespace(buffer[i])) |
||
607 | ++i; |
||
608 | |||
609 | Directive directive; |
||
610 | |||
611 | if (strncmp("section", &buffer[i], 7) == 0) |
||
612 | { |
||
613 | directive = SECTION_DIRECTIVE; |
||
614 | i += 7; |
||
615 | } |
||
616 | else if (strncmp("item", &buffer[i], 4) == 0) |
||
617 | { |
||
618 | directive = ITEM_DIRECTIVE; |
||
619 | i += 4; |
||
620 | } |
||
621 | else if (strncmp("list", &buffer[i], 4) == 0) |
||
622 | { |
||
623 | directive = LIST_DIRECTIVE; |
||
624 | i += 4; |
||
625 | } |
||
626 | else |
||
627 | { |
||
628 | return INVALID_DIRECTIVE; |
||
629 | } |
||
630 | |||
631 | if (!is_whitespace_or_end(buffer[i])) |
||
632 | return INVALID_DIRECTIVE; |
||
633 | |||
634 | *index = i; |
||
635 | |||
636 | return directive; |
||
637 | } |
||
638 | |||
639 | static bool match_nonquote_field( const char *buffer, size_t *index, size_t *length ) |
||
640 | { |
||
641 | size_t i = *index; |
||
642 | |||
643 | for (; ; ++i) |
||
644 | { |
||
645 | char c = buffer[i]; |
||
646 | |||
647 | if (is_whitespace_or_end(c)) |
||
648 | { |
||
649 | break; |
||
650 | } |
||
651 | else if (c <= ' ' || c > '~' || c == '#' || c == '\'' || c == '"') |
||
652 | { |
||
653 | return false; |
||
654 | } |
||
655 | } |
||
656 | |||
657 | *length = i - *index; |
||
658 | *index = i; |
||
659 | |||
660 | return *length > 0; |
||
661 | } |
||
662 | |||
663 | static bool parse_quote_field( char *buffer, size_t *index, size_t *length ) |
||
664 | { |
||
665 | size_t i = *index; |
||
666 | size_t o = *index; |
||
667 | |||
668 | char quote = buffer[i]; |
||
669 | |||
670 | for (; ; ) |
||
671 | { |
||
672 | char c = buffer[++i]; |
||
673 | |||
674 | if (c == quote) |
||
675 | { |
||
676 | ++i; |
||
677 | break; |
||
678 | } |
||
679 | else if (c == '\\') |
||
680 | { |
||
681 | c = buffer[++i]; |
||
682 | if (c == quote) |
||
683 | { |
||
684 | buffer[o++] = quote; |
||
685 | } |
||
686 | else |
||
687 | { |
||
688 | switch (c) |
||
689 | { |
||
690 | case 't': |
||
691 | buffer[o++] = '\t'; |
||
692 | break; |
||
693 | case 'n': |
||
694 | buffer[o++] = '\n'; |
||
695 | break; |
||
696 | case 'r': |
||
697 | buffer[o++] = '\r'; |
||
698 | break; |
||
699 | case '\\': |
||
700 | buffer[o++] = '\\'; |
||
701 | break; |
||
702 | case 'x': |
||
703 | /* parse two hex digits */ |
||
704 | c = buffer[++i]; |
||
705 | char m = (c >= '0' && c <= '9') ? '0' : |
||
706 | (c >= 'a' && c <= 'f') ? 'a' - 10 : |
||
707 | (c >= 'A' && c <= 'F') ? 'A' - 10 : 0; |
||
708 | if (m == 0) |
||
709 | return false; |
||
710 | char h = c - m; |
||
711 | c = buffer[++i]; |
||
712 | m = (c >= '0' && c <= '9') ? '0' : |
||
713 | (c >= 'a' && c <= 'f') ? 'a' - 10 : |
||
714 | (c >= 'A' && c <= 'F') ? 'A' - 10 : 0; |
||
715 | if (m == 0) |
||
716 | return false; |
||
717 | buffer[o++] = (h << 4) | (c - m); |
||
718 | break; |
||
719 | default: |
||
720 | return false; |
||
721 | } |
||
722 | } |
||
723 | } |
||
724 | else if (c >= ' ' && c <= '~') |
||
725 | { |
||
726 | buffer[o++] = c; |
||
727 | } |
||
728 | else |
||
729 | { |
||
730 | return false; |
||
731 | } |
||
732 | } |
||
733 | |||
734 | *length = o - *index; |
||
735 | *index = i; |
||
736 | |||
737 | return true; |
||
738 | } |
||
739 | |||
740 | static bool parse_field( char *buffer, size_t *index, size_t *start, size_t *length ) |
||
741 | { |
||
742 | size_t i = *index; |
||
743 | |||
744 | while (is_whitespace(buffer[i])) |
||
745 | ++i; |
||
746 | |||
747 | *start = i; |
||
748 | |||
749 | if (buffer[i] == '"' || buffer[i] == '\'') |
||
750 | { |
||
751 | if (!parse_quote_field(buffer, &i, length)) |
||
752 | return false; |
||
753 | } |
||
754 | else |
||
755 | { |
||
756 | if (!match_nonquote_field(buffer, &i, length)) |
||
757 | return false; |
||
758 | } |
||
759 | |||
760 | if (!is_whitespace_or_end(buffer[i])) |
||
761 | return INVALID_DIRECTIVE; |
||
762 | |||
763 | *index = i; |
||
764 | |||
765 | return true; |
||
766 | } |
||
767 | |||
768 | bool config_parse( Config *config, FILE *file ) |
||
769 | { |
||
770 | assert(config != NULL); |
||
771 | assert(file != NULL); |
||
772 | |||
773 | config_init(config); |
||
774 | |||
775 | ConfigSection *section = NULL; |
||
776 | ConfigOption *option = NULL; |
||
777 | |||
778 | size_t buffer_cap = 128; |
||
779 | char *buffer = malloc(buffer_cap * sizeof(char)); |
||
780 | if (buffer == NULL) |
||
781 | config_oom(); |
||
782 | size_t buffer_end = 1; |
||
783 | buffer[buffer_end - 1] = '\0'; |
||
784 | |||
785 | for (size_t line = 0, next_line = 0; ; line = next_line) |
||
786 | { |
||
787 | /* find begining of next line */ |
||
788 | while (next_line < buffer_end) |
||
789 | { |
||
790 | char c = buffer[next_line]; |
||
791 | |||
792 | if (c == '\0' && next_line == buffer_end - 1) |
||
793 | { |
||
794 | if (line > 0) |
||
795 | { |
||
796 | /* shift to front */ |
||
797 | memmove(&buffer[0], &buffer[line], buffer_end - line); |
||
798 | buffer_end -= line; |
||
799 | next_line -= line; |
||
800 | line = 0; |
||
801 | } |
||
802 | else if (buffer_end > 1) |
||
803 | { |
||
804 | /* need larger capacity */ |
||
805 | buffer_cap *= 2; |
||
806 | char *new_buffer = realloc(buffer, buffer_cap * sizeof(char)); |
||
807 | if (new_buffer == NULL) |
||
808 | config_oom(); |
||
809 | buffer = new_buffer; |
||
810 | } |
||
811 | |||
812 | size_t read = fread(&buffer[buffer_end - 1], sizeof(char), buffer_cap - buffer_end, file); |
||
813 | if (read == 0) |
||
814 | break; |
||
815 | |||
816 | buffer_end += read; |
||
817 | buffer[buffer_end - 1] = '\0'; |
||
818 | } |
||
819 | else |
||
820 | { |
||
821 | ++next_line; |
||
822 | |||
823 | if (c == '\n' || c == '\r') |
||
824 | break; |
||
825 | } |
||
826 | } |
||
827 | |||
828 | /* if at end of file */ |
||
829 | if (next_line == line) |
||
830 | break; |
||
831 | |||
832 | size_t i = line; |
||
833 | |||
834 | Directive directive = match_directive(buffer, &i); |
||
835 | |||
836 | switch (directive) |
||
837 | { |
||
838 | case INVALID_DIRECTIVE: |
||
839 | continue; |
||
840 | case SECTION_DIRECTIVE: |
||
841 | { |
||
842 | size_t type_start; |
||
843 | size_t type_length; |
||
844 | |||
845 | if (!parse_field(buffer, &i, &type_start, &type_length)) |
||
846 | continue; |
||
847 | |||
848 | size_t name_start; |
||
849 | size_t name_length; |
||
850 | |||
851 | bool has_name = parse_field(buffer, &i, &name_start, &name_length); |
||
852 | |||
853 | section = config_add_section_len(config, |
||
854 | &buffer[type_start], type_length, |
||
855 | has_name ? &buffer[name_start] : NULL, has_name ? name_length : 0); |
||
856 | if (section == NULL) |
||
857 | config_oom(); |
||
858 | option = NULL; |
||
859 | } |
||
860 | break; |
||
861 | case ITEM_DIRECTIVE: |
||
862 | case LIST_DIRECTIVE: |
||
863 | { |
||
864 | if (section == NULL) |
||
865 | continue; |
||
866 | |||
867 | size_t key_start; |
||
868 | size_t key_length; |
||
869 | |||
870 | if (!parse_field(buffer, &i, &key_start, &key_length)) |
||
871 | continue; |
||
872 | |||
873 | size_t value_start; |
||
874 | size_t value_length; |
||
875 | |||
876 | if (!parse_field(buffer, &i, &value_start, &value_length)) |
||
877 | continue; |
||
878 | |||
879 | if (directive == ITEM_DIRECTIVE) |
||
880 | { |
||
881 | option = config_set_option_len(section, |
||
882 | &buffer[key_start], key_length, |
||
883 | &buffer[value_start], value_length); |
||
884 | } |
||
885 | else |
||
886 | { |
||
887 | if (option == NULL || !string_equal_len(&option->key, &buffer[key_start], key_length)) |
||
888 | option = config_get_or_set_option_len(section, |
||
889 | &buffer[key_start], key_length, |
||
890 | NULL, 0); |
||
891 | if (option != NULL) |
||
892 | option = config_add_value_len(option, |
||
893 | &buffer[value_start], value_length); |
||
894 | } |
||
895 | if (option == NULL) |
||
896 | config_oom(); |
||
897 | } |
||
898 | break; |
||
899 | } |
||
900 | |||
901 | assert(i <= next_line); |
||
902 | } |
||
903 | |||
904 | free(buffer); |
||
905 | |||
906 | return config; |
||
907 | } |
||
908 | |||
909 | /* config writer */ |
||
910 | |||
911 | static void write_field( const ConfigString *field, FILE *file ) |
||
912 | { |
||
913 | fputc('\'', file); |
||
914 | |||
915 | char buffer[128]; |
||
916 | size_t o = 0; |
||
917 | |||
918 | for (const char *ci = config_string_to_cstr(field); *ci != '\0'; ++ci) |
||
919 | { |
||
920 | char c = *ci; |
||
921 | |||
922 | size_t l; |
||
923 | switch (c) |
||
924 | { |
||
925 | case '\t': |
||
926 | case '\n': |
||
927 | case '\r': |
||
928 | case '\'': |
||
929 | case '\\': |
||
930 | l = 2; |
||
931 | break; |
||
932 | default: |
||
933 | l = (c >= ' ' && c <= '~') ? 1 : 4; |
||
934 | break; |
||
935 | } |
||
936 | |||
937 | if (o + l > COUNTOF(buffer)) |
||
938 | { |
||
939 | fwrite(buffer, sizeof(*buffer), o, file); |
||
940 | o = 0; |
||
941 | } |
||
942 | |||
943 | switch (l) |
||
944 | { |
||
945 | case 1: |
||
946 | buffer[o++] = c; |
||
947 | break; |
||
948 | case 2: |
||
949 | switch (c) |
||
950 | { |
||
951 | case '\t': |
||
952 | buffer[o++] = '\\'; |
||
953 | buffer[o++] = 't'; |
||
954 | break; |
||
955 | case '\n': |
||
956 | buffer[o++] = '\\'; |
||
957 | buffer[o++] = 'n'; |
||
958 | break; |
||
959 | case '\r': |
||
960 | buffer[o++] = '\\'; |
||
961 | buffer[o++] = 'r'; |
||
962 | break; |
||
963 | case '\'': |
||
964 | case '\\': |
||
965 | buffer[o++] = '\\'; |
||
966 | buffer[o++] = c; |
||
967 | break; |
||
968 | } |
||
969 | break; |
||
970 | case 4: |
||
971 | buffer[o++] = '\\'; |
||
972 | buffer[o++] = 'x'; |
||
973 | char n = (c >> 4) & 0x0f; |
||
974 | buffer[o++] = (n < 10 ? '0' : ('a' - 10)) + n; |
||
975 | n = c & 0x0f; |
||
976 | buffer[o++] = (n < 10 ? '0' : ('a' - 10)) + n; |
||
977 | break; |
||
978 | } |
||
979 | } |
||
980 | |||
981 | if (o > 0) |
||
982 | fwrite(buffer, sizeof(*buffer), o, file); |
||
983 | |||
984 | fputc('\'', file); |
||
985 | } |
||
986 | |||
987 | void config_write( const Config *config, FILE *file ) |
||
988 | { |
||
989 | assert(config != NULL); |
||
990 | assert(file != NULL); |
||
991 | |||
992 | for (unsigned int s = 0; s < config->sections_count; ++s) |
||
993 | { |
||
994 | ConfigSection *section = &config->sections[s]; |
||
995 | |||
996 | fputs("section ", file); |
||
997 | write_field(§ion->type, file); |
||
998 | if (config_string_to_cstr(§ion->name) != NULL) |
||
999 | { |
||
1000 | fputc(' ', file); |
||
1001 | write_field(§ion->name, file); |
||
1002 | } |
||
1003 | fputc('\n', file); |
||
1004 | |||
1005 | for (unsigned int o = 0; o < section->options_count; ++o) |
||
1006 | { |
||
1007 | ConfigOption *option = §ion->options[o]; |
||
1008 | |||
1009 | if (option->values_count == 0 && config_string_to_cstr(&option->v.value) != NULL) |
||
1010 | { |
||
1011 | fputs("\titem ", file); |
||
1012 | write_field(&option->key, file); |
||
1013 | fputc(' ', file); |
||
1014 | write_field(&option->v.value, file); |
||
1015 | fputc('\n', file); |
||
1016 | } |
||
1017 | else |
||
1018 | { |
||
1019 | ConfigString *values_end = &option->v.values[option->values_count]; |
||
1020 | for (ConfigString *value = &option->v.values[0]; value < values_end; ++value) |
||
1021 | { |
||
1022 | fputs("\tlist ", file); |
||
1023 | write_field(&option->key, file); |
||
1024 | fputc(' ', file); |
||
1025 | write_field(value, file); |
||
1026 | fputc('\n', file); |
||
1027 | } |
||
1028 | } |
||
1029 | } |
||
1030 | |||
1031 | fputc('\n', file); |
||
1032 | } |
||
1033 | }>>>>>=>=>>=>><>=>=>=>=>=>=>=>>>>>>>>>>> |