2 * Copyright (C) 2013 Andy Spencer <andy753421@gmail.com>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27 /* Saved formattnig struct */
28 typedef struct line_t {
39 static const char *booleans[] = {
45 static char *filename;
46 static parser_t parser;
49 static line_t *settings;
51 /* Parsing and saving */
55 static char *lastgroup;
56 static char *lastname;
59 /* Helper functions */
60 static void conf_error(const char *value)
63 error("invalid value '%s' for %s.%s.%s",
64 value, lastgroup, lastname, lastkey);
66 error("invalid value '%s' for %s.%s",
67 value, lastgroup, lastkey);
70 static void set_value(const char *group, const char *name,
71 const char *key, const char *value)
74 line_t *groupend = NULL;
75 line_t *fileend = NULL;
77 /* Queue a save next time around */
80 /* Look though for existing items */
81 for (line_t *line = settings; line; line = line->next) {
82 /* Search for the correct group */
84 ingroup = match(line->group, group) &&
85 match(line->name, name);
87 if (ingroup && match(line->key, key)) {
89 line->value = strcopy(value);
93 /* Record positions for new keys */
94 if (ingroup && line->key && line->value)
100 /* Create new items */
102 /* Append to group */
103 line_t *line = new0(line_t);
104 line->key = strcopy(key);
105 line->value = strcopy(value);
106 line->next = groupend->next;
107 groupend->next = line;
108 } else if (fileend) {
109 /* Create new group */
110 line_t *blank = new0(line_t);
111 line_t *header = new0(line_t);
112 line_t *line = new0(line_t);
113 fileend->next = blank;
114 blank->next = header;
115 header->group = strcopy(group);
116 header->name = strcopy(name);
118 line->key = strcopy(key);
119 line->value = strcopy(value);
121 /* Create new file */
122 line_t *header = new0(line_t);
123 line_t *line = new0(line_t);
125 header->group = strcopy(group);
126 header->name = strcopy(name);
128 line->key = strcopy(key);
129 line->value = strcopy(value);
133 /* Parsing functions */
134 static char *scan_white(char *src)
136 while (*src == ' ' || *src == '\t')
141 static char *scan_word(char *dst, char *src)
143 while (islower(*src))
149 static char *scan_value(char *dst, char *src)
152 while (*src != '#' && *src != '\0')
156 while (dst > start && (*dst == ' ' || *dst == '\t'));
160 static char *scan_quote(char *dst, char *src)
162 while (*src != '"' && *src != '\0') {
165 case '\0': *dst++ = '\0'; break;
166 case '\\': *dst++ = '\\'; src++; break;
167 case 't': *dst++ = '\t'; src++; break;
168 case 'r': *dst++ = '\r'; src++; break;
169 case 'n': *dst++ = '\n'; src++; break;
170 default: *dst++ = *src; src++; break;
182 static void parse_line(line_t *dst, char *text, const char *file, int lnum)
184 char *chr = scan_white(text);
186 dst->group = malloc(strlen(chr)+1);
187 chr = scan_white(chr+1);
188 chr = scan_word(dst->group, chr);
189 chr = scan_white(chr);
191 dst->name = malloc(strlen(chr)+1);
192 chr = scan_quote(dst->name, chr+1);
193 chr = scan_white(chr);
196 error("parsing group at %s:%d,%d -- '%.8s..'",
197 file, lnum, 1+chr-text, chr);
199 else if (islower(*chr)) {
200 dst->key = malloc(strlen(chr)+1);
201 chr = scan_white(chr);
202 chr = scan_word(dst->key, chr);
203 chr = scan_white(chr);
205 error("parsing key at %s:%d,%d -- '%.8s..'",
206 file, lnum, 1+chr-text, chr);
209 chr = scan_white(chr);
210 dst->value = malloc(strlen(chr)+1);
212 chr = scan_quote(dst->value, chr+1);
214 chr = scan_value(dst->value, chr);
215 } else if (*chr != '#' && *chr != '\n' && *chr != '\0') {
216 error("parsing file at %s:%d,%d -- '%.8s..'",
217 file, lnum, 1+chr-text, chr);
221 /* File I/O functions */
222 static void conf_load(const char *path, parser_t parser)
225 char *group = NULL, *name = NULL;
227 /* read the whole file */
229 char *start = read_file(path, &len);
236 for (lnum = 1, sol = start; sol < (start+len); lnum++, sol = eol+1) {
237 eol = strchr(sol, '\n') ?: &start[len];
240 /* update current group info */
241 line_t *line = new0(line_t);
242 line->text = strcopy(sol);
243 parse_line(line, sol, path, lnum);
244 group = line->group ? line->group : group;
245 name = line->group ? line->name : name;
247 /* Parse dynamic groups */
248 if (line->group && line->name) {
250 lastgroup = line->group;
251 lastname = line->name;
253 parser(line->group, line->name, "", "");
255 error("unknown group: line %d - [%s \"%s\"]",
256 lnum, group, name ?: "");
259 /* Parse static key/value pairs */
260 if (group && line->key && line->value) {
265 parser(group, name?:"", line->key, line->value);
267 error("unknown setting: line %d - %s.%s.%s = '%s'\n",
268 lnum, group, name?:"", line->key, line->value);
272 debug("parse: %s.%s.%s = '%s'", group, name, line->key, line->value);
274 /* save line formatting for the next write */
285 void conf_save(const char *path)
287 FILE *fd = fopen(path, "wt+");
291 for (line_t *cur = settings; cur; cur = cur->next) {
292 /* Output existing items */
293 if (cur->text && !cur->dirty)
294 fprintf(fd, "%s\n", cur->text);
296 /* Output group and name headers */
297 else if (cur->group && cur->name)
298 fprintf(fd, "[%s \"%s\"]\n", cur->group, cur->name);
300 /* Output group only headers */
302 fprintf(fd, "[%s]\n", cur->group);
304 /* Output key/value pairs - todo: add quotes */
305 else if (cur->key && cur->value)
306 fprintf(fd, "\t%s = %s\n", cur->key, cur->value);
308 /* Output blank lines */
317 void conf_setup(const char *_name, parser_t _parser)
319 const char *home = getenv("HOME");
320 filename = alloc0(strlen(home) + 1 + strlen(_name) + 1);
321 sprintf(filename, "%s/%s", home, _name);
328 conf_load(filename, parser);
340 int get_enum(const char *value, const char **map, int n)
343 for (int i = 0; i < n; i++)
344 if (match(map[i], value))
350 int get_bool(const char *value)
353 return get_enum(value, booleans, N_ELEMENTS(booleans));
356 int get_number(const char *value)
360 int rval = atoi(value);
366 char *get_string(const char *value)
372 char *get_name(const char *name)
379 void set_enum(const char *group, const char *name,
380 const char *key, int value,
381 const char **map, int n)
383 if (value >= 0 && value < n)
384 set_value(group, name, key, map[value]);
387 void set_bool(const char *group, const char *name,
388 const char *key, int value)
390 set_enum(group, name, key, value,
391 booleans, N_ELEMENTS(booleans));
394 void set_number(const char *group, const char *name,
395 const char *key, int value)
398 snprintf(buf, sizeof(buf), "%d", value);
399 set_value(group, name, key, buf);
402 void set_string(const char *group, const char *name,
403 const char *key, const char *value)
405 set_value(group, name, key, value);
408 void set_name(const char *group, const char *name, const char *value)
410 for (line_t *line = settings; line; line = line->next) {
411 if (match(line->group, group) &&
412 match(line->name, name)) {
414 line->name = strcopy(value);
422 static const char *colors[] = {"red", "green", "blue"};
424 static int test_bin = 1;
425 static int test_num = 42;
426 static char *test_str = "str";
427 static int test_clr = 0;
429 static void test_parser(const char *group, const char *name,
430 const char *key, const char *value)
432 if (match(group, "test")) {
433 if (match(key, "bin"))
434 test_bin = get_bool(value);
435 else if (match(key, "clr"))
436 test_clr = get_enum(value, colors, N_ELEMENTS(colors));
437 else if (match(key, "num"))
438 test_num = get_number(value);
439 else if (match(key, "str"))
440 test_str = get_string(value);
446 printf("conf_test:\n");
448 /* Read values from a file */
449 conf_load("data/test.rc", test_parser);
452 printf(" bin: %-8d\n", test_bin);
453 printf(" clr: %-8s\n", colors[test_clr]);
454 printf(" num: %-8d\n", test_num);
455 printf(" str: %-8s\n", test_str);
458 set_bool ("test", 0, "bin", 1);
459 set_enum ("test", 0, "clr", 2, colors, N_ELEMENTS(colors));
460 set_number("test", 0, "num", -9999);
461 set_string("test", 0, "str", "hello");
463 set_string("test", 0, "new", "new0");
464 set_string("test", "new", "new", "new1");
465 set_string("new", 0, "newa", "new2");
466 set_string("new", 0, "newb", "new3");
467 set_string("new", "new", "newa", "new4");
468 set_string("new", "new", "newb", "new5");
470 set_name ("func", "name", "test");
472 /* Write back to file */
473 conf_save("data/test_out.rc");