From 0dffa9a91ef47fc0ac6b96cb5e96e7e73b8018b8 Mon Sep 17 00:00:00 2001
From: "Rafael G. Martins" <rafael@rafaelmartins.eng.br>
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 <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#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 <rafael@rafaelmartins.eng.br>
+ *
+ * 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 <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#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