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