]> Pileus Git - lackey/blob - src/conf.c
e379cbf4f598bab3cafefacf93ede4499ae8068d
[lackey] / src / conf.c
1 /*
2  * Copyright (C) 2013 Andy Spencer <andy753421@gmail.com>
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <ctype.h>
22 #include <errno.h>
23
24 #include "util.h"
25 #include "conf.h"
26
27 /* Saved formattnig struct */
28 typedef struct line_t {
29         char *group;
30         char *name;
31         char *key;
32         char *value;
33         char *text;
34         int   dirty;
35         struct line_t *next;
36 } line_t;
37
38 /* Constat data */
39 static const char *booleans[] = {
40         "false",
41         "true"
42 };
43
44 /* Setup info */
45 static char     *filename;
46 static parser_t  parser;
47 static int       argc;
48 static char    **argv;
49
50 /* Static data */
51 static line_t   *settings;
52
53 /* Parsing and saving */
54 static int       wasfound;
55 static int       needsave;
56
57 static char     *lastgroup;
58 static char     *lastname;
59 static char     *lastkey;
60
61 /* Helper functions */
62 static void conf_error(const char *value)
63 {
64         if (lastname)
65                 error("invalid value '%s' for %s.%s.%s",
66                                 value, lastgroup, lastname, lastkey);
67         else
68                 error("invalid value '%s' for %s.%s",
69                                 value, lastgroup, lastkey);
70 }
71
72 static void set_value(const char *group, const char *name,
73                 const char *key, const char *value)
74 {
75         int     ingroup  = 0;
76         line_t *groupend = NULL;
77         line_t *fileend  = NULL;
78
79         /* Queue a save next time around */
80         needsave = 1;
81
82         /* Look though for existing items */
83         for (line_t *line = settings; line; line = line->next) {
84                 /* Search for the correct group */
85                 if (line->group)
86                         ingroup = match(line->group, group) &&
87                                   match(line->name,  name);
88                 /* Set value */
89                 if (ingroup && match(line->key, key)) {
90                         free(line->value);
91                         line->value = strcopy(value);
92                         line->dirty = 1;
93                         return;
94                 }
95                 /* Record positions for new keys */
96                 if (ingroup && line->key && line->value)
97                         groupend = line;
98                 else
99                         fileend = line;
100         }
101
102         /* Create new items */
103         if (groupend) {
104                 /* Append to group */
105                 line_t *line = new0(line_t);
106                 line->key      = strcopy(key);
107                 line->value    = strcopy(value);
108                 line->next     = groupend->next;
109                 groupend->next = line;
110         } else if (fileend)  {
111                 /* Create new group */
112                 line_t *blank  = new0(line_t);
113                 line_t *header = new0(line_t);
114                 line_t *line   = new0(line_t);
115                 fileend->next = blank;
116                 blank->next   = header;
117                 header->group = strcopy(group);
118                 header->name  = strcopy(name);
119                 header->next  = line;
120                 line->key     = strcopy(key);
121                 line->value   = strcopy(value);
122         } else {
123                 /* Create new file */
124                 line_t *header = new0(line_t);
125                 line_t *line   = new0(line_t);
126                 settings      = header;
127                 header->group = strcopy(group);
128                 header->name  = strcopy(name);
129                 header->next  = line;
130                 line->key     = strcopy(key);
131                 line->value   = strcopy(value);
132         }
133 }
134
135 /* Parsing functions */
136 static char *scan_white(char *src)
137 {
138         while (*src == ' ' || *src == '\t')
139                 src++;
140         return src;
141 }
142
143 static char *scan_word(char *dst, char *src)
144 {
145         while (islower(*src))
146                 *dst++ = *src++;
147         *dst = '\0';
148         return src;
149 }
150
151 static char *scan_value(char *dst, char *src)
152 {
153         char *start = dst;
154         while (*src != '#' && *src != '\0')
155                 *dst++ = *src++;
156         do
157                 *dst-- = '\0';
158         while (dst > start && (*dst == ' ' || *dst == '\t'));
159         return src;
160 }
161
162 static char *scan_quote(char *dst, char *src)
163 {
164         while (*src != '"' && *src != '\0') {
165                 if (*src == '\\') {
166                         switch (*++src) {
167                                 case '\0': *dst++ = '\0';        break;
168                                 case '\\': *dst++ = '\\'; src++; break;
169                                 case 't':  *dst++ = '\t'; src++; break;
170                                 case 'r':  *dst++ = '\r'; src++; break;
171                                 case 'n':  *dst++ = '\n'; src++; break;
172                                 default:   *dst++ = *src; src++; break;
173                         }
174                 } else {
175                         *dst++ = *src++;
176                 }
177         }
178         if (*src == '"')
179                 src++;
180         *dst = '\0';
181         return src;
182 }
183
184 static void parse_line(line_t *dst, char *text, const char *file, int lnum)
185 {
186         char *chr = scan_white(text);
187         if (*chr == '[') {
188                 dst->group = malloc(strlen(chr)+1);
189                 chr = scan_white(chr+1);
190                 chr = scan_word(dst->group, chr);
191                 chr = scan_white(chr);
192                 if (*chr == '"') {
193                         dst->name = malloc(strlen(chr)+1);
194                         chr = scan_quote(dst->name, chr+1);
195                         chr = scan_white(chr);
196                 }
197                 if (*chr != ']')
198                         error("parsing group at %s:%d,%d -- '%.8s..'",
199                                         file, lnum, 1+chr-text, chr);
200         }
201         else if (islower(*chr)) {
202                 dst->key = malloc(strlen(chr)+1);
203                 chr = scan_white(chr);
204                 chr = scan_word(dst->key, chr);
205                 chr = scan_white(chr);
206                 if (*chr != '=')
207                         error("parsing key at %s:%d,%d -- '%.8s..'",
208                                         file, lnum, 1+chr-text, chr);
209                 else
210                         chr++;
211                 chr = scan_white(chr);
212                 dst->value = malloc(strlen(chr)+1);
213                 if (*chr == '"')
214                         chr = scan_quote(dst->value, chr+1);
215                 else
216                         chr = scan_value(dst->value, chr);
217         } else if (*chr != '#' && *chr != '\n' && *chr != '\0') {
218                 error("parsing file at %s:%d,%d -- '%.8s..'",
219                                 file, lnum, 1+chr-text, chr);
220         }
221 }
222
223 /* File I/O functions */
224 static void conf_load(const char *path, parser_t parser)
225 {
226         line_t *prev = NULL;
227         char *group = NULL, *name = NULL;
228
229         /* read the whole file */
230         int   len;
231         char *start = read_file(path, &len);
232         if (!start)
233                 return;
234
235         /* run parser */
236         int lnum;
237         char *sol, *eol;
238         for (lnum = 1, sol = start; sol < (start+len); lnum++, sol = eol+1) {
239                 eol = strchr(sol, '\n') ?: &start[len];
240                 eol[0] = '\0';
241
242                 /* update current group info */
243                 line_t *line = new0(line_t);
244                 line->text = strcopy(sol);
245                 parse_line(line, sol, path, lnum);
246                 group = line->group ? line->group : group;
247                 name  = line->group ? line->name  : name;
248
249                 /* Parse dynamic groups */
250                 if (line->group && line->name) {
251                         wasfound  = 0;
252                         lastgroup = line->group;
253                         lastname  = line->name;
254                         lastkey   = NULL;
255                         parser(line->group, line->name, "", "");
256                         if (!wasfound)
257                                 error("unknown group: line %d - [%s \"%s\"]",
258                                                 lnum, group, name ?: "");
259                 }
260
261                 /* Parse static key/value pairs */
262                 if (group && line->key && line->value) {
263                         wasfound  = 0;
264                         lastgroup = group;
265                         lastname  = name;
266                         lastkey   = line->key;
267                         parser(group, name?:"", line->key, line->value);
268                         if (!wasfound)
269                                 error("unknown setting: line %d - %s.%s.%s = '%s'\n",
270                                                 lnum, group, name?:"", line->key, line->value);
271                 }
272
273                 /* debug printout */
274                 debug("parse: %s.%s.%s = '%s'", group, name, line->key, line->value);
275
276                 /* save line formatting for the next write */
277                 if (prev == NULL)
278                         settings = line;
279                 else
280                         prev->next = line;
281                 prev = line;
282
283         }
284         free(start);
285 }
286
287 void conf_save(const char *path)
288 {
289         FILE *fd = fopen(path, "wt+");
290         if (!fd)
291                 return;
292
293         for (line_t *cur = settings; cur; cur = cur->next) {
294                 /* Output existing items */
295                 if (cur->text && !cur->dirty)
296                         fprintf(fd, "%s\n", cur->text);
297
298                 /* Output group and name headers */
299                 else if (cur->group && cur->name)
300                         fprintf(fd, "[%s \"%s\"]\n", cur->group, cur->name);
301
302                 /* Output group only headers */
303                 else if (cur->group)
304                         fprintf(fd, "[%s]\n", cur->group);
305
306                 /* Output key/value pairs - todo: add quotes */
307                 else if (cur->key && cur->value)
308                         fprintf(fd, "\t%s = %s\n", cur->key, cur->value);
309
310                 /* Output blank lines */
311                 else
312                         fprintf(fd, "\n");
313         }
314
315         fclose(fd);
316 }
317
318 /* Initialize */
319 void conf_setup(int _argc, char **_argv, const char *_name, parser_t _parser)
320 {
321         const char *home = getenv("HOME");
322         filename = alloc0(strlen(home) + 1 + strlen(_name) + 1);
323         sprintf(filename, "%s/%s", home, _name);
324         parser   = _parser;
325         argc     = _argc;
326         argv     = _argv;
327 }
328
329 /* Initialize */
330 void conf_init(void)
331 {
332         conf_load(filename, parser);
333 }
334
335 /* Update */
336 void conf_sync(void)
337 {
338         if (needsave)
339                 conf_save(filename);
340         needsave = 0;
341 }
342
343 /* Getters */
344 int get_enum(const char *value, const char **map, int n)
345 {
346         wasfound = 1;
347         for (int i = 0; i < n; i++)
348                 if (match(map[i], value))
349                         return i;
350         conf_error(value);
351         return 0;
352 }
353
354 int get_bool(const char *value)
355 {
356         wasfound = 1;
357         return get_enum(value, booleans, N_ELEMENTS(booleans));
358 }
359
360 int get_number(const char *value)
361 {
362         wasfound = 1;
363         errno = 0;
364         int rval = atoi(value);
365         if (errno)
366                 conf_error(value);
367         return rval;
368 }
369
370 char *get_string(const char *value)
371 {
372         wasfound = 1;
373         return (char*)value;
374 }
375
376 char *get_name(const char *name)
377 {
378         wasfound = 1;
379         return (char*)name;
380 }
381
382 /* Setters */
383 void set_enum(const char *group, const char *name,
384                 const char *key, int value,
385                 const char **map, int n)
386 {
387         if (value >= 0 && value < n)
388                 set_value(group, name, key, map[value]);
389 }
390
391 void set_bool(const char *group, const char *name,
392                 const char *key, int value)
393 {
394         set_enum(group, name, key, value,
395                         booleans, N_ELEMENTS(booleans));
396 }
397
398 void set_number(const char *group, const char *name,
399                 const char *key, int value)
400 {
401         char buf[32];
402         snprintf(buf, sizeof(buf), "%d", value);
403         set_value(group, name, key, buf);
404 }
405
406 void set_string(const char *group, const char *name,
407                 const char *key, const char *value)
408 {
409         set_value(group, name, key, value);
410 }
411
412 void set_name(const char *group, const char *name, const char *value)
413 {
414         for (line_t *line = settings; line; line = line->next) {
415                 if (match(line->group, group) &&
416                     match(line->name,  name)) {
417                         free(line->name);
418                         line->name = strcopy(value);
419                         line->dirty = 1;
420                         break;
421                 }
422         }
423 }
424
425 /* Config parser */
426 static const char *colors[] = {"red", "green", "blue"};
427
428 static int   test_bin = 1;
429 static int   test_num = 42;
430 static char *test_str = "str";
431 static int   test_clr = 0;
432
433 static void  test_parser(const char *group, const char *name,
434                 const char *key, const char *value)
435 {
436         if (match(group, "test")) {
437                 if (match(key, "bin"))
438                         test_bin = get_bool(value);
439                 else if (match(key, "clr"))
440                         test_clr = get_enum(value, colors, N_ELEMENTS(colors));
441                 else if (match(key, "num"))
442                         test_num = get_number(value);
443                 else if (match(key, "str"))
444                         test_str = get_string(value);
445         }
446 }
447
448 void conf_test(void)
449 {
450         printf("conf_test:\n");
451
452         /* Read values from a file */
453         conf_load("data/test.rc", test_parser);
454
455         printf("\nload:\n");
456         printf("  bin: %-8d\n", test_bin);
457         printf("  clr: %-8s\n", colors[test_clr]);
458         printf("  num: %-8d\n", test_num);
459         printf("  str: %-8s\n", test_str);
460
461         /* Update values */
462         set_bool  ("test", 0,      "bin",  1);
463         set_enum  ("test", 0,      "clr",  2, colors, N_ELEMENTS(colors));
464         set_number("test", 0,      "num",  -9999);
465         set_string("test", 0,      "str",  "hello");
466
467         set_string("test", 0,      "new",  "new0");
468         set_string("test", "new",  "new",  "new1");
469         set_string("new",  0,      "newa", "new2");
470         set_string("new",  0,      "newb", "new3");
471         set_string("new",  "new",  "newa", "new4");
472         set_string("new",  "new",  "newb", "new5");
473
474         set_name  ("func", "name", "test");
475
476         /* Write back to file */
477         conf_save("data/test_out.rc");
478 }