aboutsummaryrefslogtreecommitdiff
path: root/src/config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/config.c')
-rw-r--r--src/config.c186
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);
+}