From 0dffa9a91ef47fc0ac6b96cb5e96e7e73b8018b8 Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Fri, 9 Sep 2016 02:33:19 +0200 Subject: common: added config-parser --- .gitignore | 1 + Makefile.am | 20 ++ src/common/config-parser.c | 238 ++++++++++++++++++++ src/common/config-parser.h | 26 +++ src/common/error.h | 3 + tests/common/check_config_parser.c | 434 +++++++++++++++++++++++++++++++++++++ 6 files changed, 722 insertions(+) create mode 100644 src/common/config-parser.c create mode 100644 src/common/config-parser.h create mode 100644 tests/common/check_config_parser.c diff --git a/.gitignore b/.gitignore index 22ac84a..60fd880 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ blogc*.html /tests/blogc/check_renderer /tests/blogc/check_source_parser /tests/blogc/check_template_parser +/tests/common/check_config_parser /tests/common/check_error /tests/common/check_utf8 /tests/common/check_utils diff --git a/Makefile.am b/Makefile.am index bf07f5c..d025b16 100644 --- a/Makefile.am +++ b/Makefile.am @@ -40,6 +40,7 @@ noinst_HEADERS = \ src/blogc/renderer.h \ src/blogc/source-parser.h \ src/blogc/template-parser.h \ + src/common/config-parser.h \ src/common/error.h \ src/common/utf8.h \ src/common/utils.h \ @@ -93,6 +94,7 @@ libblogc_la_LIBADD = \ libblogc_common_la_SOURCES = \ + src/common/config-parser.c \ src/common/error.c \ src/common/utf8.c \ src/common/utils.c \ @@ -273,6 +275,7 @@ check_PROGRAMS += \ tests/blogc/check_renderer \ tests/blogc/check_source_parser \ tests/blogc/check_template_parser \ + tests/common/check_config_parser \ tests/common/check_error \ tests/common/check_utf8 \ tests/common/check_utils \ @@ -388,6 +391,23 @@ tests_blogc_check_template_parser_LDADD = \ libblogc_common.la \ $(NULL) +tests_common_check_config_parser_SOURCES = \ + tests/common/check_config_parser.c \ + $(NULL) + +tests_common_check_config_parser_CFLAGS = \ + $(CMOCKA_CFLAGS) \ + $(NULL) + +tests_common_check_config_parser_LDFLAGS = \ + -no-install \ + $(NULL) + +tests_common_check_config_parser_LDADD = \ + $(CMOCKA_LIBS) \ + libblogc_common.la \ + $(NULL) + tests_common_check_error_SOURCES = \ tests/common/check_error.c \ $(NULL) diff --git a/src/common/config-parser.c b/src/common/config-parser.c new file mode 100644 index 0000000..9361908 --- /dev/null +++ b/src/common/config-parser.c @@ -0,0 +1,238 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2014-2016 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include +#include "error.h" +#include "utils.h" +#include "config-parser.h" + + +typedef enum { + CONFIG_START = 1, + CONFIG_SECTION_START, + CONFIG_SECTION, + CONFIG_SECTION_KEY, + CONFIG_SECTION_VALUE_START, + CONFIG_SECTION_VALUE, +} bc_configparser_state_t; + + +bc_config_t* +bc_config_parse(const char *src, size_t src_len, bc_error_t **err) +{ + if (err != NULL && *err != NULL) + return NULL; + + size_t current = 0; + size_t start = 0; + + bc_trie_t *section = NULL; + + char *section_name = NULL; + char *key = NULL; + char *value = NULL; + + bc_config_t *rv = bc_malloc(sizeof(bc_config_t)); + rv->root = bc_trie_new((bc_free_func_t) bc_trie_free); + + bc_configparser_state_t state = CONFIG_START; + + while (current < src_len) { + char c = src[current]; + bool is_last = current == src_len - 1; + + switch (state) { + + case CONFIG_START: + if (c == '#' || c == ';') { + while (current < src_len) { + if (src[current] == '\r' || src[current] == '\n') + break; + current++; + } + break; + } + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + break; + if (c == '[') { + state = CONFIG_SECTION_START; + break; + } + if (section != NULL) { + start = current; + state = CONFIG_SECTION_KEY; + continue; + } + if (err != NULL) + *err = bc_error_new_printf(BC_ERROR_CONFIG_PARSER, + "File must start with section"); + break; + + case CONFIG_SECTION_START: + start = current; + state = CONFIG_SECTION; + break; + + case CONFIG_SECTION: + if (c == ']') { + section_name = bc_strndup(src + start, current - start); + section = bc_trie_new(free); + bc_trie_insert(rv->root, section_name, section); + free(section_name); + section_name = NULL; + state = CONFIG_START; + break; + } + if (c != '\r' && c != '\n') + break; + if (err != NULL) + *err = bc_error_new_printf(BC_ERROR_CONFIG_PARSER, + "Section names can't have new lines"); + break; + + case CONFIG_SECTION_KEY: + if (c == '=') { + key = bc_strndup(src + start, current - start); + state = CONFIG_SECTION_VALUE_START; + break; + } + if (c != '\r' && c != '\n' && !is_last) + break; + // key without value, should we support it? + if (err != NULL) { + size_t end = is_last && c != '\n' && c != '\r' ? src_len : + current; + key = bc_strndup(src + start, end - start); + *err = bc_error_new_printf(BC_ERROR_CONFIG_PARSER, + "Key without value: %s", key); + free(key); + key = NULL; + } + break; + + case CONFIG_SECTION_VALUE_START: + start = current; + state = CONFIG_SECTION_VALUE; + break; + + case CONFIG_SECTION_VALUE: + if (c == '\r' || c == '\n' || is_last) { + size_t end = is_last && c != '\n' && c != '\r' ? src_len : + current; + value = bc_strndup(src + start, end - start); + bc_trie_insert(section, bc_str_strip(key), + bc_strdup(bc_str_strip(value))); + free(key); + key = NULL; + free(value); + value = NULL; + state = CONFIG_START; + break; + } + break; + + } + + if (err != NULL && *err != NULL) { + bc_config_free(rv); + rv = NULL; + break; + } + + current++; + } + + free(section_name); + free(key); + free(value); + + return rv; +} + + +static void +list_keys(const char *key, const char value, bc_slist_t **l) +{ + *l = bc_slist_append(*l, bc_strdup(key)); +} + + +char** +bc_config_list_sections(bc_config_t *config) +{ + if (config == NULL) + return NULL; + + bc_slist_t *l = NULL; + bc_trie_foreach(config->root, (bc_trie_foreach_func_t) list_keys, &l); + + char **rv = bc_malloc(sizeof(char*) * (bc_slist_length(l) + 1)); + + unsigned int i = 0; + for (bc_slist_t *tmp = l; tmp != NULL; tmp = tmp->next, i++) + rv[i] = tmp->data; + rv[i] = NULL; + + bc_slist_free(l); + + return rv; +} + + +char** +bc_config_list_keys(bc_config_t *config, const char *section) +{ + if (config == NULL) + return NULL; + + bc_trie_t *s = bc_trie_lookup(config->root, section); + if (s == NULL) + return NULL; + + bc_slist_t *l = NULL; + bc_trie_foreach(s, (bc_trie_foreach_func_t) list_keys, &l); + + char **rv = bc_malloc(sizeof(char*) * (bc_slist_length(l) + 1)); + + unsigned int i = 0; + for (bc_slist_t *tmp = l; tmp != NULL; tmp = tmp->next, i++) + rv[i] = tmp->data; + rv[i] = NULL; + + bc_slist_free(l); + + return rv; +} + + +const char* +bc_config_get(bc_config_t *config, const char *section, const char *key) +{ + if (config == NULL) + return NULL; + + bc_trie_t *s = bc_trie_lookup(config->root, section); + if (s == NULL) + return NULL; + + return bc_trie_lookup(s, key); +} + + +void +bc_config_free(bc_config_t *config) +{ + if (config == NULL) + return; + bc_trie_free(config->root); + free(config); +} diff --git a/src/common/config-parser.h b/src/common/config-parser.h new file mode 100644 index 0000000..0d30c49 --- /dev/null +++ b/src/common/config-parser.h @@ -0,0 +1,26 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2014-2016 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifndef _CONFIG_PARSER_H +#define _CONFIG_PARSER_H + +#include "utils.h" +#include "error.h" + +typedef struct { + bc_trie_t *root; +} bc_config_t; + +bc_config_t* bc_config_parse(const char *src, size_t src_len, bc_error_t **err); +char** bc_config_list_sections(bc_config_t *config); +char** bc_config_list_keys(bc_config_t *config, const char *section); +const char* bc_config_get(bc_config_t *config, const char *section, + const char *key); +void bc_config_free(bc_config_t *config); + +#endif /* _CONFIG_PARSER_H */ diff --git a/src/common/error.h b/src/common/error.h index 200f9a7..2569538 100644 --- a/src/common/error.h +++ b/src/common/error.h @@ -14,6 +14,9 @@ // error handling is centralized here for the sake of simplicity :/ typedef enum { + // errors for src/common + BC_ERROR_CONFIG_PARSER = 1, + // errors for src/blogc BLOGC_ERROR_SOURCE_PARSER = 100, BLOGC_ERROR_TEMPLATE_PARSER, diff --git a/tests/common/check_config_parser.c b/tests/common/check_config_parser.c new file mode 100644 index 0000000..2a6ad67 --- /dev/null +++ b/tests/common/check_config_parser.c @@ -0,0 +1,434 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#include +#include + +#include +#include + +#include "../../src/common/config-parser.h" +#include "../../src/common/error.h" +#include "../../src/common/utils.h" + + +static void +test_config_empty(void **state) +{ + const char *a = ""; + bc_error_t *err = NULL; + bc_config_t *c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 0); + bc_config_free(c); +} + + +static void +test_config_section_empty(void **state) +{ + const char *a = "[foo]"; + bc_error_t *err = NULL; + bc_config_t *c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 1); + char **s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 1); + assert_string_equal(s[0], "foo"); + assert_null(s[1]); + bc_strv_free(s); + char **k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 0); + assert_null(k[0]); + bc_strv_free(k); + bc_config_free(c); +} + + +static void +test_config_section(void **state) +{ + const char *a = + "[foo]\n" + "asd = zxc"; + bc_error_t *err = NULL; + bc_config_t *c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 1); + char **s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 1); + assert_string_equal(s[0], "foo"); + assert_null(s[1]); + bc_strv_free(s); + assert_string_equal(bc_config_get(c, "foo", "asd"), "zxc"); + char **k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 1); + assert_string_equal(k[0], "asd"); + assert_null(k[1]); + bc_strv_free(k); + bc_config_free(c); + + a = + "[foo]\n" + "asd = zxc\n"; + err = NULL; + c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 1); + s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 1); + assert_string_equal(s[0], "foo"); + assert_null(s[1]); + bc_strv_free(s); + assert_string_equal(bc_config_get(c, "foo", "asd"), "zxc"); + k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 1); + assert_string_equal(k[0], "asd"); + assert_null(k[1]); + bc_strv_free(k); + bc_config_free(c); + + a = + "[foo]\r\n" + "asd = zxc\r\n"; + err = NULL; + c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 1); + s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 1); + assert_string_equal(s[0], "foo"); + assert_null(s[1]); + bc_strv_free(s); + assert_string_equal(bc_config_get(c, "foo", "asd"), "zxc"); + k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 1); + assert_string_equal(k[0], "asd"); + assert_null(k[1]); + bc_strv_free(k); + bc_config_free(c); +} + + +static void +test_config_section_multiple_keys(void **state) +{ + const char *a = + "[foo]\n" + "asd = zxc\n" + "qwe = rty\n" + "zxc = vbn"; + bc_error_t *err = NULL; + bc_config_t *c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 1); + char **s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 1); + assert_string_equal(s[0], "foo"); + assert_null(s[1]); + bc_strv_free(s); + assert_string_equal(bc_config_get(c, "foo", "asd"), "zxc"); + assert_string_equal(bc_config_get(c, "foo", "qwe"), "rty"); + assert_string_equal(bc_config_get(c, "foo", "zxc"), "vbn"); + char **k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 3); + assert_string_equal(k[0], "asd"); + assert_string_equal(k[1], "qwe"); + assert_string_equal(k[2], "zxc"); + assert_null(k[3]); + bc_strv_free(k); + bc_config_free(c); + + a = + "[foo]\n" + "asd = zxc\n" + "qwe = rty\n" + "zxc = vbn\n"; + err = NULL; + c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 1); + s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 1); + assert_string_equal(s[0], "foo"); + assert_null(s[1]); + bc_strv_free(s); + assert_string_equal(bc_config_get(c, "foo", "asd"), "zxc"); + assert_string_equal(bc_config_get(c, "foo", "qwe"), "rty"); + assert_string_equal(bc_config_get(c, "foo", "zxc"), "vbn"); + k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 3); + assert_string_equal(k[0], "asd"); + assert_string_equal(k[1], "qwe"); + assert_string_equal(k[2], "zxc"); + assert_null(k[3]); + bc_strv_free(k); + bc_config_free(c); + + a = + "[foo]\r\n" + "asd = zxc\r\n" + "qwe = rty\r\n" + "zxc = vbn\r\n"; + err = NULL; + c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 1); + s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 1); + assert_string_equal(s[0], "foo"); + assert_null(s[1]); + bc_strv_free(s); + assert_string_equal(bc_config_get(c, "foo", "asd"), "zxc"); + assert_string_equal(bc_config_get(c, "foo", "qwe"), "rty"); + assert_string_equal(bc_config_get(c, "foo", "zxc"), "vbn"); + k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 3); + assert_string_equal(k[0], "asd"); + assert_string_equal(k[1], "qwe"); + assert_string_equal(k[2], "zxc"); + assert_null(k[3]); + bc_strv_free(k); + bc_config_free(c); +} + + +static void +test_config_section_multiple_sections(void **state) +{ + const char *a = + "[foo]\n" + "asd = zxc\n" + "qwe = rty\n" + "zxc = vbn\n" + "\n" + "[bar]\n" + "lol = hehe"; + bc_error_t *err = NULL; + bc_config_t *c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 2); + char **s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 2); + assert_string_equal(s[0], "foo"); + assert_string_equal(s[1], "bar"); + assert_null(s[2]); + bc_strv_free(s); + assert_string_equal(bc_config_get(c, "foo", "asd"), "zxc"); + assert_string_equal(bc_config_get(c, "foo", "qwe"), "rty"); + assert_string_equal(bc_config_get(c, "foo", "zxc"), "vbn"); + assert_string_equal(bc_config_get(c, "bar", "lol"), "hehe"); + char **k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 3); + assert_string_equal(k[0], "asd"); + assert_string_equal(k[1], "qwe"); + assert_string_equal(k[2], "zxc"); + assert_null(k[3]); + bc_strv_free(k); + k = bc_config_list_keys(c, "bar"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 1); + assert_string_equal(k[0], "lol"); + assert_null(k[1]); + bc_strv_free(k); + bc_config_free(c); + + a = + "[foo]\n" + "asd = zxc\n" + "qwe = rty\n" + "zxc = vbn\n" + "\n" + "[bar]\n" + "lol = hehe\n"; + err = NULL; + c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 2); + s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 2); + assert_string_equal(s[0], "foo"); + assert_string_equal(s[1], "bar"); + assert_null(s[2]); + bc_strv_free(s); + assert_string_equal(bc_config_get(c, "foo", "asd"), "zxc"); + assert_string_equal(bc_config_get(c, "foo", "qwe"), "rty"); + assert_string_equal(bc_config_get(c, "foo", "zxc"), "vbn"); + assert_string_equal(bc_config_get(c, "bar", "lol"), "hehe"); + k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 3); + assert_string_equal(k[0], "asd"); + assert_string_equal(k[1], "qwe"); + assert_string_equal(k[2], "zxc"); + assert_null(k[3]); + bc_strv_free(k); + k = bc_config_list_keys(c, "bar"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 1); + assert_string_equal(k[0], "lol"); + assert_null(k[1]); + bc_strv_free(k); + bc_config_free(c); + + a = + "[foo]\r\n" + "asd = zxc\r\n" + "qwe = rty\r\n" + "zxc = vbn\r\n" + "\r\n" + "[bar]\r\n" + "lol = hehe\r\n"; + err = NULL; + c = bc_config_parse(a, strlen(a), &err); + assert_null(err); + assert_non_null(c); + assert_non_null(c->root); + assert_int_equal(bc_trie_size(c->root), 2); + s = bc_config_list_sections(c); + assert_non_null(s); + assert_int_equal(bc_strv_length(s), 2); + assert_string_equal(s[0], "foo"); + assert_string_equal(s[1], "bar"); + assert_null(s[2]); + bc_strv_free(s); + assert_string_equal(bc_config_get(c, "foo", "asd"), "zxc"); + assert_string_equal(bc_config_get(c, "foo", "qwe"), "rty"); + assert_string_equal(bc_config_get(c, "foo", "zxc"), "vbn"); + assert_string_equal(bc_config_get(c, "bar", "lol"), "hehe"); + k = bc_config_list_keys(c, "foo"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 3); + assert_string_equal(k[0], "asd"); + assert_string_equal(k[1], "qwe"); + assert_string_equal(k[2], "zxc"); + assert_null(k[3]); + bc_strv_free(k); + k = bc_config_list_keys(c, "bar"); + assert_non_null(k); + assert_int_equal(bc_strv_length(k), 1); + assert_string_equal(k[0], "lol"); + assert_null(k[1]); + bc_strv_free(k); + bc_config_free(c); +} + + +static void +test_config_error_start(void **state) +{ + const char *a = + "asd\n" + "[foo]"; + bc_error_t *err = NULL; + bc_config_t *c = bc_config_parse(a, strlen(a), &err); + assert_non_null(err); + assert_null(c); + assert_int_equal(err->type, BC_ERROR_CONFIG_PARSER); + assert_string_equal(err->msg, "File must start with section"); + bc_error_free(err); +} + + +static void +test_config_error_section_with_newline(void **state) +{ + const char *a = + "[foo\nbar]"; + bc_error_t *err = NULL; + bc_config_t *c = bc_config_parse(a, strlen(a), &err); + assert_non_null(err); + assert_null(c); + assert_int_equal(err->type, BC_ERROR_CONFIG_PARSER); + assert_string_equal(err->msg, "Section names can't have new lines"); + bc_error_free(err); +} + + +static void +test_config_error_key_without_value(void **state) +{ + const char *a = + "[foobar]\n" + "asd = 12\n" + "foo"; + bc_error_t *err = NULL; + bc_config_t *c = bc_config_parse(a, strlen(a), &err); + assert_non_null(err); + assert_null(c); + assert_int_equal(err->type, BC_ERROR_CONFIG_PARSER); + assert_string_equal(err->msg, "Key without value: foo"); + bc_error_free(err); + a = + "[foobar]\n" + "asd = 12\n" + "foo\n"; + err = NULL; + c = bc_config_parse(a, strlen(a), &err); + assert_non_null(err); + assert_null(c); + assert_int_equal(err->type, BC_ERROR_CONFIG_PARSER); + assert_string_equal(err->msg, "Key without value: foo"); + bc_error_free(err); +} + + +int +main(void) +{ + const UnitTest tests[] = { + unit_test(test_config_empty), + unit_test(test_config_section_empty), + unit_test(test_config_section), + unit_test(test_config_section_multiple_keys), + unit_test(test_config_section_multiple_sections), + unit_test(test_config_error_start), + unit_test(test_config_error_section_with_newline), + unit_test(test_config_error_key_without_value), + }; + return run_tests(tests); +} -- cgit v1.2.3-18-g5258