]> granicus.if.org Git - neomutt/commitdiff
config files
authorRichard Russon <rich@flatcap.org>
Mon, 30 Oct 2017 00:50:11 +0000 (00:50 +0000)
committerRichard Russon <rich@flatcap.org>
Mon, 23 Jul 2018 14:44:54 +0000 (15:44 +0100)
36 files changed:
.clang-format
Makefile.autosetup
config/Makefile.am [new file with mode: 0644]
config/address.c [new file with mode: 0644]
config/address.h [new file with mode: 0644]
config/bool.c [new file with mode: 0644]
config/bool.h [new file with mode: 0644]
config/command.c [new file with mode: 0644]
config/command.h [new file with mode: 0644]
config/dump.c [new file with mode: 0644]
config/dump.h [new file with mode: 0644]
config/inheritance.h [new file with mode: 0644]
config/lib.h [new file with mode: 0644]
config/long.c [new file with mode: 0644]
config/long.h [new file with mode: 0644]
config/magic.c [new file with mode: 0644]
config/magic.h [new file with mode: 0644]
config/mbtable.c [new file with mode: 0644]
config/mbtable.h [new file with mode: 0644]
config/number.c [new file with mode: 0644]
config/number.h [new file with mode: 0644]
config/path.c [new file with mode: 0644]
config/path.h [new file with mode: 0644]
config/quad.c [new file with mode: 0644]
config/quad.h [new file with mode: 0644]
config/regex.c [new file with mode: 0644]
config/regex2.h [new file with mode: 0644]
config/set.c [new file with mode: 0644]
config/set.h [new file with mode: 0644]
config/sort.c [new file with mode: 0644]
config/sort.h [new file with mode: 0644]
config/string.c [new file with mode: 0644]
config/string3.h [new file with mode: 0644]
config/types.h [new file with mode: 0644]
mutt/regex3.h
po/POTFILES.in

index c41d0a208c4ad92e0b91002fc0d039049d0b63a4..fbb3509cd57e54dd1aadd1b07d923e09d653c475 100644 (file)
@@ -17,6 +17,8 @@ IncludeCategories:
   - Regex:      '".*_private\.h"'
     Priority:   -6
   - Regex:      '"mutt/.*\.h"'
+    Priority:   -5
+  - Regex:      '"config/.*\.h"'
     Priority:   -4
   - Regex:      '"email/.*\.h"'
     Priority:   -3
index e03cbaa2f75c83922ac73af3ccb02f871babb227..4cfb62b740fc8bea2ad16e057bf27c3926561fcd 100644 (file)
@@ -207,6 +207,18 @@ CLEANFILES+=       $(LIBCONN) $(LIBCONNOBJS)
 MUTTLIBS+=     $(LIBCONN)
 ALLOBJS+=      $(LIBCONNOBJS)
 
+###############################################################################
+# libconfig
+LIBCONFIG=     libconfig.a
+LIBCONFIGOBJS= config/address.o config/bool.o config/command.o config/dump.o \
+               config/long.o config/magic.o config/mbtable.o config/number.o \
+               config/path.o config/quad.o config/regex.o config/set.o \
+               config/sort.o config/string.o
+
+CLEANFILES+=   $(LIBCONFIG) $(LIBCONFIGOBJS)
+MUTTLIBS+=     $(LIBCONFIG)
+ALLOBJS+=      $(LIBCONFIGOBJS)
+
 ###############################################################################
 # libhcache
 @if USE_HCACHE
@@ -333,6 +345,13 @@ $(LIBCONN): $(PWD)/conn $(LIBCONNOBJS)
 $(PWD)/conn:
        $(MKDIR_P) $(PWD)/conn
 
+# libconfig
+$(LIBCONFIG): $(PWD)/config $(LIBCONFIGOBJS)
+       $(AR) cr $@ $(LIBCONFIGOBJS)
+       $(RANLIB) $@
+$(PWD)/config:
+       $(MKDIR_P) $(PWD)/config
+
 # libhcache
 hcache/hcache.o:       hcache/hcversion.h
 $(LIBHCACHE): $(PWD)/hcache $(LIBHCACHEOBJS)
diff --git a/config/Makefile.am b/config/Makefile.am
new file mode 100644 (file)
index 0000000..5fdd1a2
--- /dev/null
@@ -0,0 +1,13 @@
+## Process this file with automake to produce Makefile.in
+include $(top_srcdir)/flymake.am
+
+AUTOMAKE_OPTIONS = 1.6 foreign
+
+EXTRA_DIST = account.h address.h bool.h inheritance.h magic.h mbtable.h number.h path.h quad.h regex2.h set.h sort.h string3.h types.h
+
+AM_CPPFLAGS = -I$(top_srcdir)
+
+noinst_LIBRARIES = libconfig.a
+
+libconfig_a_SOURCES = account.c address.c bool.c magic.c mbtable.c number.c path.c quad.c regex.c set.c sort.c string.c
+
diff --git a/config/address.c b/config/address.c
new file mode 100644 (file)
index 0000000..2a39e25
--- /dev/null
@@ -0,0 +1,316 @@
+/**
+ * @file
+ * Type representing an email address
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-address Type: Email address
+ *
+ * Type representing an email address.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "email/address.h"
+#include "address.h"
+#include "set.h"
+#include "types.h"
+
+size_t mutt_addr_write(char *buf, size_t buflen, struct Address *addr, bool display);
+
+/**
+ * address_destroy - Destroy an Address object
+ * @param cs   Config items
+ * @param var  Variable to destroy
+ * @param cdef Variable definition
+ */
+static void address_destroy(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef)
+{
+  if (!cs || !var || !cdef)
+    return; /* LCOV_EXCL_LINE */
+
+  struct Address **a = (struct Address **) var;
+  if (!*a)
+    return;
+
+  address_free(a);
+}
+
+/**
+ * address_string_set - Set an Address 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 address_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 */
+
+  struct Address *addr = NULL;
+
+  /* An empty address "" will be stored as NULL */
+  if (var && value && (value[0] != '\0'))
+  {
+    // addr = mutt_addr_parse_list(NULL, value);
+  }
+
+  int rc = CSR_SUCCESS;
+
+  if (var)
+  {
+    if (cdef->validator)
+    {
+      rc = cdef->validator(cs, cdef, (intptr_t) addr, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+      {
+        address_destroy(cs, &addr, cdef);
+        return (rc | CSR_INV_VALIDATOR);
+      }
+    }
+
+    /* ordinary variable setting */
+    address_destroy(cs, var, cdef);
+
+    *(struct Address **) var = addr;
+
+    if (!addr)
+      rc |= CSR_SUC_EMPTY;
+  }
+  else
+  {
+    /* set the default/initial value */
+    if (cdef->type & DT_INITIAL_SET)
+      FREE(&cdef->initial);
+
+    cdef->type |= DT_INITIAL_SET;
+    cdef->initial = IP mutt_str_strdup(value);
+  }
+
+  return rc;
+}
+
+/**
+ * address_string_get - Get an Address 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 address_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 */
+
+  char tmp[HUGE_STRING] = "";
+  const char *str = NULL;
+
+  if (var)
+  {
+    struct Address *a = *(struct Address **) var;
+    if (a)
+    {
+      mutt_addr_write(tmp, sizeof(tmp), a, false);
+      str = tmp;
+    }
+  }
+  else
+  {
+    str = (char *) cdef->initial;
+  }
+
+  if (!str)
+    return (CSR_SUCCESS | CSR_SUC_EMPTY); /* empty string */
+
+  mutt_buffer_addstr(result, str);
+  return CSR_SUCCESS;
+}
+
+/**
+ * address_dup - Create a copy of an Address object
+ * @param addr Address to duplicate
+ * @retval ptr New Address object
+ */
+static struct Address *address_dup(struct Address *addr)
+{
+  if (!addr)
+    return NULL; /* LCOV_EXCL_LINE */
+
+  struct Address *a = mutt_mem_calloc(1, sizeof(*a));
+  a->personal = mutt_str_strdup(addr->personal);
+  a->mailbox = mutt_str_strdup(addr->mailbox);
+  return a;
+}
+
+/**
+ * address_native_set - Set an Address config item by Address object
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Address pointer
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int address_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);
+  }
+
+  address_free(var);
+
+  struct Address *addr = address_dup((struct Address *) value);
+
+  rc = CSR_SUCCESS;
+  if (!addr)
+    rc |= CSR_SUC_EMPTY;
+
+  *(struct Address **) var = addr;
+  return rc;
+}
+
+/**
+ * address_native_get - Get an Address object from an Address config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Address pointer
+ */
+static intptr_t address_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 Address *addr = *(struct Address **) var;
+
+  return (intptr_t) addr;
+}
+
+/**
+ * address_reset - Reset an Address 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 address_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 Address *a = NULL;
+  const char *initial = (const char *) cdef->initial;
+
+  if (initial)
+    a = address_create(initial);
+
+  int rc = CSR_SUCCESS;
+
+  if (cdef->validator)
+  {
+    rc = cdef->validator(cs, cdef, (intptr_t) a, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+    {
+      address_destroy(cs, &a, cdef);
+      return (rc | CSR_INV_VALIDATOR);
+    }
+  }
+
+  if (!a)
+    rc |= CSR_SUC_EMPTY;
+
+  address_destroy(cs, var, cdef);
+
+  *(struct Address **) var = a;
+  return rc;
+}
+
+/**
+ * address_init - Register the Address config type
+ * @param cs Config items
+ */
+void address_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_address = {
+    "address",          address_string_set, address_string_get,
+    address_native_set, address_native_get, address_reset,
+    address_destroy,
+  };
+  cs_register_type(cs, DT_ADDRESS, &cst_address);
+}
+
+/**
+ * address_create - Create an Address from a string
+ * @param addr Email address to parse
+ * @retval ptr New Address object
+ */
+struct Address *address_create(const char *addr)
+{
+  struct Address *a = mutt_mem_calloc(1, sizeof(*a));
+  a->personal = mutt_str_strdup(addr);
+  a->mailbox = mutt_str_strdup("dummy3");
+  return a;
+}
+
+/**
+ * address_free - Free an Address object
+ * @param addr Address to free
+ */
+void address_free(struct Address **addr)
+{
+  if (!addr || !*addr)
+    return; /* LCOV_EXCL_LINE */
+
+  FREE(&(*addr)->personal);
+  FREE(&(*addr)->mailbox);
+  FREE(addr);
+}
diff --git a/config/address.h b/config/address.h
new file mode 100644 (file)
index 0000000..bbfbc64
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * @file
+ * Type representing an email address
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_ADDRESS_H
+#define _CONFIG_ADDRESS_H
+
+#include <stdbool.h>
+
+struct ConfigSet;
+
+void address_init(struct ConfigSet *cs);
+struct Address *address_create(const char *addr);
+void address_free(struct Address **addr);
+
+#endif /* _CONFIG_ADDRESS_H */
diff --git a/config/bool.c b/config/bool.c
new file mode 100644 (file)
index 0000000..413191c
--- /dev/null
@@ -0,0 +1,291 @@
+/**
+ * @file
+ * Type representing a boolean
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-bool Type: Boolean
+ *
+ * Type representing a boolean.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/hash.h"
+#include "mutt/logging.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * bool_values - Valid strings for creating a Bool
+ *
+ * These strings are case-insensitive.
+ */
+const char *bool_values[] = {
+  "no", "yes", "n", "y", "false", "true", "0", "1", "off", "on", NULL,
+};
+
+/**
+ * bool_string_set - Set a Bool 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 bool_string_set(const struct ConfigSet *cs, void *var, struct ConfigDef *cdef,
+                           const char *value, struct Buffer *err)
+{
+  if (!cs || !cdef || !value)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  int num = -1;
+  for (size_t i = 0; bool_values[i]; i++)
+  {
+    if (mutt_str_strcasecmp(bool_values[i], value) == 0)
+    {
+      num = i % 2;
+      break;
+    }
+  }
+
+  if (num < 0)
+  {
+    mutt_buffer_printf(err, "Invalid boolean value: %s", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if (var)
+  {
+    if (num == (*(bool *) var))
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      int rc = cdef->validator(cs, cdef, (intptr_t) num, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    *(bool *) var = num;
+  }
+  else
+  {
+    cdef->initial = num;
+  }
+
+  return CSR_SUCCESS;
+}
+
+/**
+ * bool_string_get - Get a Bool 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 bool_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 */
+
+  int index;
+
+  if (var)
+    index = *(bool *) var;
+  else
+    index = (int) cdef->initial;
+
+  if (index > 1)
+  {
+    mutt_debug(1, "Variable has an invalid value: %d\n", index);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  mutt_buffer_addstr(result, bool_values[index]);
+  return CSR_SUCCESS;
+}
+
+/**
+ * bool_native_set - Set a Bool config item by bool
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Bool value
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int bool_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 */
+
+  if ((value < 0) || (value > 1))
+  {
+    mutt_buffer_printf(err, "Invalid boolean value: %ld", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if (value == (*(bool *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(bool *) var = value;
+  return CSR_SUCCESS;
+}
+
+/**
+ * bool_native_get - Get a bool from a Bool config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Bool
+ */
+static intptr_t bool_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 */
+
+  return *(bool *) var;
+}
+
+/**
+ * bool_reset - Reset a Bool 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 bool_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 */
+
+  if (cdef->initial == (*(bool *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(bool *) var = cdef->initial;
+  return CSR_SUCCESS;
+}
+
+/**
+ * bool_init - Register the Bool config type
+ * @param cs Config items
+ */
+void bool_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_bool = {
+    "boolean",
+    bool_string_set,
+    bool_string_get,
+    bool_native_set,
+    bool_native_get,
+    bool_reset,
+    NULL,
+  };
+  cs_register_type(cs, DT_BOOL, &cst_bool);
+}
+
+/**
+ * bool_he_toggle - Toggle the value of a bool
+ * @param cs  Config items
+ * @param he  HashElem representing config item
+ * @param err Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int bool_he_toggle(struct ConfigSet *cs, struct HashElem *he, struct Buffer *err)
+{
+  if (!cs || !he || !he->data)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  if (DTYPE(he->type) != DT_BOOL)
+    return CSR_ERR_CODE;
+
+  const struct ConfigDef *cdef = he->data;
+  char *var = cdef->var;
+
+  char value = *var;
+  if ((value < 0) || (value > 1))
+  {
+    mutt_buffer_printf(err, "Invalid boolean value: %ld", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  *(char *) var = !value;
+
+  cs_notify_listeners(cs, he, he->key.strkey, CE_SET);
+  return CSR_SUCCESS;
+}
+
+/**
+ * bool_str_toggle - Toggle the value of a bool
+ * @param cs   Config items
+ * @param name Name of config item
+ * @param err  Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int bool_str_toggle(struct ConfigSet *cs, const char *name, struct Buffer *err)
+{
+  if (!cs || !name)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct HashElem *he = cs_get_elem(cs, name);
+  if (!he)
+  {
+    mutt_buffer_printf(err, "Unknown var '%s'", name);
+    return CSR_ERR_UNKNOWN;
+  }
+
+  return bool_he_toggle(cs, he, err);
+}
diff --git a/config/bool.h b/config/bool.h
new file mode 100644 (file)
index 0000000..acb937e
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * @file
+ * Type representing a boolean
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_BOOL_H
+#define _CONFIG_BOOL_H
+
+struct Buffer;
+struct ConfigSet;
+struct HashElem;
+
+extern const char *bool_values[];
+
+void bool_init(struct ConfigSet *cs);
+int  bool_he_toggle(struct ConfigSet *cs, struct HashElem *he, struct Buffer *err);
+int  bool_str_toggle(struct ConfigSet *cs, const char *name, struct Buffer *err);
+
+#endif /* _CONFIG_BOOL_H */
diff --git a/config/command.c b/config/command.c
new file mode 100644 (file)
index 0000000..39ff342
--- /dev/null
@@ -0,0 +1,283 @@
+/**
+ * @file
+ * Type representing a command
+ *
+ * @authors
+ * Copyright (C) 2018 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-command Type: Command
+ *
+ * Type representing a command.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * command_destroy - Destroy a Command
+ * @param cs   Config items
+ * @param var  Variable to destroy
+ * @param cdef Variable definition
+ */
+static void command_destroy(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef)
+{
+  if (!cs || !var || !cdef)
+    return; /* LCOV_EXCL_LINE */
+
+  const char **str = (const char **) var;
+  if (!*str)
+    return;
+
+  /* Don't free strings from the var definition */
+  if (*(char **) var == (char *) cdef->initial)
+  {
+    *(char **) var = NULL;
+    return;
+  }
+
+  FREE(var);
+}
+
+/**
+ * command_string_set - Set a Command 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 command_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;
+
+  if (!value && (cdef->type & DT_NOT_EMPTY))
+  {
+    mutt_buffer_printf(err, "Option %s may not be empty", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  int rc = CSR_SUCCESS;
+
+  if (var)
+  {
+    if (mutt_str_strcmp(value, (*(char **) var)) == 0)
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      rc = cdef->validator(cs, cdef, (intptr_t) value, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    command_destroy(cs, var, cdef);
+
+    const char *str = mutt_str_strdup(value);
+    if (!str)
+      rc |= CSR_SUC_EMPTY;
+
+    *(const char **) var = str;
+  }
+  else
+  {
+    /* we're already using the initial value */
+    if (*(char **) cdef->var == (char *) cdef->initial)
+      *(char **) cdef->var = mutt_str_strdup((char *) cdef->initial);
+
+    if (cdef->type & DT_INITIAL_SET)
+      FREE(&cdef->initial);
+
+    cdef->type |= DT_INITIAL_SET;
+    cdef->initial = IP mutt_str_strdup(value);
+  }
+
+  return rc;
+}
+
+/**
+ * command_string_get - Get a Command 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 command_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 */
+
+  const char *str = NULL;
+
+  if (var)
+    str = *(const char **) var;
+  else
+    str = (char *) cdef->initial;
+
+  if (!str)
+    return (CSR_SUCCESS | CSR_SUC_EMPTY); /* empty string */
+
+  mutt_buffer_addstr(result, str);
+  return CSR_SUCCESS;
+}
+
+/**
+ * command_native_set - Set a Command config item by string
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Native pointer/value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int command_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 */
+
+  const char *str = (const char *) value;
+
+  /* Store empty strings as NULL */
+  if (str && (str[0] == '\0'))
+    value = 0;
+
+  if ((value == 0) && (cdef->type & DT_NOT_EMPTY))
+  {
+    mutt_buffer_printf(err, "Option %s may not be empty", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  if (mutt_str_strcmp((const char *) value, (*(char **) var)) == 0)
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  int rc;
+
+  if (cdef->validator)
+  {
+    rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  command_destroy(cs, var, cdef);
+
+  str = mutt_str_strdup(str);
+  rc = CSR_SUCCESS;
+  if (!str)
+    rc |= CSR_SUC_EMPTY;
+
+  *(const char **) var = str;
+  return rc;
+}
+
+/**
+ * command_native_get - Get a string from a Command config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Command string
+ */
+static intptr_t command_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 */
+
+  const char *str = *(const char **) var;
+
+  return (intptr_t) str;
+}
+
+/**
+ * command_reset - Reset a Command 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 command_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 */
+
+  int rc = CSR_SUCCESS;
+
+  const char *command = (const char *) cdef->initial;
+  if (!command)
+    rc |= CSR_SUC_EMPTY;
+
+  if (mutt_str_strcmp(command, (*(char **) var)) == 0)
+    return (rc | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  command_destroy(cs, var, cdef);
+
+  if (!command)
+    rc |= CSR_SUC_EMPTY;
+
+  *(const char **) var = command;
+  return rc;
+}
+
+/**
+ * command_init - Register the Command config type
+ * @param cs Config items
+ */
+void command_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_command = {
+    "command",          command_string_set, command_string_get,
+    command_native_set, command_native_get, command_reset,
+    command_destroy,
+  };
+  cs_register_type(cs, DT_COMMAND, &cst_command);
+}
diff --git a/config/command.h b/config/command.h
new file mode 100644 (file)
index 0000000..fae068a
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * @file
+ * Type representing a command
+ *
+ * @authors
+ * Copyright (C) 2018 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 _CONFIG_COMMAND_H
+#define _CONFIG_COMMAND_H
+
+struct ConfigSet;
+
+void command_init(struct ConfigSet *cs);
+
+#endif /* _CONFIG_COMMAND_H */
diff --git a/config/dump.c b/config/dump.c
new file mode 100644 (file)
index 0000000..768d37b
--- /dev/null
@@ -0,0 +1,328 @@
+/**
+ * @file
+ * Dump all the config
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-dump Dump all the config
+ *
+ * Dump all the config items in various formats.
+ */
+
+#include "config.h"
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mutt/buffer.h"
+#include "mutt/hash.h"
+#include "mutt/logging.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "dump.h"
+#include "set.h"
+#include "types.h"
+
+void mutt_pretty_mailbox(char *s, size_t buflen)
+{
+}
+
+/**
+ * escape_string - Write a string to a buffer, escaping special characters
+ * @param buf Buffer to write to
+ * @param src String to write
+ * @retval num Bytes written to buffer
+ */
+size_t escape_string(struct Buffer *buf, const char *src)
+{
+  if (!buf || !src)
+    return 0;
+
+  size_t len = 0;
+  for (; *src; src++)
+  {
+    switch (*src)
+    {
+      case '\n':
+        len += mutt_buffer_addstr(buf, "\\n");
+        break;
+      case '\r':
+        len += mutt_buffer_addstr(buf, "\\r");
+        break;
+      case '\t':
+        len += mutt_buffer_addstr(buf, "\\t");
+        break;
+      default:
+        if ((*src == '\\') || (*src == '"'))
+          len += mutt_buffer_addch(buf, '\\');
+        len += mutt_buffer_addch(buf, src[0]);
+    }
+  }
+  return len;
+}
+
+/**
+ * pretty_var - Escape and stringify a config item value
+ * @param str    String to escape
+ * @param buf    Buffer to write to
+ * @retval num Number of bytes written to buffer
+ */
+size_t pretty_var(const char *str, struct Buffer *buf)
+{
+  if (!buf || !str)
+    return 0;
+
+  int len = 0;
+
+  len += mutt_buffer_addch(buf, '"');
+  len += escape_string(buf, str);
+  len += mutt_buffer_addch(buf, '"');
+
+  return len;
+}
+
+/**
+ * elem_list_sort - Sort two HashElem pointers to config
+ * @param a First HashElem
+ * @param b Second HashElem
+ * @retval -1 a precedes b
+ * @retval  0 a and b are identical
+ * @retval  1 b precedes a
+ */
+int elem_list_sort(const void *a, const void *b)
+{
+  const struct HashElem *hea = *(struct HashElem **) a;
+  const struct HashElem *heb = *(struct HashElem **) b;
+
+  return mutt_str_strcasecmp(hea->key.strkey, heb->key.strkey);
+}
+
+/**
+ * get_elem_list - Create a sorted list of all config items
+ * @param cs ConfigSet to read
+ * @retval ptr Null-terminated array of HashElem
+ */
+struct HashElem **get_elem_list(struct ConfigSet *cs)
+{
+  if (!cs)
+    return NULL;
+
+  struct HashElem **list = mutt_mem_calloc(1024, sizeof(struct HashElem *));
+  size_t index = 0;
+
+  struct HashWalkState walk;
+  memset(&walk, 0, sizeof(walk));
+
+  struct HashElem *he = NULL;
+  while ((he = mutt_hash_walk(cs->hash, &walk)))
+  {
+    list[index++] = he;
+    if (index == 1022)
+    {
+      mutt_debug(1, "Too many config items to sort\n");
+      break;
+    }
+  }
+
+  qsort(list, index, sizeof(struct HashElem *), elem_list_sort);
+
+  return list;
+}
+
+/**
+ * dump_config_mutt - Dump the config in the style of Mutt
+ * @param cs      Config items
+ * @param he      HashElem representing config item
+ * @param value   Current value of the config item
+ * @param initial Initial value of the config item
+ * @param flags   Flags, e.g. #CS_DUMP_ONLY_CHANGED
+ */
+void dump_config_mutt(struct ConfigSet *cs, struct HashElem *he,
+                      struct Buffer *value, struct Buffer *initial, int flags)
+{
+  const char *name = he->key.strkey;
+
+  if (DTYPE(he->type) == DT_BOOL)
+  {
+    if ((value->data[0] == 'y') || ((value->data[0] == '"') && (value->data[1] == 'y')))
+    {
+      printf("%s is set\n", name);
+    }
+    else
+    {
+      printf("%s is unset\n", name);
+    }
+  }
+  else
+  {
+    printf("%s=%s\n", name, value->data);
+  }
+}
+
+/**
+ * dump_config_neo - Dump the config in the style of NeoMutt
+ * @param cs      Config items
+ * @param he      HashElem representing config item
+ * @param value   Current value of the config item
+ * @param initial Initial value of the config item
+ * @param flags   Flags, e.g. #CS_DUMP_ONLY_CHANGED
+ */
+void dump_config_neo(struct ConfigSet *cs, struct HashElem *he,
+                     struct Buffer *value, struct Buffer *initial, int flags)
+{
+  const char *name = he->key.strkey;
+
+  if ((flags & CS_DUMP_ONLY_CHANGED) && (mutt_str_strcmp(value->data, initial->data) == 0))
+    return;
+
+  if (he->type == DT_SYNONYM)
+  {
+    const struct ConfigDef *cdef = he->data;
+    const char *syn = (const char *) cdef->initial;
+    printf("# synonym: %s -> %s\n", name, syn);
+    return;
+  }
+
+  bool show_name = !(flags & CS_DUMP_HIDE_NAME);
+  bool show_value = !(flags & CS_DUMP_HIDE_VALUE);
+
+  if (show_name && show_value)
+    printf("set ");
+  if (show_name)
+    printf("%s", name);
+  if (show_name && show_value)
+    printf(" = ");
+  if (show_value)
+    printf("%s", value->data);
+  if (show_name || show_value)
+    printf("\n");
+
+  if (flags & CS_DUMP_SHOW_DEFAULTS)
+  {
+    const struct ConfigSetType *cst = cs_get_type_def(cs, he->type);
+    printf("# %s %s %s\n", cst->name, name, value->data);
+  }
+}
+
+/**
+ * dump_config - Write all the config to stdout
+ * @param cs    ConfigSet to dump
+ * @param style Output style, e.g. #CS_DUMP_STYLE_MUTT
+ * @param flags Display flags, e.g. #CS_DUMP_ONLY_CHANGED
+ */
+bool dump_config(struct ConfigSet *cs, int style, int flags)
+{
+  if (!cs)
+    return false;
+
+  struct HashElem *he = NULL;
+
+  struct HashElem **list = get_elem_list(cs);
+  if (!list)
+    return false;
+
+  bool result = true;
+
+  struct Buffer *value = mutt_buffer_alloc(1024);
+  struct Buffer *initial = mutt_buffer_alloc(1024);
+  struct Buffer *tmp = mutt_buffer_alloc(1024);
+
+  for (size_t i = 0; list[i]; i++)
+  {
+    mutt_buffer_reset(value);
+    mutt_buffer_reset(initial);
+    he = list[i];
+    const int type = DTYPE(he->type);
+
+    if ((type == DT_SYNONYM) && !(flags & CS_DUMP_SHOW_SYNONYMS))
+      continue;
+
+    if ((type == DT_DISABLED) && !(flags & CS_DUMP_SHOW_DISABLED))
+      continue;
+
+    if (type != DT_SYNONYM)
+    {
+      /* If necessary, get the current value */
+      if ((flags & CS_DUMP_ONLY_CHANGED) || !(flags & CS_DUMP_HIDE_VALUE) ||
+          (flags & CS_DUMP_SHOW_DEFAULTS))
+      {
+        int rc = cs_he_string_get(cs, he, value);
+        if (CSR_RESULT(rc) != CSR_SUCCESS)
+        {
+          result = false;
+          break;
+        }
+
+        const struct ConfigDef *cdef = he->data;
+        if (IS_SENSITIVE(*cdef) && (flags & CS_DUMP_HIDE_SENSITIVE) &&
+            !mutt_buffer_is_empty(value))
+        {
+          mutt_buffer_reset(value);
+          mutt_buffer_addstr(value, "***");
+        }
+
+        if (type == DT_PATH)
+          mutt_pretty_mailbox(value->data, value->dsize);
+
+        if ((type != DT_BOOL) && (type != DT_NUMBER) && (type != DT_QUAD) &&
+            !(flags & CS_DUMP_NO_ESCAPING))
+        {
+          mutt_buffer_reset(tmp);
+          size_t len = pretty_var(value->data, tmp);
+          mutt_str_strfcpy(value->data, tmp->data, len + 1);
+        }
+      }
+
+      /* If necessary, get the default value */
+      if (flags & (CS_DUMP_ONLY_CHANGED | CS_DUMP_SHOW_DEFAULTS))
+      {
+        int rc = cs_he_initial_get(cs, he, initial);
+        if (CSR_RESULT(rc) != CSR_SUCCESS)
+        {
+          result = false;
+          break;
+        }
+
+        if (type == DT_PATH)
+          mutt_pretty_mailbox(value->data, value->dsize);
+
+        if ((type != DT_BOOL) && (type != DT_NUMBER) && (type != DT_QUAD) &&
+            !(flags & CS_DUMP_NO_ESCAPING))
+        {
+          mutt_buffer_reset(tmp);
+          size_t len = pretty_var(initial->data, tmp);
+          mutt_str_strfcpy(value->data, tmp->data, len + 1);
+        }
+      }
+    }
+
+    if (style == CS_DUMP_STYLE_MUTT)
+      dump_config_mutt(cs, he, value, initial, flags);
+    else
+      dump_config_neo(cs, he, value, initial, flags);
+  }
+
+  FREE(&list);
+  mutt_buffer_free(&value);
+  mutt_buffer_free(&initial);
+  mutt_buffer_free(&tmp);
+
+  return result;
+}
diff --git a/config/dump.h b/config/dump.h
new file mode 100644 (file)
index 0000000..cc0e760
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * @file
+ * Dump all the config
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_DUMP_H
+#define _CONFIG_DUMP_H
+
+#include <stddef.h>
+
+struct ConfigSet;
+
+#define CS_DUMP_STYLE_MUTT   0 /**< Display config in Mutt style */
+#define CS_DUMP_STYLE_NEO    1 /**< Display config in NeoMutt style */
+
+#define CS_DUMP_ONLY_CHANGED   (1 << 0) /**< Only show config that the user has changed */
+#define CS_DUMP_HIDE_SENSITIVE (1 << 1) /**< Obscure sensitive information like passwords */
+#define CS_DUMP_NO_ESCAPING    (1 << 2) /**< Do not escape special chars, or quote the string */
+#define CS_DUMP_HIDE_NAME      (1 << 3) /**< Do not print the name of the config item */
+#define CS_DUMP_HIDE_VALUE     (1 << 4) /**< Do not print the value of the config item */
+#define CS_DUMP_SHOW_DEFAULTS  (1 << 5) /**< Show the default value for the config item */
+#define CS_DUMP_SHOW_DISABLED  (1 << 6) /**< Show disabled config items, too */
+#define CS_DUMP_SHOW_SYNONYMS  (1 << 7) /**< Show synonyms and the config items their linked to */
+
+void              dump_config_mutt(struct ConfigSet *cs, struct HashElem *he, struct Buffer *value, struct Buffer *initial, int flags);
+void              dump_config_neo(struct ConfigSet *cs, struct HashElem *he, struct Buffer *value, struct Buffer *initial, int flags);
+bool              dump_config(struct ConfigSet *cs, int style, int flags);
+int               elem_list_sort(const void *a, const void *b);
+size_t            escape_string(struct Buffer *buf, const char *src);
+struct HashElem **get_elem_list(struct ConfigSet *cs);
+size_t            pretty_var(const char *str, struct Buffer *buf);
+
+#endif /* _CONFIG_DUMP_H */
diff --git a/config/inheritance.h b/config/inheritance.h
new file mode 100644 (file)
index 0000000..0199556
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * @file
+ * An inherited config item
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_INHERITANCE_H
+#define _CONFIG_INHERITANCE_H
+
+#include <stdint.h>
+
+/**
+ * struct Inheritance - An inherited config item
+ */
+struct Inheritance
+{
+  struct HashElem *parent; /**< HashElem of parent config item */
+  const char *name;        /**< Name of this config item */
+  struct Account *ac;      /**< Account holding this config item */
+  intptr_t var;            /**< (Pointer to) value, of config item */
+};
+
+#endif /* _CONFIG_INHERITANCE_H */
diff --git a/config/lib.h b/config/lib.h
new file mode 100644 (file)
index 0000000..56052fd
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * @file
+ * Convenience wrapper for the config headers
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 Flexible handling of config items
+ *
+ * User configurable variables.
+ *
+ * -# @subpage config-set
+ * -# @subpage config-dump
+ * -# @subpage config-address
+ * -# @subpage config-bool
+ * -# @subpage config-magic
+ * -# @subpage config-mbtable
+ * -# @subpage config-number
+ * -# @subpage config-path
+ * -# @subpage config-quad
+ * -# @subpage config-regex
+ * -# @subpage config-sort
+ * -# @subpage config-string
+ */
+
+#ifndef _CONFIG_CONFIG_H
+#define _CONFIG_CONFIG_H
+
+#include "address.h"
+#include "bool.h"
+#include "command.h"
+#include "dump.h"
+#include "inheritance.h"
+#include "long.h"
+#include "magic.h"
+#include "mbtable.h"
+#include "number.h"
+#include "path.h"
+#include "quad.h"
+#include "regex2.h"
+#include "set.h"
+#include "sort.h"
+#include "string3.h"
+#include "types.h"
+
+#endif /* _CONFIG_CONFIG_H */
diff --git a/config/long.c b/config/long.c
new file mode 100644 (file)
index 0000000..73e7e39
--- /dev/null
@@ -0,0 +1,228 @@
+/**
+ * @file
+ * Type representing a long
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-long Type: Long
+ *
+ * Type representing a long.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/string2.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * long_string_set - Set a Long 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 long_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 */
+
+  long num = 0;
+  if (!value || !value[0] || (mutt_str_atol(value, &num) < 0))
+  {
+    mutt_buffer_printf(err, "Invalid long: %s", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if ((num < LONG_MIN) || (num > LONG_MAX))
+  {
+    mutt_buffer_printf(err, "Long is too big: %s", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if ((num < 0) && (cdef->type & DT_NOT_NEGATIVE))
+  {
+    mutt_buffer_printf(err, "Option %s may not be negative", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  if (var)
+  {
+    if (num == (*(short *) var))
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      int rc = cdef->validator(cs, cdef, (intptr_t) num, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    *(short *) var = num;
+  }
+  else
+  {
+    cdef->initial = num;
+  }
+
+  return CSR_SUCCESS;
+}
+
+/**
+ * long_string_get - Get a Long 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 long_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 */
+
+  int value;
+
+  if (var)
+    value = *(short *) var;
+  else
+    value = (int) cdef->initial;
+
+  mutt_buffer_printf(result, "%d", value);
+  return CSR_SUCCESS;
+}
+
+/**
+ * long_native_set - Set a Long config item by int
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Long
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int long_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 */
+
+  if ((value < LONG_MIN) || (value > LONG_MAX))
+  {
+    mutt_buffer_printf(err, "Invalid long: %ld", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if ((value < 0) && (cdef->type & DT_NOT_NEGATIVE))
+  {
+    mutt_buffer_printf(err, "Option %s may not be negative", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  if (value == (*(short *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(short *) var = value;
+  return CSR_SUCCESS;
+}
+
+/**
+ * long_native_get - Get an int from a Long config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Long
+ */
+static intptr_t long_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 */
+
+  return *(short *) var;
+}
+
+/**
+ * long_reset - Reset a Long 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 long_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 */
+
+  if (cdef->initial == (*(short *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(short *) var = cdef->initial;
+  return CSR_SUCCESS;
+}
+
+/**
+ * long_init - Register the Long config type
+ * @param cs Config items
+ */
+void long_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_long = {
+    "long",
+    long_string_set,
+    long_string_get,
+    long_native_set,
+    long_native_get,
+    long_reset,
+    NULL,
+  };
+  cs_register_type(cs, DT_LONG, &cst_long);
+}
diff --git a/config/long.h b/config/long.h
new file mode 100644 (file)
index 0000000..95ab884
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * @file
+ * Type representing a long
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_LONG_H
+#define _CONFIG_LONG_H
+
+struct ConfigSet;
+
+void long_init(struct ConfigSet *cs);
+
+#endif /* _CONFIG_LONG_H */
diff --git a/config/magic.c b/config/magic.c
new file mode 100644 (file)
index 0000000..9947c7d
--- /dev/null
@@ -0,0 +1,234 @@
+/**
+ * @file
+ * Type representing a mailbox
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-magic Type: Mailbox types
+ *
+ * Type representing a mailbox.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/logging.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * magic_values - Valid strings for mailbox types
+ */
+const char *magic_values[] = {
+  NULL, "mbox", "MMDF", "MH", "Maildir", NULL,
+};
+
+/**
+ * magic_string_set - Set a Mailbox Magic 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 magic_string_set(const struct ConfigSet *cs, void *var, struct ConfigDef *cdef,
+                            const char *value, struct Buffer *err)
+{
+  if (!cs || !cdef || !value)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  int num = -1;
+  for (size_t i = 1; magic_values[i]; i++)
+  {
+    if (mutt_str_strcasecmp(magic_values[i], value) == 0)
+    {
+      num = i;
+      break;
+    }
+  }
+
+  if (num < 1)
+  {
+    mutt_buffer_printf(err, "Invalid magic value: %s", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if (var)
+  {
+    if (num == (*(short *) var))
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      int rc = cdef->validator(cs, cdef, (intptr_t) num, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    *(short *) var = num;
+  }
+  else
+  {
+    cdef->initial = num;
+  }
+
+  return CSR_SUCCESS;
+}
+
+/**
+ * magic_string_get - Get a Mailbox Magic 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 magic_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 */
+
+  unsigned int value;
+
+  if (var)
+    value = *(short *) var;
+  else
+    value = (int) cdef->initial;
+
+  if ((value < 1) || (value >= (mutt_array_size(magic_values) - 1)))
+  {
+    mutt_debug(1, "Variable has an invalid value: %d\n", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  mutt_buffer_addstr(result, magic_values[value]);
+  return CSR_SUCCESS;
+}
+
+/**
+ * magic_native_set - Set a Mailbox Magic config item by int
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Mailbox magic
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int magic_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 */
+
+  if ((value < 1) || (value >= (mutt_array_size(magic_values) - 1)))
+  {
+    mutt_buffer_printf(err, "Invalid magic value: %ld", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if (value == (*(short *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(short *) var = value;
+  return CSR_SUCCESS;
+}
+
+/**
+ * magic_native_get - Get an int from a Mailbox Magic 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 magic
+ */
+static intptr_t magic_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 */
+
+  return *(short *) var;
+}
+
+/**
+ * magic_reset - Reset a Mailbox Magic 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 magic_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 */
+
+  if (cdef->initial == (*(short *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(short *) var = cdef->initial;
+  return CSR_SUCCESS;
+}
+
+/**
+ * magic_init - Register the Mailbox Magic config type
+ * @param cs Config items
+ */
+void magic_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_magic = {
+    "magic",
+    magic_string_set,
+    magic_string_get,
+    magic_native_set,
+    magic_native_get,
+    magic_reset,
+    NULL,
+  };
+  cs_register_type(cs, DT_MAGIC, &cst_magic);
+}
diff --git a/config/magic.h b/config/magic.h
new file mode 100644 (file)
index 0000000..449ff6b
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * @file
+ * Type representing a mailbox
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_MAGIC_H
+#define _CONFIG_MAGIC_H
+
+struct ConfigSet;
+
+extern const char *magic_values[];
+
+/**
+ * enum MailboxTypes - Supported mailbox formats
+ */
+enum MailboxTypes
+{
+  MUTT_MBOX = 1,
+  MUTT_MMDF,
+  MUTT_MH,
+  MUTT_MAILDIR,
+  MUTT_NNTP,
+  MUTT_IMAP,
+  MUTT_NOTMUCH,
+  MUTT_POP,
+  MUTT_COMPRESSED,
+};
+
+void magic_init(struct ConfigSet *cs);
+
+#endif /* _CONFIG_MAGIC_H */
diff --git a/config/mbtable.c b/config/mbtable.c
new file mode 100644 (file)
index 0000000..aa4f41c
--- /dev/null
@@ -0,0 +1,352 @@
+/**
+ * @file
+ * Type representing a multibyte character table
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-mbtable Type: Multi-byte character table
+ *
+ * Type representing a multibyte character table.
+ */
+
+#include "config.h"
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+#include <wchar.h>
+#include "mutt/buffer.h"
+#include "mutt/logging.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "config/mbtable.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * mbtable_parse - Parse a multibyte string into a table
+ * @param s String of multibyte characters
+ * @retval ptr New MbTable object
+ */
+struct MbTable *mbtable_parse(const char *s)
+{
+  struct MbTable *t = NULL;
+  size_t slen, k;
+  mbstate_t mbstate;
+  char *d = NULL;
+
+  slen = mutt_str_strlen(s);
+  if (!slen)
+    return NULL;
+
+  t = mutt_mem_calloc(1, sizeof(struct MbTable));
+
+  t->orig_str = mutt_str_strdup(s);
+  /* This could be more space efficient.  However, being used on tiny
+   * strings (Tochars and StatusChars), the overhead is not great. */
+  t->chars = mutt_mem_calloc(slen, sizeof(char *));
+  d = t->segmented_str = mutt_mem_calloc(slen * 2, sizeof(char));
+
+  memset(&mbstate, 0, sizeof(mbstate));
+  while (slen && (k = mbrtowc(NULL, s, slen, &mbstate)))
+  {
+    if (k == (size_t)(-1) || k == (size_t)(-2))
+    {
+      /* XXX put message in err buffer; fail? warning? */
+      mutt_debug(1, "mbtable_parse: mbrtowc returned %d converting %s in %s\n",
+                 (k == (size_t)(-1)) ? -1 : -2, s, t->orig_str);
+      if (k == (size_t)(-1))
+        memset(&mbstate, 0, sizeof(mbstate));
+      k = (k == (size_t)(-1)) ? 1 : slen;
+    }
+
+    slen -= k;
+    t->chars[t->len++] = d;
+    while (k--)
+      *d++ = *s++;
+    *d++ = '\0';
+  }
+
+  return t;
+}
+
+/**
+ * mbtable_destroy - Destroy an MbTable object
+ * @param cs   Config items
+ * @param var  Variable to destroy
+ * @param cdef Variable definition
+ */
+static void mbtable_destroy(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef)
+{
+  if (!cs || !var || !cdef)
+    return; /* LCOV_EXCL_LINE */
+
+  struct MbTable **m = (struct MbTable **) var;
+  if (!*m)
+    return;
+
+  mbtable_free(m);
+}
+
+/**
+ * mbtable_string_set - Set a MbTable 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 mbtable_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 */
+
+  if (value && (value[0] == '\0'))
+    value = NULL;
+
+  struct MbTable *table = NULL;
+
+  int rc = CSR_SUCCESS;
+
+  if (var)
+  {
+    struct MbTable *curval = *(struct MbTable **) var;
+    if (curval && (mutt_str_strcmp(value, curval->orig_str) == 0))
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    table = mbtable_parse(value);
+
+    if (cdef->validator)
+    {
+      rc = cdef->validator(cs, cdef, (intptr_t) table, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+      {
+        mbtable_free(&table);
+        return (rc | CSR_INV_VALIDATOR);
+      }
+    }
+
+    mbtable_destroy(cs, var, cdef);
+
+    *(struct MbTable **) var = table;
+
+    if (!table)
+      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 rc;
+}
+
+/**
+ * mbtable_string_get - Get a MbTable 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 mbtable_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 */
+
+  const char *str = NULL;
+
+  if (var)
+  {
+    struct MbTable *table = *(struct MbTable **) var;
+    if (!table || !table->orig_str)
+      return (CSR_SUCCESS | CSR_SUC_EMPTY); /* empty string */
+    str = table->orig_str;
+  }
+  else
+  {
+    str = (char *) cdef->initial;
+  }
+
+  mutt_buffer_addstr(result, str);
+  return CSR_SUCCESS;
+}
+
+/**
+ * mbtable_dup - Create a copy of an MbTable object
+ * @param table MbTable to duplicate
+ * @retval ptr New MbTable object
+ */
+static struct MbTable *mbtable_dup(struct MbTable *table)
+{
+  if (!table)
+    return NULL; /* LCOV_EXCL_LINE */
+
+  struct MbTable *m = mutt_mem_calloc(1, sizeof(*m));
+  m->orig_str = mutt_str_strdup(table->orig_str);
+  return m;
+}
+
+/**
+ * mbtable_native_set - Set a MbTable config item by MbTable object
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value MbTable pointer
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int mbtable_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);
+  }
+
+  mbtable_free(var);
+
+  struct MbTable *table = mbtable_dup((struct MbTable *) value);
+
+  rc = CSR_SUCCESS;
+  if (!table)
+    rc |= CSR_SUC_EMPTY;
+
+  *(struct MbTable **) var = table;
+  return rc;
+}
+
+/**
+ * mbtable_native_get - Get an MbTable object from a MbTable config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t MbTable pointer
+ */
+static intptr_t mbtable_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 MbTable *table = *(struct MbTable **) var;
+
+  return (intptr_t) table;
+}
+
+/**
+ * mbtable_reset - Reset an MbTable 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 mbtable_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 MbTable *table = NULL;
+  const char *initial = (const char *) cdef->initial;
+
+  struct MbTable *curtable = *(struct MbTable **) var;
+  const char *curval = curtable ? curtable->orig_str : NULL;
+
+  int rc = CSR_SUCCESS;
+  if (!curtable)
+    rc |= CSR_SUC_EMPTY;
+
+  if (mutt_str_strcmp(initial, curval) == 0)
+    return (rc | CSR_SUC_NO_CHANGE);
+
+  if (initial)
+    table = mbtable_parse(initial);
+
+  if (cdef->validator)
+  {
+    rc = cdef->validator(cs, cdef, (intptr_t) table, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+    {
+      mbtable_destroy(cs, &table, cdef);
+      return (rc | CSR_INV_VALIDATOR);
+    }
+  }
+
+  if (!table)
+    rc |= CSR_SUC_EMPTY;
+
+  mbtable_destroy(cs, var, cdef);
+
+  *(struct MbTable **) var = table;
+  return rc;
+}
+
+/**
+ * mbtable_init - Register the MbTable config type
+ * @param cs Config items
+ */
+void mbtable_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_mbtable = {
+    "mbtable",          mbtable_string_set, mbtable_string_get,
+    mbtable_native_set, mbtable_native_get, mbtable_reset,
+    mbtable_destroy,
+  };
+  cs_register_type(cs, DT_MBTABLE, &cst_mbtable);
+}
+
+/**
+ * mbtable_free - Free an MbTable object
+ * @param table MbTable to free
+ */
+void mbtable_free(struct MbTable **table)
+{
+  if (!table || !*table)
+    return; /* LCOV_EXCL_LINE */
+
+  FREE(&(*table)->orig_str);
+  FREE(&(*table)->chars);
+  FREE(&(*table)->segmented_str);
+  FREE(table);
+}
diff --git a/config/mbtable.h b/config/mbtable.h
new file mode 100644 (file)
index 0000000..8af5877
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * @file
+ * Type representing a multibyte character table
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_MBTABLE_H
+#define _CONFIG_MBTABLE_H
+
+struct ConfigSet;
+
+/**
+ * struct MbTable - multibyte character table
+ *
+ * Allows for direct access to the individual multibyte characters in a
+ * string.  This is used for the Flagchars, Fromchars, StatusChars and Tochars
+ * option types.
+ */
+struct MbTable
+{
+  char *orig_str;      /**< Original string used to generate this object */
+  int len;             /**< Number of characters */
+  char **chars;        /**< The array of multibyte character strings */
+  char *segmented_str; /**< Each chars entry points inside this string */
+};
+
+void mbtable_init(struct ConfigSet *cs);
+struct MbTable *mbtable_parse(const char *str);
+void mbtable_free(struct MbTable **table);
+
+#endif /* _CONFIG_MBTABLE_H */
diff --git a/config/number.c b/config/number.c
new file mode 100644 (file)
index 0000000..227def2
--- /dev/null
@@ -0,0 +1,229 @@
+/**
+ * @file
+ * Type representing a number
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-number Type: Number
+ *
+ * Type representing a number.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/string2.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * number_string_set - Set a Number 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 number_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 */
+
+  int num = 0;
+  if (!value || !value[0] || (mutt_str_atoi(value, &num) < 0))
+  {
+    mutt_buffer_printf(err, "Invalid number: %s", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if ((num < SHRT_MIN) || (num > SHRT_MAX))
+  {
+    mutt_buffer_printf(err, "Number is too big: %s", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if ((num < 0) && (cdef->type & DT_NOT_NEGATIVE))
+  {
+    mutt_buffer_printf(err, "Option %s may not be negative", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  if (var)
+  {
+    if (num == (*(short *) var))
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      int rc = cdef->validator(cs, cdef, (intptr_t) num, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    *(short *) var = num;
+  }
+  else
+  {
+    cdef->initial = num;
+  }
+
+  return CSR_SUCCESS;
+}
+
+/**
+ * number_string_get - Get a Number 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 number_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 */
+
+  int value;
+
+  if (var)
+    value = *(short *) var;
+  else
+    value = (int) cdef->initial;
+
+  mutt_buffer_printf(result, "%d", value);
+  return CSR_SUCCESS;
+}
+
+/**
+ * number_native_set - Set a Number config item by int
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Number
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int number_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 */
+
+  if ((value < SHRT_MIN) || (value > SHRT_MAX))
+  {
+    mutt_buffer_printf(err, "Invalid number: %ld", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if ((value < 0) && (cdef->type & DT_NOT_NEGATIVE))
+  {
+    mutt_buffer_printf(err, "Option %s may not be negative", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  if (value == (*(short *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(short *) var = value;
+  return CSR_SUCCESS;
+}
+
+/**
+ * number_native_get - Get an int from a Number config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Number
+ */
+static intptr_t number_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 */
+
+  return *(short *) var;
+}
+
+/**
+ * number_reset - Reset a Number 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 number_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 */
+
+  if (cdef->initial == (*(short *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(short *) var = cdef->initial;
+  return CSR_SUCCESS;
+}
+
+/**
+ * number_init - Register the Number config type
+ * @param cs Config items
+ */
+void number_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_number = {
+    "number",
+    number_string_set,
+    number_string_get,
+    number_native_set,
+    number_native_get,
+    number_reset,
+    NULL,
+  };
+  cs_register_type(cs, DT_NUMBER, &cst_number);
+}
diff --git a/config/number.h b/config/number.h
new file mode 100644 (file)
index 0000000..9e5378a
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * @file
+ * Type representing a number
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_NUMBER_H
+#define _CONFIG_NUMBER_H
+
+struct ConfigSet;
+
+void number_init(struct ConfigSet *cs);
+
+#endif /* _CONFIG_NUMBER_H */
diff --git a/config/path.c b/config/path.c
new file mode 100644 (file)
index 0000000..66116f8
--- /dev/null
@@ -0,0 +1,281 @@
+/**
+ * @file
+ * Type representing a path
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-path Type: Path
+ *
+ * Type representing a path.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * path_destroy - Destroy a Path
+ * @param cs   Config items
+ * @param var  Variable to destroy
+ * @param cdef Variable definition
+ */
+static void path_destroy(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef)
+{
+  if (!cs || !var || !cdef)
+    return; /* LCOV_EXCL_LINE */
+
+  const char **str = (const char **) var;
+  if (!*str)
+    return;
+
+  /* Don't free strings from the var definition */
+  if (*(char **) var == (char *) cdef->initial)
+  {
+    *(char **) var = NULL;
+    return;
+  }
+
+  FREE(var);
+}
+
+/**
+ * path_string_set - Set a Path 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 path_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;
+
+  if (!value && (cdef->type & DT_NOT_EMPTY))
+  {
+    mutt_buffer_printf(err, "Option %s may not be empty", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  int rc = CSR_SUCCESS;
+
+  if (var)
+  {
+    if (mutt_str_strcmp(value, (*(char **) var)) == 0)
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      rc = cdef->validator(cs, cdef, (intptr_t) value, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    path_destroy(cs, var, cdef);
+
+    const char *str = mutt_str_strdup(value);
+    if (!str)
+      rc |= CSR_SUC_EMPTY;
+
+    *(const char **) var = str;
+  }
+  else
+  {
+    /* we're already using the initial value */
+    if (*(char **) cdef->var == (char *) cdef->initial)
+      *(char **) cdef->var = mutt_str_strdup((char *) cdef->initial);
+
+    if (cdef->type & DT_INITIAL_SET)
+      FREE(&cdef->initial);
+
+    cdef->type |= DT_INITIAL_SET;
+    cdef->initial = IP mutt_str_strdup(value);
+  }
+
+  return rc;
+}
+
+/**
+ * path_string_get - Get a Path 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 path_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 */
+
+  const char *str = NULL;
+
+  if (var)
+    str = *(const char **) var;
+  else
+    str = (char *) cdef->initial;
+
+  if (!str)
+    return (CSR_SUCCESS | CSR_SUC_EMPTY); /* empty string */
+
+  mutt_buffer_addstr(result, str);
+  return CSR_SUCCESS;
+}
+
+/**
+ * path_native_set - Set a Path config item by string
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Native pointer/value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int path_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 */
+
+  const char *str = (const char *) value;
+
+  /* Store empty strings as NULL */
+  if (str && (str[0] == '\0'))
+    value = 0;
+
+  if ((value == 0) && (cdef->type & DT_NOT_EMPTY))
+  {
+    mutt_buffer_printf(err, "Option %s may not be empty", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  if (mutt_str_strcmp((const char *) value, (*(char **) var)) == 0)
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  int rc;
+
+  if (cdef->validator)
+  {
+    rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  path_destroy(cs, var, cdef);
+
+  str = mutt_str_strdup(str);
+  rc = CSR_SUCCESS;
+  if (!str)
+    rc |= CSR_SUC_EMPTY;
+
+  *(const char **) var = str;
+  return rc;
+}
+
+/**
+ * path_native_get - Get a string from a Path config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Path string
+ */
+static intptr_t path_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 */
+
+  const char *str = *(const char **) var;
+
+  return (intptr_t) str;
+}
+
+/**
+ * path_reset - Reset a Path 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 path_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 */
+
+  int rc = CSR_SUCCESS;
+
+  const char *path = (const char *) cdef->initial;
+  if (!path)
+    rc |= CSR_SUC_EMPTY;
+
+  if (mutt_str_strcmp(path, (*(char **) var)) == 0)
+    return (rc | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  path_destroy(cs, var, cdef);
+
+  if (!path)
+    rc |= CSR_SUC_EMPTY;
+
+  *(const char **) var = path;
+  return rc;
+}
+
+/**
+ * path_init - Register the Path config type
+ * @param cs Config items
+ */
+void path_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_path = {
+    "path",          path_string_set, path_string_get, path_native_set,
+    path_native_get, path_reset,      path_destroy,
+  };
+  cs_register_type(cs, DT_PATH, &cst_path);
+}
diff --git a/config/path.h b/config/path.h
new file mode 100644 (file)
index 0000000..a82ddff
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * @file
+ * Type representing a path
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_PATH_H
+#define _CONFIG_PATH_H
+
+struct ConfigSet;
+
+void path_init(struct ConfigSet *cs);
+
+#endif /* _CONFIG_PATH_H */
diff --git a/config/quad.c b/config/quad.c
new file mode 100644 (file)
index 0000000..be5f011
--- /dev/null
@@ -0,0 +1,282 @@
+/**
+ * @file
+ * Type representing a quad-option
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-quad Type: Quad-option
+ *
+ * Type representing a quad-option.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/hash.h"
+#include "mutt/logging.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * quad_values - Valid strings for creating a QuadValue
+ *
+ * These strings are case-insensitive.
+ */
+const char *quad_values[] = {
+  "no", "yes", "ask-no", "ask-yes", NULL,
+};
+
+/**
+ * quad_string_set - Set a Quad-option 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 quad_string_set(const struct ConfigSet *cs, void *var, struct ConfigDef *cdef,
+                           const char *value, struct Buffer *err)
+{
+  if (!cs || !cdef || !value)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  int num = -1;
+  for (size_t i = 0; quad_values[i]; i++)
+  {
+    if (mutt_str_strcasecmp(quad_values[i], value) == 0)
+    {
+      num = i;
+      break;
+    }
+  }
+
+  if (num < 0)
+  {
+    mutt_buffer_printf(err, "Invalid quad value: %s", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if (var)
+  {
+    if (num == (*(char *) var))
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      int rc = cdef->validator(cs, cdef, (intptr_t) num, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    *(char *) var = num;
+  }
+  else
+  {
+    cdef->initial = num;
+  }
+
+  return CSR_SUCCESS;
+}
+
+/**
+ * quad_string_get - Get a Quad-option 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 quad_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 */
+
+  unsigned int value;
+
+  if (var)
+    value = *(char *) var;
+  else
+    value = (int) cdef->initial;
+
+  if (value >= (mutt_array_size(quad_values) - 1))
+  {
+    mutt_debug(1, "Variable has an invalid value: %d\n", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  mutt_buffer_addstr(result, quad_values[value]);
+  return CSR_SUCCESS;
+}
+
+/**
+ * quad_native_set - Set a Quad-option config item by int
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Quad-option value
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int quad_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 */
+
+  if ((value < 0) || (value >= (mutt_array_size(quad_values) - 1)))
+  {
+    mutt_buffer_printf(err, "Invalid quad value: %ld", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if (value == (*(char *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(char *) var = value;
+  return CSR_SUCCESS;
+}
+
+/**
+ * quad_native_get - Get an int object from a Quad-option config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Quad-option value
+ */
+static intptr_t quad_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 */
+
+  return *(char *) var;
+}
+
+/**
+ * quad_reset - Reset a Quad-option 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 quad_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 */
+
+  if (cdef->initial == (*(char *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(char *) var = cdef->initial;
+  return CSR_SUCCESS;
+}
+
+/**
+ * quad_init - Register the Quad-option config type
+ * @param cs Config items
+ */
+void quad_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_quad = {
+    "quad",
+    quad_string_set,
+    quad_string_get,
+    quad_native_set,
+    quad_native_get,
+    quad_reset,
+    NULL,
+  };
+  cs_register_type(cs, DT_QUAD, &cst_quad);
+}
+
+/**
+ * quad_toggle - Toggle (invert) the value of a quad option
+ *
+ * By toggling the low bit, the following are swapped:
+ * - #MUTT_NO    <--> #MUTT_YES
+ * - #MUTT_ASKNO <--> #MUTT_ASKYES
+ */
+static int quad_toggle(int opt)
+{
+  return opt ^= 1;
+}
+
+/**
+ * quad_he_toggle - Toggle the value of a quad
+ * @param cs  Config items
+ * @param he  HashElem representing config item
+ * @param err Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ *
+ * @sa quad_toggle()
+ */
+int quad_he_toggle(struct ConfigSet *cs, struct HashElem *he, struct Buffer *err)
+{
+  if (!cs || !he || !he->data)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  if (DTYPE(he->type) != DT_QUAD)
+    return CSR_ERR_CODE;
+
+  const struct ConfigDef *cdef = he->data;
+  char *var = cdef->var;
+
+  char value = *var;
+  if ((value < 0) || (value >= (mutt_array_size(quad_values) - 1)))
+  {
+    mutt_buffer_printf(err, "Invalid quad value: %ld", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  *(char *) var = quad_toggle(value);
+
+  cs_notify_listeners(cs, he, he->key.strkey, CE_SET);
+  return CSR_SUCCESS;
+}
diff --git a/config/quad.h b/config/quad.h
new file mode 100644 (file)
index 0000000..61c4374
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * @file
+ * Type representing a quad-option
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_QUAD_H
+#define _CONFIG_QUAD_H
+
+struct Buffer;
+struct ConfigSet;
+struct HashElem;
+
+extern const char *quad_values[];
+
+/**
+ * enum QuadOption - Possible values for a quad-option
+ */
+enum QuadOption
+{
+  MUTT_ABORT = -1,
+  MUTT_NO,
+  MUTT_YES,
+  MUTT_ASKNO,
+  MUTT_ASKYES
+};
+
+void quad_init(struct ConfigSet *cs);
+int quad_he_toggle(struct ConfigSet *cs, struct HashElem *he, struct Buffer *err);
+
+#endif /* _CONFIG_QUAD_H */
diff --git a/config/regex.c b/config/regex.c
new file mode 100644 (file)
index 0000000..4111083
--- /dev/null
@@ -0,0 +1,359 @@
+/**
+ * @file
+ * Type representing a regular expression
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-regex Type: Regular expression
+ *
+ * Type representing a regular expression.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/mbyte.h"
+#include "mutt/memory.h"
+#include "mutt/regex3.h"
+#include "mutt/string2.h"
+#include "regex2.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * regex_destroy - Destroy a Regex object
+ * @param cs   Config items
+ * @param var  Variable to destroy
+ * @param cdef Variable definition
+ */
+static void regex_destroy(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef)
+{
+  if (!cs || !var || !cdef)
+    return; /* LCOV_EXCL_LINE */
+
+  struct Regex **r = (struct Regex **) var;
+  if (!*r)
+    return;
+
+  regex_free(r);
+}
+
+/**
+ * regex_string_set - Set a Regex 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 regex_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 Regex *r = NULL;
+
+  int rc = CSR_SUCCESS;
+
+  if (var)
+  {
+    struct Regex *curval = *(struct Regex **) var;
+    if (curval && (mutt_str_strcmp(value, curval->pattern) == 0))
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (value)
+    {
+      r = regex_create(value, cdef->type, err);
+      if (!r)
+        return CSR_ERR_INVALID;
+    }
+
+    if (cdef->validator)
+    {
+      rc = cdef->validator(cs, cdef, (intptr_t) r, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+      {
+        regex_free(&r);
+        return (rc | CSR_INV_VALIDATOR);
+      }
+    }
+
+    regex_destroy(cs, var, cdef);
+
+    *(struct Regex **) var = r;
+
+    if (!r)
+      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 rc;
+}
+
+/**
+ * regex_string_get - Get a Regex 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 regex_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 */
+
+  const char *str = NULL;
+
+  if (var)
+  {
+    struct Regex *r = *(struct Regex **) var;
+    if (r)
+      str = r->pattern;
+  }
+  else
+  {
+    str = (char *) cdef->initial;
+  }
+
+  if (!str)
+    return (CSR_SUCCESS | CSR_SUC_EMPTY); /* empty string */
+
+  mutt_buffer_addstr(result, str);
+  return CSR_SUCCESS;
+}
+
+/**
+ * regex_native_set - Set a Regex config item by Regex object
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Regex pointer
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int regex_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);
+  }
+
+  rc = CSR_SUCCESS;
+  struct Regex *orig = (struct Regex *) value;
+  struct Regex *r = NULL;
+
+  if (orig && orig->pattern)
+  {
+    r = regex_create(orig->pattern, cdef->flags, err);
+    if (!r)
+      rc = CSR_ERR_INVALID;
+  }
+  else
+  {
+    rc |= CSR_SUC_EMPTY;
+  }
+
+  if (CSR_RESULT(rc) == CSR_SUCCESS)
+  {
+    regex_free(var);
+    *(struct Regex **) var = r;
+  }
+
+  return rc;
+}
+
+/**
+ * regex_native_get - Get a Regex object from a Regex config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Regex pointer
+ */
+static intptr_t regex_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 Regex *r = *(struct Regex **) var;
+
+  return (intptr_t) r;
+}
+
+/**
+ * regex_reset - Reset a Regex 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 regex_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 Regex *r = NULL;
+  const char *initial = (const char *) cdef->initial;
+
+  struct Regex *currx = *(struct Regex **) var;
+  const char *curval = currx ? currx->pattern : NULL;
+
+  int rc = CSR_SUCCESS;
+  if (!currx)
+    rc |= CSR_SUC_EMPTY;
+
+  if (mutt_str_strcmp(initial, curval) == 0)
+    return (rc | CSR_SUC_NO_CHANGE);
+
+  if (initial)
+  {
+    r = regex_create(initial, cdef->flags, err);
+    if (!r)
+      return CSR_ERR_CODE;
+  }
+
+  if (cdef->validator)
+  {
+    rc = cdef->validator(cs, cdef, (intptr_t) r, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+    {
+      regex_destroy(cs, &r, cdef);
+      return (rc | CSR_INV_VALIDATOR);
+    }
+  }
+
+  if (!r)
+    rc |= CSR_SUC_EMPTY;
+
+  regex_destroy(cs, var, cdef);
+
+  *(struct Regex **) var = r;
+  return rc;
+}
+
+/**
+ * regex_init - Register the Regex config type
+ * @param cs Config items
+ */
+void regex_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_regex = {
+    "regex",          regex_string_set, regex_string_get, regex_native_set,
+    regex_native_get, regex_reset,      regex_destroy,
+  };
+  cs_register_type(cs, DT_REGEX, &cst_regex);
+}
+
+/**
+ * regex_create - Create an Regex from a string
+ * @param str   Regular expression
+ * @param flags Type flags, e.g. #DT_REGEX_MATCH_CASE
+ * @param err   Buffer for error messages
+ * @retval ptr New Regex object
+ * @retval NULL Error
+ */
+struct Regex *regex_create(const char *str, int flags, struct Buffer *err)
+{
+  if (!str)
+    return NULL; /* LCOV_EXCL_LINE */
+
+  int rflags = 0;
+  struct Regex *reg = mutt_mem_calloc(1, sizeof(struct Regex));
+
+  reg->regex = mutt_mem_calloc(1, sizeof(regex_t));
+  reg->pattern = mutt_str_strdup(str);
+
+  /* Should we use smart case matching? */
+  if (((flags & DT_REGEX_MATCH_CASE) == 0) && mutt_mb_is_lower(str))
+    rflags |= REG_ICASE;
+
+  if ((flags & DT_REGEX_NOSUB))
+    rflags |= REG_NOSUB;
+
+  /* Is a prefix of '!' allowed? */
+  if (((flags & DT_REGEX_ALLOW_NOT) != 0) && (str[0] == '!'))
+  {
+    reg->not = true;
+    str++;
+  }
+
+  int rc = REGCOMP(reg->regex, str, rflags);
+  if ((rc != 0) && err)
+  {
+    regerror(rc, reg->regex, err->data, err->dsize);
+    regex_free(&reg);
+    return NULL;
+  }
+
+  return reg;
+}
+
+/**
+ * regex_free - Free a Regex object
+ * @param r Regex to free
+ */
+void regex_free(struct Regex **r)
+{
+  if (!r || !*r)
+    return; /* LCOV_EXCL_LINE */
+
+  FREE(&(*r)->pattern);
+  if ((*r)->regex)
+    regfree((*r)->regex);
+  FREE(&(*r)->regex);
+  FREE(r);
+}
diff --git a/config/regex2.h b/config/regex2.h
new file mode 100644 (file)
index 0000000..6b2d4c9
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * @file
+ * Type representing a regular expression
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_REGEX_H
+#define _CONFIG_REGEX_H
+
+#include <features.h>
+#include <regex.h>
+#include <stdbool.h>
+
+struct Buffer;
+struct ConfigSet;
+
+void regex_init(struct ConfigSet *cs);
+struct Regex *regex_create(const char *str, int flags, struct Buffer *err);
+void regex_free(struct Regex **regex);
+
+#endif /* _CONFIG_REGEX_H */
diff --git a/config/set.c b/config/set.c
new file mode 100644 (file)
index 0000000..8fdca90
--- /dev/null
@@ -0,0 +1,908 @@
+/**
+ * @file
+ * A collection of config items
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-set Config Set
+ *
+ * A collection of config items.
+ */
+
+#include "config.h"
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mutt/buffer.h"
+#include "mutt/hash.h"
+#include "mutt/logging.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "set.h"
+#include "inheritance.h"
+#include "types.h"
+
+struct ConfigSetType RegisteredTypes[18] = {
+  { NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+};
+
+/**
+ * destroy - Callback function for the Hash Table
+ * @param type Object type, e.g. #DT_STRING
+ * @param obj  Object to destroy
+ * @param data ConfigSet associated with the object
+ */
+static void destroy(int type, void *obj, intptr_t data)
+{
+  if (!obj || (data == 0))
+    return; /* LCOV_EXCL_LINE */
+
+  struct ConfigSet *cs = (struct ConfigSet *) data;
+
+  const struct ConfigSetType *cst = NULL;
+
+  if (type & DT_INHERITED)
+  {
+    struct Inheritance *i = obj;
+    FREE(&i->name);
+    FREE(&i);
+  }
+  else
+  {
+    struct ConfigDef *cdef = obj;
+
+    cst = cs_get_type_def(cs, type);
+    if (cst && cst->destroy)
+      cst->destroy(cs, cdef->var, cdef);
+
+    /* If we allocated the initial value, clean it up */
+    if (cdef->type & DT_INITIAL_SET)
+      FREE(&cdef->initial);
+  }
+}
+
+/**
+ * create_synonym - Create an alternative name for a config item
+ * @param cs   Config items
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval ptr New HashElem representing the config item synonym
+ */
+static struct HashElem *create_synonym(const struct ConfigSet *cs,
+                                       struct ConfigDef *cdef, struct Buffer *err)
+{
+  if (!cs || !cdef)
+    return NULL; /* LCOV_EXCL_LINE */
+
+  const char *name = (const char *) cdef->initial;
+  struct HashElem *parent = cs_get_elem(cs, name);
+  if (!parent)
+  {
+    mutt_buffer_printf(err, "No such variable: %s", name);
+    return NULL;
+  }
+
+  struct HashElem *child =
+      mutt_hash_typed_insert(cs->hash, cdef->name, cdef->type, (void *) cdef);
+  if (!child)
+    return NULL;
+
+  cdef->var = parent;
+  return child;
+}
+
+/**
+ * reg_one_var - Register one config item
+ * @param cs   Config items
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval ptr New HashElem representing the config item
+ */
+static struct HashElem *reg_one_var(const struct ConfigSet *cs,
+                                    struct ConfigDef *cdef, struct Buffer *err)
+{
+  if (!cs || !cdef)
+    return NULL; /* LCOV_EXCL_LINE */
+
+  if (cdef->type == DT_SYNONYM)
+    return create_synonym(cs, cdef, err);
+
+  const struct ConfigSetType *cst = cs_get_type_def(cs, cdef->type);
+  if (!cst)
+  {
+    mutt_buffer_printf(err, "Variable '%s' has an invalid type %d", cdef->name, cdef->type);
+    return NULL;
+  }
+
+  struct HashElem *he =
+      mutt_hash_typed_insert(cs->hash, cdef->name, cdef->type, (void *) cdef);
+  if (!he)
+    return NULL;
+
+  if (cst && cst->reset)
+    cst->reset(cs, cdef->var, cdef, err);
+
+  return he;
+}
+
+/**
+ * cs_create - Create a new Config Set
+ * @param size Number of expected config items
+ * @retval ptr New ConfigSet object
+ */
+struct ConfigSet *cs_create(size_t size)
+{
+  struct ConfigSet *cs = mutt_mem_malloc(sizeof(*cs));
+  cs_init(cs, size);
+  return cs;
+}
+
+/**
+ * cs_init - Initialise a Config Set
+ * @param cs   Config items
+ * @param size Number of expected config items
+ */
+void cs_init(struct ConfigSet *cs, size_t size)
+{
+  if (!cs)
+    return; /* LCOV_EXCL_LINE */
+
+  memset(cs, 0, sizeof(*cs));
+  cs->hash = mutt_hash_create(size, 0);
+  mutt_hash_set_destructor(cs->hash, destroy, (intptr_t) cs);
+}
+
+/**
+ * cs_free - Free a Config Set
+ * @param cs Config items
+ */
+void cs_free(struct ConfigSet **cs)
+{
+  if (!cs || !*cs)
+    return; /* LCOV_EXCL_LINE */
+
+  mutt_hash_destroy(&(*cs)->hash);
+  FREE(cs);
+}
+
+/**
+ * cs_get_elem - Get the HashElem representing a config item
+ * @param cs   Config items
+ * @param name Name of config item
+ * @retval ptr HashElem representing the config item
+ */
+struct HashElem *cs_get_elem(const struct ConfigSet *cs, const char *name)
+{
+  if (!cs || !name)
+    return NULL; /* LCOV_EXCL_LINE */
+
+  struct HashElem *he = mutt_hash_find_elem(cs->hash, name);
+  if (!he)
+    return NULL;
+
+  if (he->type != DT_SYNONYM)
+    return he;
+
+  const struct ConfigDef *cdef = he->data;
+
+  return cdef->var;
+}
+
+/**
+ * cs_get_type_def - Get the definition for a type
+ * @param cs   Config items
+ * @param type Type to lookup, e.g. #DT_NUMBER
+ * @retval ptr ConfigSetType representing the type
+ */
+const struct ConfigSetType *cs_get_type_def(const struct ConfigSet *cs, unsigned int type)
+{
+  if (!cs)
+    return NULL; /* LCOV_EXCL_LINE */
+
+  type = DTYPE(type);
+  if ((type < 1) || (type >= mutt_array_size(cs->types)))
+    return NULL;
+
+  if (!cs->types[type].name)
+    return NULL;
+
+  return &cs->types[type];
+}
+
+/**
+ * cs_register_type - Register a type of config item
+ * @param cs   Config items
+ * @param type Object type, e.g. #DT_BOOL
+ * @param cst  Structure defining the type
+ * @retval bool True, if type was registered successfully
+ */
+bool cs_register_type(struct ConfigSet *cs, unsigned int type, const struct ConfigSetType *cst)
+{
+  if (!cs || !cst)
+    return false; /* LCOV_EXCL_LINE */
+
+  if (!cst->name || !cst->string_set || !cst->string_get || !cst->reset ||
+      !cst->native_set || !cst->native_get)
+    return false;
+
+  if (type >= mutt_array_size(cs->types))
+    return false;
+
+  if (cs->types[type].name)
+    return false; /* already registered */
+
+  cs->types[type] = *cst;
+  return true;
+}
+
+/**
+ * cs_register_variables - Register a set of config items
+ * @param cs    Config items
+ * @param vars  Variable definition
+ * @param flags Flags, e.g. #CS_REG_DISABLED
+ * @retval bool True, if all variables were registered successfully
+ */
+bool cs_register_variables(const struct ConfigSet *cs, struct ConfigDef vars[], int flags)
+{
+  if (!cs || !vars)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct Buffer err;
+  mutt_buffer_init(&err);
+  err.data = calloc(1, STRING);
+  err.dsize = STRING;
+
+  bool rc = true;
+
+  for (size_t i = 0; vars[i].name; i++)
+  {
+    if (!reg_one_var(cs, &vars[i], &err))
+    {
+      mutt_debug(1, "%s\n", err.data);
+      rc = false;
+    }
+  }
+
+  FREE(&err.data);
+  return rc;
+}
+
+/**
+ * cs_inherit_variable - Create in inherited config item
+ * @param cs     Config items
+ * @param parent HashElem of parent config item
+ * @param name   Name of account-specific config item
+ * @retval ptr New HashElem representing the inherited config item
+ */
+struct HashElem *cs_inherit_variable(const struct ConfigSet *cs,
+                                     struct HashElem *parent, const char *name)
+{
+  if (!cs || !parent)
+    return NULL; /* LCOV_EXCL_LINE */
+
+  struct Buffer err;
+  mutt_buffer_init(&err);
+  err.data = calloc(1, STRING);
+  err.dsize = STRING;
+
+  struct Inheritance *i = mutt_mem_calloc(1, sizeof(*i));
+  i->parent = parent;
+  i->name = mutt_str_strdup(name);
+
+  struct HashElem *he = mutt_hash_typed_insert(cs->hash, i->name, DT_INHERITED, i);
+  if (!he)
+  {
+    FREE(&i->name);
+    FREE(&i);
+  }
+
+  FREE(&err.data);
+  return he;
+}
+
+/**
+ * cs_add_listener - Add a listener (callback function)
+ * @param cs Config items
+ * @param fn Listener callback function
+ */
+void cs_add_listener(struct ConfigSet *cs, cs_listener fn)
+{
+  if (!cs || !fn)
+    return; /* LCOV_EXCL_LINE */
+
+  for (size_t i = 0; i < mutt_array_size(cs->listeners); i++)
+  {
+    if (cs->listeners[i] == fn)
+    {
+      mutt_debug(1, "Listener was already registered\n");
+      return;
+    }
+  }
+
+  for (size_t i = 0; i < mutt_array_size(cs->listeners); i++)
+  {
+    if (!cs->listeners[i])
+    {
+      cs->listeners[i] = fn;
+      return;
+    }
+  }
+}
+
+/**
+ * cs_remove_listener - Remove a listener (callback function)
+ * @param cs Config items
+ * @param fn Listener callback function
+ */
+void cs_remove_listener(struct ConfigSet *cs, cs_listener fn)
+{
+  if (!cs || !fn)
+    return; /* LCOV_EXCL_LINE */
+
+  for (size_t i = 0; i < mutt_array_size(cs->listeners); i++)
+  {
+    if (cs->listeners[i] == fn)
+    {
+      cs->listeners[i] = NULL;
+      return;
+    }
+  }
+  mutt_debug(1, "Listener wasn't registered\n");
+}
+
+/**
+ * cs_notify_listeners - Notify all listeners of an event
+ * @param cs   Config items
+ * @param he   HashElem representing config item
+ * @param name Name of config item
+ * @param ev   Type of event
+ */
+void cs_notify_listeners(const struct ConfigSet *cs, struct HashElem *he,
+                         const char *name, enum ConfigEvent ev)
+{
+  if (!cs || !he || !name)
+    return; /* LCOV_EXCL_LINE */
+
+  for (size_t i = 0; i < mutt_array_size(cs->listeners); i++)
+  {
+    if (!cs->listeners[i])
+      return;
+
+    cs->listeners[i](cs, he, name, ev);
+  }
+}
+
+/**
+ * cs_he_reset - Reset a config item to its initial value
+ * @param cs   Config items
+ * @param he   HashElem representing config item
+ * @param err  Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_he_reset(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *err)
+{
+  if (!cs || !he)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  /* An inherited var that's already pointing to its parent.
+   * Return 'success', but don't send a notification. */
+  if ((he->type & DT_INHERITED) && (DTYPE(he->type) == 0))
+    return CSR_SUCCESS;
+
+  const struct ConfigSetType *cst = NULL;
+  const struct ConfigDef *cdef = NULL;
+
+  int rc = CSR_SUCCESS;
+
+  if (he->type & DT_INHERITED)
+  {
+    struct Inheritance *i = he->data;
+    cst = cs_get_type_def(cs, i->parent->type);
+    cdef = i->parent->data;
+
+    if (cst && cst->destroy)
+      cst->destroy(cs, (void **) &i->var, cdef);
+
+    he->type = DT_INHERITED;
+  }
+  else
+  {
+    cst = cs_get_type_def(cs, he->type);
+    cdef = he->data;
+
+    if (cst)
+      rc = cst->reset(cs, cdef->var, cdef, err);
+  }
+
+  if ((CSR_RESULT(rc) == CSR_SUCCESS) && !(rc & CSR_SUC_NO_CHANGE))
+    cs_notify_listeners(cs, he, he->key.strkey, CE_RESET);
+  return rc;
+}
+
+/**
+ * cs_str_reset - Reset a config item to its initial value
+ * @param cs   Config items
+ * @param name Name of config item
+ * @param err  Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_str_reset(const struct ConfigSet *cs, const char *name, struct Buffer *err)
+{
+  if (!cs || !name)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct HashElem *he = cs_get_elem(cs, name);
+  if (!he)
+  {
+    mutt_buffer_printf(err, "Unknown var '%s'", name);
+    return CSR_ERR_UNKNOWN;
+  }
+
+  return cs_he_reset(cs, he, err);
+}
+
+/**
+ * cs_he_initial_set - Set the initial value of a config item
+ * @param cs    Config items
+ * @param he    HashElem representing config item
+ * @param value Value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_he_initial_set(const struct ConfigSet *cs, struct HashElem *he,
+                      const char *value, struct Buffer *err)
+{
+  if (!cs || !he)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct ConfigDef *cdef = NULL;
+  const struct ConfigSetType *cst = NULL;
+
+  if (he->type & DT_INHERITED)
+  {
+    struct Inheritance *i = he->data;
+    cdef = i->parent->data;
+    mutt_debug(1, "Variable '%s' is inherited type.\n", cdef->name);
+    return CSR_ERR_CODE;
+  }
+
+  cdef = he->data;
+  cst = cs_get_type_def(cs, he->type);
+  if (!cst)
+  {
+    mutt_debug(1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type);
+    return CSR_ERR_CODE;
+  }
+
+  int rc = cst->string_set(cs, NULL, cdef, value, err);
+  if (CSR_RESULT(rc) != CSR_SUCCESS)
+    return rc;
+
+  cs_notify_listeners(cs, he, he->key.strkey, CE_INITIAL_SET);
+  return CSR_SUCCESS;
+}
+
+/**
+ * cs_str_initial_set - Set the initial value of a config item
+ * @param cs    Config items
+ * @param name  Name of config item
+ * @param value Value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_str_initial_set(const struct ConfigSet *cs, const char *name,
+                       const char *value, struct Buffer *err)
+{
+  if (!cs || !name)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct HashElem *he = cs_get_elem(cs, name);
+  if (!he)
+  {
+    mutt_buffer_printf(err, "Unknown var '%s'", name);
+    return CSR_ERR_UNKNOWN;
+  }
+
+  return cs_he_initial_set(cs, he, value, err);
+}
+
+/**
+ * cs_he_initial_get - Get the initial, or parent, value of a config item
+ * @param cs     Config items
+ * @param he     HashElem representing config item
+ * @param result Buffer for results or error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ *
+ * If a config item is inherited from another, then this will get the parent's
+ * value.  Otherwise, it will get the config item's initial value.
+ */
+int cs_he_initial_get(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *result)
+{
+  if (!cs || !he)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct Inheritance *i = NULL;
+  const struct ConfigDef *cdef = NULL;
+  const struct ConfigSetType *cst = NULL;
+
+  if (he->type & DT_INHERITED)
+  {
+    i = he->data;
+    cdef = i->parent->data;
+    cst = cs_get_type_def(cs, i->parent->type);
+  }
+  else
+  {
+    cdef = he->data;
+    cst = cs_get_type_def(cs, he->type);
+  }
+
+  if (!cst)
+  {
+    mutt_debug(1, "Variable '%s' has an invalid type %d\n", cdef->name, DTYPE(he->type));
+    return CSR_ERR_CODE;
+  }
+
+  return cst->string_get(cs, NULL, cdef, result);
+}
+
+/**
+ * cs_str_initial_get - Get the initial, or parent, value of a config item
+ * @param cs     Config items
+ * @param name   Name of config item
+ * @param result Buffer for results or error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ *
+ * If a config item is inherited from another, then this will get the parent's
+ * value.  Otherwise, it will get the config item's initial value.
+ */
+int cs_str_initial_get(const struct ConfigSet *cs, const char *name, struct Buffer *result)
+{
+  if (!cs || !name)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct HashElem *he = cs_get_elem(cs, name);
+  if (!he)
+  {
+    mutt_buffer_printf(result, "Unknown var '%s'", name);
+    return CSR_ERR_UNKNOWN;
+  }
+
+  return cs_he_initial_get(cs, he, result);
+}
+
+/**
+ * cs_he_string_set - Set a config item by string
+ * @param cs    Config items
+ * @param he    HashElem representing config item
+ * @param value Value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_he_string_set(const struct ConfigSet *cs, struct HashElem *he,
+                     const char *value, struct Buffer *err)
+{
+  if (!cs || !he)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct ConfigDef *cdef = NULL;
+  const struct ConfigSetType *cst = NULL;
+  void *var = NULL;
+
+  if (he->type & DT_INHERITED)
+  {
+    struct Inheritance *i = he->data;
+    cdef = i->parent->data;
+    var = &i->var;
+    cst = cs_get_type_def(cs, i->parent->type);
+  }
+  else
+  {
+    cdef = he->data;
+    var = cdef->var;
+    cst = cs_get_type_def(cs, he->type);
+  }
+
+  if (!cst)
+  {
+    mutt_debug(1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type);
+    return CSR_ERR_CODE;
+  }
+
+  if (!var)
+    return CSR_ERR_CODE;
+
+  int rc = cst->string_set(cs, var, cdef, value, err);
+  if (CSR_RESULT(rc) != CSR_SUCCESS)
+    return rc;
+
+  if (he->type & DT_INHERITED)
+  {
+    struct Inheritance *i = he->data;
+    he->type = i->parent->type | DT_INHERITED;
+  }
+  if (!(rc & CSR_SUC_NO_CHANGE))
+    cs_notify_listeners(cs, he, he->key.strkey, CE_SET);
+  return rc;
+}
+
+/**
+ * cs_str_string_set - Set a config item by string
+ * @param cs    Config items
+ * @param name  Name of config item
+ * @param value Value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_str_string_set(const struct ConfigSet *cs, const char *name,
+                      const char *value, struct Buffer *err)
+{
+  if (!cs || !name)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct HashElem *he = cs_get_elem(cs, name);
+  if (!he)
+  {
+    mutt_buffer_printf(err, "Unknown var '%s'", name);
+    return CSR_ERR_UNKNOWN;
+  }
+
+  return cs_he_string_set(cs, he, value, err);
+}
+
+/**
+ * cs_he_string_get - Get a config item as a string
+ * @param cs     Config items
+ * @param he     HashElem representing config item
+ * @param result Buffer for results or error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_he_string_get(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *result)
+{
+  if (!cs || !he)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct Inheritance *i = NULL;
+  const struct ConfigDef *cdef = NULL;
+  const struct ConfigSetType *cst = NULL;
+  void *var = NULL;
+
+  if (he->type & DT_INHERITED)
+  {
+    i = he->data;
+    cdef = i->parent->data;
+    cst = cs_get_type_def(cs, i->parent->type);
+  }
+  else
+  {
+    cdef = he->data;
+    cst = cs_get_type_def(cs, he->type);
+  }
+
+  if ((he->type & DT_INHERITED) && (DTYPE(he->type) != 0))
+  {
+    var = &i->var; /* Local value */
+  }
+  else
+  {
+    var = cdef->var; /* Normal var */
+  }
+
+  if (!cst)
+  {
+    mutt_debug(1, "Variable '%s' has an invalid type %d\n", cdef->name, DTYPE(he->type));
+    return CSR_ERR_CODE;
+  }
+
+  return cst->string_get(cs, var, cdef, result);
+}
+
+/**
+ * cs_str_string_get - Get a config item as a string
+ * @param cs     Config items
+ * @param name   Name of config item
+ * @param result Buffer for results or error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_str_string_get(const struct ConfigSet *cs, const char *name, struct Buffer *result)
+{
+  if (!cs || !name)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct HashElem *he = cs_get_elem(cs, name);
+  if (!he)
+  {
+    mutt_buffer_printf(result, "Unknown var '%s'", name);
+    return CSR_ERR_UNKNOWN;
+  }
+
+  return cs_he_string_get(cs, he, result);
+}
+
+/**
+ * cs_he_native_set - Natively set the value of a HashElem config item
+ * @param cs    Config items
+ * @param he    HashElem representing config item
+ * @param value Native pointer/value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_he_native_set(const struct ConfigSet *cs, struct HashElem *he,
+                     intptr_t value, struct Buffer *err)
+{
+  if (!cs || !he)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  const struct ConfigDef *cdef = NULL;
+  const struct ConfigSetType *cst = NULL;
+  void *var = NULL;
+
+  if (he->type & DT_INHERITED)
+  {
+    struct Inheritance *i = he->data;
+    cdef = i->parent->data;
+    var = &i->var;
+    cst = cs_get_type_def(cs, i->parent->type);
+  }
+  else
+  {
+    cdef = he->data;
+    var = cdef->var;
+    cst = cs_get_type_def(cs, he->type);
+  }
+
+  if (!cst)
+  {
+    mutt_debug(1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type);
+    return CSR_ERR_CODE;
+  }
+
+  int rc = cst->native_set(cs, var, cdef, value, err);
+  if (CSR_RESULT(rc) == CSR_SUCCESS)
+  {
+    if (he->type & DT_INHERITED)
+      he->type = cdef->type | DT_INHERITED;
+    if (!(rc & CSR_SUC_NO_CHANGE))
+      cs_notify_listeners(cs, he, cdef->name, CE_SET);
+  }
+
+  return rc;
+}
+
+/**
+ * cs_str_native_set - Natively set the value of a string config item
+ * @param cs    Config items
+ * @param name  Name of config item
+ * @param value Native pointer/value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+int cs_str_native_set(const struct ConfigSet *cs, const char *name,
+                      intptr_t value, struct Buffer *err)
+{
+  if (!cs || !name)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  struct HashElem *he = cs_get_elem(cs, name);
+  if (!he)
+  {
+    mutt_buffer_printf(err, "Unknown var '%s'", name);
+    return CSR_ERR_UNKNOWN;
+  }
+
+  const struct ConfigDef *cdef = NULL;
+  const struct ConfigSetType *cst = NULL;
+  void *var = NULL;
+
+  if (he->type & DT_INHERITED)
+  {
+    struct Inheritance *i = he->data;
+    cdef = i->parent->data;
+    var = &i->var;
+    cst = cs_get_type_def(cs, i->parent->type);
+  }
+  else
+  {
+    cdef = he->data;
+    var = cdef->var;
+    cst = cs_get_type_def(cs, he->type);
+  }
+
+  if (!cst)
+  {
+    mutt_debug(1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type);
+    return CSR_ERR_CODE;
+  }
+
+  int rc = cst->native_set(cs, var, cdef, value, err);
+  if (CSR_RESULT(rc) == CSR_SUCCESS)
+  {
+    if (he->type & DT_INHERITED)
+      he->type = cdef->type | DT_INHERITED;
+    if (!(rc & CSR_SUC_NO_CHANGE))
+      cs_notify_listeners(cs, he, cdef->name, CE_SET);
+  }
+
+  return rc;
+}
+
+/**
+ * cs_he_native_get - Natively get the value of a HashElem config item
+ * @param cs  Config items
+ * @param he  HashElem representing config item
+ * @param err Buffer for results or error messages
+ * @retval intptr_t Native pointer/value
+ */
+intptr_t cs_he_native_get(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *err)
+{
+  if (!cs || !he)
+    return INT_MIN; /* LCOV_EXCL_LINE */
+
+  struct Inheritance *i = NULL;
+  const struct ConfigDef *cdef = NULL;
+  const struct ConfigSetType *cst = NULL;
+  void *var = NULL;
+
+  if (he->type & DT_INHERITED)
+  {
+    i = he->data;
+    cdef = i->parent->data;
+    cst = cs_get_type_def(cs, i->parent->type);
+  }
+  else
+  {
+    cdef = he->data;
+    cst = cs_get_type_def(cs, he->type);
+  }
+
+  if ((he->type & DT_INHERITED) && (DTYPE(he->type) != 0))
+  {
+    var = &i->var;
+  }
+  else
+  {
+    var = cdef->var;
+  }
+
+  if (!cst)
+  {
+    mutt_buffer_printf(err, "Variable '%s' has an invalid type %d", cdef->name, he->type);
+    return INT_MIN;
+  }
+
+  return cst->native_get(cs, var, cdef, err);
+}
+
+/**
+ * cs_str_native_get - Natively get the value of a string config item
+ * @param cs   Config items
+ * @param name Name of config item
+ * @param err  Buffer for error messages
+ * @retval intptr_t Native pointer/value
+ */
+intptr_t cs_str_native_get(const struct ConfigSet *cs, const char *name, struct Buffer *err)
+{
+  if (!cs || !name)
+    return INT_MIN; /* LCOV_EXCL_LINE */
+
+  struct HashElem *he = cs_get_elem(cs, name);
+  return cs_he_native_get(cs, he, err);
+}
diff --git a/config/set.h b/config/set.h
new file mode 100644 (file)
index 0000000..04b7589
--- /dev/null
@@ -0,0 +1,164 @@
+/**
+ * @file
+ * A collection of config items
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_SET_H
+#define _CONFIG_SET_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+struct Buffer;
+struct ConfigSet;
+struct HashElem;
+struct ConfigDef;
+
+/**
+ * enum ConfigEvent - Config notification types
+ */
+enum ConfigEvent
+{
+  CE_SET = 1,   /**< Config item has been set */
+  CE_RESET, /**< Config item has been reset to initial, or parent, value */
+  CE_INITIAL_SET, /**< Config item's initial value has been set */
+};
+
+/* Config Set Results */
+#define CSR_SUCCESS       0 /**< Action completed successfully */
+#define CSR_ERR_CODE      1 /**< Problem with the code */
+#define CSR_ERR_UNKNOWN   2 /**< Unrecognised config item */
+#define CSR_ERR_INVALID   3 /**< Value hasn't been set */
+
+/* Flags for CSR_SUCCESS */
+#define CSR_SUC_INHERITED (1 << 4) /**< Value is inherited */
+#define CSR_SUC_EMPTY     (1 << 5) /**< Value is empty/unset */
+#define CSR_SUC_WARNING   (1 << 6) /**< Notify the user of a warning */
+#define CSR_SUC_NO_CHANGE (1 << 7) /**< The value hasn't changed */
+
+/* Flags for CSR_INVALID */
+#define CSR_INV_TYPE      (1 << 4) /**< Value is not valid for the type */
+#define CSR_INV_VALIDATOR (1 << 5) /**< Value was rejected by the validator */
+
+#define CSR_RESULT_MASK 0x0F
+#define CSR_RESULT(x) ((x) & CSR_RESULT_MASK)
+
+/**
+ * enum CsListenerAction - Config Listener responses
+ */
+enum CsListenerAction
+{
+  CSLA_CONTINUE = 1, /**< Continue notifying listeners */
+  CSLA_STOP,         /**< Stop notifying listeners */
+};
+
+typedef bool    (*cs_listener)   (const struct ConfigSet *cs, struct HashElem *he, const char *name, enum ConfigEvent ev);
+typedef int     (*cs_validator)  (const struct ConfigSet *cs, const struct ConfigDef *cdef, intptr_t value, struct Buffer *err);
+
+typedef int     (*cst_string_set)(const struct ConfigSet *cs, void *var,       struct ConfigDef *cdef, const char *value, struct Buffer *err);
+typedef int     (*cst_string_get)(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef,                    struct Buffer *result);
+typedef int     (*cst_native_set)(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef, intptr_t value,    struct Buffer *err);
+typedef intptr_t(*cst_native_get)(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef,                    struct Buffer *err);
+
+typedef int     (*cst_reset)     (const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef, struct Buffer *err);
+typedef void    (*cst_destroy)   (const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef);
+
+#define IP (intptr_t)
+
+#define CS_REG_DISABLED (1 << 0)
+
+/**
+ * struct ConfigDef - Config item definition
+ *
+ * Every config variable that NeoMutt supports is backed by a ConfigDef.
+ */
+struct ConfigDef
+{
+  const char   *name;      /**< User-visible name */
+  unsigned int  type;      /**< Variable type, e.g. #DT_STRING */
+  intptr_t      flags;     /**< Notification flags, e.g. #R_PAGER */
+  void         *var;       /**< Pointer to the global variable */
+  intptr_t      initial;   /**< Initial value */
+  cs_validator  validator; /**< Validator callback function */
+};
+
+/**
+ * struct ConfigSetType - Type definition for a config item
+ *
+ * Each config item has a type which is defined by a set of callback functions.
+ */
+struct ConfigSetType
+{
+  const char *name;          /**< Name of the type, e.g. "String" */
+  cst_string_set string_set; /**< Convert the variable to a string */
+  cst_string_get string_get; /**< Initialise a variable from a string */
+  cst_native_set native_set; /**< Set the variable using a C-native type */
+  cst_native_get native_get; /**< Get the variable's value as a C-native type */
+  cst_reset reset;           /**< Reset the variable to its initial, or parent, value */
+  cst_destroy destroy;       /**< Free the resources for a variable */
+};
+
+/**
+ * struct ConfigSet - Container for lots of config items
+ *
+ * The config items are stored in a HashTable so that their names can be looked
+ * up efficiently.  Each config item is repesented by a HashElem.  Once
+ * created, this HashElem is static and may be used for the lifetime of the
+ * ConfigSet.
+ */
+struct ConfigSet
+{
+  struct Hash *hash;              /**< HashTable storing the config itesm */
+  struct ConfigSetType types[18]; /**< All the defined config types */
+  cs_listener listeners[8];       /**< Listeners for notifications of changes to config items */
+};
+
+struct ConfigSet *cs_create(size_t size);
+void              cs_init(struct ConfigSet *cs, size_t size);
+void              cs_free(struct ConfigSet **cs);
+
+struct HashElem *           cs_get_elem(const struct ConfigSet *cs, const char *name);
+const struct ConfigSetType *cs_get_type_def(const struct ConfigSet *cs, unsigned int type);
+
+bool             cs_register_type(struct ConfigSet *cs, unsigned int type, const struct ConfigSetType *cst);
+bool             cs_register_variables(const struct ConfigSet *cs, struct ConfigDef vars[], int flags);
+struct HashElem *cs_inherit_variable(const struct ConfigSet *cs, struct HashElem *parent, const char *name);
+
+void cs_add_listener(struct ConfigSet *cs, cs_listener fn);
+void cs_remove_listener(struct ConfigSet *cs, cs_listener fn);
+void cs_notify_listeners(const struct ConfigSet *cs, struct HashElem *he, const char *name, enum ConfigEvent ev);
+
+int      cs_he_initial_get (const struct ConfigSet *cs, struct HashElem *he,                    struct Buffer *result);
+int      cs_he_initial_set (const struct ConfigSet *cs, struct HashElem *he, const char *value, struct Buffer *err);
+intptr_t cs_he_native_get  (const struct ConfigSet *cs, struct HashElem *he,                    struct Buffer *err);
+int      cs_he_native_set  (const struct ConfigSet *cs, struct HashElem *he, intptr_t value,    struct Buffer *err);
+int      cs_he_reset       (const struct ConfigSet *cs, struct HashElem *he,                    struct Buffer *err);
+int      cs_he_string_get  (const struct ConfigSet *cs, struct HashElem *he,                    struct Buffer *result);
+int      cs_he_string_set  (const struct ConfigSet *cs, struct HashElem *he, const char *value, struct Buffer *err);
+
+int      cs_str_initial_get(const struct ConfigSet *cs, const char *name,                       struct Buffer *result);
+int      cs_str_initial_set(const struct ConfigSet *cs, const char *name,    const char *value, struct Buffer *err);
+intptr_t cs_str_native_get (const struct ConfigSet *cs, const char *name,                       struct Buffer *err);
+int      cs_str_native_set (const struct ConfigSet *cs, const char *name,    intptr_t value,    struct Buffer *err);
+int      cs_str_reset      (const struct ConfigSet *cs, const char *name,                       struct Buffer *err);
+int      cs_str_string_get (const struct ConfigSet *cs, const char *name,                       struct Buffer *result);
+int      cs_str_string_set (const struct ConfigSet *cs, const char *name,    const char *value, struct Buffer *err);
+
+#endif /* _CONFIG_SET_H */
diff --git a/config/sort.c b/config/sort.c
new file mode 100644 (file)
index 0000000..b16d9d4
--- /dev/null
@@ -0,0 +1,418 @@
+/**
+ * @file
+ * Type representing a sort option
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-sort Type: Sorting
+ *
+ * Type representing a sort option.
+ */
+
+#include "config.h"
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+#include "mutt/buffer.h"
+#include "mutt/logging.h"
+#include "mutt/mapping.h"
+#include "mutt/string2.h"
+#include "sort.h"
+#include "set.h"
+#include "types.h"
+
+// clang-format off
+/**
+ * SortAliasMethods - Sort methods for email aliases
+ */
+const struct Mapping SortAliasMethods[] = {
+  { "address",  SORT_ADDRESS },
+  { "alias",    SORT_ALIAS },
+  { "unsorted", SORT_ORDER },
+  { NULL,       0 },
+};
+
+/**
+ * SortAuxMethods - Sort methods for '$sort_aux' for the index
+ */
+const struct Mapping SortAuxMethods[] = {
+  { "date",          SORT_DATE },
+  { "date-received", SORT_RECEIVED },
+  { "date-sent",     SORT_DATE },
+  { "from",          SORT_FROM },
+  { "label",         SORT_LABEL },
+  { "mailbox-order", SORT_ORDER },
+  { "score",         SORT_SCORE },
+  { "size",          SORT_SIZE },
+  { "spam",          SORT_SPAM },
+  { "subject",       SORT_SUBJECT },
+  { "threads",       SORT_DATE },
+  { "to",            SORT_TO },
+  { NULL,            0 },
+};
+
+/**
+ * SortBrowserMethods - Sort methods for the folder/dir browser
+ */
+const struct Mapping SortBrowserMethods[] = {
+  { "alpha",    SORT_SUBJECT },
+  { "count",    SORT_COUNT },
+  { "date",     SORT_DATE },
+  { "desc",     SORT_DESC },
+  { "new",      SORT_UNREAD },
+  { "unread",   SORT_UNREAD },
+  { "size",     SORT_SIZE },
+  { "unsorted", SORT_ORDER },
+  { NULL,       0 },
+};
+
+/**
+ * SortKeyMethods - Sort methods for encryption keys
+ */
+const struct Mapping SortKeyMethods[] = {
+  { "address", SORT_ADDRESS },
+  { "date",    SORT_DATE },
+  { "keyid",   SORT_KEYID },
+  { "trust",   SORT_TRUST },
+  { NULL,      0 },
+};
+
+/**
+ * SortMethods - Sort methods for '$sort' for the index
+ */
+const struct Mapping SortMethods[] = {
+  { "date",          SORT_DATE },
+  { "date-received", SORT_RECEIVED },
+  { "date-sent",     SORT_DATE },
+  { "from",          SORT_FROM },
+  { "label",         SORT_LABEL },
+  { "mailbox-order", SORT_ORDER },
+  { "score",         SORT_SCORE },
+  { "size",          SORT_SIZE },
+  { "spam",          SORT_SPAM },
+  { "subject",       SORT_SUBJECT },
+  { "threads",       SORT_THREADS },
+  { "to",            SORT_TO },
+  { NULL,            0 },
+};
+
+/**
+ * SortSidebarMethods - Sort methods for the sidebar
+ */
+const struct Mapping SortSidebarMethods[] = {
+  { "alpha",         SORT_PATH },
+  { "count",         SORT_COUNT },
+  { "desc",          SORT_DESC },
+  { "flagged",       SORT_FLAGGED },
+  { "mailbox-order", SORT_ORDER },
+  { "name",          SORT_PATH },
+  { "new",           SORT_UNREAD },
+  { "path",          SORT_PATH },
+  { "unread",        SORT_UNREAD },
+  { "unsorted",      SORT_ORDER },
+  { NULL,            0 },
+};
+// clang-format on
+
+/**
+ * sort_string_set - Set a Sort 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 sort_string_set(const struct ConfigSet *cs, void *var, struct ConfigDef *cdef,
+                           const char *value, struct Buffer *err)
+{
+  if (!cs || !cdef || !value)
+    return CSR_ERR_CODE; /* LCOV_EXCL_LINE */
+
+  intptr_t id = -1;
+  int flags = 0;
+
+  if (mutt_str_strncmp("reverse-", value, 8) == 0)
+  {
+    flags |= SORT_REVERSE;
+    value += 8;
+  }
+
+  if (mutt_str_strncmp("last-", value, 5) == 0)
+  {
+    flags |= SORT_LAST;
+    value += 5;
+  }
+
+  switch (cdef->type & DT_SUBTYPE_MASK)
+  {
+    case DT_SORT_INDEX:
+      id = mutt_map_get_value(value, SortMethods);
+      break;
+    case DT_SORT_ALIAS:
+      id = mutt_map_get_value(value, SortAliasMethods);
+      break;
+    case DT_SORT_AUX:
+      id = mutt_map_get_value(value, SortAuxMethods);
+      break;
+    case DT_SORT_BROWSER:
+      id = mutt_map_get_value(value, SortBrowserMethods);
+      break;
+    case DT_SORT_KEYS:
+      id = mutt_map_get_value(value, SortKeyMethods);
+      break;
+    case DT_SORT_SIDEBAR:
+      id = mutt_map_get_value(value, SortSidebarMethods);
+      break;
+    default:
+      mutt_debug(1, "Invalid sort type: %u\n", cdef->type & DT_SUBTYPE_MASK);
+      return CSR_ERR_CODE;
+      break;
+  }
+
+  if (id < 0)
+  {
+    mutt_buffer_printf(err, "Invalid sort name: %s", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  id |= flags;
+
+  if (var)
+  {
+    if (id == (*(short *) var))
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      int rc = cdef->validator(cs, cdef, (intptr_t) id, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    *(short *) var = id;
+  }
+  else
+  {
+    cdef->initial = id;
+  }
+
+  return CSR_SUCCESS;
+}
+
+/**
+ * sort_string_get - Get a Sort 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 sort_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 */
+
+  int sort;
+
+  if (var)
+    sort = *(short *) var;
+  else
+    sort = (int) cdef->initial;
+
+  if (sort & SORT_REVERSE)
+    mutt_buffer_addstr(result, "reverse-");
+  if (sort & SORT_LAST)
+    mutt_buffer_addstr(result, "last-");
+
+  sort &= SORT_MASK;
+
+  const char *str = NULL;
+
+  switch (cdef->type & DT_SUBTYPE_MASK)
+  {
+    case DT_SORT_INDEX:
+      str = mutt_map_get_name(sort, SortMethods);
+      break;
+    case DT_SORT_ALIAS:
+      str = mutt_map_get_name(sort, SortAliasMethods);
+      break;
+    case DT_SORT_AUX:
+      str = mutt_map_get_name(sort, SortAuxMethods);
+      break;
+    case DT_SORT_BROWSER:
+      str = mutt_map_get_name(sort, SortBrowserMethods);
+      break;
+    case DT_SORT_KEYS:
+      str = mutt_map_get_name(sort, SortKeyMethods);
+      break;
+    case DT_SORT_SIDEBAR:
+      str = mutt_map_get_name(sort, SortSidebarMethods);
+      break;
+    default:
+      mutt_debug(1, "Invalid sort type: %u\n", cdef->type & DT_SUBTYPE_MASK);
+      return CSR_ERR_CODE;
+      break;
+  }
+
+  if (!str)
+  {
+    mutt_debug(1, "Variable has an invalid value: %d/%d\n",
+               cdef->type & DT_SUBTYPE_MASK, sort);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  mutt_buffer_addstr(result, str);
+  return CSR_SUCCESS;
+}
+
+/**
+ * sort_native_set - Set a Sort config item by int
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Sort value
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int sort_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 */
+
+  const char *str = NULL;
+
+  switch (cdef->type & DT_SUBTYPE_MASK)
+  {
+    case DT_SORT_INDEX:
+      str = mutt_map_get_name((value & SORT_MASK), SortMethods);
+      break;
+    case DT_SORT_ALIAS:
+      str = mutt_map_get_name((value & SORT_MASK), SortAliasMethods);
+      break;
+    case DT_SORT_AUX:
+      str = mutt_map_get_name((value & SORT_MASK), SortAuxMethods);
+      break;
+    case DT_SORT_BROWSER:
+      str = mutt_map_get_name((value & SORT_MASK), SortBrowserMethods);
+      break;
+    case DT_SORT_KEYS:
+      str = mutt_map_get_name((value & SORT_MASK), SortKeyMethods);
+      break;
+    case DT_SORT_SIDEBAR:
+      str = mutt_map_get_name((value & SORT_MASK), SortSidebarMethods);
+      break;
+    default:
+      mutt_debug(1, "Invalid sort type: %u\n", cdef->type & DT_SUBTYPE_MASK);
+      return CSR_ERR_CODE;
+      break;
+  }
+
+  if (!str)
+  {
+    mutt_buffer_printf(err, "Invalid sort type: %ld", value);
+    return (CSR_ERR_INVALID | CSR_INV_TYPE);
+  }
+
+  if (value == (*(short *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(short *) var = value;
+  return CSR_SUCCESS;
+}
+
+/**
+ * sort_native_get - Get an int from a Sort config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t Sort ID
+ */
+static intptr_t sort_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 */
+
+  return *(short *) var;
+}
+
+/**
+ * sort_reset - Reset a Sort 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 sort_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 */
+
+  if (cdef->initial == (*(short *) var))
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    int rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  *(short *) var = cdef->initial;
+  return CSR_SUCCESS;
+}
+
+/**
+ * sort_init - Register the Sort config type
+ * @param cs Config items
+ */
+void sort_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_sort = {
+    "sort",
+    sort_string_set,
+    sort_string_get,
+    sort_native_set,
+    sort_native_get,
+    sort_reset,
+    NULL,
+  };
+  cs_register_type(cs, DT_SORT, &cst_sort);
+}
diff --git a/config/sort.h b/config/sort.h
new file mode 100644 (file)
index 0000000..b04024d
--- /dev/null
@@ -0,0 +1,83 @@
+/**
+ * @file
+ * Type representing a sort option
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_SORT_H
+#define _CONFIG_SORT_H
+
+#include "mutt/mapping.h"
+
+struct ConfigSet;
+
+extern const struct Mapping SortAliasMethods[];
+extern const struct Mapping SortAuxMethods[];
+extern const struct Mapping SortBrowserMethods[];
+extern const struct Mapping SortKeyMethods[];
+extern const struct Mapping SortMethods[];
+extern const struct Mapping SortSidebarMethods[];
+
+/* ... DT_SORT */
+#define DT_SORT_INDEX   0x000 /**< Sort id for #SortMethods */
+#define DT_SORT_ALIAS   0x040 /**< Sort id for #SortAliasMethods */
+#define DT_SORT_BROWSER 0x080 /**< Sort id for #SortBrowserMethods */
+#define DT_SORT_KEYS    0x100 /**< Sort id for #SortKeyMethods */
+#define DT_SORT_AUX     0x200 /**< Sort id for #SortAliasMethods */
+#define DT_SORT_SIDEBAR 0x400 /**< Sort id for #SortSidebarMethods */
+
+#define SORT_DATE      1 /**< Sort by the date the email was sent. */
+#define SORT_SIZE      2 /**< Sort by the size of the email */
+#define SORT_ALPHA     3 /**< Required by makedoc.c */
+#define SORT_SUBJECT   3 /**< Sort by the email's subject */
+#define SORT_FROM      4 /**< Sort by the email's From field */
+#define SORT_ORDER     5 /**< Sort by the order the messages appear in the mailbox */
+#define SORT_THREADS   6 /**< Sort by email threads */
+#define SORT_RECEIVED  7 /**< Sort by when the message were delivered locally */
+#define SORT_TO        8 /**< Sort by the email's To field */
+#define SORT_SCORE     9 /**< Sort by the email's score */
+#define SORT_ALIAS    10 /**< Sort by email alias */
+#define SORT_ADDRESS  11 /**< Sort by email address */
+#define SORT_KEYID    12 /**< Sort by the encryption key's ID */
+#define SORT_TRUST    13 /**< Sort by encryption key's trust level */
+#define SORT_SPAM     14 /**< Sort by the email's spam score */
+#define SORT_COUNT    15 /**< Sort by number of emails in a folder */
+#define SORT_UNREAD   16 /**< Sort by the number of unread emails */
+#define SORT_FLAGGED  17 /**< Sort by the number of flagged emails */
+#define SORT_PATH     18 /**< Sort by the folder's path */
+#define SORT_LABEL    19 /**< Sort by the emails label */
+#define SORT_DESC     20 /**< Sort by the folder's description */
+
+/* Sort and sort_aux are shorts, and are a composite of a constant sort
+ * operation number and a set of compounded bitflags.
+ *
+ * Everything below SORT_MASK is a constant. There's room for SORT_MASK
+ * constant SORT_ values.
+ *
+ * Everything above is a bitflag. It's OK to move SORT_MASK down by powers of 2
+ * if we need more, so long as we don't collide with the constants above. (Or
+ * we can just expand sort and sort_aux to uint32_t.)
+ */
+#define SORT_MASK    ((1 << 8) - 1) /**< Mask for the sort id */
+#define SORT_REVERSE  (1 << 8)      /**< Reverse the order of the sort */
+#define SORT_LAST     (1 << 9)      /**< Sort thread by last-X, e.g. received date */
+
+void sort_init(struct ConfigSet *cs);
+
+#endif /* _CONFIG_SORT_H */
diff --git a/config/string.c b/config/string.c
new file mode 100644 (file)
index 0000000..08df411
--- /dev/null
@@ -0,0 +1,282 @@
+/**
+ * @file
+ * Type representing a string
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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-string Type: String
+ *
+ * Type representing a string.
+ */
+
+#include "config.h"
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+#include "mutt/buffer.h"
+#include "mutt/memory.h"
+#include "mutt/string2.h"
+#include "set.h"
+#include "types.h"
+
+/**
+ * string_destroy - Destroy a String
+ * @param cs   Config items
+ * @param var  Variable to destroy
+ * @param cdef Variable definition
+ */
+static void string_destroy(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef)
+{
+  if (!cs || !var || !cdef)
+    return; /* LCOV_EXCL_LINE */
+
+  const char **str = (const char **) var;
+  if (!*str)
+    return;
+
+  /* Don't free strings from the var definition */
+  if (*(char **) var == (char *) cdef->initial)
+  {
+    *(char **) var = NULL;
+    return;
+  }
+
+  FREE(var);
+}
+
+/**
+ * string_string_set - Set a String 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 string_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;
+
+  if (!value && (cdef->type & DT_NOT_EMPTY))
+  {
+    mutt_buffer_printf(err, "Option %s may not be empty", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  int rc = CSR_SUCCESS;
+
+  if (var)
+  {
+    if (mutt_str_strcmp(value, (*(char **) var)) == 0)
+      return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+    if (cdef->validator)
+    {
+      rc = cdef->validator(cs, cdef, (intptr_t) value, err);
+
+      if (CSR_RESULT(rc) != CSR_SUCCESS)
+        return (rc | CSR_INV_VALIDATOR);
+    }
+
+    string_destroy(cs, var, cdef);
+
+    const char *str = mutt_str_strdup(value);
+    if (!str)
+      rc |= CSR_SUC_EMPTY;
+
+    *(const char **) var = str;
+  }
+  else
+  {
+    /* we're already using the initial value */
+    if (*(char **) cdef->var == (char *) cdef->initial)
+      *(char **) cdef->var = mutt_str_strdup((char *) cdef->initial);
+
+    if (cdef->type & DT_INITIAL_SET)
+      FREE(&cdef->initial);
+
+    cdef->type |= DT_INITIAL_SET;
+    cdef->initial = IP mutt_str_strdup(value);
+  }
+
+  return rc;
+}
+
+/**
+ * string_string_get - Get a String 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 string_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 */
+
+  const char *str = NULL;
+
+  if (var)
+    str = *(const char **) var;
+  else
+    str = (char *) cdef->initial;
+
+  if (!str)
+    return (CSR_SUCCESS | CSR_SUC_EMPTY); /* empty string */
+
+  mutt_buffer_addstr(result, str);
+  return CSR_SUCCESS;
+}
+
+/**
+ * string_native_set - Set a String config item by string
+ * @param cs    Config items
+ * @param var   Variable to set
+ * @param cdef  Variable definition
+ * @param value Native pointer/value to set
+ * @param err   Buffer for error messages
+ * @retval int Result, e.g. #CSR_SUCCESS
+ */
+static int string_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 */
+
+  const char *str = (const char *) value;
+
+  /* Store empty strings as NULL */
+  if (str && (str[0] == '\0'))
+    value = 0;
+
+  if ((value == 0) && (cdef->type & DT_NOT_EMPTY))
+  {
+    mutt_buffer_printf(err, "Option %s may not be empty", cdef->name);
+    return (CSR_ERR_INVALID | CSR_INV_VALIDATOR);
+  }
+
+  if (mutt_str_strcmp((const char *) value, (*(char **) var)) == 0)
+    return (CSR_SUCCESS | CSR_SUC_NO_CHANGE);
+
+  int rc;
+
+  if (cdef->validator)
+  {
+    rc = cdef->validator(cs, cdef, value, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  string_destroy(cs, var, cdef);
+
+  str = mutt_str_strdup(str);
+  rc = CSR_SUCCESS;
+  if (!str)
+    rc |= CSR_SUC_EMPTY;
+
+  *(const char **) var = str;
+  return rc;
+}
+
+/**
+ * string_native_get - Get a string from a String config item
+ * @param cs   Config items
+ * @param var  Variable to get
+ * @param cdef Variable definition
+ * @param err  Buffer for error messages
+ * @retval intptr_t String pointer
+ */
+static intptr_t string_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 */
+
+  const char *str = *(const char **) var;
+
+  return (intptr_t) str;
+}
+
+/**
+ * string_reset - Reset a String 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 string_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 */
+
+  int rc = CSR_SUCCESS;
+
+  const char *str = (const char *) cdef->initial;
+  if (!str)
+    rc |= CSR_SUC_EMPTY;
+
+  if (mutt_str_strcmp(str, (*(char **) var)) == 0)
+    return (rc | CSR_SUC_NO_CHANGE);
+
+  if (cdef->validator)
+  {
+    rc = cdef->validator(cs, cdef, cdef->initial, err);
+
+    if (CSR_RESULT(rc) != CSR_SUCCESS)
+      return (rc | CSR_INV_VALIDATOR);
+  }
+
+  string_destroy(cs, var, cdef);
+
+  if (!str)
+    rc |= CSR_SUC_EMPTY;
+
+  *(const char **) var = str;
+  return rc;
+}
+
+/**
+ * string_init - Register the String config type
+ * @param cs Config items
+ */
+void string_init(struct ConfigSet *cs)
+{
+  const struct ConfigSetType cst_string = {
+    "string",          string_string_set, string_string_get, string_native_set,
+    string_native_get, string_reset,      string_destroy,
+  };
+  cs_register_type(cs, DT_STRING, &cst_string);
+}
diff --git a/config/string3.h b/config/string3.h
new file mode 100644 (file)
index 0000000..ab24f49
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * @file
+ * Type representing a string
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_STRING_H
+#define _CONFIG_STRING_H
+
+struct ConfigSet;
+
+void string_init(struct ConfigSet *cs);
+
+#endif /* _CONFIG_STRING_H */
diff --git a/config/types.h b/config/types.h
new file mode 100644 (file)
index 0000000..3fc0edb
--- /dev/null
@@ -0,0 +1,75 @@
+/**
+ * @file
+ * Constants for all the config types
+ *
+ * @authors
+ * Copyright (C) 2017-2018 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 _CONFIG_TYPES_H
+#define _CONFIG_TYPES_H
+
+/* Data Types */
+#define DT_ADDRESS    1   /**< e-mail address */
+#define DT_BOOL       2   /**< boolean option */
+#define DT_COMMAND    3   /**< a command */
+#define DT_HCACHE     4   /**< header cache backend */
+#define DT_LONG       5   /**< a number (long) */
+#define DT_MAGIC      6   /**< mailbox type */
+#define DT_MBTABLE    7   /**< multibyte char table */
+#define DT_NUMBER     8   /**< a number */
+#define DT_PATH       9   /**< a pathname */
+#define DT_QUAD      10   /**< quad-option (no/yes/ask-no/ask-yes) */
+#define DT_REGEX     11   /**< regular expressions */
+#define DT_SORT      12   /**< sorting methods */
+#define DT_STRING    13   /**< a string */
+#define DT_SYNONYM   14   /**< synonym for another variable */
+
+#define DTYPE(x) ((x) & 0x1f) /**< Mask for the Data Type */
+
+#define DT_NOT_EMPTY    0x40  /**< Empty strings are not allowed */
+#define DT_NOT_NEGATIVE 0x80  /**< Negative numbers are not allowed */
+
+/* subtypes for... */
+#define DT_SUBTYPE_MASK 0xfe0 /**< Mask for the Data Subtype */
+
+/* Private config item flags */
+#define DT_INHERITED    0x1000 /**< Config item is inherited */
+#define DT_INITIAL_SET  0x2000 /**< Config item must have its initial value freed */
+#define DT_DISABLED     0x4000 /**< Config item is disabled */
+#define DT_MY_CONFIG    0x8000 /**< Config item is a "my_" variable */
+
+/* forced redraw/resort types + other flags */
+#define R_NONE        0        /**< No refresh/resort flags */
+#define R_INDEX       (1 << 0) /**< redraw the index menu (MENU_MAIN) */
+#define R_PAGER       (1 << 1) /**< redraw the pager menu */
+#define R_PAGER_FLOW  (1 << 2) /**< reflow line_info and redraw the pager menu */
+#define R_RESORT      (1 << 3) /**< resort the mailbox */
+#define R_RESORT_SUB  (1 << 4) /**< resort subthreads */
+#define R_RESORT_INIT (1 << 5) /**< resort from scratch */
+#define R_TREE        (1 << 6) /**< redraw the thread tree */
+#define R_REFLOW      (1 << 7) /**< reflow window layout and full redraw */
+#define R_SIDEBAR     (1 << 8) /**< redraw the sidebar */
+#define R_MENU        (1 << 9) /**< redraw all menus */
+#define R_BOTH        (R_INDEX | R_PAGER)
+#define R_RESORT_BOTH (R_RESORT | R_RESORT_SUB)
+
+/* general flags, to be OR'd with the R_ flags above (so keep shifting..) */
+#define F_SENSITIVE   (1 << 10) /**< Config item contains sensitive value */
+#define IS_SENSITIVE(x) (((x).flags & F_SENSITIVE) == F_SENSITIVE)
+
+#endif /* _CONFIG_TYPES_H */
index 50b601f7f71f7e56330f39014f08d0b4c9ecd924..bb943842d942fb0127e35ef169a8096a55b41b21 100644 (file)
@@ -32,6 +32,7 @@ struct Buffer;
 /* ... DT_REGEX */
 #define DT_REGEX_MATCH_CASE 0x010 /**< Case-sensitive matching */
 #define DT_REGEX_ALLOW_NOT  0x020 /**< Regex can begin with '!' */
+#define DT_REGEX_NOSUB      0x100 /**< Do not report what was matched (REG_NOSUB) */
 
 /* This is a non-standard option supported by Solaris 2.5.x
  * which allows patterns of the form \<...\> */
index a699b96bd785fcb802fa920157e449f806937068..64903c352b947591d4b9492f8b0d84fb1701a5e3 100644 (file)
@@ -8,6 +8,23 @@ commands.c
 complete.c
 compose.c
 compress.c
+config/account.c
+config/address.c
+config/bool.c
+config/command.c
+config/dump.c
+config/enum.c
+config/long.c
+config/magic.c
+config/mbtable.c
+config/number.c
+config/path.c
+config/quad.c
+config/regex.c
+config/set.c
+config/slist.c
+config/sort.c
+config/string.c
 conn/conn_globals.c
 conn/conn_raw.c
 conn/getdomain.c