From f51399ebccfb9c8a42d89ea3807ae41ed19f30d1 Mon Sep 17 00:00:00 2001 From: Richard Russon Date: Sun, 9 Jun 2019 16:23:55 +0100 Subject: [PATCH] add slist type Add a string list type. This manages strings delimited by space, comma or colon. The NeoMutt code doesn't need to parse the string any more, it's simply presented with a STAILQ (list) of strings. --- Makefile.autosetup | 4 +- config/lib.h | 2 + config/slist.c | 274 ++++++++++++ config/slist.h | 30 ++ config/types.h | 2 +- init.c | 1 + mutt/mutt.h | 2 + mutt/slist.c | 262 +++++++++++ mutt/slist.h | 60 +++ po/POTFILES.in | 2 + test/Makefile.autosetup | 1 + test/config/slist.c | 966 ++++++++++++++++++++++++++++++++++++++++ test/config/slist.h | 30 ++ test/main.c | 1 + 14 files changed, 1634 insertions(+), 3 deletions(-) create mode 100644 config/slist.c create mode 100644 config/slist.h create mode 100644 mutt/slist.c create mode 100644 mutt/slist.h create mode 100644 test/config/slist.c create mode 100644 test/config/slist.h diff --git a/Makefile.autosetup b/Makefile.autosetup index a17276d01..7acf73cf3 100644 --- a/Makefile.autosetup +++ b/Makefile.autosetup @@ -230,7 +230,7 @@ LIBCONFIG= libconfig.a LIBCONFIGOBJS= config/address.o config/bool.o config/dump.o config/enum.o \ config/long.o config/magic.o config/mbtable.o config/number.o \ config/quad.o config/regex.o config/set.o \ - config/sort.o config/string.o + config/slist.o config/sort.o config/string.o CLEANFILES+= $(LIBCONFIG) $(LIBCONFIGOBJS) MUTTLIBS+= $(LIBCONFIG) @@ -265,7 +265,7 @@ LIBMUTTOBJS= mutt/base64.o mutt/buffer.o mutt/charset.o mutt/date.o \ mutt/history.o mutt/list.o mutt/logging.o mutt/mapping.o \ mutt/mbyte.o mutt/md5.o mutt/memory.o mutt/notify.o \ mutt/path.o mutt/pool.o \ - mutt/regex.o mutt/sha1.o mutt/signal.o mutt/string.o + mutt/regex.o mutt/sha1.o mutt/slist.o mutt/signal.o mutt/string.o CLEANFILES+= $(LIBMUTT) $(LIBMUTTOBJS) MUTTLIBS+= $(LIBMUTT) ALLOBJS+= $(LIBMUTTOBJS) diff --git a/config/lib.h b/config/lib.h index 2dfe9fff0..caf96a8e4 100644 --- a/config/lib.h +++ b/config/lib.h @@ -38,6 +38,7 @@ * | config/quad.c | @subpage config_quad | * | config/regex.c | @subpage config_regex | * | config/set.c | @subpage config_set | + * | config/slist.c | @subpage config_slist | * | config/sort.c | @subpage config_sort | * | config/string.c | @subpage config_string | */ @@ -57,6 +58,7 @@ #include "quad.h" #include "regex2.h" #include "set.h" +#include "slist.h" #include "sort.h" #include "string3.h" #include "types.h" diff --git a/config/slist.c b/config/slist.c new file mode 100644 index 000000000..57ed006ea --- /dev/null +++ b/config/slist.c @@ -0,0 +1,274 @@ +/** + * @file + * Type representing a list of strings + * + * @authors + * Copyright (C) 2018-2019 Richard Russon + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page config_slist Type: List of strings + * + * Type representing a list of strings. + */ + +#include "config.h" +#include +#include +#include +#include +#include "mutt/mutt.h" +#include "set.h" +#include "types.h" + +/** + * slist_destroy - Destroy an Slist object + * @param cs Config items + * @param var Variable to destroy + * @param cdef Variable definition + */ +static void slist_destroy(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef) +{ + if (!cs || !var || !cdef) + return; /* LCOV_EXCL_LINE */ + + struct Slist **l = (struct Slist **) var; + if (!*l) + return; + + slist_free(l); +} + +/** + * slist_string_set - Set a Slist by string + * @param cs Config items + * @param var Variable to set + * @param cdef Variable definition + * @param value Value to set + * @param err Buffer for error messages + * @retval int Result, e.g. #CSR_SUCCESS + * + * If var is NULL, then the config item's initial value will be set. + */ +static int slist_string_set(const struct ConfigSet *cs, void *var, struct ConfigDef *cdef, + const char *value, struct Buffer *err) +{ + if (!cs || !cdef) + return CSR_ERR_CODE; /* LCOV_EXCL_LINE */ + + /* Store empty strings as NULL */ + if (value && (value[0] == '\0')) + value = NULL; + + struct Slist *list = NULL; + + int rc = CSR_SUCCESS; + + if (var) + { + list = slist_parse(value, cdef->type); + + if (cdef->validator) + { + rc = cdef->validator(cs, cdef, (intptr_t) list, err); + + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + slist_free(&list); + return (rc | CSR_INV_VALIDATOR); + } + } + + slist_destroy(cs, var, cdef); + + *(struct Slist **) var = list; + + if (!list) + rc |= CSR_SUC_EMPTY; + } + else + { + if (cdef->type & DT_INITIAL_SET) + FREE(&cdef->initial); + + cdef->type |= DT_INITIAL_SET; + cdef->initial = IP mutt_str_strdup(value); + } + + return CSR_SUCCESS; +} + +/** + * slist_string_get - Get a Slist as a string + * @param cs Config items + * @param var Variable to get + * @param cdef Variable definition + * @param result Buffer for results or error messages + * @retval int Result, e.g. #CSR_SUCCESS + * + * If var is NULL, then the config item's initial value will be returned. + */ +static int slist_string_get(const struct ConfigSet *cs, void *var, + const struct ConfigDef *cdef, struct Buffer *result) +{ + if (!cs || !cdef) + return CSR_ERR_CODE; /* LCOV_EXCL_LINE */ + + if (var) + { + struct Slist *list = *(struct Slist **) var; + if (!list) + return (CSR_SUCCESS | CSR_SUC_EMPTY); /* empty string */ + + struct ListNode *np = NULL; + STAILQ_FOREACH(np, &list->head, entries) + { + mutt_buffer_addstr(result, np->data); + if (STAILQ_NEXT(np, entries)) + { + int sep = (cdef->type & SLIST_SEP_MASK); + if (sep == SLIST_SEP_COMMA) + mutt_buffer_addch(result, ','); + else if (sep == SLIST_SEP_COLON) + mutt_buffer_addch(result, ':'); + else + mutt_buffer_addch(result, ' '); + } + } + } + else + { + mutt_buffer_addstr(result, (char *) cdef->initial); + } + + int rc = CSR_SUCCESS; + if (mutt_buffer_is_empty(result)) + rc |= CSR_SUC_EMPTY; + + return rc; +} + +/** + * slist_native_set - Set a Slist config item by STAILQ + * @param cs Config items + * @param var Variable to set + * @param cdef Variable definition + * @param value Mailbox slist + * @param err Buffer for error messages + * @retval int Result, e.g. #CSR_SUCCESS + */ +static int slist_native_set(const struct ConfigSet *cs, void *var, + const struct ConfigDef *cdef, intptr_t value, struct Buffer *err) +{ + if (!cs || !var || !cdef) + return CSR_ERR_CODE; /* LCOV_EXCL_LINE */ + + int rc; + + if (cdef->validator) + { + rc = cdef->validator(cs, cdef, value, err); + + if (CSR_RESULT(rc) != CSR_SUCCESS) + return (rc | CSR_INV_VALIDATOR); + } + + slist_free(var); + + struct Slist *list = slist_dup((struct Slist *) value); + + rc = CSR_SUCCESS; + if (!list) + rc |= CSR_SUC_EMPTY; + + *(struct Slist **) var = list; + return rc; +} + +/** + * slist_native_get - Get a STAILQ from a Slist config item + * @param cs Config items + * @param var Variable to get + * @param cdef Variable definition + * @param err Buffer for error messages + * @retval intptr_t Mailbox slist + */ +static intptr_t slist_native_get(const struct ConfigSet *cs, void *var, + const struct ConfigDef *cdef, struct Buffer *err) +{ + if (!cs || !var || !cdef) + return INT_MIN; /* LCOV_EXCL_LINE */ + + struct Slist *list = *(struct Slist **) var; + + return (intptr_t) list; +} + +/** + * slist_reset - Reset a Slist to its initial value + * @param cs Config items + * @param var Variable to reset + * @param cdef Variable definition + * @param err Buffer for error messages + * @retval int Result, e.g. #CSR_SUCCESS + */ +static int slist_reset(const struct ConfigSet *cs, void *var, + const struct ConfigDef *cdef, struct Buffer *err) +{ + if (!cs || !var || !cdef) + return CSR_ERR_CODE; /* LCOV_EXCL_LINE */ + + struct Slist *list = NULL; + const char *initial = (const char *) cdef->initial; + + if (initial) + list = slist_parse(initial, cdef->type); + + int rc = CSR_SUCCESS; + + if (cdef->validator) + { + rc = cdef->validator(cs, cdef, (intptr_t) list, err); + + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + slist_destroy(cs, &list, cdef); + return (rc | CSR_INV_VALIDATOR); + } + } + + if (!list) + rc |= CSR_SUC_EMPTY; + + slist_destroy(cs, var, cdef); + + *(struct Slist **) var = list; + return rc; +} + +/** + * slist_init - Register the Slist config type + * @param cs Config items + */ +void slist_init(struct ConfigSet *cs) +{ + const struct ConfigSetType cst_slist = { + "slist", slist_string_set, slist_string_get, slist_native_set, + slist_native_get, slist_reset, slist_destroy, + }; + cs_register_type(cs, DT_SLIST, &cst_slist); +} diff --git a/config/slist.h b/config/slist.h new file mode 100644 index 000000000..e0e89b450 --- /dev/null +++ b/config/slist.h @@ -0,0 +1,30 @@ +/** + * @file + * Type representing a list of strings + * + * @authors + * Copyright (C) 2018-2019 Richard Russon + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef MUTT_CONFIG_SLIST_H +#define MUTT_CONFIG_SLIST_H + +struct ConfigSet; + +void slist_init(struct ConfigSet *cs); + +#endif /* MUTT_CONFIG_SLIST_H */ diff --git a/config/types.h b/config/types.h index be3039e34..65ac19bd5 100644 --- a/config/types.h +++ b/config/types.h @@ -57,7 +57,7 @@ /* subtypes for... */ #define DT_SUBTYPE_MASK 0x0FE0 ///< Mask for the Data Subtype -typedef uint16_t ConfigRedrawFlags; ///< Flags for redraw/resort, e.g. #R_INDEX +typedef uint32_t ConfigRedrawFlags; ///< Flags for redraw/resort, e.g. #R_INDEX #define R_REDRAW_NO_FLAGS 0 ///< No refresh/resort flags #define R_INDEX (1 << 17) ///< Redraw the index menu (MENU_MAIN) #define R_PAGER (1 << 18) ///< Redraw the pager menu diff --git a/init.c b/init.c index 95eb8b5b1..92824f583 100644 --- a/init.c +++ b/init.c @@ -3822,6 +3822,7 @@ struct ConfigSet *init_config(size_t size) number_init(cs); quad_init(cs); regex_init(cs); + slist_init(cs); sort_init(cs); string_init(cs); diff --git a/mutt/mutt.h b/mutt/mutt.h index ffdfe99c3..37c2d7bac 100644 --- a/mutt/mutt.h +++ b/mutt/mutt.h @@ -48,6 +48,7 @@ * | mutt/pool.c | @subpage pool | * | mutt/regex.c | @subpage regex | * | mutt/sha1.c | @subpage sha1 | + * | mutt/slist.c | @subpage slist | * | mutt/signal.c | @subpage signal | * | mutt/string.c | @subpage string | * @@ -83,6 +84,7 @@ #include "regex3.h" #include "sha1.h" #include "signal2.h" +#include "slist.h" #include "string2.h" #endif /* MUTT_LIB_MUTT_H */ diff --git a/mutt/slist.c b/mutt/slist.c new file mode 100644 index 000000000..49c35d697 --- /dev/null +++ b/mutt/slist.c @@ -0,0 +1,262 @@ +/** + * @file + * A separated list of strings + * + * @authors + * Copyright (C) 2018-2019 Richard Russon + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page slist A separated list of strings + * + * A separated list of strings + */ + +#include "config.h" +#include +#include "slist.h" +#include "list.h" +#include "memory.h" +#include "queue.h" +#include "string2.h" + +/** + * slist_add_list - Add a list to another list + * @param list String list to add to + * @param add String list to add + * @retval ptr Modified list + */ +struct Slist *slist_add_list(struct Slist *list, const struct Slist *add) +{ + if (!add) + return list; + if (!list) + return slist_dup(add); + + struct ListNode *np = NULL; + STAILQ_FOREACH(np, &add->head, entries) + { + mutt_list_insert_tail(&list->head, mutt_str_strdup((char *) np->data)); + list->count++; + } + return list; +} + +/** + * slist_add_string - Add a string to a list + * @param list List to modify + * @param str String to add + * @retval ptr Modified list + */ +struct Slist *slist_add_string(struct Slist *list, const char *str) +{ + if (!list) + return NULL; + + if (str && (str[0] == '\0')) + str = NULL; + + if (!str && !(list->flags & SLIST_ALLOW_EMPTY)) + return list; + + mutt_list_insert_tail(&list->head, mutt_str_strdup(str)); + list->count++; + + return list; +} + +/** + * slist_compare - Compare two string lists + * @param a First list + * @param b Second list + * @retval true If they are identical + */ +bool slist_compare(const struct Slist *a, const struct Slist *b) +{ + if (!a && !b) /* both empty */ + return true; + if (!a ^ !b) /* one is empty, but not the other */ + return false; + if (a->count != b->count) + return false; + + return mutt_list_compare(&a->head, &b->head); +} + +/** + * slist_dup - Create a copy of an Slist object + * @param list Slist to duplicate + * @retval ptr New Slist object + */ +struct Slist *slist_dup(const struct Slist *list) +{ + if (!list) + return NULL; /* LCOV_EXCL_LINE */ + + struct Slist *l = mutt_mem_calloc(1, sizeof(*l)); + l->flags = list->flags; + STAILQ_INIT(&l->head); + + struct ListNode *np = NULL; + STAILQ_FOREACH(np, &list->head, entries) + { + mutt_list_insert_tail(&l->head, mutt_str_strdup(np->data)); + } + return l; +} + +/** + * slist_empty - Empty out an Slist object + * @param list Slist to duplicate + * @retval ptr New Slist object + */ +struct Slist *slist_empty(struct Slist **list) +{ + if (!list || !*list) + return NULL; /* LCOV_EXCL_LINE */ + + mutt_list_free(&(*list)->head); + + if ((*list)->flags & SLIST_ALLOW_EMPTY) + { + (*list)->count = 0; + return *list; + } + + FREE(list); + return NULL; +} + +/** + * slist_free - Free an Slist object + * @param list Slist to free + */ +void slist_free(struct Slist **list) +{ + if (!list || !*list) + return; /* LCOV_EXCL_LINE */ + + mutt_list_free(&(*list)->head); + FREE(list); +} + +/** + * slist_is_member - Is a string a member of a list? + * @param list List to modify + * @param str String to find + * @retval true String is in the list + */ +bool slist_is_member(const struct Slist *list, const char *str) +{ + if (!list) + return false; + + if (!str && !(list->flags & SLIST_ALLOW_EMPTY)) + return false; + + struct ListNode *np = NULL; + STAILQ_FOREACH(np, &list->head, entries) + { + if (mutt_str_strcmp(np->data, str) == 0) + return true; + } + return false; +} + +/** + * slist_parse - Parse a list of strings into a list + * @param str String of strings + * @param flags Flags, e.g. #SLIST_ALLOW_EMPTY + * @retval ptr New Slist object + */ +struct Slist *slist_parse(const char *str, int flags) +{ + char *src = mutt_str_strdup(str); + if (!src && !(flags & SLIST_ALLOW_EMPTY)) + return NULL; + + char sep = ' '; + if ((flags & SLIST_SEP_MASK) == SLIST_SEP_COMMA) + sep = ','; + else if ((flags & SLIST_SEP_MASK) == SLIST_SEP_COLON) + sep = ':'; + + struct Slist *list = mutt_mem_calloc(1, sizeof(struct Slist)); + list->flags = flags; + STAILQ_INIT(&list->head); + + if (!src) + return list; + + char *start = src; + for (char *p = start; *p; p++) + { + if ((p[0] == '\\') && (p[1] != '\0')) + { + p++; + continue; + } + + if (p[0] == sep) + { + p[0] = '\0'; + mutt_list_insert_tail(&list->head, mutt_str_strdup(start)); + list->count++; + start = p + 1; + } + } + + mutt_list_insert_tail(&list->head, mutt_str_strdup(start)); + list->count++; + + FREE(&src); + return list; +} + +/** + * slist_remove_string - Remove a string from a list + * @param list List to modify + * @param str String to remove + * @retval ptr Modified list + */ +struct Slist *slist_remove_string(struct Slist *list, const char *str) +{ + if (!list) + return NULL; + if (!str && !(list->flags & SLIST_ALLOW_EMPTY)) + return list; + + struct ListNode *prev = NULL; + struct ListNode *np = NULL; + struct ListNode *tmp = NULL; + STAILQ_FOREACH_SAFE(np, &list->head, entries, tmp) + { + if (mutt_str_strcmp(np->data, str) == 0) + { + if (prev) + STAILQ_REMOVE_AFTER(&list->head, prev, entries); + else + STAILQ_REMOVE_HEAD(&list->head, entries); + FREE(&np->data); + FREE(&np); + list->count--; + break; + } + prev = np; + } + return list; +} diff --git a/mutt/slist.h b/mutt/slist.h new file mode 100644 index 000000000..fe31c872b --- /dev/null +++ b/mutt/slist.h @@ -0,0 +1,60 @@ +/** + * @file + * A separated list of strings + * + * @authors + * Copyright (C) 2018-2019 Richard Russon + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef MUTT_LIB_SLIST_H +#define MUTT_LIB_SLIST_H + +#include +#include +#include "mutt/list.h" + +#define SLIST_SEP_SPACE (1 << 13) +#define SLIST_SEP_COMMA (1 << 14) +#define SLIST_SEP_COLON (1 << 15) + +#define SLIST_SEP_MASK 0xE000 + +#define SLIST_ALLOW_DUPES (1 << 17) +#define SLIST_ALLOW_EMPTY (1 << 18) +#define SLIST_CASE_SENSITIVE (1 << 19) + +/** + * struct Slist - String list + */ +struct Slist +{ + struct ListHead head; + size_t count; + unsigned int flags; +}; + +struct Slist *slist_add_list(struct Slist *list, const struct Slist *add); +struct Slist *slist_add_string(struct Slist *list, const char *str); +bool slist_compare(const struct Slist *a, const struct Slist *b); +struct Slist *slist_dup(const struct Slist *list); +struct Slist *slist_empty(struct Slist **list); +void slist_free(struct Slist **list); +bool slist_is_member(const struct Slist *list, const char *str); +struct Slist *slist_parse(const char *str, int flags); +struct Slist *slist_remove_string(struct Slist *list, const char *str); + +#endif /* MUTT_LIB_SLIST_H */ diff --git a/po/POTFILES.in b/po/POTFILES.in index 40db22480..da6c32f6c 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -23,6 +23,7 @@ config/number.c config/quad.c config/regex.c config/set.c +config/slist.c config/sort.c config/string.c conn/conn_globals.c @@ -116,6 +117,7 @@ mutt/pool.c mutt/regex.c mutt/sha1.c mutt/signal.c +mutt/slist.c mutt/string.c muttlib.c mutt_account.c diff --git a/test/Makefile.autosetup b/test/Makefile.autosetup index 661e11428..cbf64e757 100644 --- a/test/Makefile.autosetup +++ b/test/Makefile.autosetup @@ -95,6 +95,7 @@ CONFIG_OBJS = test/config/account.o \ test/config/quad.o \ test/config/regex.o \ test/config/set.o \ + test/config/slist.o \ test/config/sort.o \ test/config/string.o \ test/config/synonym.o \ diff --git a/test/config/slist.c b/test/config/slist.c new file mode 100644 index 000000000..b5456acb4 --- /dev/null +++ b/test/config/slist.c @@ -0,0 +1,966 @@ +/** + * @file + * Test code for the Slist object + * + * @authors + * Copyright (C) 2018-2019 Richard Russon + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#define TEST_NO_MAIN +#include "acutest.h" +#include "config.h" +#include +#include +#include +#include +#include +#include "mutt/mutt.h" +#include "config/common.h" +#include "config/lib.h" +#include "account.h" + +static struct Slist *VarApple; +static struct Slist *VarBanana; +static struct Slist *VarCherry; +static struct Slist *VarDamson; +static struct Slist *VarElderberry; +static struct Slist *VarFig; +static struct Slist *VarGuava; +static struct Slist *VarHawthorn; +static struct Slist *VarIlama; +static struct Slist *VarJackfruit; +// static struct Slist *VarKumquat; +static struct Slist *VarLemon; +static struct Slist *VarMango; +static struct Slist *VarNectarine; +static struct Slist *VarOlive; +static struct Slist *VarPapaya; +static struct Slist *VarQuince; +#if 0 +static struct Slist *VarRaspberry; +static struct Slist *VarStrawberry; +#endif + +// clang-format off +static struct ConfigDef VarsColon[] = { + { "Apple", DT_SLIST|SLIST_SEP_COLON, &VarApple, IP "apple", 0, NULL }, /* test_initial_values */ + { "Banana", DT_SLIST|SLIST_SEP_COLON, &VarBanana, IP "apple:banana", 0, NULL }, + { "Cherry", DT_SLIST|SLIST_SEP_COLON, &VarCherry, IP "apple:banana:cherry", 0, NULL }, + { "Damson", DT_SLIST|SLIST_SEP_COLON, &VarDamson, IP "apple:banana", 0, NULL }, /* test_string_set */ + { "Elderberry", DT_SLIST|SLIST_SEP_COLON, &VarElderberry, 0, 0, NULL }, + { "Fig", DT_SLIST|SLIST_SEP_COLON, &VarFig, IP ":apple", 0, NULL }, /* test_string_get */ + { "Guava", DT_SLIST|SLIST_SEP_COLON, &VarGuava, IP "apple::cherry", 0, NULL }, + { "Hawthorn", DT_SLIST|SLIST_SEP_COLON, &VarHawthorn, IP "apple:", 0, NULL }, + { NULL }, +}; + +static struct ConfigDef VarsComma[] = { + { "Apple", DT_SLIST|SLIST_SEP_COMMA, &VarApple, IP "apple", 0, NULL }, /* test_initial_values */ + { "Banana", DT_SLIST|SLIST_SEP_COMMA, &VarBanana, IP "apple,banana", 0, NULL }, + { "Cherry", DT_SLIST|SLIST_SEP_COMMA, &VarCherry, IP "apple,banana,cherry", 0, NULL }, + { "Damson", DT_SLIST|SLIST_SEP_COLON, &VarDamson, IP "apple,banana", 0, NULL }, /* test_string_set */ + { "Elderberry", DT_SLIST|SLIST_SEP_COLON, &VarElderberry, 0, 0, NULL }, + { "Fig", DT_SLIST|SLIST_SEP_COLON, &VarFig, IP ",apple", 0, NULL }, /* test_string_get */ + { "Guava", DT_SLIST|SLIST_SEP_COLON, &VarGuava, IP "apple,,cherry", 0, NULL }, + { "Hawthorn", DT_SLIST|SLIST_SEP_COLON, &VarHawthorn, IP "apple,", 0, NULL }, + { NULL }, +}; + +static struct ConfigDef VarsSpace[] = { + { "Apple", DT_SLIST|SLIST_SEP_SPACE, &VarApple, IP "apple", 0, NULL }, /* test_initial_values */ + { "Banana", DT_SLIST|SLIST_SEP_SPACE, &VarBanana, IP "apple banana", 0, NULL }, + { "Cherry", DT_SLIST|SLIST_SEP_SPACE, &VarCherry, IP "apple banana cherry", 0, NULL }, + { "Damson", DT_SLIST|SLIST_SEP_COLON, &VarDamson, IP "apple banana", 0, NULL }, /* test_string_set */ + { "Elderberry", DT_SLIST|SLIST_SEP_COLON, &VarElderberry, 0, 0, NULL }, + { "Fig", DT_SLIST|SLIST_SEP_COLON, &VarFig, IP " apple", 0, NULL }, /* test_string_get */ + { "Guava", DT_SLIST|SLIST_SEP_COLON, &VarGuava, IP "apple cherry", 0, NULL }, + { "Hawthorn", DT_SLIST|SLIST_SEP_COLON, &VarHawthorn, IP "apple ", 0, NULL }, + { NULL }, +}; + +static struct ConfigDef VarsOther[] = { + { "Ilama", DT_SLIST|SLIST_SEP_COLON, &VarIlama, 0, 0, NULL }, /* test_native_set */ + { "Jackfruit", DT_SLIST|SLIST_SEP_COLON, &VarJackfruit, IP "apple:banana:cherry", 0, NULL }, /* test_native_get */ + { "Lemon", DT_SLIST|SLIST_SEP_COLON, &VarLemon, IP "lemon", 0, NULL }, /* test_reset */ + { "Mango", DT_SLIST|SLIST_SEP_COLON, &VarMango, IP "mango", 0, validator_fail }, + { "Nectarine", DT_SLIST|SLIST_SEP_COLON, &VarNectarine, IP "nectarine", 0, validator_succeed }, /* test_validator */ + { "Olive", DT_SLIST|SLIST_SEP_COLON, &VarOlive, IP "olive", 0, validator_warn }, + { "Papaya", DT_SLIST|SLIST_SEP_COLON, &VarPapaya, IP "papaya", 0, validator_fail }, + { "Quince", DT_SLIST|SLIST_SEP_COLON, &VarQuince, 0, 0, NULL }, /* test_inherit */ + { NULL }, +}; +// clang-format on + +static void slist_flags(unsigned int flags, struct Buffer *buf) +{ + if (!buf) + return; + + switch (flags & SLIST_SEP_MASK) + { + case SLIST_SEP_SPACE: + mutt_buffer_addstr(buf, "SPACE"); + break; + case SLIST_SEP_COMMA: + mutt_buffer_addstr(buf, "COMMA"); + break; + case SLIST_SEP_COLON: + mutt_buffer_addstr(buf, "COLON"); + break; + default: + mutt_buffer_addstr(buf, "UNKNOWN"); + return; + } + + if (flags & SLIST_ALLOW_DUPES) + mutt_buffer_addstr(buf, " | SLIST_ALLOW_DUPES"); + if (flags & SLIST_ALLOW_EMPTY) + mutt_buffer_addstr(buf, " | SLIST_ALLOW_EMPTY"); + if (flags & SLIST_CASE_SENSITIVE) + mutt_buffer_addstr(buf, " | SLIST_CASE_SENSITIVE"); +} + +static void slist_dump(const struct Slist *list, struct Buffer *buf) +{ + if (!list || !buf) + return; + + mutt_buffer_printf(buf, "[%ld] ", list->count); + + struct ListNode *np = NULL; + STAILQ_FOREACH(np, &list->head, entries) + { + if (np->data) + mutt_buffer_add_printf(buf, "'%s'", np->data); + else + mutt_buffer_addstr(buf, "NULL"); + if (STAILQ_NEXT(np, entries)) + mutt_buffer_addstr(buf, ","); + } + TEST_MSG("%s\n", mutt_b2s(buf)); + mutt_buffer_reset(buf); +} + +static bool test_slist_parse(struct Buffer *err) +{ + mutt_buffer_reset(err); + + static const char *init[] = { + NULL, + "", + "apple", + "apple:banana", + "apple:banana:cherry", + ":apple", + "banana:", + ":", + "::", + "apple:banana:apple", + "apple::banana", + }; + + unsigned int flags = SLIST_SEP_COLON | SLIST_ALLOW_EMPTY; + slist_flags(flags, err); + TEST_MSG("Flags: %s", mutt_b2s(err)); + TEST_MSG("\n"); + mutt_buffer_reset(err); + + struct Slist *list = NULL; + for (size_t i = 0; i < mutt_array_size(init); i++) + { + TEST_MSG(">>%s<<\n", init[i] ? init[i] : "NULL"); + list = slist_parse(init[i], flags); + slist_dump(list, err); + slist_free(&list); + } + + return true; +} + +static bool test_slist_add_string(struct Buffer *err) +{ + { + struct Slist *list = slist_parse(NULL, SLIST_ALLOW_EMPTY); + slist_dump(list, err); + + slist_add_string(list, NULL); + slist_dump(list, err); + + slist_empty(&list); + slist_add_string(list, ""); + slist_dump(list, err); + + slist_empty(&list); + slist_add_string(list, "apple"); + slist_dump(list, err); + slist_add_string(list, "banana"); + slist_dump(list, err); + slist_add_string(list, "apple"); + slist_dump(list, err); + + slist_free(&list); + } + + { + struct Slist *list = slist_parse("apple", 0); + slist_add_string(NULL, "apple"); + slist_add_string(list, NULL); + slist_free(&list); + } + + return true; +} + +static bool test_slist_remove_string(struct Buffer *err) +{ + { + mutt_buffer_reset(err); + + unsigned int flags = SLIST_SEP_COLON | SLIST_ALLOW_EMPTY; + struct Slist *list = slist_parse("apple:banana::cherry", flags); + slist_dump(list, err); + + slist_remove_string(list, NULL); + slist_dump(list, err); + + slist_remove_string(list, "apple"); + slist_dump(list, err); + + slist_remove_string(list, "damson"); + slist_dump(list, err); + + slist_free(&list); + } + + { + struct Slist *list = slist_parse("apple:banana::cherry", SLIST_SEP_COLON); + TEST_CHECK(slist_remove_string(NULL, "apple") == NULL); + TEST_CHECK(slist_remove_string(list, NULL) == list); + slist_free(&list); + } + + { + struct Slist *list = slist_parse("apple:ba\\:nana::cherry", SLIST_SEP_COLON); + slist_dump(list, err); + TEST_CHECK(slist_empty(&list) == NULL); + slist_free(&list); + } + + return true; +} + +static bool test_slist_is_member(struct Buffer *err) +{ + { + mutt_buffer_reset(err); + + TEST_CHECK(slist_is_member(NULL, "apple") == false); + + unsigned int flags = SLIST_SEP_COLON | SLIST_ALLOW_EMPTY; + struct Slist *list = slist_parse("apple:banana::cherry", flags); + slist_dump(list, err); + + static const char *values[] = { "apple", "", "damson", NULL }; + + for (size_t i = 0; i < mutt_array_size(values); i++) + { + TEST_MSG("member '%s' : %s\n", values[i], + slist_is_member(list, values[i]) ? "yes" : "no"); + } + + slist_free(&list); + } + + { + struct Slist *list = slist_parse("apple", 0); + TEST_CHECK(slist_is_member(list, NULL) == false); + slist_free(&list); + } + return true; +} + +static bool test_slist_add_list(struct Buffer *err) +{ + mutt_buffer_reset(err); + + unsigned int flags = SLIST_SEP_COLON | SLIST_ALLOW_EMPTY; + + struct Slist *list1 = slist_parse("apple:banana::cherry", flags); + slist_dump(list1, err); + + struct Slist *list2 = slist_parse("damson::apple:apple", flags); + slist_dump(list2, err); + + list1 = slist_add_list(list1, list2); + slist_dump(list1, err); + + list1 = slist_add_list(list1, NULL); + slist_dump(list1, err); + + slist_free(&list1); + slist_free(&list2); + + list1 = NULL; + slist_dump(list1, err); + + list2 = slist_parse("damson::apple:apple", flags); + slist_dump(list2, err); + + list1 = slist_add_list(list1, list2); + slist_dump(list1, err); + + slist_free(&list1); + slist_free(&list2); + + return true; +} + +static bool test_slist_compare(struct Buffer *err) +{ + struct Slist *list1 = + slist_parse("apple:banana::cherry", SLIST_SEP_COLON | SLIST_ALLOW_EMPTY); + slist_dump(list1, err); + + struct Slist *list2 = + slist_parse("apple,banana,,cherry", SLIST_SEP_COMMA | SLIST_ALLOW_EMPTY); + slist_dump(list2, err); + + struct Slist *list3 = + slist_parse("apple,banana,,cherry,damson", SLIST_SEP_COMMA | SLIST_ALLOW_EMPTY); + slist_dump(list2, err); + + bool result = true; + + if (!TEST_CHECK(slist_compare(NULL, NULL) == true)) + result = false; + + if (!TEST_CHECK(slist_compare(list1, NULL) == false)) + result = false; + + if (!TEST_CHECK(slist_compare(NULL, list2) == false)) + result = false; + + if (!TEST_CHECK(slist_compare(list1, list2) == true)) + result = false; + + if (!TEST_CHECK(slist_compare(list1, list3) == false)) + result = false; + + slist_free(&list1); + slist_free(&list2); + slist_free(&list3); + + return result; +} + +static bool test_initial_values(struct ConfigSet *cs, struct Buffer *err) +{ + log_line(__func__); + + static const char *values[] = { "apple", "banana", "cherry", NULL }; + + slist_flags(VarApple->flags, err); + TEST_MSG("Apple, %ld items, %s flags\n", VarApple->count, mutt_b2s(err)); + mutt_buffer_reset(err); + if (VarApple->count != 1) + { + TEST_MSG("Apple should have 1 item\n"); + return false; + } + + struct ListNode *np = NULL; + size_t i = 0; + STAILQ_FOREACH(np, &VarApple->head, entries) + { + if (mutt_str_strcmp(values[i], np->data) != 0) + return false; + i++; + } + + slist_flags(VarBanana->flags, err); + TEST_MSG("Banana, %ld items, %s flags\n", VarBanana->count, mutt_b2s(err)); + mutt_buffer_reset(err); + if (VarBanana->count != 2) + { + TEST_MSG("Banana should have 2 items\n"); + return false; + } + + np = NULL; + i = 0; + STAILQ_FOREACH(np, &VarBanana->head, entries) + { + if (mutt_str_strcmp(values[i], np->data) != 0) + return false; + i++; + } + + slist_flags(VarCherry->flags, err); + TEST_MSG("Cherry, %ld items, %s flags\n", VarCherry->count, mutt_b2s(err)); + mutt_buffer_reset(err); + if (VarCherry->count != 3) + { + TEST_MSG("Cherry should have 3 items\n"); + return false; + } + + np = NULL; + i = 0; + STAILQ_FOREACH(np, &VarCherry->head, entries) + { + if (mutt_str_strcmp(values[i], np->data) != 0) + return false; + i++; + } + + const char *name = "Cherry"; + const char *value = "raspberry"; + int rc = cs_str_initial_set(cs, name, value, err); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + value = "strawberry"; + rc = cs_str_initial_set(cs, name, value, err); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + name = "Elderberry"; + mutt_buffer_reset(err); + rc = cs_str_initial_get(cs, name, err); + if (!TEST_CHECK(rc == (CSR_SUCCESS | CSR_SUC_EMPTY))) + { + TEST_MSG("%s\n", err->data); + return false; + } + + return true; +} + +static bool test_string_set(struct ConfigSet *cs, struct Buffer *err) +{ + log_line(__func__); + + int rc; + + mutt_buffer_reset(err); + char *name = "Damson"; + rc = cs_str_string_set(cs, name, "pig:quail:rhino", err); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + mutt_buffer_reset(err); + name = "Damson"; + rc = cs_str_string_set(cs, name, "", err); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + mutt_buffer_reset(err); + name = "Damson"; + rc = cs_str_string_set(cs, name, NULL, err); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + mutt_buffer_reset(err); + name = "Elderberry"; + rc = cs_str_string_set(cs, name, "pig:quail:rhino", err); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + return true; +} + +static bool test_string_get(struct ConfigSet *cs, struct Buffer *err) +{ + log_line(__func__); + + struct Buffer initial; + mutt_buffer_init(&initial); + initial.data = mutt_mem_calloc(1, 256); + initial.dsize = 256; + + mutt_buffer_reset(err); + mutt_buffer_reset(&initial); + char *name = "Fig"; + + int rc = cs_str_initial_get(cs, name, &initial); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + rc = cs_str_string_get(cs, name, err); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + if (mutt_str_strcmp(initial.data, err->data) != 0) + { + TEST_MSG("Differ: %s '%s' '%s'\n", name, initial.data, err->data); + return false; + } + TEST_MSG("Match: %s '%s' '%s'\n", name, initial.data, err->data); + + mutt_buffer_reset(err); + mutt_buffer_reset(&initial); + name = "Guava"; + + rc = cs_str_initial_get(cs, name, &initial); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + rc = cs_str_string_get(cs, name, err); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + if (mutt_str_strcmp(initial.data, err->data) != 0) + { + TEST_MSG("Differ: %s '%s' '%s'\n", name, initial.data, err->data); + return false; + } + TEST_MSG("Match: %s '%s' '%s'\n", name, initial.data, err->data); + + mutt_buffer_reset(err); + mutt_buffer_reset(&initial); + name = "Hawthorn"; + + rc = cs_str_initial_get(cs, name, &initial); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + rc = cs_str_string_get(cs, name, err); + if (CSR_RESULT(rc) != CSR_SUCCESS) + { + TEST_MSG("%s\n", err->data); + return false; + } + + if (mutt_str_strcmp(initial.data, err->data) != 0) + { + TEST_MSG("Differ: %s '%s' '%s'\n", name, initial.data, err->data); + return false; + } + TEST_MSG("Match: %s '%s' '%s'\n", name, initial.data, err->data); + + FREE(&initial.data); + return true; +} + +static bool test_native_set(struct ConfigSet *cs, struct Buffer *err) +{ + log_line(__func__); + const char *init = "apple:banana::cherry"; + const char *name = "Ilama"; + struct Slist *list = slist_parse(init, SLIST_SEP_COLON); + + mutt_buffer_reset(err); + int rc = cs_str_native_set(cs, name, (intptr_t) list, err); + if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("%s\n", err->data); + return false; + } + + mutt_buffer_reset(err); + rc = cs_str_string_get(cs, name, err); + if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("%s\n", err->data); + return false; + } + + if (!TEST_CHECK(strcmp(mutt_b2s(err), init) == 0)) + return false; + + mutt_buffer_reset(err); + rc = cs_str_native_set(cs, name, (intptr_t) NULL, err); + if (!TEST_CHECK(rc == (CSR_SUCCESS | CSR_SUC_EMPTY))) + { + TEST_MSG("%s\n", err->data); + return false; + } + + slist_free(&list); + return true; +} + +static bool test_native_get(struct ConfigSet *cs, struct Buffer *err) +{ + log_line(__func__); + + const char *name = "Jackfruit"; + + mutt_buffer_reset(err); + intptr_t value = cs_str_native_get(cs, name, err); + struct Slist *sl = (struct Slist *) value; + + if (!TEST_CHECK(VarJackfruit == sl)) + { + TEST_MSG("Get failed: %s\n", err->data); + return false; + } + + return true; +} + +static bool test_reset(struct ConfigSet *cs, struct Buffer *err) +{ + log_line(__func__); + + const char *name = "Lemon"; + + mutt_buffer_reset(err); + + char *item = STAILQ_FIRST(&VarLemon->head)->data; + TEST_MSG("Initial: %s = '%s'\n", name, item); + int rc = cs_str_string_set(cs, name, "apple", err); + if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + return false; + item = STAILQ_FIRST(&VarLemon->head)->data; + TEST_MSG("Set: %s = '%s'\n", name, item); + + rc = cs_str_reset(cs, name, err); + if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("%s\n", err->data); + return false; + } + + item = STAILQ_FIRST(&VarLemon->head)->data; + if (!TEST_CHECK(mutt_str_strcmp(item, "lemon") == 0)) + { + TEST_MSG("Value of %s wasn't changed\n", name); + return false; + } + + TEST_MSG("Reset: %s = '%s'\n", name, item); + + name = "Mango"; + mutt_buffer_reset(err); + + item = STAILQ_FIRST(&VarMango->head)->data; + TEST_MSG("Initial: %s = '%s'\n", name, item); + dont_fail = true; + rc = cs_str_string_set(cs, name, "banana", err); + if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + return false; + item = STAILQ_FIRST(&VarMango->head)->data; + TEST_MSG("Set: %s = '%s'\n", name, item); + dont_fail = false; + + rc = cs_str_reset(cs, name, err); + if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS)) + { + TEST_MSG("Expected error: %s\n", err->data); + } + else + { + TEST_MSG("%s\n", err->data); + return false; + } + + item = STAILQ_FIRST(&VarMango->head)->data; + if (!TEST_CHECK(mutt_str_strcmp(item, "banana") == 0)) + { + TEST_MSG("Value of %s changed\n", name); + return false; + } + + item = STAILQ_FIRST(&VarMango->head)->data; + TEST_MSG("Reset: %s = '%s'\n", name, item); + + log_line(__func__); + return true; +} + +static bool test_validator(struct ConfigSet *cs, struct Buffer *err) +{ + log_line(__func__); + + char *item = NULL; + struct Slist *list = slist_parse("apple", SLIST_SEP_COMMA); + bool result = false; + + const char *name = "Nectarine"; + mutt_buffer_reset(err); + int rc = cs_str_string_set(cs, name, "banana", err); + if (TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("%s\n", err->data); + } + else + { + TEST_MSG("%s\n", err->data); + goto tv_out; + } + item = STAILQ_FIRST(&VarNectarine->head)->data; + TEST_MSG("Address: %s = %s\n", name, item); + + mutt_buffer_reset(err); + rc = cs_str_native_set(cs, name, IP list, err); + if (TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("%s\n", err->data); + } + else + { + TEST_MSG("%s\n", err->data); + goto tv_out; + } + item = STAILQ_FIRST(&VarNectarine->head)->data; + TEST_MSG("Native: %s = %s\n", name, item); + + name = "Olive"; + mutt_buffer_reset(err); + rc = cs_str_string_set(cs, name, "cherry", err); + if (TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("%s\n", err->data); + } + else + { + TEST_MSG("%s\n", err->data); + goto tv_out; + } + item = STAILQ_FIRST(&VarOlive->head)->data; + TEST_MSG("Address: %s = %s\n", name, item); + + mutt_buffer_reset(err); + rc = cs_str_native_set(cs, name, IP list, err); + if (TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("%s\n", err->data); + } + else + { + TEST_MSG("%s\n", err->data); + goto tv_out; + } + item = STAILQ_FIRST(&VarOlive->head)->data; + TEST_MSG("Native: %s = %s\n", name, item); + + name = "Papaya"; + mutt_buffer_reset(err); + rc = cs_str_string_set(cs, name, "damson", err); + if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS)) + { + TEST_MSG("Expected error: %s\n", err->data); + } + else + { + TEST_MSG("%s\n", err->data); + goto tv_out; + } + item = STAILQ_FIRST(&VarPapaya->head)->data; + TEST_MSG("Address: %s = %s\n", name, item); + + mutt_buffer_reset(err); + rc = cs_str_native_set(cs, name, IP list, err); + if (TEST_CHECK(CSR_RESULT(rc) != CSR_SUCCESS)) + { + TEST_MSG("Expected error: %s\n", err->data); + } + else + { + TEST_MSG("%s\n", err->data); + goto tv_out; + } + item = STAILQ_FIRST(&VarPapaya->head)->data; + TEST_MSG("Native: %s = %s\n", name, item); + + result = true; +tv_out: + slist_free(&list); + log_line(__func__); + return result; +} + +static void dump_native(struct ConfigSet *cs, const char *parent, const char *child) +{ + intptr_t pval = cs_str_native_get(cs, parent, NULL); + intptr_t cval = cs_str_native_get(cs, child, NULL); + + struct Slist *pl = (struct Slist *) pval; + struct Slist *cl = (struct Slist *) cval; + + char *pstr = pl ? STAILQ_FIRST(&pl->head)->data : NULL; + char *cstr = cl ? STAILQ_FIRST(&cl->head)->data : NULL; + + TEST_MSG("%15s = %s\n", parent, NONULL(pstr)); + TEST_MSG("%15s = %s\n", child, NONULL(cstr)); +} + +static bool test_inherit(struct ConfigSet *cs, struct Buffer *err) +{ + log_line(__func__); + bool result = false; + + const char *account = "fruit"; + const char *parent = "Quince"; + char child[128]; + snprintf(child, sizeof(child), "%s:%s", account, parent); + + const char *AccountVarAddr[] = { + parent, + NULL, + }; + + struct Account *a = account_new(); + account_add_config(a, cs, account, AccountVarAddr); + + // set parent + mutt_buffer_reset(err); + int rc = cs_str_string_set(cs, parent, "apple", err); + if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("Error: %s\n", err->data); + goto ti_out; + } + dump_native(cs, parent, child); + + // set child + mutt_buffer_reset(err); + rc = cs_str_string_set(cs, child, "banana", err); + if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("Error: %s\n", err->data); + goto ti_out; + } + dump_native(cs, parent, child); + + // reset child + mutt_buffer_reset(err); + rc = cs_str_reset(cs, child, err); + if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("Error: %s\n", err->data); + goto ti_out; + } + dump_native(cs, parent, child); + + // reset parent + mutt_buffer_reset(err); + rc = cs_str_reset(cs, parent, err); + if (!TEST_CHECK(CSR_RESULT(rc) == CSR_SUCCESS)) + { + TEST_MSG("Error: %s\n", err->data); + goto ti_out; + } + dump_native(cs, parent, child); + + log_line(__func__); + result = true; +ti_out: + account_free(&a); + return result; +} + +bool slist_test_separator(struct ConfigDef Vars[], struct Buffer *err) +{ + log_line(__func__); + + mutt_buffer_reset(err); + struct ConfigSet *cs = cs_new(30); + + slist_init(cs); + if (!cs_register_variables(cs, Vars, 0)) + return false; + + notify_observer_add(cs->notify, NT_CONFIG, 0, log_observer, 0); + + set_list(cs); + + if (!test_initial_values(cs, err)) + return false; + if (!test_string_set(cs, err)) + return false; + if (!test_string_get(cs, err)) + return false; + + cs_free(&cs); + return true; +} + +void config_slist(void) +{ + log_line(__func__); + + struct Buffer err; + mutt_buffer_init(&err); + err.data = mutt_mem_calloc(1, 256); + err.dsize = 256; + + TEST_CHECK(test_slist_parse(&err)); + TEST_CHECK(test_slist_add_string(&err)); + TEST_CHECK(test_slist_remove_string(&err)); + TEST_CHECK(test_slist_is_member(&err)); + TEST_CHECK(test_slist_add_list(&err)); + TEST_CHECK(test_slist_compare(&err)); + + TEST_CHECK(slist_test_separator(VarsColon, &err)); + TEST_CHECK(slist_test_separator(VarsComma, &err)); + TEST_CHECK(slist_test_separator(VarsSpace, &err)); + + struct ConfigSet *cs = cs_new(30); + + slist_init(cs); + dont_fail = true; + if (!cs_register_variables(cs, VarsOther, 0)) + return; + dont_fail = false; + + notify_observer_add(cs->notify, NT_CONFIG, 0, log_observer, 0); + + TEST_CHECK(test_native_set(cs, &err)); + TEST_CHECK(test_native_get(cs, &err)); + TEST_CHECK(test_reset(cs, &err)); + TEST_CHECK(test_validator(cs, &err)); + TEST_CHECK(test_inherit(cs, &err)); + + cs_free(&cs); + FREE(&err.data); + log_line(__func__); +} diff --git a/test/config/slist.h b/test/config/slist.h new file mode 100644 index 000000000..5373b7ff2 --- /dev/null +++ b/test/config/slist.h @@ -0,0 +1,30 @@ +/** + * @file + * Test code for the Slist object + * + * @authors + * Copyright (C) 2018-2019 Richard Russon + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef _TEST_SLIST_H +#define _TEST_SLIST_H + +#include + +void config_slist(void); + +#endif /* _TEST_SLIST_H */ diff --git a/test/main.c b/test/main.c index 669c6416a..64a9979c1 100644 --- a/test/main.c +++ b/test/main.c @@ -117,6 +117,7 @@ NEOMUTT_TEST_ITEM(config_number) \ NEOMUTT_TEST_ITEM(config_quad) \ NEOMUTT_TEST_ITEM(config_regex) \ + NEOMUTT_TEST_ITEM(config_slist) \ NEOMUTT_TEST_ITEM(config_sort) \ NEOMUTT_TEST_ITEM(config_string) \ NEOMUTT_TEST_ITEM(config_dump) \ -- 2.40.0