src/properties.c

changeset 924
3c90dfc35f06
parent 923
45da884269c8
--- a/src/properties.c	Thu Oct 10 18:40:27 2024 +0200
+++ b/src/properties.c	Sat Oct 12 19:34:19 2024 +0200
@@ -28,10 +28,259 @@
 
 #include "cx/properties.h"
 
+#include <string.h>
+#include <assert.h>
+
+static const int CX_PROPERTIES_FLAG_USE_STACK = 0x01;
+
 const CxPropertiesConfig cx_properties_config_default = {
         '=',
-        '\\',
+        //'\\',
         '#',
         '\0',
         '\0'
 };
+
+void cxPropertiesInit(
+        CxProperties *prop,
+        CxPropertiesConfig config
+) {
+    memset(prop, 0, sizeof(CxProperties));
+    prop->config = config;
+}
+
+void cxPropertiesDestroy(CxProperties *prop) {
+    if (0 == (prop->flags & CX_PROPERTIES_FLAG_USE_STACK)) {
+        free(prop->buf);
+    }
+    prop->buf = NULL;
+    prop->buf_capacity = prop->buf_size = 0;
+}
+
+static int cx_properties_ensure_buf_capacity(CxProperties *prop, size_t cap) {
+    if (prop->buf_capacity >= cap) {
+        return 0;
+    }
+
+    // not enough capacity - are we on the stack right now?
+    if ((prop->flags & CX_PROPERTIES_FLAG_USE_STACK) != 0) {
+        // move to the heap
+        char *newbuf = malloc(cap);
+        if (newbuf == NULL) return 1;
+        memcpy(newbuf, prop->buf, prop->buf_size);
+        prop->buf = newbuf;
+        prop->flags &= CX_PROPERTIES_FLAG_USE_STACK;
+    } else {
+        // we are on the heap already, reallocate
+        // this is legit, because realloc() behaves like malloc() when the
+        // current pointer is NULL
+        char *newbuf = realloc(prop->buf, cap);
+        if (newbuf == NULL) return 1;
+        prop->buf = newbuf;
+    }
+
+    // store new capacity and return
+    prop->buf_capacity = cap;
+    return 0;
+}
+
+static int cx_properties_rescuen_input(CxProperties *prop, size_t len) {
+    if (cx_properties_ensure_buf_capacity(prop, prop->buf_size + len)) {
+        return 1;
+    }
+    const char *src = prop->text + prop->text_pos;
+    char *dest = prop->buf + prop->buf_size;
+    memcpy(dest, src, len);
+    prop->buf_size += len;
+    return 0;
+}
+
+static int cx_properties_rescue_input(CxProperties *prop) {
+    // someone fucked around with our integers, exit immediately
+    if (prop->text_pos > prop->text_size) return 0;
+
+    // determine the bytes needed
+    size_t len = prop->text_size - prop->text_pos;
+
+    return cx_properties_rescuen_input(prop, len);
+}
+
+void cxPropertiesInput(
+        CxProperties *prop,
+        const char *buf,
+        size_t len
+) {
+    prop->text = buf;
+    prop->text_size = len;
+    prop->text_pos = 0;
+}
+
+int cxPropertiesFill(
+        CxProperties *prop,
+        const char *buf,
+        size_t len
+) {
+    if (cx_properties_rescue_input(prop)) return 1;
+    cxPropertiesInput(prop, buf, len);
+    return 0;
+}
+
+void cxPropertiesUseStack(
+        CxProperties *prop,
+        char *buf,
+        size_t capacity
+) {
+    assert(buf == NULL);
+    prop->buf = buf;
+    prop->buf_capacity = capacity;
+    prop->buf_size = 0;
+    prop->flags |= CX_PROPERTIES_FLAG_USE_STACK;
+}
+
+enum cx_properties_status cxPropertiesNext(
+        CxProperties *prop,
+        cxstring *key,
+        cxstring *value
+) {
+    // check if we have a text buffer
+    if (prop->text == NULL) {
+        return CX_PROPERTIES_NULL_INPUT;
+    }
+    // check if we have rescued data
+    if (prop->buf_size > 0) {
+        // check if we can now get a complete line
+        const char *buf = prop->text + prop->text_pos;
+        size_t len = prop->text_size - prop->text_pos;
+        cxstring str = cx_strn(buf, len);
+        cxstring nl = cx_strchr(str, '\n');
+        if(nl.length > 0) {
+            // we add as much data to the rescue buffer as we need
+            // to complete the line
+            size_t len_until_nl = (size_t)(nl.ptr - buf) + 1;
+
+            if (cx_properties_rescuen_input(prop, len_until_nl)) {
+                return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
+            }
+
+            // the tmp buffer contains exactly one line now
+            // we use a trick here: we swap the buffers and recurse
+            const char *orig_text = prop->text;
+            size_t orig_size = prop->text_size;
+            prop->text = prop->buf;
+            prop->text_size = prop->buf_size;
+            prop->text_pos = 0;
+            prop->buf_size = 0;
+
+            enum cx_properties_status result;
+            result = cxPropertiesNext(prop, key, value);
+
+            // restore original buffer
+            prop->text = orig_text;
+            prop->text_size = orig_size;
+
+            // set the position to after the newline
+            prop->text_pos = len_until_nl;
+
+            // check the result
+            if (result == CX_PROPERTIES_NO_ERROR) {
+                // reset the rescue buffer and return with the result
+                prop->buf_size = 0;
+                return result;
+            } else if (result == CX_PROPERTIES_NO_DATA) {
+                // rescue buffer contained only blanks or comments
+                // reset the rescue buffer and retry with text buffer
+                prop->buf_size = 0;
+                return cxPropertiesNext(prop, key, value);
+            } else {
+                // CX_PROPERTIES_INCOMPLETE_DATA is not possible
+                // so it must have been another error
+                // do not reset the rescue buffer and return the error
+                return result;
+            }
+        } else {
+            // still not enough data
+            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 (prop->text_pos < prop->text_size) {
+        const char *buf = prop->text + prop->text_pos;
+        size_t len = prop->text_size - prop->text_pos;
+
+        /*
+         * First we check if we have at least one line. We also get indices of
+         * delimiter and comment chars
+         */
+        size_t delimiter_index = 0;
+        size_t comment_index = 0;
+        bool has_comment = false;
+
+        size_t i = 0;
+        char c = 0;
+        for (; i < len; i++) {
+            c = buf[i];
+            if (c == comment1 || c == comment2 || c == comment3) {
+                if (comment_index == 0) {
+                    comment_index = i;
+                    has_comment = true;
+                }
+            } else if (c == delimiter) {
+                if (delimiter_index == 0 && !has_comment) {
+                    delimiter_index = i;
+                }
+            } else if (c == '\n') {
+                break;
+            }
+        }
+
+        if (c != '\n') {
+            // we don't have enough data for a line
+            return CX_PROPERTIES_INCOMPLETE_DATA;
+        }
+
+        cxstring line = has_comment ?
+                        cx_strn(buf, comment_index) :
+                        cx_strn(buf, i);
+        // check line
+        if (delimiter_index == 0) {
+            // if line is not blank ...
+            line = cx_strtrim(line);
+            // ... either no delimiter found, or key is empty
+            if (line.length > 0) {
+                if (line.ptr[0] == delimiter) {
+                    return CX_PROPERTIES_INVALID_EMPTY_KEY;
+                } else {
+                    return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
+                }
+            }
+        } else {
+            cxstring k = cx_strn(buf, delimiter_index);
+            cxstring val = cx_strn(
+                    buf + delimiter_index + 1,
+                    line.length - delimiter_index - 1);
+            k = cx_strtrim(k);
+            val = cx_strtrim(val);
+            if (k.length > 0) {
+                *key = k;
+                *value = val;
+                prop->text_pos += i + 1;
+                assert(prop->text_pos <= prop->text_size);
+                return CX_PROPERTIES_NO_ERROR;
+            } else {
+                return CX_PROPERTIES_INVALID_EMPTY_KEY;
+            }
+        }
+
+        prop->text_pos += i + 1;
+    }
+
+    // when we come to this point, all data must have been read
+    assert(prop->text_pos == prop->text_size);
+    return CX_PROPERTIES_NO_DATA;
+}

mercurial