Add config module
This commit is contained in:
parent
93234df78d
commit
17ce2c5753
|
@ -0,0 +1,264 @@
|
|||
/******************************************************************************
|
||||
*
|
||||
* 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 <stdbool.h>
|
||||
#include <cStuff/file.h>
|
||||
#include <cStuff/config.h>
|
||||
|
||||
static const char
|
||||
m_mixed_line_indent[] = "Line indent contains tabs and spaces"
|
||||
, m_mixed_block_indent[] = "Block was initiated with different indent"
|
||||
, m_incomplete_key[] = "Incomplete key"
|
||||
, m_parser_error[] = "Parser error"
|
||||
, m_out_of_memory[] = "Out of memory"
|
||||
;
|
||||
|
||||
struct config_path {
|
||||
char *key;
|
||||
int size;
|
||||
int indent;
|
||||
int level;
|
||||
bool is_block;
|
||||
};
|
||||
|
||||
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;
|
||||
} else {
|
||||
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;
|
||||
int k_len = 0, eq = -1, v_len = 0, last_ch = -1, tab = 0;
|
||||
char c, *key, *val = NULL;
|
||||
|
||||
cfg->line_num++;
|
||||
|
||||
if ((indent = config_get_indent(line)) != -1) {
|
||||
key = &line[indent];
|
||||
|
||||
for (i = indent; i < length; i++) {
|
||||
c = line[i];
|
||||
if (islanum(c) || c == '_' || c == '-') {
|
||||
continue;
|
||||
}
|
||||
k_len++;
|
||||
}
|
||||
|
||||
while (i < length) {
|
||||
switch ((c = line[i++])) {
|
||||
case ':':
|
||||
evt = CSTUFF_CONFIG_NODE;
|
||||
continue;
|
||||
case '=':
|
||||
evt = CSTUFF_CONFIG_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) {
|
||||
int l = i;
|
||||
val = &line[i];
|
||||
while (i < length) {
|
||||
switch (line[i++]) {
|
||||
case ';':
|
||||
case '\n':
|
||||
break;
|
||||
case ' ':
|
||||
case '\t':
|
||||
continue;
|
||||
default:
|
||||
v_len = &line[i - 1];
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
evt = CSTUFF_CONFIG_ERROR;
|
||||
err = m_parser_error;
|
||||
}
|
||||
} else {
|
||||
evt = cSTUFF_CONFIG_ERROR;
|
||||
err = m_mixed_line_indent;
|
||||
}
|
||||
|
||||
/* Key ! */
|
||||
key[k_len] = 0;
|
||||
|
||||
switch ((cfg->evt = evt)) {
|
||||
case CSTUFF_CONFIG_PAIR:
|
||||
cfg->data.pair.key = key;
|
||||
cfg->data.pair.value = val;
|
||||
cfg->data.pair.value_len = v_len;
|
||||
cfg->data.pair.key_len = k_len;
|
||||
val[v_len] = 0;
|
||||
return indent;
|
||||
case CSTUFF_CONFIG_NODE:
|
||||
cfg->data.pair.node = key;
|
||||
cfg->data.pair.node_len = k_len;
|
||||
return indent;
|
||||
case CSTUFF_CONFIG_ERROR:
|
||||
default:
|
||||
cfg->data.error.char_num = i;
|
||||
cfg->data.error.line_text = line;
|
||||
cfg->data.error.err_text = 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, 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) {
|
||||
ctx->cfg.evt = CSTUFF_CONFIG_ERROR;
|
||||
ctx->cfg.data.error.line_text = NULL;
|
||||
ctx->cfg.data.error.err_text = 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 + 1);
|
||||
}
|
||||
|
||||
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_text = NULL;
|
||||
ctx->cfg.data.error.err_text = m_out_of_memory;
|
||||
ctx->cfg.data.error.char_num = 0;
|
||||
return;
|
||||
}
|
||||
ctx->path.key = new_key;
|
||||
ctx->path.size = len;
|
||||
}
|
||||
|
||||
*(ptr++) = '.';
|
||||
strcpy(ptr, key);
|
||||
path->level = level;
|
||||
|
||||
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 *)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, indent, is_node);
|
||||
|
||||
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.path.indent = 0;
|
||||
ctx.path.in_chr = 0;
|
||||
}
|
||||
ctx.u_ptr = u_ptr;
|
||||
ctx.callback = callback;
|
||||
|
||||
rc = cstuff_file_get_lines(file, config_get_line, (void *)&ctx);
|
||||
|
||||
if (ctx.path.key)
|
||||
free(ctx.path.key);
|
||||
return rc;
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/******************************************************************************
|
||||
*
|
||||
* Copyright (c) 2017-2019 by Löwenware Ltd
|
||||
* Please, refer LICENSE file for legal information
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file config.h
|
||||
* @author Ilja Kartašov <ik@lowenware.com>
|
||||
* @brief
|
||||
*
|
||||
* @see https://lowenware.com/
|
||||
*/
|
||||
|
||||
#ifndef CSTUFF_CONFIG_H_6207A85E_26F1_4B64_818F_92C6286FC5F0
|
||||
#define CSTUFF_CONFIG_H_6207A85E_26F1_4B64_818F_92C6286FC5F0
|
||||
|
||||
#ifndef CSTUFF_CONFIG_PATH_SIZE
|
||||
#define CSTUFF_CONFIG_PATH_SIZE 64
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
CSTUFF_CONFIG_NONE
|
||||
, CSTUFF_CONFIG_NODE
|
||||
, CSTUFF_CONFIG_PAIR
|
||||
, CSTUFF_CONFIG_ERROR
|
||||
} CStuffConfigEvent;
|
||||
|
||||
struct cstuff_config_node {
|
||||
const char *node;
|
||||
int node_len;
|
||||
};
|
||||
|
||||
struct cstuff_config_pair {
|
||||
const char *key;
|
||||
const char *value;
|
||||
int key_len;
|
||||
int value_len;
|
||||
};
|
||||
|
||||
struct cstuff_config_error {
|
||||
const char *line_text;
|
||||
const char *err_text;
|
||||
int char_num;
|
||||
};
|
||||
|
||||
struct cstuff_config {
|
||||
CStuffConfigEvent evt;
|
||||
int line_number;
|
||||
union {
|
||||
struct cstuff_config_node node;
|
||||
struct cstuff_config_pair pair;
|
||||
struct cstuff_config_ierror error;
|
||||
} data;
|
||||
};
|
||||
|
||||
typedef struct cstuff_config * CStuffConfig;
|
||||
|
||||
|
||||
typedef int
|
||||
(* CStuffConfigCallback)(struct cstuff_config *config, void *u_ptr);
|
||||
|
||||
|
||||
CStuffRetcode
|
||||
cstuff_config_parse(const char *file, CStuffConfigCallback callback, void *u_ptr);
|
||||
|
||||
|
||||
#endif /* !CSTUFF_CONFIG_H */
|
Loading…
Reference in New Issue