Add config module: INI loader with schema-driven defaults

Config_Def schema tables declare section/key/type/default per module.
Typed getters: config_get_str, _u16, _u32, _flags.
FLAGS type parses space/comma-separated tokens via a Config_Flag_Def table.
config_defaults() gives schema defaults without a file.
config_dump() prints effective values for diagnostics.

config_cli: load a file or --defaults and dump effective config.
dev/example.cfg: sample config covering node, discovery, transport.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 22:37:53 +00:00
parent d6e34ed95b
commit ba26bd0cb7
7 changed files with 457 additions and 3 deletions

76
include/config.h Normal file
View File

@@ -0,0 +1,76 @@
#pragma once
#include <stdint.h>
#include "error.h"
/*
* INI-style config file loader with schema-driven defaults and validation.
*
* File format:
* [section]
* key = value ; inline comments with ; or #
* key = value # same
*
* Whitespace around keys and values is stripped.
* Commas in values are treated as whitespace (useful for flag lists).
*/
typedef enum Config_Type {
CONFIG_STRING, /* arbitrary string value */
CONFIG_UINT16, /* decimal integer, fits u16 */
CONFIG_UINT32, /* decimal integer, fits u32 */
CONFIG_FLAGS, /* space/comma-separated tokens mapped via a flag table */
} Config_Type;
/*
* One entry in a flags table — maps a token string to a bitmask value.
* The table must be terminated by an entry with token = NULL.
*/
struct Config_Flag_Def {
const char *token;
uint32_t value;
};
/*
* One entry in a config schema.
* Provide a table of these terminated by an entry with section = NULL.
*/
struct Config_Def {
const char *section;
const char *key;
Config_Type type;
const char *default_val; /* string form of the default */
const struct Config_Flag_Def *flags; /* required when type = CONFIG_FLAGS */
};
struct Config;
/*
* Load a config file and validate it against the schema.
* Keys present in the file but absent from the schema are ignored.
* Keys absent from the file are filled with their schema default.
*/
struct App_Error config_load(struct Config **out,
const char *path,
const struct Config_Def *schema);
/*
* Load defaults only (no file). Useful when no config file is present
* but you still want schema-driven defaults.
*/
struct App_Error config_defaults(struct Config **out,
const struct Config_Def *schema);
void config_free(struct Config *cfg);
/*
* Typed getters. Return the effective value (file value or default).
* Caller must not free the returned string — it is owned by the Config.
*/
const char *config_get_str (struct Config *cfg, const char *section, const char *key);
uint16_t config_get_u16 (struct Config *cfg, const char *section, const char *key);
uint32_t config_get_u32 (struct Config *cfg, const char *section, const char *key);
uint32_t config_get_flags(struct Config *cfg, const char *section, const char *key);
/* Print all effective key/value pairs to stdout — useful for diagnostics. */
void config_dump(struct Config *cfg);