]> granicus.if.org Git - neomutt/commitdiff
add slist type
authorRichard Russon <rich@flatcap.org>
Sun, 9 Jun 2019 15:23:55 +0000 (16:23 +0100)
committerRichard Russon <rich@flatcap.org>
Wed, 12 Jun 2019 23:23:35 +0000 (00:23 +0100)
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.

14 files changed:
Makefile.autosetup
config/lib.h
config/slist.c [new file with mode: 0644]
config/slist.h [new file with mode: 0644]
config/types.h
init.c
mutt/mutt.h
mutt/slist.c [new file with mode: 0644]
mutt/slist.h [new file with mode: 0644]
po/POTFILES.in
test/Makefile.autosetup
test/config/slist.c [new file with mode: 0644]
test/config/slist.h [new file with mode: 0644]
test/main.c

index a17276d019acc7a3b4e9943d4a6cf7a6ede4cb4a..7acf73cf30bf936c3a4c51c4b24d9cc9c848c38d 100644 (file)
@@ -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)
index 2dfe9fff0948e8e26e162bf44235c566c7e0b581..caf96a8e4914f487a5740e5a29516fe4ddb0f836 100644 (file)
@@ -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 (file)
index 0000000..57ed006
--- /dev/null
@@ -0,0 +1,274 @@
+/**
+ * @file
+ * Type representing a list of strings
+ *
+ * @authors
+ * Copyright (C) 2018-2019 Richard Russon <rich@flatcap.org>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @page config_slist Type: List of strings
+ *
+ * Type representing a list of strings.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+#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 (file)
index 0000000..e0e89b4
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * @file
+ * Type representing a list of strings
+ *
+ * @authors
+ * Copyright (C) 2018-2019 Richard Russon <rich@flatcap.org>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MUTT_CONFIG_SLIST_H
+#define MUTT_CONFIG_SLIST_H
+
+struct ConfigSet;
+
+void slist_init(struct ConfigSet *cs);
+
+#endif /* MUTT_CONFIG_SLIST_H */
index be3039e34f2c793715aee5285bcb8d79caaf33b6..65ac19bd52aa4db3409907aa3056ca7e7b9b25a9 100644 (file)
@@ -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 95eb8b5b155faaefc3634d65ae8f854f8e2f0a86..92824f583d6b57ef15d3f71043dfc4c682120bde 100644 (file)
--- 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);
 
index ffdfe99c30f4ac81d077f666b31e94e0c79e2823..37c2d7bacc9973fe89bf9ba55173e69b02b53127 100644 (file)
@@ -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 (file)
index 0000000..49c35d6
--- /dev/null
@@ -0,0 +1,262 @@
+/**
+ * @file
+ * A separated list of strings
+ *
+ * @authors
+ * Copyright (C) 2018-2019 Richard Russon <rich@flatcap.org>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @page slist A separated list of strings
+ *
+ * A separated list of strings
+ */
+
+#include "config.h"
+#include <stddef.h>
+#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 (file)
index 0000000..fe31c87
--- /dev/null
@@ -0,0 +1,60 @@
+/**
+ * @file
+ * A separated list of strings
+ *
+ * @authors
+ * Copyright (C) 2018-2019 Richard Russon <rich@flatcap.org>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MUTT_LIB_SLIST_H
+#define MUTT_LIB_SLIST_H
+
+#include <stddef.h>
+#include <stdbool.h>
+#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 */
index 40db22480c4c7cf3490b3ddb32fab1ccbcbdb357..da6c32f6c17300ce82d2bd313ebf0fc60b7b8b9e 100644 (file)
@@ -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
index 661e11428a726c5e8daec3a5f3c6f47ec7796699..cbf64e757c7b215dacc2a708bf2c6659262c0db5 100644 (file)
@@ -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 (file)
index 0000000..b5456ac
--- /dev/null
@@ -0,0 +1,966 @@
+/**
+ * @file
+ * Test code for the Slist object
+ *
+ * @authors
+ * Copyright (C) 2018-2019 Richard Russon <rich@flatcap.org>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+#define TEST_NO_MAIN
+#include "acutest.h"
+#include "config.h"
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#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 (file)
index 0000000..5373b7f
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * @file
+ * Test code for the Slist object
+ *
+ * @authors
+ * Copyright (C) 2018-2019 Richard Russon <rich@flatcap.org>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TEST_SLIST_H
+#define _TEST_SLIST_H
+
+#include <stdbool.h>
+
+void config_slist(void);
+
+#endif /* _TEST_SLIST_H */
index 669c6416ad666224e0d370a18b70af3bbeb19608..64a9979c100c5ef3cf0f449fcbabf243b422a052 100644 (file)
   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)                                               \