diff --git a/config.c b/config.c new file mode 100644 index 0000000..e6c209d --- /dev/null +++ b/config.c @@ -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 + * @brief + * + * @see https://lowenware.com/ + */ +#include +#include +#include + +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; +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..1045c41 --- /dev/null +++ b/config.h @@ -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 + * @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 */