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:
@@ -8,6 +8,7 @@ V4L2_CTRL_OBJ = $(BUILD)/v4l2_ctrl/v4l2_ctrl.o
|
|||||||
SERIAL_OBJ = $(BUILD)/serial/serial.o
|
SERIAL_OBJ = $(BUILD)/serial/serial.o
|
||||||
TRANSPORT_OBJ = $(BUILD)/transport/transport.o
|
TRANSPORT_OBJ = $(BUILD)/transport/transport.o
|
||||||
DISCOVERY_OBJ = $(BUILD)/discovery/discovery.o
|
DISCOVERY_OBJ = $(BUILD)/discovery/discovery.o
|
||||||
|
CONFIG_OBJ = $(BUILD)/config/config.o
|
||||||
|
|
||||||
.PHONY: all clean modules
|
.PHONY: all clean modules
|
||||||
|
|
||||||
@@ -15,7 +16,8 @@ all: modules \
|
|||||||
$(CLI_BUILD)/media_ctrl_cli \
|
$(CLI_BUILD)/media_ctrl_cli \
|
||||||
$(CLI_BUILD)/v4l2_ctrl_cli \
|
$(CLI_BUILD)/v4l2_ctrl_cli \
|
||||||
$(CLI_BUILD)/transport_cli \
|
$(CLI_BUILD)/transport_cli \
|
||||||
$(CLI_BUILD)/discovery_cli
|
$(CLI_BUILD)/discovery_cli \
|
||||||
|
$(CLI_BUILD)/config_cli
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
$(MAKE) -C $(ROOT)/src/modules/common
|
$(MAKE) -C $(ROOT)/src/modules/common
|
||||||
@@ -24,6 +26,7 @@ modules:
|
|||||||
$(MAKE) -C $(ROOT)/src/modules/serial
|
$(MAKE) -C $(ROOT)/src/modules/serial
|
||||||
$(MAKE) -C $(ROOT)/src/modules/transport
|
$(MAKE) -C $(ROOT)/src/modules/transport
|
||||||
$(MAKE) -C $(ROOT)/src/modules/discovery
|
$(MAKE) -C $(ROOT)/src/modules/discovery
|
||||||
|
$(MAKE) -C $(ROOT)/src/modules/config
|
||||||
|
|
||||||
$(CLI_BUILD)/media_ctrl_cli: media_ctrl_cli.c $(COMMON_OBJ) $(MEDIA_CTRL_OBJ) | $(CLI_BUILD)
|
$(CLI_BUILD)/media_ctrl_cli: media_ctrl_cli.c $(COMMON_OBJ) $(MEDIA_CTRL_OBJ) | $(CLI_BUILD)
|
||||||
$(CC) $(CFLAGS) -o $@ $^
|
$(CC) $(CFLAGS) -o $@ $^
|
||||||
@@ -37,6 +40,9 @@ $(CLI_BUILD)/transport_cli: transport_cli.c $(COMMON_OBJ) $(SERIAL_OBJ) $(TRANSP
|
|||||||
$(CLI_BUILD)/discovery_cli: discovery_cli.c $(COMMON_OBJ) $(SERIAL_OBJ) $(DISCOVERY_OBJ) | $(CLI_BUILD)
|
$(CLI_BUILD)/discovery_cli: discovery_cli.c $(COMMON_OBJ) $(SERIAL_OBJ) $(DISCOVERY_OBJ) | $(CLI_BUILD)
|
||||||
$(CC) $(CFLAGS) -o $@ $^ -lpthread
|
$(CC) $(CFLAGS) -o $@ $^ -lpthread
|
||||||
|
|
||||||
|
$(CLI_BUILD)/config_cli: config_cli.c $(COMMON_OBJ) $(CONFIG_OBJ) | $(CLI_BUILD)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^
|
||||||
|
|
||||||
$(CLI_BUILD):
|
$(CLI_BUILD):
|
||||||
mkdir -p $@
|
mkdir -p $@
|
||||||
|
|
||||||
@@ -45,4 +51,5 @@ clean:
|
|||||||
$(CLI_BUILD)/media_ctrl_cli \
|
$(CLI_BUILD)/media_ctrl_cli \
|
||||||
$(CLI_BUILD)/v4l2_ctrl_cli \
|
$(CLI_BUILD)/v4l2_ctrl_cli \
|
||||||
$(CLI_BUILD)/transport_cli \
|
$(CLI_BUILD)/transport_cli \
|
||||||
$(CLI_BUILD)/discovery_cli
|
$(CLI_BUILD)/discovery_cli \
|
||||||
|
$(CLI_BUILD)/config_cli
|
||||||
|
|||||||
73
dev/cli/config_cli.c
Normal file
73
dev/cli/config_cli.c
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "discovery.h"
|
||||||
|
#include "transport.h"
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Example schema covering node identity, discovery, and transport.
|
||||||
|
* Each module would normally provide its own schema table; they are
|
||||||
|
* combined here for the CLI demo.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const struct Config_Flag_Def function_flag_defs[] = {
|
||||||
|
{ "source", DISCOVERY_FLAG_SOURCE },
|
||||||
|
{ "relay", DISCOVERY_FLAG_RELAY },
|
||||||
|
{ "sink", DISCOVERY_FLAG_SINK },
|
||||||
|
{ "controller", DISCOVERY_FLAG_CONTROLLER },
|
||||||
|
{ NULL, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct Config_Def schema[] = {
|
||||||
|
/* section key type default */
|
||||||
|
{ "node", "name", CONFIG_STRING, "unnamed:0", NULL },
|
||||||
|
{ "node", "site_id", CONFIG_UINT16, "0", NULL },
|
||||||
|
{ "node", "tcp_port", CONFIG_UINT16, "8000", NULL },
|
||||||
|
{ "node", "function", CONFIG_FLAGS, "source", function_flag_defs },
|
||||||
|
|
||||||
|
{ "discovery", "interval_ms", CONFIG_UINT32, "5000", NULL },
|
||||||
|
{ "discovery", "timeout_intervals", CONFIG_UINT32, "3", NULL },
|
||||||
|
{ "discovery", "connect_mask", CONFIG_FLAGS, "", function_flag_defs },
|
||||||
|
|
||||||
|
{ "transport", "max_connections", CONFIG_UINT32, "16", NULL },
|
||||||
|
{ "transport", "max_payload", CONFIG_UINT32, "16777216", NULL },
|
||||||
|
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static void usage(void) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"usage: config_cli <file>\n"
|
||||||
|
" config_cli --defaults\n"
|
||||||
|
"\n"
|
||||||
|
" <file> load config file and print effective values\n"
|
||||||
|
" --defaults print schema defaults without a file\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc < 2) {
|
||||||
|
usage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Config *cfg;
|
||||||
|
struct App_Error err;
|
||||||
|
|
||||||
|
if (argv[1][0] == '-') {
|
||||||
|
err = config_defaults(&cfg, schema);
|
||||||
|
} else {
|
||||||
|
err = config_load(&cfg, argv[1], schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!APP_IS_OK(err)) {
|
||||||
|
fprintf(stderr, "config_load: errno %d\n", err.detail.syscall.err_no);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
config_dump(cfg);
|
||||||
|
|
||||||
|
config_free(cfg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
17
dev/example.cfg
Normal file
17
dev/example.cfg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Example node configuration.
|
||||||
|
# All keys are optional — missing keys use schema defaults.
|
||||||
|
|
||||||
|
[node]
|
||||||
|
name = v4l2:microscope
|
||||||
|
site_id = 0
|
||||||
|
tcp_port = 8001
|
||||||
|
function = source
|
||||||
|
|
||||||
|
[discovery]
|
||||||
|
interval_ms = 5000
|
||||||
|
timeout_intervals = 3
|
||||||
|
connect_mask = relay, controller ; auto-connect to relays and controllers
|
||||||
|
|
||||||
|
[transport]
|
||||||
|
max_connections = 16
|
||||||
|
max_payload = 16777216 ; 16 MB
|
||||||
76
include/config.h
Normal file
76
include/config.h
Normal 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);
|
||||||
@@ -57,7 +57,7 @@ Modules are listed in intended build order. Each depends only on modules above i
|
|||||||
| 14 | `xorg` | not started | X11 screen geometry queries (XRandR), screen grab source (calls codec), frame viewer sink — see architecture.md |
|
| 14 | `xorg` | not started | X11 screen geometry queries (XRandR), screen grab source (calls codec), frame viewer sink — see architecture.md |
|
||||||
| 15 | `web node` | not started | Node.js/Express peer — speaks binary protocol on socket side, HTTP/WebSocket to browser; `protocol.mjs` mirrors C protocol module |
|
| 15 | `web node` | not started | Node.js/Express peer — speaks binary protocol on socket side, HTTP/WebSocket to browser; `protocol.mjs` mirrors C protocol module |
|
||||||
| — | `mjpeg_scan` | future | EOI marker scanner for misbehaving hardware that does not deliver clean per-buffer frames; not part of the primary pipeline |
|
| — | `mjpeg_scan` | future | EOI marker scanner for misbehaving hardware that does not deliver clean per-buffer frames; not part of the primary pipeline |
|
||||||
| — | `config` | future | Unified config file reader; nodes currently configured via CLI args — needed before production deployment |
|
| — | `config` | done | INI file loader with schema-driven defaults, typed getters, FLAGS type for bitmask values |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
17
src/modules/config/Makefile
Normal file
17
src/modules/config/Makefile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
ROOT := $(abspath ../../..)
|
||||||
|
include $(ROOT)/common.mk
|
||||||
|
|
||||||
|
MODULE_BUILD = $(BUILD)/config
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(MODULE_BUILD)/config.o
|
||||||
|
|
||||||
|
$(MODULE_BUILD)/config.o: config.c $(ROOT)/include/config.h | $(MODULE_BUILD)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
$(MODULE_BUILD):
|
||||||
|
mkdir -p $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(MODULE_BUILD)/config.o
|
||||||
264
src/modules/config/config.c
Normal file
264
src/modules/config/config.c
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#define MAX_ENTRIES 256
|
||||||
|
#define MAX_LINE 512
|
||||||
|
#define MAX_STR 256
|
||||||
|
|
||||||
|
struct Config_Entry {
|
||||||
|
char section[MAX_STR];
|
||||||
|
char key[MAX_STR];
|
||||||
|
char value[MAX_STR];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
struct Config_Entry entries[MAX_ENTRIES];
|
||||||
|
int count;
|
||||||
|
const struct Config_Def *schema;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -- string helpers -------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void strip(char *s) {
|
||||||
|
/* strip leading whitespace */
|
||||||
|
char *start = s;
|
||||||
|
while (*start && isspace((unsigned char)*start)) { start++; }
|
||||||
|
|
||||||
|
/* strip trailing whitespace */
|
||||||
|
char *end = start + strlen(start);
|
||||||
|
while (end > start && isspace((unsigned char)*(end - 1))) { end--; }
|
||||||
|
*end = '\0';
|
||||||
|
|
||||||
|
if (start != s) { memmove(s, start, (size_t)(end - start) + 1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* strip inline comment (# or ;) that appears outside a value */
|
||||||
|
static void strip_comment(char *s) {
|
||||||
|
for (char *p = s; *p; p++) {
|
||||||
|
if (*p == '#' || *p == ';') {
|
||||||
|
*p = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* replace commas with spaces so flag lists parse uniformly */
|
||||||
|
static void commas_to_spaces(char *s) {
|
||||||
|
for (char *p = s; *p; p++) {
|
||||||
|
if (*p == ',') { *p = ' '; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- schema lookup --------------------------------------------------------- */
|
||||||
|
|
||||||
|
static const struct Config_Def *find_def(const struct Config_Def *schema,
|
||||||
|
const char *section, const char *key)
|
||||||
|
{
|
||||||
|
for (const struct Config_Def *d = schema; d->section; d++) {
|
||||||
|
if (strcmp(d->section, section) == 0 && strcmp(d->key, key) == 0) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- entry lookup ---------------------------------------------------------- */
|
||||||
|
|
||||||
|
static struct Config_Entry *find_entry(struct Config *cfg,
|
||||||
|
const char *section, const char *key)
|
||||||
|
{
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct Config_Entry *add_entry(struct Config *cfg,
|
||||||
|
const char *section, const char *key, const char *value)
|
||||||
|
{
|
||||||
|
if (cfg->count >= MAX_ENTRIES) { return NULL; }
|
||||||
|
struct Config_Entry *e = &cfg->entries[cfg->count++];
|
||||||
|
strncpy(e->section, section, MAX_STR - 1);
|
||||||
|
strncpy(e->key, key, MAX_STR - 1);
|
||||||
|
strncpy(e->value, value, MAX_STR - 1);
|
||||||
|
e->section[MAX_STR - 1] = '\0';
|
||||||
|
e->key [MAX_STR - 1] = '\0';
|
||||||
|
e->value [MAX_STR - 1] = '\0';
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- fill defaults --------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void fill_defaults(struct Config *cfg) {
|
||||||
|
for (const struct Config_Def *d = cfg->schema; d->section; d++) {
|
||||||
|
if (!find_entry(cfg, d->section, d->key)) {
|
||||||
|
add_entry(cfg, d->section, d->key,
|
||||||
|
d->default_val ? d->default_val : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- parse ----------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static struct App_Error parse_file(struct Config *cfg, const char *path) {
|
||||||
|
FILE *f = fopen(path, "r");
|
||||||
|
if (!f) { return APP_SYSCALL_ERROR(); }
|
||||||
|
|
||||||
|
char line[MAX_LINE];
|
||||||
|
char section[MAX_STR] = "";
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), f)) {
|
||||||
|
strip_comment(line);
|
||||||
|
strip(line);
|
||||||
|
|
||||||
|
if (line[0] == '\0') { continue; }
|
||||||
|
|
||||||
|
if (line[0] == '[') {
|
||||||
|
/* section header */
|
||||||
|
char *end = strchr(line, ']');
|
||||||
|
if (!end) { continue; }
|
||||||
|
*end = '\0';
|
||||||
|
strncpy(section, line + 1, MAX_STR - 1);
|
||||||
|
section[MAX_STR - 1] = '\0';
|
||||||
|
strip(section);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *eq = strchr(line, '=');
|
||||||
|
if (!eq || section[0] == '\0') { continue; }
|
||||||
|
|
||||||
|
*eq = '\0';
|
||||||
|
char *key = line;
|
||||||
|
char *val = eq + 1;
|
||||||
|
strip(key);
|
||||||
|
strip(val);
|
||||||
|
|
||||||
|
if (key[0] == '\0') { continue; }
|
||||||
|
|
||||||
|
/* only store keys that appear in the schema */
|
||||||
|
const struct Config_Def *def = find_def(cfg->schema, section, key);
|
||||||
|
if (!def) { continue; }
|
||||||
|
|
||||||
|
/* normalise flag values: commas → spaces */
|
||||||
|
char normalised[MAX_STR];
|
||||||
|
strncpy(normalised, val, MAX_STR - 1);
|
||||||
|
normalised[MAX_STR - 1] = '\0';
|
||||||
|
if (def->type == CONFIG_FLAGS) { commas_to_spaces(normalised); }
|
||||||
|
|
||||||
|
struct Config_Entry *e = find_entry(cfg, section, key);
|
||||||
|
if (e) {
|
||||||
|
strncpy(e->value, normalised, MAX_STR - 1);
|
||||||
|
} else {
|
||||||
|
add_entry(cfg, section, key, normalised);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
return APP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- public API ------------------------------------------------------------ */
|
||||||
|
|
||||||
|
struct App_Error config_load(struct Config **out, const char *path,
|
||||||
|
const struct Config_Def *schema)
|
||||||
|
{
|
||||||
|
struct Config *cfg = calloc(1, sizeof(*cfg));
|
||||||
|
if (!cfg) { return APP_SYSCALL_ERROR(); }
|
||||||
|
cfg->schema = schema;
|
||||||
|
|
||||||
|
struct App_Error err = parse_file(cfg, path);
|
||||||
|
if (!APP_IS_OK(err)) {
|
||||||
|
free(cfg);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
fill_defaults(cfg);
|
||||||
|
*out = cfg;
|
||||||
|
return APP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App_Error config_defaults(struct Config **out,
|
||||||
|
const struct Config_Def *schema)
|
||||||
|
{
|
||||||
|
struct Config *cfg = calloc(1, sizeof(*cfg));
|
||||||
|
if (!cfg) { return APP_SYSCALL_ERROR(); }
|
||||||
|
cfg->schema = schema;
|
||||||
|
fill_defaults(cfg);
|
||||||
|
*out = cfg;
|
||||||
|
return APP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_free(struct Config *cfg) {
|
||||||
|
free(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *config_get_str(struct Config *cfg,
|
||||||
|
const char *section, const char *key)
|
||||||
|
{
|
||||||
|
struct Config_Entry *e = find_entry(cfg, section, key);
|
||||||
|
return e ? e->value : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t config_get_u16(struct Config *cfg,
|
||||||
|
const char *section, const char *key)
|
||||||
|
{
|
||||||
|
struct Config_Entry *e = find_entry(cfg, section, key);
|
||||||
|
if (!e || e->value[0] == '\0') { return 0; }
|
||||||
|
return (uint16_t)strtoul(e->value, NULL, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t config_get_u32(struct Config *cfg,
|
||||||
|
const char *section, const char *key)
|
||||||
|
{
|
||||||
|
struct Config_Entry *e = find_entry(cfg, section, key);
|
||||||
|
if (!e || e->value[0] == '\0') { return 0; }
|
||||||
|
return (uint32_t)strtoul(e->value, NULL, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t config_get_flags(struct Config *cfg,
|
||||||
|
const char *section, const char *key)
|
||||||
|
{
|
||||||
|
struct Config_Entry *e = find_entry(cfg, section, key);
|
||||||
|
if (!e || e->value[0] == '\0') { return 0; }
|
||||||
|
|
||||||
|
const struct Config_Def *def = find_def(cfg->schema, section, key);
|
||||||
|
if (!def || !def->flags) { return 0; }
|
||||||
|
|
||||||
|
uint32_t result = 0;
|
||||||
|
char buf[MAX_STR];
|
||||||
|
strncpy(buf, e->value, MAX_STR - 1);
|
||||||
|
buf[MAX_STR - 1] = '\0';
|
||||||
|
|
||||||
|
char *token = strtok(buf, " \t");
|
||||||
|
while (token) {
|
||||||
|
for (const struct Config_Flag_Def *fd = def->flags; fd->token; fd++) {
|
||||||
|
if (strcmp(token, fd->token) == 0) {
|
||||||
|
result |= fd->value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
token = strtok(NULL, " \t");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_dump(struct Config *cfg) {
|
||||||
|
const char *cur_section = NULL;
|
||||||
|
for (int i = 0; i < cfg->count; i++) {
|
||||||
|
struct Config_Entry *e = &cfg->entries[i];
|
||||||
|
if (!cur_section || strcmp(cur_section, e->section) != 0) {
|
||||||
|
printf("[%s]\n", e->section);
|
||||||
|
cur_section = e->section;
|
||||||
|
}
|
||||||
|
printf(" %-24s = %s\n", e->key, e->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user