diff -r 45da884269c8 -r 3c90dfc35f06 src/properties.c --- 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 +#include + +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; +}