ucx/properties.c

changeset 30
d33eaaec15da
parent 23
b26390e77237
--- a/ucx/properties.c	Thu Dec 11 22:58:02 2025 +0100
+++ b/ucx/properties.c	Fri Dec 12 10:42:53 2025 +0100
@@ -29,12 +29,15 @@
 #include "cx/properties.h"
 
 #include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
 
 const CxPropertiesConfig cx_properties_config_default = {
-        '=',
-        '#',
-        '\0',
-        '\0',
+    '=',
+    '#',
+    '\0',
+    '\0',
     '\\',
 };
 
@@ -94,13 +97,30 @@
 
     // a pointer to the buffer we want to read from
     CxBuffer *current_buffer = &prop->input;
-
+    
+    char comment1 = prop->config.comment1;
+    char comment2 = prop->config.comment2;
+    char comment3 = prop->config.comment3;
+    char delimiter = prop->config.delimiter;
+    char continuation = prop->config.continuation;
+    
     // check if we have rescued data
     if (!cxBufferEof(&prop->buffer)) {
         // check if we can now get a complete line
         cxstring input = cx_strn(prop->input.space + prop->input.pos,
             prop->input.size - prop->input.pos);
         cxstring nl = cx_strchr(input, '\n');
+        while (nl.length > 0) {
+            // check for line continuation
+            char previous = nl.ptr > input.ptr ? nl.ptr[-1] : prop->buffer.space[prop->buffer.size-1];
+            if (previous == continuation) {
+                // this nl is a line continuation, check the next newline
+                nl = cx_strchr(cx_strsubs(nl, 1), '\n');
+            } else {
+                break;
+            }
+        }
+        
         if (nl.length > 0) {
             // we add as much data to the rescue buffer as we need
             // to complete the line
@@ -127,12 +147,7 @@
             return CX_PROPERTIES_INCOMPLETE_DATA;
         }
     }
-
-    char comment1 = prop->config.comment1;
-    char comment2 = prop->config.comment2;
-    char comment3 = prop->config.comment3;
-    char delimiter = prop->config.delimiter;
-
+   
     // get one line and parse it
     while (!cxBufferEof(current_buffer)) {
         const char *buf = current_buffer->space + current_buffer->pos;
@@ -145,6 +160,7 @@
         size_t delimiter_index = 0;
         size_t comment_index = 0;
         bool has_comment = false;
+        bool has_continuation = false;
 
         size_t i = 0;
         char c = 0;
@@ -159,6 +175,9 @@
                 if (delimiter_index == 0 && !has_comment) {
                     delimiter_index = i;
                 }
+            } else if (delimiter_index > 0 && c == continuation && i+1 < len && buf[i+1] == '\n') {
+                has_continuation = true;
+                i++;
             } else if (c == '\n') {
                 break;
             }
@@ -223,10 +242,53 @@
             k = cx_strtrim(k);
             val = cx_strtrim(val);
             if (k.length > 0) {
+                current_buffer->pos += i + 1; 
+                assert(current_buffer->pos <= current_buffer->size);
+                assert(current_buffer != &prop->buffer || current_buffer->pos == current_buffer->size);
+                
+                if (has_continuation) {
+                    char *ptr = (char*)val.ptr;
+                    if (current_buffer != &prop->buffer) {
+                        // move value to the rescue buffer
+                        if (prop->buffer.space == NULL) {
+                            cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND);
+                        }
+                        prop->buffer.size = 0;
+                        prop->buffer.pos = 0;
+                        if (cxBufferWrite(val.ptr, 1, val.length, &prop->buffer) != val.length) {
+                            return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
+                        }
+                        val.ptr = prop->buffer.space;
+                        ptr = prop->buffer.space;
+                    }
+                    // value.ptr is now inside the rescue buffer and we can
+                    // remove the continuation character from the value
+                    bool trim = false;
+                    size_t x = 0;
+                    for(size_t j=0;j<val.length;j++) {
+                        c = ptr[j];
+                        if (j+1 < val.length && c == '\\' && ptr[j+1] == '\n') {
+                            // skip continuation and newline character
+                            j++;
+                            trim = true; // enable trim in the next line
+                            continue;
+                        }
+                        if (j > x) {
+                            if (trim) {
+                                if (isspace((unsigned char)c)) {
+                                    continue;
+                                }
+                                trim = false;
+                            }
+                            ptr[x] = c;
+                        }
+                        x++;
+                    }
+                    val.length = x;
+                }
                 *key = k;
                 *value = val;
-                current_buffer->pos += i + 1;
-                assert(current_buffer->pos <= current_buffer->size);
+                
                 return CX_PROPERTIES_NO_ERROR;
             } else {
                 return CX_PROPERTIES_INVALID_EMPTY_KEY;
@@ -241,180 +303,96 @@
     return CX_PROPERTIES_NO_DATA;
 }
 
-static int cx_properties_sink_map(
-        cx_attr_unused CxProperties *prop,
-        CxPropertiesSink *sink,
-        cxstring key,
-        cxstring value
-) {
-    CxMap *map = sink->sink;
-    CxAllocator *alloc = sink->data;
-    cxmutstr v = cx_strdup_a(alloc, value);
-    int r = cxMapPut(map, key, v.ptr);
-    if (r != 0) cx_strfree_a(alloc, &v);
-    return r;
-}
-
-CxPropertiesSink cxPropertiesMapSink(CxMap *map) {
-    CxPropertiesSink sink;
-    sink.sink = map;
-    sink.data = (void*) cxDefaultAllocator;
-    sink.sink_func = cx_properties_sink_map;
-    return sink;
-}
+#ifndef CX_PROPERTIES_LOAD_FILL_SIZE
+#define CX_PROPERTIES_LOAD_FILL_SIZE 1024
+#endif
+const unsigned cx_properties_load_fill_size = CX_PROPERTIES_LOAD_FILL_SIZE;
+#ifndef CX_PROPERTIES_LOAD_BUF_SIZE
+#define CX_PROPERTIES_LOAD_BUF_SIZE 256
+#endif
+const unsigned cx_properties_load_buf_size = CX_PROPERTIES_LOAD_BUF_SIZE;
 
-static int cx_properties_read_string(
-        CxProperties *prop,
-        CxPropertiesSource *src,
-        cxstring *target
-) {
-    if (prop->input.space == src->src) {
-        // when the input buffer already contains the string
-        // we have nothing more to provide
-        target->length = 0;
-    } else {
-        target->ptr = src->src;
-        target->length = src->data_size;
+CxPropertiesStatus cx_properties_load(CxPropertiesConfig config,
+        const CxAllocator *allocator, cxstring filename, CxMap *target) {
+    if (allocator == NULL) {
+        allocator = cxDefaultAllocator;
     }
-    return 0;
-}
-
-static int cx_properties_read_file(
-        cx_attr_unused CxProperties *prop,
-        CxPropertiesSource *src,
-        cxstring *target
-) {
-    target->ptr = src->data_ptr;
-    target->length = fread(src->data_ptr, 1, src->data_size, src->src);
-    return ferror((FILE*)src->src);
-}
-
-static int cx_properties_read_init_file(
-        cx_attr_unused CxProperties *prop,
-        CxPropertiesSource *src
-) {
-    src->data_ptr = cxMallocDefault(src->data_size);
-    if (src->data_ptr == NULL) return 1;
-    return 0;
-}
 
-static void cx_properties_read_clean_file(
-        cx_attr_unused CxProperties *prop,
-        CxPropertiesSource *src
-) {
-    cxFreeDefault(src->data_ptr);
-}
-
-CxPropertiesSource cxPropertiesStringSource(cxstring str) {
-    CxPropertiesSource src;
-    src.src = (void*) str.ptr;
-    src.data_size = str.length;
-    src.data_ptr = NULL;
-    src.read_func = cx_properties_read_string;
-    src.read_init_func = NULL;
-    src.read_clean_func = NULL;
-    return src;
-}
-
-CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) {
-    CxPropertiesSource src;
-    src.src = (void*) str;
-    src.data_size = len;
-    src.data_ptr = NULL;
-    src.read_func = cx_properties_read_string;
-    src.read_init_func = NULL;
-    src.read_clean_func = NULL;
-    return src;
-}
+    // sanity check for the map
+    const bool use_cstring = cxCollectionStoresPointers(target);
+    if (!use_cstring && cxCollectionElementSize(target) != sizeof(cxmutstr)) {
+        return CX_PROPERTIES_MAP_ERROR;
+    }
 
-CxPropertiesSource cxPropertiesCstrSource(const char *str) {
-    CxPropertiesSource src;
-    src.src = (void*) str;
-    src.data_size = strlen(str);
-    src.data_ptr = NULL;
-    src.read_func = cx_properties_read_string;
-    src.read_init_func = NULL;
-    src.read_clean_func = NULL;
-    return src;
-}
+    // create a duplicate to guarantee zero-termination
+    cxmutstr fname = cx_strdup(filename);
+    if (fname.ptr == NULL) {
+        return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+    }
 
-CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) {
-    CxPropertiesSource src;
-    src.src = file;
-    src.data_size = chunk_size;
-    src.data_ptr = NULL;
-    src.read_func = cx_properties_read_file;
-    src.read_init_func = cx_properties_read_init_file;
-    src.read_clean_func = cx_properties_read_clean_file;
-    return src;
-}
-
-CxPropertiesStatus cxPropertiesLoad(
-        CxProperties *prop,
-        CxPropertiesSink sink,
-        CxPropertiesSource source
-) {
-    assert(source.read_func != NULL);
-    assert(sink.sink_func != NULL);
-
-    // initialize reader
-    if (source.read_init_func != NULL) {
-        if (source.read_init_func(prop, &source)) {
-            return CX_PROPERTIES_READ_INIT_FAILED;  // LCOV_EXCL_LINE
-        }
+    // open the file
+    FILE *f = fopen(fname.ptr, "r");
+    if (f == NULL) {
+        cx_strfree(&fname);
+        return CX_PROPERTIES_FILE_ERROR;
     }
 
-    // transfer the data from the source to the sink
+    // initialize the parser
+    char linebuf[cx_properties_load_buf_size];
+    char fillbuf[cx_properties_load_fill_size];
     CxPropertiesStatus status;
-    CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA;
-    bool found = false;
-    while (true) {
-        // read input
-        cxstring input;
-        if (source.read_func(prop, &source, &input)) { // LCOV_EXCL_START
-            status = CX_PROPERTIES_READ_FAILED;
-            break;
-        } // LCOV_EXCL_STOP
+    CxProperties parser;
+    cxPropertiesInit(&parser, config);
+    cxPropertiesUseStack(&parser, linebuf, cx_properties_load_buf_size);
 
-        // no more data - break
-        if (input.length == 0) {
-            if (found) {
-                // something was found, check the last kv_status
-                if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) {
-                    status = CX_PROPERTIES_INCOMPLETE_DATA;
-                } else {
-                    status = CX_PROPERTIES_NO_ERROR;
-                }
-            } else {
-                // nothing found
-                status = CX_PROPERTIES_NO_DATA;
-            }
+    // read/fill/parse loop
+    status = CX_PROPERTIES_NO_DATA;
+    size_t keys_found = 0;
+    while (true) {
+        size_t r = fread(fillbuf, 1, cx_properties_load_fill_size, f);
+        if (ferror(f)) {
+            status = CX_PROPERTIES_FILE_ERROR;
+            break;
+        }
+        if (r == 0) {
             break;
         }
-
-        // set the input buffer and read the k/v-pairs
-        cxPropertiesFill(prop, input);
-
-        do {
-            cxstring key, value;
-            kv_status = cxPropertiesNext(prop, &key, &value);
-            if (kv_status == CX_PROPERTIES_NO_ERROR) {
-                found = true;
-                if (sink.sink_func(prop, &sink, key, value)) {
-                    kv_status = CX_PROPERTIES_SINK_FAILED;  // LCOV_EXCL_LINE
+        if (cxPropertiesFilln(&parser, fillbuf, r)) {
+            status = CX_PROPERTIES_BUFFER_ALLOC_FAILED;
+            break;
+        }
+        cxstring key, value;
+        while (true) {
+            status = cxPropertiesNext(&parser, &key, &value);
+            if (status != CX_PROPERTIES_NO_ERROR) {
+                break;
+            } else {
+                cxmutstr v = cx_strdup_a(allocator, value);
+                if (v.ptr == NULL) {
+                    status = CX_PROPERTIES_MAP_ERROR;
+                    break;
                 }
+                void *mv = use_cstring ? (void*)v.ptr : &v;
+                if (cxMapPut(target, key, mv)) {
+                    cx_strfree(&v);
+                    status = CX_PROPERTIES_MAP_ERROR;
+                    break;
+                }
+                keys_found++;
             }
-        } while (kv_status == CX_PROPERTIES_NO_ERROR);
-
-        if (kv_status > CX_PROPERTIES_OK) {
-            status = kv_status;
+        }
+        if (status > CX_PROPERTIES_OK) {
             break;
         }
     }
 
-    if (source.read_clean_func != NULL) {
-        source.read_clean_func(prop, &source);
+    // cleanup and exit
+    fclose(f);
+    cxPropertiesDestroy(&parser);
+    cx_strfree(&fname);
+    if (status == CX_PROPERTIES_NO_DATA && keys_found > 0) {
+        return CX_PROPERTIES_NO_ERROR;
+    } else {
+        return status;
     }
-
-    return status;
 }

mercurial