--- a/docs/Writerside/topics/properties.h.md Sun Dec 07 15:33:16 2025 +0100 +++ b/docs/Writerside/topics/properties.h.md Sun Dec 07 15:34:46 2025 +0100 @@ -61,9 +61,15 @@ CxPropertiesStatus cxPropertiesNext(CxProperties *prop, cxstring *key, cxstring *value); - + void cxPropertiesUseStack(CxProperties *prop, char *buf, size_t capacity); + +CxPropertiesStatus cxPropertiesLoad(CxPropertiesConfig config, + AnyStr filename, CxMap *target); + +CxPropertiesStatus cxPropertiesLoadDefault( + AnyStr filename, CxMap *target); ``` The first step is to initialize a `CxProperties` structure with a call to `cxPropertiesInit()` using the desired config. @@ -105,242 +111,32 @@ > It is strongly recommended to always call `cxPropertiesDestroy()` when you are done with the parser, > even if you did not expect any allocations because you used `cxPropertiesUseStack()`. +All the above operations are combined in the function `cxPropertiesLoad()`, +which opens the file designated by the `filename` and loads all properties from that file into the specified `CxMap`. +The convenience macro `cxPropertiesLoadDefault()` uses the default parser configuration for this. +The target map must either store pointers of type `char*` or elements of type `cxmutstr`. + +> The stack buffers used by `cxPropertiesLoad()` can be changed when building UCX from sources +> by setting the `CX_PROPERTIES_LOAD_FILL_SIZE` and `CX_PROPERTIES_LOAD_BUF_SIZE` macros +> (see [](install.md#small-buffer-optimizations)). + ### List of Status Codes -Below is a full list of status codes for `cxPropertiesNext()`. +Below is a full list of status codes for `cxPropertiesNext()` and `cxPropertiesLoad()`. | Status Code | Meaning | |-----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | CX_PROPERTIES_NO_ERROR | A key/value pair was found and returned. | -| CX_PROPERTIES_NO_DATA | The input buffer does not contain more data. | +| CX_PROPERTIES_NO_DATA | The input buffer does not contain data. | | CX_PROPERTIES_INCOMPLETE_DATA | The input ends unexpectedly. This can happen when the last line does not terminate with a line break, or when the input ends with a parsed key but no value. Use `cxPropertiesFill()` to add more data before retrying. | | CX_PROPERTIES_NULL_INPUT | The input buffer was never initialized. Probably you forgot to call `cxPropertiesFill()` at least once. | | CX_PROPERTIES_INVALID_EMPTY_KEY | Only white-spaces were found on the left hand-side of the delimiter. Keys must not be empty. | | CX_PROPERTIES_INVALID_MISSING_DELIMITER | A line contains data, but no delimiter. | | CX_PROPERTIES_BUFFER_ALLOC_FAILED | More internal buffer was needed, but could not be allocated. | - - -## Sources and Sinks - -```C -#include <cx/properties.h> - -CxPropertiesSource -cxPropertiesStringSource(cxstring str); - -CxPropertiesSource -cxPropertiesCstrSource(const char *str); - -CxPropertiesSource -cxPropertiesCstrnSource(const char *str, size_t len); - -CxPropertiesSource -cxPropertiesFileSource(FILE *file, size_t chunk_size); - -CxPropertiesSink -cxPropertiesMapSink(CxMap *map); - -CxPropertiesStatus -cxPropertiesLoad(CxProperties *prop, - CxPropertiesSink sink, CxPropertiesSource source); -``` - -The basic idea of `cxPropertiesLoad()` is that key/value-pairs are extracted from a _source_ and ingested by a _sink_. -For the most common scenarios where properties data is read from a string or a file and put into a map, several functions are available. -But you can specify your [own sources and sinks](#creating-own-sources-and-sinks), as well. - -The following example shows a simple function which loads all properties data from a file. -The `chunk_size` argument when creating the file source specifies -how many bytes are read from the file and filled into the properties parser in one read/sink cycle. - -```C -#include <stdio.h> -#include <cx/properties.h> - -int load_props_from_file(const char *filename, CxMap *map) { - FILE *f = fopen(filename, "r"); - if (!f) return -1; - CxProperties prop; - cxPropertiesInitDefault(&prop); - CxPropertiesSink sink = cxPropertiesMapSink(map); - CxPropertiesSource src = cxPropertiesFileSource(f, 512); - CxPropertiesStatus status = cxPropertiesLoad(&prop, sink, src); - fclose(f); - return status; -} - -// usage: -CxMap *map = cxHashMapCreateSimple(CX_STORE_POINTERS); -if (load_props_from_file("my-props.properties", map)) { - // error handling -} else { - // assuming my-props.properties contains the following line: - // my-key = some value - char *value = cxMapGet(map, "my-key"); -} -``` - -> The function `cxPropertiesLoad()` should usually not return `CX_PROPERTIES_INCOMPLETE_DATA` because the parser is automatically refilled from the source. -> If it does, it could mean that the source was unable to provide all the data, or the properties data ended unexpectedly. -> The most expected status code is `CX_PROPERTIES_NO_ERROR` which means that at least one key/value-pair was found. -> If `cxPropertiesLoad()` returns `CX_PROPERTIES_NO_DATA` it means that the source did not provide any key/value-pair. -> There are several special status codes that are documented [below](#additional-status-codes). - -### Creating own Sources and Sinks - -```C -#include <cx/properties.h> - -typedef int(*cx_properties_read_init_func)(CxProperties *prop, - CxPropertiesSource *src); - -typedef int(*cx_properties_read_func)(CxProperties *prop, - CxPropertiesSource *src, cxstring *target); - -typedef void(*cx_properties_read_clean_func)(CxProperties *prop, - CxPropertiesSource *src); - -typedef int(*cx_properties_sink_func)(CxProperties *prop, - CxPropertiesSink *sink, cxstring key, cxstring value); - -typedef struct cx_properties_source_s { - void *src; - void *data_ptr; - size_t data_size; - cx_properties_read_func read_func; - cx_properties_read_init_func read_init_func; - cx_properties_read_clean_func read_clean_func; -} CxPropertiesSource; - -typedef struct cx_properties_sink_s { - void *sink; - void *data; - cx_properties_sink_func sink_func; -} CxPropertiesSink; -``` - -You can create your own sources and sinks by initializing the respective structures. - -For a source, only the `read_func` is mandatory, the other two functions are optional and used for initialization and cleanup, if required. -The file source created by `cxPropertiesFileSource()`, for example, -uses the `read_init_func` to allocate, and the `read_clean_func` to free the read buffer, respectively. - -Since the default map sink created by `cxPropertiesMapSink()` stores `char*` pointers into a map, -the following example uses a different sink, which stores them as `cxmutstr` values, automatically freeing them -when the map gets destroyed. -And instead of reading the data from a file with `fread()`, it uses `mmap()` to map the file into memory for reading. +| CX_PROPERTIES_FILE_ERROR | A file operation failed (only for `cxPropertiesLoad()`). | -```C -#include <stdio.h> -#include <unistd.h> -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/mman.h> -#include <cx/properties.h> -#include <cx/hash_map.h> - -static int prop_mmap(CxProperties *prop, CxPropertiesSource *src) { - struct stat s; - int fd = open(src->src, O_RDONLY); - if (fd < 0) return -1; - // re-use the data field to store the fd - // there are cleaner ways, but this is just for illustration - src->src = (void*) fd; - fstat(fd, &s); - // memory map the entire file - // and store the address and length in the properties source - src->data_ptr = mmap(0, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - src->data_size = s.st_size; - return src->data_ptr == NULL; -} - -static int prop_read(CxProperties *prop, CxPropertiesSource *src, - cxstring *target) { - // copy the address and length of the mapped data to the target - target->ptr = src->data_ptr; - target->length = src->data_size; - // set the new size to zero to indicate that there is no more data - src->data_size = 0; - return 0; -} - -static void prop_unmap(CxProperties *prop, CxPropertiesSource *src) { - // unmap the memory and close the file - munmap(src->data_ptr, src->data_size); - close((int)src->src); -} - -static int prop_sink(CxProperties *prop, CxPropertiesSink *sink, - cxstring key, cxstring value) { - CxMap *map = sink->sink; - // copy the string and store it into the map - cxmutstr v = cx_strdup(value); - int r = cxMapPut(map, key, &v); - if (r != 0) cx_strfree(&v); - return r; -} - -int load_props_from_file(const char *filename, CxMap *map) { - CxProperties prop; - cxPropertiesInitDefault(&prop); - CxPropertiesSource src; - src.src = (void*) filename; - src.read_init_func = prop_mmap; - src.read_func = prop_read; - src.read_clean_func = prop_unmap; - CxPropertiesSink sink; - sink.sink = map; - sink.sink_func = prop_sink; - return cxPropertiesLoad(&prop, sink, src); -} - -int main() { - // in contrast to the default map sink, - // this one here stores the UCX strings by value - CxMap *map = cxHashMapCreateSimple(sizeof(cxmutstr)); - - // automatically free the UCX string when removed from the map - cxDefineDestructor(map, cx_strfree); - - // use our custom load function to load the data from the file - if (load_props_from_file("my-props.properties", map)) { - fputs("Error reading properties.\n", stderr); - return 1; - } - - // output the read key/value pairs for illustration - CxMapIterator iter = cxMapIterator(map); - cx_foreach(CxMapEntry *, entry, iter) { - cxstring k = cx_strn(entry->key->data, entry->key->len); - cxmutstr *v = entry->value; - printf("%" CX_PRIstr " = %" CX_PRIstr "\n", - CX_SFMT(k), CX_SFMT(*v)); - } - - // freeing the map also frees the strings - // because we have registered cx_strfree() as destructor function - cxMapFree(map); - - return 0; -} -``` - -> A cleaner implementation that does not produce a warning for bluntly casting an `int` to a `void*` -> can be achieved by declaring a struct that contains the information, allocate memory for -> that struct, and store the pointer in `data_ptr`. -> For illustrating how properties sources and sinks can be implemented, this was not necessary. - -### Additional Status Codes - -For sources and sinks there are three additional special status codes, -which only appear as return values for `cxPropertiesLoad()`. - -| Status Code | Meaning | -|-----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| CX_PROPERTIES_READ_INIT_FAILED | Initializing the properties source failed and the `cx_properties_read_init_func` returned non-zero. | -| CX_PROPERTIES_READ_FAILED | Reading from a properties source failed and the `cx_properties_read_func` returned non-zero. | -| CX_PROPERTIES_SINK_FAILED | Sinking a key/value-pair failed and the `cx_properties_sink_func` returned non-zero. | - +For `cxPropertiesLoad()` the status code `CX_PROPERTIES_NO_ERROR` means that at least one property was loaded into the map, +while `CX_PROPERTIES_NO_DATA` means that the file is syntactically fine but does not contain any properties. <seealso> <category ref="apidoc">