172 lines
4.5 KiB
C
172 lines
4.5 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/*
|
|
* Simple configuration file parser.
|
|
* Supports a subset of TOML: [sections], key = value
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#define MAX_LINE 1024
|
|
#define MAX_ENTRIES 256
|
|
#define MAX_KEY_LEN 128
|
|
#define MAX_VAL_LEN 512
|
|
|
|
typedef struct {
|
|
char section[MAX_KEY_LEN];
|
|
char key[MAX_KEY_LEN];
|
|
char value[MAX_VAL_LEN];
|
|
} config_entry;
|
|
|
|
struct sw_config {
|
|
config_entry entries[MAX_ENTRIES];
|
|
int count;
|
|
};
|
|
|
|
/* Trim whitespace from both ends */
|
|
static char* trim(char* str) {
|
|
while (isspace((unsigned char)*str)) str++;
|
|
if (*str == 0) return str;
|
|
|
|
char* end = str + strlen(str) - 1;
|
|
while (end > str && isspace((unsigned char)*end)) end--;
|
|
end[1] = '\0';
|
|
|
|
return str;
|
|
}
|
|
|
|
/* Remove quotes from string value */
|
|
static void unquote(char* str) {
|
|
size_t len = strlen(str);
|
|
if (len >= 2) {
|
|
if ((str[0] == '"' && str[len-1] == '"') ||
|
|
(str[0] == '\'' && str[len-1] == '\'')) {
|
|
memmove(str, str + 1, len - 2);
|
|
str[len - 2] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
sw_config* sw_config_load(const char* path) {
|
|
FILE* f = fopen(path, "r");
|
|
if (!f) return NULL;
|
|
|
|
sw_config* cfg = calloc(1, sizeof(sw_config));
|
|
if (!cfg) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
|
|
char line[MAX_LINE];
|
|
char current_section[MAX_KEY_LEN] = "";
|
|
|
|
while (fgets(line, sizeof(line), f)) {
|
|
char* trimmed = trim(line);
|
|
|
|
/* Skip empty lines and comments */
|
|
if (*trimmed == '\0' || *trimmed == '#') {
|
|
continue;
|
|
}
|
|
|
|
/* Section header: [section] */
|
|
if (*trimmed == '[') {
|
|
char* end = strchr(trimmed, ']');
|
|
if (end) {
|
|
*end = '\0';
|
|
strncpy(current_section, trimmed + 1, MAX_KEY_LEN - 1);
|
|
current_section[MAX_KEY_LEN - 1] = '\0';
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Key = value */
|
|
char* eq = strchr(trimmed, '=');
|
|
if (eq && cfg->count < MAX_ENTRIES) {
|
|
*eq = '\0';
|
|
char* key = trim(trimmed);
|
|
char* value = trim(eq + 1);
|
|
|
|
/* Remove quotes from value */
|
|
unquote(value);
|
|
|
|
config_entry* e = &cfg->entries[cfg->count++];
|
|
strncpy(e->section, current_section, MAX_KEY_LEN - 1);
|
|
strncpy(e->key, key, MAX_KEY_LEN - 1);
|
|
strncpy(e->value, value, MAX_VAL_LEN - 1);
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
return cfg;
|
|
}
|
|
|
|
void sw_config_free(sw_config* cfg) {
|
|
free(cfg);
|
|
}
|
|
|
|
static const char* find_value(sw_config* cfg, const char* section, const char* key) {
|
|
if (!cfg) return NULL;
|
|
|
|
for (int i = 0; i < cfg->count; i++) {
|
|
if (strcmp(cfg->entries[i].section, section) == 0 &&
|
|
strcmp(cfg->entries[i].key, key) == 0) {
|
|
return cfg->entries[i].value;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const char* sw_config_get_str(sw_config* cfg, const char* section,
|
|
const char* key, const char* default_val) {
|
|
const char* val = find_value(cfg, section, key);
|
|
return val ? val : default_val;
|
|
}
|
|
|
|
int sw_config_get_int(sw_config* cfg, const char* section,
|
|
const char* key, int default_val) {
|
|
const char* val = find_value(cfg, section, key);
|
|
if (!val) return default_val;
|
|
return atoi(val);
|
|
}
|
|
|
|
float sw_config_get_float(sw_config* cfg, const char* section,
|
|
const char* key, float default_val) {
|
|
const char* val = find_value(cfg, section, key);
|
|
if (!val) return default_val;
|
|
return (float)atof(val);
|
|
}
|
|
|
|
bool sw_config_get_bool(sw_config* cfg, const char* section,
|
|
const char* key, bool default_val) {
|
|
const char* val = find_value(cfg, section, key);
|
|
if (!val) return default_val;
|
|
|
|
/* Check for true values */
|
|
if (strcmp(val, "true") == 0 ||
|
|
strcmp(val, "True") == 0 ||
|
|
strcmp(val, "TRUE") == 0 ||
|
|
strcmp(val, "yes") == 0 ||
|
|
strcmp(val, "1") == 0) {
|
|
return true;
|
|
}
|
|
|
|
/* Check for false values */
|
|
if (strcmp(val, "false") == 0 ||
|
|
strcmp(val, "False") == 0 ||
|
|
strcmp(val, "FALSE") == 0 ||
|
|
strcmp(val, "no") == 0 ||
|
|
strcmp(val, "0") == 0) {
|
|
return false;
|
|
}
|
|
|
|
return default_val;
|
|
}
|
|
|
|
bool sw_config_has_key(sw_config* cfg, const char* section, const char* key) {
|
|
return find_value(cfg, section, key) != NULL;
|
|
}
|