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