diff options
Diffstat (limited to 'src/config.c')
| -rw-r--r-- | src/config.c | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..d676a41 --- /dev/null +++ b/src/config.c @@ -0,0 +1,186 @@ +#include <stdbool.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <string.h> + +#include "any_log.h" +#include "config.h" + +#define ANY_INI_IMPLEMENT +#include "any_ini.h" + +typedef enum { + CONFIG_STRING, + CONFIG_INT_ANY, + CONFIG_INT_POSITIVE, + CONFIG_DOUBLE, +} config_type_t; + +typedef struct { + const char *key; + config_type_t type; + size_t offset; +} config_entry_t; + +static const config_entry_t config_entries[] = { + { "width", CONFIG_INT_POSITIVE, offsetof(config_t, width) }, + { "height", CONFIG_INT_POSITIVE, offsetof(config_t, height) }, + { "font", CONFIG_STRING, offsetof(config_t, font) }, + { "monitor", CONFIG_STRING, offsetof(config_t, monitor) }, +}; + +static bool config_read_string(const char *value, char **result) +{ + if (value == NULL) + value = ""; + + size_t start = 0, end = strlen(value); + if (value[0] == '"' && value[end - 1] == '"') { + start++; + end--; + } + + *result = malloc(end - start + 1); + memcpy(*result, value + start, end - start); + *result[end - start] = '\0'; + return true; +} + +static bool config_read_int(const char *value, int *result) +{ + char *end; + long n = strtol(value, &end, 0); + *result = n; + + if (n > INT_MAX || n < INT_MIN) + log_debug("Integer is too big"); + + return *end == '\0' && n != LONG_MIN && n != LONG_MAX; +} + +static bool config_read_double(const char *value, double *result) +{ + char *end; + *result = strtod(value, &end); + return *end == '\0'; +} + +static bool config_entry(config_t *config, int line, const char *section, const char *key, const char *value) +{ + const int n_entries = sizeof(config_entries) / sizeof(config_entry_t); + + const char *types[] = { + "string", + "integer", + "positive integer", + "double float", + }; + + for (int i = 0; i < n_entries; i++) { + if (!strcmp(key, config_entries[i].key)) { + bool nullable = config_entries[i].type == CONFIG_STRING; + + if ((value == NULL || *value == '\0') && !nullable) { + log_value_error("Empty config entry", + "s:section", section, + "s:key", key, + "s:expected_type", types[config_entries[i].type], + "i:line", line); + return false; + } + + void *result = (uint8_t *)config + config_entries[i].offset; + switch (config_entries[i].type) { + case CONFIG_STRING: + if (config_read_string(value, (char **)result)) + return true; + break; + + case CONFIG_INT_ANY: + if (config_read_int(value, (int *)result)) + return true; + break; + + case CONFIG_INT_POSITIVE: + if (config_read_int(value, (int *)result) && *(int *)result >= 0) + return true; + break; + + case CONFIG_DOUBLE: + if (config_read_double(value, (double *)result)) + return true; + break; + + default: + log_panic("Unreachable"); + } + + log_value_error("Invalid config entry", + "s:section", section, + "s:key", key, + "s:value", value, + "s:expected_type", types[config_entries[i].type], + "i:line", line); + + return false; + } + } + + log_value_warn("Unknown config entry", + "s:section", section, + "s:key", key, + "s:value", value, + "i:line", line); + + return true; +} + +static const config_t config_default = { + .font = "monospace 10", + .monitor = NULL, + .height = 50, + .width = 100, +}; + +void config_init(config_t *config, FILE *file) +{ + memcpy(config, &config_default, sizeof(config_t)); + log_debug("Copied default config"); + + any_ini_stream_t ini; + any_ini_file_init(&ini, file); + + int errors = 0; + char *section = NULL; + + do { + bool unknown_section = section != NULL && strcmp(section, "bar"); + + if (unknown_section) + log_warn("Unknown section '%s'", section); + + char *key; + while ((key = any_ini_stream_next_key(&ini)) != NULL) { + int line = ini.line; + char *value = any_ini_stream_next_value(&ini); + + log_value_trace("Reading config entry", + "s:section", section, + "s:key", key, + "s:value", value, + "i:line", ini.line); + + if (!unknown_section) + errors += !config_entry(config, ini.line, section, key, value); + + free(key); + free(value); + } + + free(section); + } while ((section = any_ini_stream_next_section(&ini)) != NULL); + + if (errors > 0) + log_panic("Config file contained %d errors", errors); +} |
