cStuff/config.c

290 行
5.9 KiB
C

/******************************************************************************
*
* Copyright (c) 2017-2019 by Löwenware Ltd
* Please, refer LICENSE file for legal information
*
******************************************************************************/
/**
* @file config.c
* @author Ilja Kartašov <ik@lowenware.com>
* @brief
*
* @see https://lowenware.com/
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
#include <cStuff/file.h>
#include <cStuff/config.h>
static const char
m_mixed_line_indent[] = "Line indent contains tabs and spaces"
, m_incomplete_key[] = "Incomplete key"
, m_parser_error[] = "Parser error"
, m_indent_error[] = "Indent error"
, m_out_of_memory[] = "Out of memory"
;
struct config_path {
char *key;
int size;
int indent;
int level;
bool is_node;
};
struct config_ctx {
struct cstuff_config cfg;
struct config_path path;
CStuffConfigCallback callback;
void *u_ptr;
};
static int
config_get_indent(char *line)
{
char chr = 0, c;
int result = 0;
while ((c = *line) != '\0') {
if (c == ' ' || c == '\t') {
if (!chr) {
chr = c;
}
if (chr == c) {
result++;
} else {
/* mixed indent */
return -1;
}
} else {
break;
}
line++;
}
return result;
}
static int
config_read_line(char *line, ssize_t length, struct cstuff_config *cfg)
{
const char *err = NULL;
CStuffConfigEvent evt = CSTUFF_CONFIG_NONE;
ssize_t i = 0;
int k_len = 0, v_len = 0, indent = 0;
char c, *key = NULL, *val = NULL;
cfg->line_num++;
fprintf(stderr, "cfg:%d ", cfg->line_num);
if ((indent = config_get_indent(line)) != -1) {
fprintf(stderr, "indent=%d ", indent);
key = &line[indent];
for (i = indent; i < length; i++) {
c = line[i];
if (isalnum(c) || c == '_' || c == '-') {
k_len++;
continue;
}
break;
}
while (i < length) {
switch ((c = line[i++])) {
case ':':
evt = CSTUFF_CONFIG_NODE;
fprintf(stderr, "node! ");
continue;
case '=':
evt = CSTUFF_CONFIG_PAIR;
fprintf(stderr, "pair! ");
continue;
case ' ':
case '\t':
case '\n':
continue;
default:
break;
}
break;
}
if (evt == CSTUFF_CONFIG_NODE) {
if (line[i] && line[i] != ';') {
evt = CSTUFF_CONFIG_ERROR;
err = m_incomplete_key;
}
} else if (evt == CSTUFF_CONFIG_PAIR) {
val = &line[i - 1];
while (i < length) {
switch (line[i++]) {
case ';':
case '\n':
break;
case ' ':
case '\t':
continue;
default:
v_len = (int) (&line[i] - val) - 1;
continue;
}
break;
}
} else {
evt = CSTUFF_CONFIG_ERROR;
err = m_parser_error;
}
} else {
evt = CSTUFF_CONFIG_ERROR;
err = m_mixed_line_indent;
}
/* Key ! */
if (key)
key[k_len] = 0;
fprintf(stderr, "\n");
switch ((cfg->evt = evt)) {
case CSTUFF_CONFIG_PAIR:
cfg->data.pair.key = key;
cfg->data.pair.val = val;
cfg->data.pair.val_len = v_len;
cfg->data.pair.key_len = k_len;
val[v_len] = 0;
return indent;
case CSTUFF_CONFIG_NODE:
cfg->data.node.name = key;
cfg->data.node.name_len = k_len;
return indent;
case CSTUFF_CONFIG_ERROR:
default:
cfg->data.error.char_num = i;
cfg->data.error.line_txt = line;
cfg->data.error.err_txt = err;
return -1;
}
}
static void
config_set_path_key(struct config_ctx *ctx, int line_indent, bool is_node)
{
struct config_path *path = &ctx->path;
const char *key = ctx->cfg.data.pair.key;
char *ptr;
int i, len, level, k_len = ctx->cfg.data.pair.key_len;
if (line_indent) {
if (path->indent) {
level = line_indent / path->indent;
} else {
level = 1;
path->indent = line_indent;
}
} else {
path->indent = 0;
level = 0;
}
if (level > path->level && (!path->is_node ||
level - path->level != path->indent)) {
fprintf(stderr, "cfg:%d ((%d > %d && !%d) || %d - %d != %d)\n",
ctx->cfg.line_num, level,
path->level, path->is_node & 0xFF, level, path->level, path->indent);
ctx->cfg.evt = CSTUFF_CONFIG_ERROR;
ctx->cfg.data.error.line_txt = NULL;
ctx->cfg.data.error.err_txt = m_indent_error;
ctx->cfg.data.error.char_num = 0;
return;
}
ptr = ctx->path.key;
for (i = 0; i < level; i++) {
int l = strcspn(ptr, ".");
ptr += l;
fprintf(stderr, "l = %d\n", l);
}
len = (ptr - ctx->path.key) + 1 + k_len + 1;
if (len > ctx->path.size) {
char *new_key;
if (!(new_key = realloc(ctx->path.key, len))) {
ctx->cfg.evt = CSTUFF_CONFIG_ERROR;
ctx->cfg.data.error.line_txt = NULL;
ctx->cfg.data.error.err_txt = m_out_of_memory;
ctx->cfg.data.error.char_num = 0;
return;
}
ctx->path.key = new_key;
ctx->path.size = len;
}
if (level) {
*(ptr++) = '.';
} else {
*ptr = 0;
}
strcpy(ptr, key);
path->level = level;
path->is_node = is_node;
ctx->cfg.data.pair.key = ctx->path.key;
}
static int
config_get_line(char *line, ssize_t length, void *p_ctx)
{
int result;
struct config_ctx *ctx = (struct config_ctx *)p_ctx;
memset(&ctx->cfg.data, 0, sizeof (ctx->cfg.data));
if ((result = config_read_line(line, length, &ctx->cfg)) != -1) {
bool is_node = (ctx->cfg.evt == CSTUFF_CONFIG_NODE);
/* result == indent */
config_set_path_key(ctx, result, is_node);
} else {
fprintf(stderr, "cfg: read line failed %d\n", result);
}
result = ctx->callback(&ctx->cfg, ctx->u_ptr);
return result;
}
CStuffRetcode
cstuff_config_parse(const char *file, CStuffConfigCallback callback, void *u_ptr)
{
CStuffRetcode rc;
struct config_ctx ctx;
memset(&ctx.cfg, 0, sizeof (ctx.cfg));
memset(&ctx.path, 0, sizeof (ctx.path));
if (!(ctx.path.key = malloc(CSTUFF_CONFIG_PATH_SIZE))) {
return CSTUFF_MALLOC_ERROR;
} else {
ctx.path.size = CSTUFF_CONFIG_PATH_SIZE;
}
ctx.u_ptr = u_ptr;
ctx.callback = callback;
rc = cstuff_file_get_lines(file, config_get_line, (void *)&ctx);
if (rc) {
fprintf(stderr, "cfg: get lines failed %d (%s)\n", rc, strerror(errno));
}
if (ctx.path.key)
free(ctx.path.key);
return rc;
}