From 2b8964c94d39fc6d888e0b274cfb9fbe59424c1c Mon Sep 17 00:00:00 2001 From: Andy Spencer Date: Tue, 4 Jun 2013 04:36:02 +0000 Subject: [PATCH] Add simple config file parser --- cals/dummy.c | 8 + cals/ical.c | 39 ++++- makefile | 4 +- src/cal.c | 10 ++ src/cal.h | 1 + src/conf.c | 478 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/conf.h | 45 +++++ src/main.c | 13 ++ src/test.c | 3 + src/util.c | 33 ++++ src/util.h | 3 + src/view.c | 21 +++ src/view.h | 1 + 13 files changed, 654 insertions(+), 5 deletions(-) create mode 100644 src/conf.c create mode 100644 src/conf.h diff --git a/cals/dummy.c b/cals/dummy.c index 1926bdb..4e55ade 100644 --- a/cals/dummy.c +++ b/cals/dummy.c @@ -18,6 +18,7 @@ #include #include "util.h" +#include "conf.h" #include "date.h" #include "cal.h" @@ -46,6 +47,13 @@ static todo_t todo = { static int enable; +/* Config parser */ +void dummy_config(const char *group, const char *name, const char *key, const char *value) +{ + if (match(group, "dummy") && match(key, "enable")) + enable = get_bool(value); +} + /* Event functions */ event_t *dummy_events(date_t start, date_t end) { diff --git a/cals/ical.c b/cals/ical.c index 30653ed..c814737 100644 --- a/cals/ical.c +++ b/cals/ical.c @@ -23,6 +23,7 @@ #include #include "util.h" +#include "conf.h" #include "date.h" #include "cal.h" @@ -43,9 +44,7 @@ typedef struct ical_t { } ical_t; /* Static data */ -static ical_t calendars[] = { - { .location = "data/all.ics" }, -}; +static ical_t *calendars; /* Helper functions */ static int ical_compare(const void *_a, const void *_b) @@ -238,6 +237,40 @@ static void print_todos(todo_t *start) cur->name ?: cur->desc ?: "[no summary]"); } +/* Config parser */ +void ical_config(const char *group, const char *name, const char *key, const char *value) +{ + ical_t *cal = NULL, *last = NULL; + + /* Make sure it's valid */ + if (!match(group, "ical") || !name) + return; + + /* Find existing calendar */ + for (cal = calendars; cal; last = cal, cal = cal->next) + if (match(cal->name, name)) + break; + + /* Create new calendar */ + if (!cal) { + cal = new0(ical_t); + cal->name = get_name(name); + if (last) + last->next = cal; + else + calendars = cal; + return; + } + + /* Set calendar values */ + if (match(key, "location")) + cal->location = get_string(value); + else if (match(key, "username")) + cal->username = get_string(value); + else if (match(key, "password")) + cal->password = get_string(value); +} + /* Event functions */ event_t *ical_events(date_t _start, date_t _end) { diff --git a/makefile b/makefile index ec91b83..c1acd8f 100644 --- a/makefile +++ b/makefile @@ -16,9 +16,9 @@ LDFLAGS ?= -lncursesw -lical # Sources PROG ?= lackey -PROG_SRC ?= main view date cal util +PROG_SRC ?= main view date cal conf util TEST ?= test -TEST_SRC ?= test date cal util +TEST_SRC ?= test date cal conf util VIEWS ?= day week month year events todo settings help CALS ?= dummy ical diff --git a/src/cal.c b/src/cal.c index 435bd33..627feae 100644 --- a/src/cal.c +++ b/src/cal.c @@ -23,6 +23,7 @@ /* Macros */ #define CAL(name) \ + void name##_config(const char *group, const char *name, const char *key, const char *value); \ event_t *name##_events(date_t start, date_t end); \ todo_t *name##_todos(date_t start, date_t end) @@ -150,3 +151,12 @@ void cal_load(year_t year, month_t month, day_t day, int days) ical_todos(start, end)); } + +/* Config parser */ +void cal_config(const char *group, const char *name, const char *key, const char *value) +{ + if (match(group, "dummy")) + dummy_config(group, name, key, value); + else if (match(group, "ical")) + ical_config(group, name, key, value); +} diff --git a/src/cal.h b/src/cal.h index ae41f3b..48e1af4 100644 --- a/src/cal.h +++ b/src/cal.h @@ -58,3 +58,4 @@ extern todo_t *TODOS; /* Calendar functions */ void cal_init(void); void cal_load(year_t year, month_t month, day_t day, int days); +void cal_config(const char *group, const char *name, const char *key, const char *value); diff --git a/src/conf.c b/src/conf.c new file mode 100644 index 0000000..5d59d22 --- /dev/null +++ b/src/conf.c @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2013 Andy Spencer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "util.h" +#include "conf.h" + +/* Saved formattnig struct */ +typedef struct line_t { + char *group; + char *name; + char *key; + char *value; + char *text; + int dirty; + struct line_t *next; +} line_t; + +/* Constat data */ +static const char *booleans[] = { + "false", + "true" +}; + +/* Setup info */ +static char *filename; +static parser_t parser; +static int argc; +static char **argv; + +/* Static data */ +static line_t *settings; + +/* Parsing and saving */ +static int wasfound; +static int needsave; + +static char *lastgroup; +static char *lastname; +static char *lastkey; + +/* Helper functions */ +static void conf_error(const char *value) +{ + if (lastname) + error("invalid value '%s' for %s.%s.%s", + value, lastgroup, lastname, lastkey); + else + error("invalid value '%s' for %s.%s", + value, lastgroup, lastkey); +} + +static void set_value(const char *group, const char *name, + const char *key, const char *value) +{ + int ingroup = 0; + line_t *groupend = NULL; + line_t *fileend = NULL; + + /* Queue a save next time around */ + needsave = 1; + + /* Look though for existing items */ + for (line_t *line = settings; line; line = line->next) { + /* Search for the correct group */ + if (line->group) + ingroup = match(line->group, group) && + match(line->name, name); + /* Set value */ + if (ingroup && match(line->key, key)) { + free(line->value); + line->value = strcopy(value); + line->dirty = 1; + return; + } + /* Record positions for new keys */ + if (ingroup && line->key && line->value) + groupend = line; + else + fileend = line; + } + + /* Create new items */ + if (groupend) { + /* Append to group */ + line_t *line = new0(line_t); + groupend->next = line; + line->key = strcopy(key); + line->value = strcopy(value); + line->next = groupend->next; + } else if (fileend) { + /* Create new group */ + line_t *blank = new0(line_t); + line_t *header = new0(line_t); + line_t *line = new0(line_t); + fileend->next = blank; + blank->next = header; + header->group = strcopy(group); + header->name = strcopy(name); + header->next = line; + line->key = strcopy(key); + line->value = strcopy(value); + } else { + /* Create new file */ + line_t *header = new0(line_t); + line_t *line = new0(line_t); + settings = header; + header->group = strcopy(group); + header->name = strcopy(name); + header->next = line; + line->key = strcopy(key); + line->value = strcopy(value); + } +} + +/* Parsing functions */ +static char *scan_white(char *src) +{ + while (*src == ' ' || *src == '\t') + src++; + return src; +} + +static char *scan_word(char *dst, char *src) +{ + while (islower(*src)) + *dst++ = *src++; + *dst = '\0'; + return src; +} + +static char *scan_value(char *dst, char *src) +{ + char *start = dst; + while (*src != '#' && *src != '\0') + *dst++ = *src++; + do + *dst-- = '\0'; + while (dst > start && (*dst == ' ' || *dst == '\t')); + return src; +} + +static char *scan_quote(char *dst, char *src) +{ + while (*src != '"' && *src != '\0') { + if (*src == '\\') { + switch (*++src) { + case '\0': *dst++ = '\0'; break; + case '\\': *dst++ = '\\'; src++; break; + case 't': *dst++ = '\t'; src++; break; + case 'r': *dst++ = '\r'; src++; break; + case 'n': *dst++ = '\n'; src++; break; + default: *dst++ = *src; src++; break; + } + } else { + *dst++ = *src++; + } + } + if (*src == '"') + src++; + *dst = '\0'; + return src; +} + +static void parse_line(line_t *dst, char *text, const char *file, int lnum) +{ + char *chr = scan_white(text); + if (*chr == '[') { + dst->group = malloc(strlen(chr)+1); + chr = scan_white(chr+1); + chr = scan_word(dst->group, chr); + chr = scan_white(chr); + if (*chr == '"') { + dst->name = malloc(strlen(chr)+1); + chr = scan_quote(dst->name, chr+1); + chr = scan_white(chr); + } + if (*chr != ']') + error("parsing group at %s:%d,%d -- '%.8s..'", + file, lnum, 1+chr-text, chr); + } + else if (islower(*chr)) { + dst->key = malloc(strlen(chr)+1); + chr = scan_white(chr); + chr = scan_word(dst->key, chr); + chr = scan_white(chr); + if (*chr != '=') + error("parsing key at %s:%d,%d -- '%.8s..'", + file, lnum, 1+chr-text, chr); + else + chr++; + chr = scan_white(chr); + dst->value = malloc(strlen(chr)+1); + if (*chr == '"') + chr = scan_quote(dst->value, chr+1); + else + chr = scan_value(dst->value, chr); + } else if (*chr != '#' && *chr != '\n' && *chr != '\0') { + error("parsing file at %s:%d,%d -- '%.8s..'", + file, lnum, 1+chr-text, chr); + } +} + +/* File I/O functions */ +static void conf_load(const char *path, parser_t parser) +{ + line_t *prev = NULL; + char *group = NULL, *name = NULL; + + /* read the whole file */ + int len; + char *start = read_file(path, &len); + if (!start) + return; + + /* run parser */ + int lnum; + char *sol, *eol; + for (lnum = 1, sol = start; sol < (start+len); lnum++, sol = eol+1) { + eol = strchr(sol, '\n') ?: &start[len]; + eol[0] = '\0'; + + /* update current group info */ + line_t *line = new0(line_t); + line->text = strcopy(sol); + parse_line(line, sol, path, lnum); + group = line->group ? line->group : group; + name = line->group ? line->name : name; + + /* Parse dynamic groups */ + if (line->group && line->name) { + wasfound = 0; + lastgroup = line->group; + lastname = line->name; + lastkey = NULL; + parser(line->group, line->name, "", ""); + if (!wasfound) + error("unknown group: line %d - [%s \"%s\"]", + lnum, group, name ?: ""); + } + + /* Parse static key/value pairs */ + if (group && line->key && line->value) { + wasfound = 0; + lastgroup = group; + lastname = name; + lastkey = line->key; + parser(group, name?:"", line->key, line->value); + if (!wasfound) + error("unknown setting: line %d - %s.%s.%s = '%s'\n", + lnum, group, name?:"", line->key, line->value); + } + + /* debug printout */ + printf("parse: %s.%s.%s = '%s'\n", group, name, line->key, line->value); + + /* save line formatting for the next write */ + if (prev == NULL) + settings = line; + else + prev->next = line; + prev = line; + + } + free(start); +} + +void conf_save(const char *path) +{ + FILE *fd = fopen(path, "wt+"); + if (!fd) + return; + + for (line_t *cur = settings; cur; cur = cur->next) { + /* Output existing items */ + if (cur->text && !cur->dirty) + fprintf(fd, "%s\n", cur->text); + + /* Output group and name headers */ + else if (cur->group && cur->name) + fprintf(fd, "[%s \"%s\"]\n", cur->group, cur->name); + + /* Output group only headers */ + else if (cur->group) + fprintf(fd, "[%s]\n", cur->group); + + /* Output key/value pairs - todo: add quotes */ + else if (cur->key && cur->value) + fprintf(fd, "\t%s = %s\n", cur->key, cur->value); + + /* Output blank lines */ + else + fprintf(fd, "\n"); + } + + fclose(fd); +} + +/* Initialize */ +void conf_setup(int _argc, char **_argv, const char *_name, parser_t _parser) +{ + const char *home = getenv("HOME"); + filename = alloc0(strlen(home) + 1 + strlen(_name) + 1); + sprintf(filename, "%s/%s", home, _name); + parser = _parser; + argc = _argc; + argv = _argv; +} + +/* Initialize */ +void conf_init(void) +{ + conf_load(filename, parser); +} + +/* Update */ +void conf_sync(void) +{ + if (needsave) + conf_save(filename); + needsave = 0; +} + +/* Getters */ +int get_enum(const char *value, const char **map, int n) +{ + wasfound = 1; + for (int i = 0; i < n; i++) + if (match(map[i], value)) + return i; + conf_error(value); + return 0; +} + +int get_bool(const char *value) +{ + wasfound = 1; + return get_enum(value, booleans, N_ELEMENTS(booleans)); +} + +int get_number(const char *value) +{ + wasfound = 1; + errno = 0; + int rval = atoi(value); + if (errno) + conf_error(value); + return rval; +} + +char *get_string(const char *value) +{ + wasfound = 1; + return (char*)value; +} + +char *get_name(const char *name) +{ + wasfound = 1; + return (char*)name; +} + +/* Setters */ +void set_enum(const char *group, const char *name, + const char *key, int value, + const char **map, int n) +{ + if (value >= 0 && value < n) + set_value(group, name, key, map[value]); +} + +void set_bool(const char *group, const char *name, + const char *key, int value) +{ + set_enum(group, name, key, value, + booleans, N_ELEMENTS(booleans)); +} + +void set_number(const char *group, const char *name, + const char *key, int value) +{ + char buf[32]; + snprintf(buf, sizeof(buf), "%d", value); + set_value(group, name, key, buf); +} + +void set_string(const char *group, const char *name, + const char *key, const char *value) +{ + set_value(group, name, key, value); +} + +void set_name(const char *group, const char *name, const char *value) +{ + for (line_t *line = settings; line; line = line->next) { + if (match(line->group, group) && + match(line->name, name)) { + free(line->name); + line->name = strcopy(value); + line->dirty = 1; + break; + } + } +} + +/* Config parser */ +static const char *colors[] = {"red", "green", "blue"}; + +static int test_bin = 1; +static int test_num = 42; +static char *test_str = "str"; +static int test_clr = 0; + +static void test_parser(const char *group, const char *name, + const char *key, const char *value) +{ + if (match(group, "test")) { + if (match(key, "bin")) + test_bin = get_bool(value); + else if (match(key, "clr")) + test_clr = get_enum(value, colors, N_ELEMENTS(colors)); + else if (match(key, "num")) + test_num = get_number(value); + else if (match(key, "str")) + test_str = get_string(value); + } +} + +void conf_test(void) +{ + printf("conf_test:\n"); + + /* Read values from a file */ + conf_load("data/test.rc", test_parser); + + printf("\nload:\n"); + printf(" bin: %-8d\n", test_bin); + printf(" clr: %-8s\n", colors[test_clr]); + printf(" num: %-8d\n", test_num); + printf(" str: %-8s\n", test_str); + + /* Update values */ + set_bool ("test", 0, "bin", 1); + set_enum ("test", 0, "clr", 2, colors, N_ELEMENTS(colors)); + set_number("test", 0, "num", -9999); + set_string("test", 0, "str", "hello"); + + set_string("test", 0, "new", "new0"); + set_string("test", "new", "new", "new1"); + set_string("new", 0, "newa", "new2"); + set_string("new", 0, "newb", "new3"); + set_string("new", "new", "newa", "new4"); + set_string("new", "new", "newb", "new5"); + + set_name ("func", "name", "test"); + + /* Write back to file */ + conf_save("data/test_out.rc"); +} diff --git a/src/conf.h b/src/conf.h new file mode 100644 index 0000000..ab2c885 --- /dev/null +++ b/src/conf.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 Andy Spencer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Handlers */ +typedef void (*parser_t)(const char *group, const char *name, + const char *key, const char *value); + +/* Getters */ +int get_enum(const char *value, const char **map, int n); +int get_bool(const char *value); +int get_number(const char *value); +char *get_string(const char *value); +char *get_name(const char *name); + +/* Setters */ +void set_enum(const char *group, const char *name, + const char *key, int value, + const char **map, int n); +void set_bool(const char *group, const char *name, + const char *key, int value); +void set_number(const char *group, const char *name, + const char *key, int value); +void set_string(const char *group, const char *name, + const char *key, const char *value); +void set_name(const char *group, const char *name, + const char *value); + +/* Functions */ +void conf_setup(int argc, char **argv, const char *name, parser_t parser); +void conf_init(void); +void conf_sync(void); diff --git a/src/main.c b/src/main.c index af019b3..d3fa34d 100644 --- a/src/main.c +++ b/src/main.c @@ -21,10 +21,18 @@ #include #include "util.h" +#include "conf.h" #include "date.h" #include "cal.h" #include "view.h" +/* Config parser */ +static void on_config(const char *group, const char *name, const char *key, const char *value) +{ + view_config(group, name, key, value); + cal_config(group, name, key, value); +} + /* Control-C handler, so we don't hose the therminal */ static void on_sigint(int signum) { @@ -61,8 +69,12 @@ int main(int argc, char **argv) init_pair(COLOR_WORK, COLOR_MAGENTA, -1); init_pair(COLOR_OTHER, COLOR_RED, -1); + /* Configuration */ + conf_setup(argc, argv, ".lackeyrc", on_config); + /* Initialize */ util_init(); + conf_init(); date_init(); cal_init(); view_init(); @@ -73,6 +85,7 @@ int main(int argc, char **argv) /* Run */ while (1) { MEVENT btn; + conf_sync(); int chr = getch(); if (chr == 'q') break; diff --git a/src/test.c b/src/test.c index a9ed06c..d6f9d45 100644 --- a/src/test.c +++ b/src/test.c @@ -17,16 +17,19 @@ #include "date.h" #include "cal.h" +#include "conf.h" #include "util.h" void date_test(void); void ical_test(void); +void conf_test(void); int main(int argc, char **argv) { for (int i = 1; i < argc; i++) { if (match(argv[i], "date")) date_test(); if (match(argv[i], "ical")) ical_test(); + if (match(argv[i], "conf")) conf_test(); } return 0; } diff --git a/src/util.c b/src/util.c index 197f3d8..d6c2b50 100644 --- a/src/util.c +++ b/src/util.c @@ -109,6 +109,39 @@ void *alloc0(int size) return data; } +/* File functions */ +char *read_file(const char *path, int *len) +{ + /* we could use stat, but we'll try to be portable */ + FILE *fd = fopen(path, "rt+"); + if (!fd) + return NULL; + + int block = 512; // read size + int size = 512; // buffer size + int slen = 0; // string length + char *buf = malloc(size); + if (!buf) + goto err; + + while (!feof(fd)) { + if (slen + block + 1 > size) { + size *= 2; + buf = realloc(buf, size); + if (!buf) + goto err; + } + slen += fread(&buf[slen], 1, block, fd); + buf[slen] = '\0'; + } + +err: + if (len) + *len = slen; + fclose(fd); + return buf; +} + /* Debugging functions */ void debug(char *fmt, ...) { diff --git a/src/util.h b/src/util.h index 5427686..39c0749 100644 --- a/src/util.h +++ b/src/util.h @@ -35,6 +35,9 @@ int match(const char *a, const char *b); /* Memory functions */ void *alloc0(int size); +/* File functions */ +char *read_file(const char *path, int *len); + /* Debug functions */ void debug(char *fmt, ...); void error(char *fmt, ...); diff --git a/src/view.c b/src/view.c index 3417676..1c29d54 100644 --- a/src/view.c +++ b/src/view.c @@ -21,6 +21,7 @@ #include #include "util.h" +#include "conf.h" #include "date.h" #include "cal.h" #include "view.h" @@ -54,6 +55,12 @@ VIEW(settings); VIEW(help); /* View data */ +static const char *names[] = { + "day", "week", "month", "year", + "|", "events", "todo", + "|", "settings", "help", +}; + view_t views[] = { { "Day", day_init, day_size, day_draw, day_run, {KEY_F(1), '1', } }, { "Week", week_init, week_size, week_draw, week_run, {KEY_F(2), '2', } }, @@ -228,6 +235,17 @@ void view_init(void) } } +/* Config parser */ +void view_config(const char *group, const char *name, const char *key, const char *value) +{ + if (match(group, "view")) { + if (match(key, "compact")) + COMPACT = get_bool(value); + else if (match(key, "active")) + ACTIVE = get_enum(value, names, N_ELEMENTS(names)); + } +} + /* View draw */ void view_resize(void) { @@ -256,6 +274,8 @@ int view_set(int num) { if (ACTIVE != num) { ACTIVE = num; + set_enum("view", 0, "active", ACTIVE, + names, N_ELEMENTS(names)); view_draw(); } return 1; @@ -267,6 +287,7 @@ int view_run(int key, mmask_t btn, int row, int col) /* Check for compact mode toggle */ if (key == 'c') { COMPACT ^= 1; + set_bool("view", 0, "compact", COMPACT); view_resize(); view_draw(); return 1; diff --git a/src/view.h b/src/view.h index 64e8301..352359f 100644 --- a/src/view.h +++ b/src/view.h @@ -43,6 +43,7 @@ void todo_line(WINDOW *win, todo_t *todo, int y, int x, int w, int full); /* View functions */ void view_init(void); +void view_config(const char *group, const char *name, const char *key, const char *value); void view_resize(void); void view_draw(void); int view_run(int key, mmask_t btn, int row, int col); -- 2.43.2