changeset 509:b506bff0a7ab

Some cleanup work and refactoring on the configuration file parser and writer.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 26 Dec 2019 08:10:54 +0200
parents fe5e7bf704e5
children 7d4cdc0b1aa4
files tests.c th_config.c
diffstat 2 files changed, 447 insertions(+), 290 deletions(-) [+]
line wrap: on
line diff
--- a/tests.c	Thu Dec 26 08:06:38 2019 +0200
+++ b/tests.c	Thu Dec 26 08:10:54 2019 +0200
@@ -398,7 +398,7 @@
     // Create the configuration structure
     tprint(2, "Creating configuration structure\n");
     sect1 = NULL;
-    th_cfg_add_comment(&sect1, "A comment");
+    th_cfg_add_comment(&sect1, "A comment that\nspans multiple\nlines automatically");
     th_cfg_add_string(&sect1, "a_string_setting", &v_str1, "v_str1");
 
     th_cfg_add_comment(&sect1, "Hex triplet value setting");
--- a/th_config.c	Thu Dec 26 08:06:38 2019 +0200
+++ b/th_config.c	Thu Dec 26 08:10:54 2019 +0200
@@ -202,46 +202,181 @@
     PM_NUMERIC,
     PM_BOOL,
     PM_SECTION,
-    PM_ARRAY
+    PM_LIST,
+
+    PM_LAST
+};
+
+
+static const char *th_cfg_parse_modes[PM_LAST] =
+{
+    "EOF",
+    "ERROR",
+    "IDLE",
+    "COMMENT",
+    "NEXT",
+    "KEYNAME",
+    "KEYSET",
+    "STRING",
+    "NUMERIC",
+    "BOOL",
+    "SECTION",
+    "LIST",
 };
 
+
+typedef struct
+{
+    int ch, strDelim,
+        prevMode, nextMode, parseMode;
+} th_cfgparserctx_t;
+
+
+static void th_cfg_set_parsemode(th_cfgparserctx_t *ctx, const int mode)
+{
+    ctx->prevMode = ctx->parseMode;
+    ctx->parseMode = mode;
+}
+
+
+static void th_cfg_set_next_parsemode(th_cfgparserctx_t *ctx, const int mode)
+{
+    th_cfg_set_parsemode(ctx, PM_NEXT);
+    ctx->nextMode = mode;
+}
+
+
 #define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; }
-#define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c) || ch == '#')
+
+
+static BOOL th_cfg_is_end(const int ch)
+{
+    return
+        ch == '\r' ||
+        ch == '\n' ||
+        ch == ';' ||
+        ch == '#' ||
+        th_isspace(ch);
+}
+
+
+static int th_cfg_get_parsemode(const int type)
+{
+    switch (type)
+    {
+        case ITEM_HEX_TRIPLET:
+        case ITEM_STRING:
+            return PM_STRING;
+
+        case ITEM_INT:
+        case ITEM_UINT:
+        case ITEM_FLOAT:
+            return PM_NUMERIC;
+
+        case ITEM_BOOL:
+            return PM_BOOL;
+
+        case ITEM_SECTION:
+            return PM_SECTION;
+
+        case ITEM_STRING_LIST:
+            return PM_LIST;
+
+        default:
+            return PM_ERROR;
+    }
+}
+
+
+static BOOL th_cfg_set_item(th_cfgparserctx_t *ctx, th_cfgitem_t *item, const char *str)
+{
+    BOOL ret = TRUE;
+
+    printf("th_cfg_set_item: '%s' = '%s'\n", item->name, str);
 
-static int th_cfg_read_sect(th_ioctx *fh, th_cfgitem_t *cfg, int nesting)
+    switch (item->type)
+    {
+        case ITEM_HEX_TRIPLET:
+            ret  = th_get_hex_triplet(str, item->v.val_uint);
+            break;
+
+        case ITEM_STRING:
+            th_pstr_cpy(item->v.val_str, str);
+            break;
+
+        case ITEM_STRING_LIST:
+            {
+                char *tmps;
+                if ((tmps = th_strdup(str)) == NULL)
+                    ret = FALSE;
+                else
+                {
+                    th_llist_append(item->v.list, tmps);
+                    th_cfg_set_next_parsemode(ctx, PM_LIST);
+                }
+            }
+            return ret;
+
+        case ITEM_INT:
+            *(item->v.val_int) = atoi(str);
+            break;
+
+        case ITEM_UINT:
+            *(item->v.val_uint) = atol(str);
+            break;
+
+        case ITEM_FLOAT:
+            *(item->v.val_float) = atof(str);
+            break;
+
+        case ITEM_BOOL:
+            ret = th_get_boolean(str, item->v.val_bool);
+            break;
+
+        default:
+            ret = FALSE;
+            break;
+    }
+
+    th_cfg_set_parsemode(ctx, ret ? PM_IDLE : PM_ERROR);
+    return ret;
+}
+
+
+static int th_cfg_read_sect(th_ioctx *fh, th_cfgitem_t *sect, int nesting)
 {
+    th_cfgparserctx_t ctx;
     th_cfgitem_t *item = NULL;
     char *tmpStr = NULL;
     size_t strPos;
-    int c, parseMode, prevMode, nextMode, tmpCh;
-    BOOL isFound, isStart, isError, validError, fpSet;
+    BOOL isEscaped, isFound, isStart, isError, validError, fpSet;
 
     // Initialize values
-    tmpCh = 0;
+    memset(&ctx, 0, sizeof(ctx));
+    ctx.ch = -1;
     strPos = 0;
-    c = -1;
-    fpSet = isFound = isStart = isError = validError = FALSE;
-    nextMode = prevMode = parseMode = PM_IDLE;
+    ctx.nextMode = ctx.prevMode = ctx.parseMode = PM_IDLE;
+    isEscaped = fpSet = isFound = isStart = isError = validError = FALSE;
 
     if ((tmpStr = th_malloc(SET_MAX_BUF + 1)) == NULL)
         goto out;
 
     // Parse the configuration
-    while (parseMode != PM_EOF && parseMode != PM_ERROR)
+    while (ctx.parseMode != PM_EOF && ctx.parseMode != PM_ERROR)
     {
-        if (c == -1)
+        if (ctx.ch == -1)
         {
             // Get next character
-            switch (c = thfgetc(fh))
+            switch (ctx.ch = thfgetc(fh))
             {
             case EOF:
-                if (parseMode != PM_IDLE)
+                if (ctx.parseMode != PM_IDLE)
                 {
                     th_io_error(fh, THERR_OUT_OF_DATA, "Unexpected end of file.\n");
-                    parseMode = PM_ERROR;
+                    ctx.parseMode = PM_ERROR;
                 }
                 else
-                    parseMode = PM_EOF;
+                    ctx.parseMode = PM_EOF;
                 break;
 
             case '\n':
@@ -249,86 +384,90 @@
             }
         }
 
-        switch (parseMode)
+//        tmpStr[strPos] = 0;  printf("PM_%-8s: '%c' [%s]\n", th_cfg_parse_modes[ctx.parseMode], ctx.ch, tmpStr);
+
+        switch (ctx.parseMode)
         {
         case PM_COMMENT:
             // Comment parsing mode
-            if (c == '\n')
+            if (ctx.ch == '\n')
             {
                 // End of line, end of comment
-                parseMode = prevMode;
-                prevMode = PM_COMMENT;
+                th_cfg_set_parsemode(&ctx, ctx.prevMode);
             }
-            c = -1;
+            ctx.ch = -1;
             break;
 
         case PM_IDLE:
             // Normal parsing mode
-            if (c == '#')
+            if (ctx.ch == '#')
             {
-                prevMode = parseMode;
-                parseMode = PM_COMMENT;
-                c = -1;
+                th_cfg_set_parsemode(&ctx, PM_COMMENT);
+                ctx.ch = -1;
             }
-            else if (VISEND(c))
+            else
+            if (th_cfg_is_end(ctx.ch))
             {
-                c = -1;
+                ctx.ch = -1;
             }
-            else if (c == '}')
+            else
+            if (ctx.ch == '}')
             {
                 if (nesting > 0)
+                {
                     // Check for validation errors
                     goto out;
+                }
                 else
                 {
                     th_io_error(fh, THERR_INVALID_DATA,
                         "Invalid nesting sequence encountered.\n");
-                    parseMode = PM_ERROR;
+                    ctx.parseMode = PM_ERROR;
                 }
             }
-            else if (th_isalpha(c))
+            else
+            if (th_isalpha(ctx.ch))
             {
                 // Start of key name found
-                prevMode = parseMode;
-                parseMode = PM_KEYNAME;
+                th_cfg_set_parsemode(&ctx, PM_KEYNAME);
                 strPos = 0;
             }
             else
             {
                 // Error! Invalid character found
                 th_io_error(fh, THERR_INVALID_DATA,
-                    "Unexpected character '%c'.\n", c);
-                parseMode = PM_ERROR;
+                    "Unexpected character '%c'.\n", ctx.ch);
+                ctx.parseMode = PM_ERROR;
             }
             break;
 
         case PM_KEYNAME:
             // Configuration KEY name parsing mode
-            if (c == '#')
+            if (ctx.ch == '#')
             {
                 // Start of comment
-                prevMode = parseMode;
-                parseMode = PM_COMMENT;
-                c = -1;
+                th_cfg_set_parsemode(&ctx, PM_COMMENT);
+                ctx.ch = -1;
             }
-            else if (th_iscrlf(c) || th_isspace(c) || c == '=')
+            else
+            if (th_iscrlf(ctx.ch) || th_isspace(ctx.ch) || ctx.ch == '=')
             {
                 // End of key name
-                prevMode = parseMode;
-                parseMode = PM_NEXT;
-                nextMode = PM_KEYSET;
+                th_cfg_set_next_parsemode(&ctx, PM_KEYSET);
             }
-            else if (th_isalnum(c) || c == '_' || c == '-')
+            else
+            if (th_isalnum(ctx.ch) || ctx.ch == '_' || ctx.ch == '-')
             {
                 // Add to key name string
-                VADDCH(c)
+                VADDCH(ctx.ch)
                 else
                 {
                     // Error! Key name string too long!
                     th_io_error(fh, THERR_INVALID_DATA, "Config key name too long!");
-                    parseMode = PM_ERROR;
+                    ctx.parseMode = PM_ERROR;
                 }
-                c = -1;
+                ctx.ch = -1;
+                tmpStr[strPos] = 0;
             }
             else
             {
@@ -336,18 +475,18 @@
                 tmpStr[strPos] = 0;
                 th_io_error(fh, THERR_INVALID_DATA,
                     "Unexpected character '%c' in key name '%s'.\n",
-                    c, tmpStr);
-                parseMode = PM_ERROR;
+                    ctx.ch, tmpStr);
+                ctx.parseMode = PM_ERROR;
             }
             break;
 
         case PM_KEYSET:
-            if (c == '=')
+            if (ctx.ch == '=')
             {
                 // Find key from configuration
                 tmpStr[strPos] = 0;
                 isFound = FALSE;
-                item = cfg;
+                item = sect;
                 while (item != NULL && !isFound)
                 {
                     if (item->name != NULL && strcmp(item->name, tmpStr) == 0)
@@ -360,34 +499,7 @@
                 if (isFound)
                 {
                     // Okay, set next mode
-                    switch (item->type)
-                    {
-                    case ITEM_HEX_TRIPLET:
-                    case ITEM_STRING:
-                        nextMode = PM_STRING;
-                        break;
-
-                    case ITEM_STRING_LIST:
-                        nextMode = PM_ARRAY;
-                        break;
-
-                    case ITEM_INT:
-                    case ITEM_UINT:
-                    case ITEM_FLOAT:
-                        nextMode = PM_NUMERIC;
-                        break;
-
-                    case ITEM_BOOL:
-                        nextMode = PM_BOOL;
-                        break;
-
-                    case ITEM_SECTION:
-                        nextMode = PM_SECTION;
-                        break;
-                    }
-
-                    prevMode = parseMode;
-                    parseMode = PM_NEXT;
+                    th_cfg_set_next_parsemode(&ctx, th_cfg_get_parsemode(item->type));
                     isStart = TRUE;
                     fpSet = FALSE;
                     strPos = 0;
@@ -398,95 +510,81 @@
                     th_io_error(fh, THERR_INVALID_DATA,
                         "No such configuration setting ('%s')\n",
                         tmpStr);
-                    parseMode = PM_ERROR;
+                    ctx.parseMode = PM_ERROR;
                 }
 
-                c = -1;
+                ctx.ch = -1;
             }
             else
             {
                 // Error! '=' expected!
                 th_io_error(fh, THERR_INVALID_DATA,
                     "Unexpected character '%c', assignation '=' was expected.\n",
-                    c);
-                parseMode = PM_ERROR;
+                    ctx.ch);
+                ctx.parseMode = PM_ERROR;
             }
             break;
 
         case PM_NEXT:
             // Search next item parsing mode
-            if (c == '#')
+            if (ctx.ch == '#')
             {
                 // Start of comment
-                prevMode = parseMode;
-                parseMode = PM_COMMENT;
+                th_cfg_set_parsemode(&ctx, PM_COMMENT);
             }
-            else if (th_isspace(c) || th_iscrlf(c))
+            else
+            if (th_isspace(ctx.ch) || th_iscrlf(ctx.ch))
             {
                 // Ignore whitespaces and linechanges
-                c = -1;
+                ctx.ch = -1;
             }
             else
             {
                 // Next item found
-                prevMode = parseMode;
-                parseMode = nextMode;
+                th_cfg_set_parsemode(&ctx, ctx.nextMode);
             }
             break;
 
-        case PM_ARRAY:
+        case PM_LIST:
             if (isStart)
             {
-                switch (item->type)
-                {
-                case ITEM_STRING_LIST:
-                    prevMode = parseMode;
-                    parseMode = PM_STRING;
-                    break;
-                }
+                th_cfg_set_parsemode(&ctx, PM_STRING);
             }
-            else if (c == ',')
+            else
+            if (ctx.ch == ',')
             {
-                switch (item->type)
-                {
-                case ITEM_STRING_LIST:
-                    c = -1;
-                    isStart = TRUE;
-                    prevMode = parseMode;
-                    parseMode = PM_NEXT;
-                    nextMode = PM_STRING;
-                    break;
-                }
+                th_cfg_set_next_parsemode(&ctx, PM_STRING);
+                ctx.ch = -1;
+                isStart = TRUE;
             }
             else
             {
-                prevMode = parseMode;
-                parseMode = PM_IDLE;
+                th_cfg_set_parsemode(&ctx, PM_IDLE);
             }
             break;
 
         case PM_SECTION:
             // Section parsing mode
-            if (c != '{')
+            if (ctx.ch != '{')
             {
                 // Error! Section start '{' expected!
                 th_io_error(fh, THERR_INVALID_DATA,
                     "Unexpected character '%c', section start '{' was expected.\n",
-                    c);
-                parseMode = PM_ERROR;
+                    ctx.ch);
+                ctx.parseMode = PM_ERROR;
             }
             else
             {
                 int res = th_cfg_read_sect(fh, item->v.section, nesting + 1);
-                c = -1;
+                ctx.ch = -1;
                 if (res > 0)
                     validError = TRUE;
-                else if (res < 0)
-                    parseMode = PM_ERROR;
+                else
+                if (res < 0)
+                    ctx.parseMode = PM_ERROR;
                 else
                 {
-                    prevMode = parseMode;
-                    parseMode = PM_IDLE;
+                    th_cfg_set_parsemode(&ctx, PM_IDLE);
                 }
             }
             break;
@@ -496,122 +594,100 @@
             if (isStart)
             {
                 // Start of string, get delimiter
-                tmpCh = c;
+                ctx.strDelim = ctx.ch;
                 isStart = FALSE;
                 strPos = 0;
+                isEscaped = FALSE;
             }
-            else if (c == tmpCh)
+            else
+            if (!isEscaped && ctx.ch == ctx.strDelim)
             {
                 // End of string, set the value
                 tmpStr[strPos] = 0;
-
-                switch (item->type)
-                {
-                case ITEM_HEX_TRIPLET:
-                    th_get_hex_triplet(tmpStr, item->v.val_uint);
-                    prevMode = parseMode;
-                    parseMode = PM_IDLE;
-                    break;
-                case ITEM_STRING:
-                    th_pstr_cpy(item->v.val_str, tmpStr);
-                    prevMode = parseMode;
-                    parseMode = PM_IDLE;
-                    break;
-                case ITEM_STRING_LIST:
-                    th_llist_append(item->v.list, th_strdup(tmpStr));
-                    prevMode = parseMode;
-                    parseMode = PM_NEXT;
-                    nextMode = PM_ARRAY;
-                    break;
-                }
-
+                th_cfg_set_item(&ctx, item, tmpStr);
+            }
+            else
+            if (!isEscaped && ctx.ch == '\\')
+            {
+                // Escape sequence
+                isEscaped = TRUE;
             }
             else
             {
                 // Add character to string
-                VADDCH(c)
+                VADDCH(ctx.ch)
                 else
                 {
                     // Error! String too long!
                     th_io_error(fh, THERR_INVALID_DATA,
                         "String too long! Maximum is %d characters.",
                         SET_MAX_BUF);
-                    parseMode = PM_ERROR;
+                    ctx.parseMode = PM_ERROR;
                 }
             }
 
-            c = -1;
+            ctx.ch = -1;
             break;
 
         case PM_NUMERIC:
             // Integer parsing mode
-            if (isStart && item->type == ITEM_UINT && c == '-')
+            if (isStart && item->type == ITEM_UINT && ctx.ch == '-')
             {
                 // Error! Negative values not allowed for unsigned ints
                 th_io_error(fh, THERR_INVALID_DATA,
                     "Negative value specified for %s, unsigned value expected.",
                     item->name);
-                parseMode = PM_ERROR;
+                ctx.parseMode = PM_ERROR;
             }
-            else if (isStart && (c == '-' || c == '+'))
+            else
+            if (isStart && (ctx.ch == '-' || ctx.ch == '+'))
             {
-                VADDCH(c)
+                VADDCH(ctx.ch)
                 else
                 isError = TRUE;
             }
-            else if (isStart && item->type == ITEM_FLOAT && c == '.')
+            else
+            if (isStart && item->type == ITEM_FLOAT && ctx.ch == '.')
             {
                 fpSet = TRUE;
                 VADDCH('0')
                 else
                 isError = TRUE;
 
-                VADDCH(c)
+                VADDCH(ctx.ch)
                 else
                 isError = TRUE;
             }
-            else if (item->type == ITEM_FLOAT && c == '.' && !fpSet)
+            else
+            if (item->type == ITEM_FLOAT && ctx.ch == '.' && !fpSet)
             {
                 fpSet = TRUE;
-                VADDCH(c)
-                else
-                isError = TRUE;
-            }
-            else if (th_isdigit(c))
-            {
-                VADDCH(c)
+                VADDCH(ctx.ch)
                 else
                 isError = TRUE;
             }
-            else if (VISEND(c))
+            else
+            if (th_isdigit(ctx.ch))
+            {
+                VADDCH(ctx.ch)
+                else
+                isError = TRUE;
+            }
+            else
+            if (th_cfg_is_end(ctx.ch))
             {
                 // End of integer parsing mode
                 tmpStr[strPos] = 0;
-                switch (item->type)
-                {
-                case ITEM_INT:
-                    *(item->v.val_int) = atoi(tmpStr);
-                    break;
-
-                case ITEM_UINT:
-                    *(item->v.val_uint) = atol(tmpStr);
-                    break;
-
-                case ITEM_FLOAT:
-                    *(item->v.val_float) = atof(tmpStr);
-                    break;
-                }
-
-                prevMode = parseMode;
-                parseMode = PM_IDLE;
+                th_cfg_set_item(&ctx, item, tmpStr);
+                th_cfg_set_parsemode(&ctx, PM_IDLE);
             }
             else
             {
                 // Error! Unexpected character.
                 th_io_error(fh, THERR_INVALID_DATA,
                     "Unexpected character '%c' for numeric setting '%s'.",
-                    c, item->name);
-                parseMode = PM_ERROR;
+                    ctx.ch, item->name);
+                ctx.parseMode = PM_ERROR;
             }
 
             if (isError)
@@ -620,11 +696,11 @@
                 th_io_error(fh, THERR_INVALID_DATA,
                     "String too long! Maximum is %d characters.",
                     SET_MAX_BUF);
-                parseMode = PM_ERROR;
+                ctx.parseMode = PM_ERROR;
             }
 
             isStart = FALSE;
-            c = -1;
+            ctx.ch = -1;
             break;
 
         case PM_BOOL:
@@ -635,24 +711,17 @@
                 strPos = 0;
             }
 
-            if (th_isalnum(c))
+            if (th_isalnum(ctx.ch))
             {
-                VADDCH(c)
+                VADDCH(ctx.ch)
                 else
                 isError = TRUE;
             }
             else
-            if (VISEND(c))
+            if (th_cfg_is_end(ctx.ch))
             {
-                BOOL tmpBool;
                 tmpStr[strPos] = 0;
-                isError = !th_get_boolean(tmpStr, &tmpBool);
-                if (!isError)
-                {
-                    *(item->v.val_bool) = tmpBool;
-                    prevMode = parseMode;
-                    parseMode = PM_IDLE;
-                }
+                isError = !th_cfg_set_item(&ctx, item, tmpStr);
             }
 
             if (isError)
@@ -660,22 +729,24 @@
                 th_io_error(fh, THERR_INVALID_DATA,
                     "Invalid boolean value for '%s'.\n",
                     item->name);
-                parseMode = PM_ERROR;
+                ctx.parseMode = PM_ERROR;
             }
-            c = -1;
+            ctx.ch = -1;
             break;
         }
     }
 
+    printf("EXIT: PM_%s\n", th_cfg_parse_modes[ctx.parseMode]);
+
 out:
     th_free(tmpStr);
 
     // Check for validation errors
     if (validError)
-        return 1;
+        return -91;
 
     // Return result
-    if (parseMode == PM_ERROR)
+    if (ctx.parseMode == PM_ERROR)
         return -2;
     else
         return 0;
@@ -693,120 +764,206 @@
 
 /* Write a configuration into file
  */
-static void th_print_indent(th_ioctx *fh, int nesting)
+static BOOL th_print_indent_a(th_ioctx *fh, const int nesting, const char *fmt, va_list ap)
+{
+    for (int i = 0; i < nesting * 4; i++)
+    {
+        if (thfputc(' ', fh) == EOF)
+            return FALSE;
+    }
+
+    return thvfprintf(fh, fmt, ap) >= 0;
+}
+
+
+static BOOL th_print_indent(th_ioctx *fh, const int nesting, const char *fmt, ...)
 {
-    for (int i = 0; i < nesting * 2; i++)
-        thfputc(' ', fh);
+    BOOL ret;
+    va_list ap;
+    va_start(ap, fmt);
+    ret = th_print_indent_a(fh, nesting, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+
+static BOOL th_cfg_is_item_valid(const th_cfgitem_t *item)
+{
+    switch (item->type)
+    {
+        case ITEM_STRING:
+            return (*(item->v.val_str) != NULL);
+
+        case ITEM_STRING_LIST:
+            return (*(item->v.list) != NULL);
+
+        case ITEM_SECTION:
+            return TRUE;
+
+        case ITEM_INT:
+        case ITEM_UINT:
+        case ITEM_FLOAT:
+        case ITEM_BOOL:
+        case ITEM_HEX_TRIPLET:
+            return TRUE;
+
+        default:
+            return FALSE;
+    }
 }
 
 
-static int th_cfg_write_sect(th_ioctx *fh, const th_cfgitem_t *item, int nesting)
+static BOOL th_cfg_write_string_escaped(th_ioctx *fh, const char *str, const char delim)
+{
+    for (const char *ptr = str; *ptr; ptr++)
+    {
+        if (*ptr == delim)
+        {
+            if (thfputc('\\', fh) == EOF ||
+                thfputc(*ptr, fh) == EOF)
+                return FALSE;
+        }
+        else
+        {
+            if (thfputc(*ptr, fh) == EOF)
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+static int th_cfg_write_item(th_ioctx *fh, const th_cfgitem_t *item)
+{
+    switch (item->type)
+    {
+        case ITEM_STRING:
+            if (thfputc('"', fh) != EOF &&
+                th_cfg_write_string_escaped(fh, *(item->v.val_str), '"') &&
+                thfputc('"', fh) != EOF)
+                return 1;
+            else
+                return -1;
+
+        case ITEM_INT:
+            return thfprintf(fh, "%d", *(item->v.val_int));
+
+        case ITEM_UINT:
+            return thfprintf(fh, "%u", *(item->v.val_uint));
+
+        case ITEM_FLOAT:
+            return thfprintf(fh, "%1.5f", *(item->v.val_float));
+
+        case ITEM_BOOL:
+            return thfprintf(fh, "%s", *(item->v.val_bool) ? "yes" : "no");
+
+        case ITEM_HEX_TRIPLET:
+            return thfprintf(fh, "\"%06x\"", *(item->v.val_int));
+
+        default:
+            return -1;
+    }
+}
+
+
+static int th_cfg_write_sect(th_ioctx *fh, const th_cfgitem_t *item, const int nesting)
 {
     while (item != NULL)
     {
+        if (item->name == NULL)
+            return -1;
+
         if (item->type == ITEM_COMMENT)
         {
-            th_print_indent(fh, nesting);
-            if (thfprintf(fh, "# %s\n",
-                (item->name != NULL) ? item->name : "") < 0)
-                return -1;
+            BOOL lineStart = TRUE, lineFeed = FALSE;
+
+            for (const char *ptr = item->name; *ptr; ptr++)
+            switch (*ptr)
+            {
+                case '\r':
+                case '\n':
+                    lineStart = lineFeed = TRUE;
+                    break;
+
+                default:
+                    if (lineFeed)
+                    {
+                        thfprintf(fh, "\n");
+                        lineFeed = FALSE;
+                    }
+                    if (lineStart)
+                    {
+                        if (!th_print_indent(fh, nesting, "# "))
+                            return -3;
+
+                        lineStart = FALSE;
+                    }
+
+                    if (thfputc(*ptr, fh) == EOF)
+                        return -9;
+            }
+
+            if (!lineFeed)
+                thfprintf(fh, "\n");
         }
-        else if (item->name != NULL)
+        else
+        if (item->type == ITEM_SECTION)
         {
-            th_print_indent(fh, nesting);
+            int res;
+
+            if (!th_print_indent(fh, nesting, "%s = {\n", item->name))
+                return -3;
+
+            if ((res = th_cfg_write_sect(fh, item->v.section, nesting + 1)) != 0)
+                return res;
 
-            switch (item->type)
+            if (!th_print_indent(fh, nesting, "}\n\n"))
+                return -3;
+        }
+        else
+        if (item->type == ITEM_STRING_LIST)
+        {
+            if (!th_cfg_is_item_valid(item))
             {
-            case ITEM_STRING:
-                if (*(item->v.val_str) == NULL)
+                if (!th_print_indent(fh, nesting, "#%s = \"\", \"\"", item->name))
+                    return -3;
+            }
+            else
+            {
+                th_llist_t *node = *(item->v.list);
+                size_t n = th_llist_length(node);
+
+                if (!th_print_indent(fh, nesting, "%s = \n", item->name))
+                    return -3;
+
+                for (; node != NULL; node = node->next)
+                if (node->data != NULL)
                 {
-                    if (thfprintf(fh, "#%s = \"\"\n",
-                        item->name) < 0)
-                        return -3;
-                }
-                else
-                {
-                    if (thfprintf(fh, "%s = \"%s\"\n",
-                        item->name, *(item->v.val_str)) < 0)
-                        return -3;
-                }
-                break;
-
-            case ITEM_STRING_LIST:
-                if (*(item->v.list) == NULL)
-                {
-                    if (thfprintf(fh,
-                        "#%s = \"\", \"\"\n", item->name) < 0)
+                    if (!th_print_indent(fh, nesting, "\"%s\"%s\n",
+                        (char *) node->data,
+                        --n > 0 ? "," : ""))
                         return -3;
                 }
-                else
-                {
-                    th_llist_t *node = *(item->v.list);
-                    size_t n = th_llist_length(node);
-                    if (thfprintf(fh, "%s = ", item->name) < 0)
-                        return -3;
 
-                    for (; node != NULL; node = node->next)
-                    {
-                        if (node->data != NULL)
-                            thfprintf(fh, "\"%s\"", (char *) node->data);
-
-                        if (--n > 0)
-                        {
-                            thfprintf(fh, ",\n");
-                            th_print_indent(fh, nesting);
-                        }
-                    }
-
-                    if (thfprintf(fh, "\n") < 0)
-                        return -3;
-                }
-                break;
-
-            case ITEM_INT:
-                if (thfprintf(fh, "%s = %i\n",
-                    item->name, *(item->v.val_int)) < 0)
-                    return -4;
-                break;
-
-            case ITEM_UINT:
-                if (thfprintf(fh, "%s = %d\n",
-                    item->name, *(item->v.val_uint)) < 0)
-                    return -5;
-                break;
-
-            case ITEM_FLOAT:
-                if (thfprintf(fh, "%s = %1.5f\n",
-                    item->name, *(item->v.val_float)) < 0)
-                    return -5;
-                break;
-
-            case ITEM_BOOL:
-                if (thfprintf(fh, "%s = %s\n", item->name,
-                    *(item->v.val_bool) ? "yes" : "no") < 0)
-                    return -6;
-                break;
-
-            case ITEM_SECTION:
-                {
-                    int res;
-                    if (thfprintf(fh, "%s = {\n", item->name) < 0)
-                        return -7;
-                    res = th_cfg_write_sect(fh, item->v.section, nesting + 1);
-                    if (res != 0)
-                        return res;
-                    if (thfprintf(fh, "}\n\n") < 0)
-                        return -8;
-                }
-                break;
-
-            case ITEM_HEX_TRIPLET:
-                if (thfprintf(fh, "%s = \"%06x\"\n",
-                    item->name, *(item->v.val_int)) < 0)
-                    return -6;
-                break;
+                if (!th_print_indent(fh, nesting, "\n"))
+                    return -3;
             }
         }
+        else
+        {
+            if (!th_print_indent(fh, nesting, "%s%s = ",
+                th_cfg_is_item_valid(item) ? "" : "#",
+                item->name))
+                return -3;
+
+            if (th_cfg_write_item(fh, item) < 0)
+                return -4;
+
+            if (thfprintf(fh, "\n") < 0)
+                return -3;
+        }
+
         item = (th_cfgitem_t *) item->node.next;
     }