/****************************************************************************** * * 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 #include #include #include #include #include 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; }