Sat, 01 Mar 2025 15:02:57 +0100
complete the properties documentation
relates to #451
docs/Writerside/topics/properties.h.md | file | annotate | diff | comparison | revisions | |
src/cx/properties.h | file | annotate | diff | comparison | revisions |
--- a/docs/Writerside/topics/properties.h.md Fri Feb 28 19:07:47 2025 +0100 +++ b/docs/Writerside/topics/properties.h.md Sat Mar 01 15:02:57 2025 +0100 @@ -2,10 +2,6 @@ The UCX properties parser can be used to parse line based key/value strings. -<warning> -New Feature - documentation work in progress! -</warning> - ## Supported Syntax Key/value pairs must be line based and separated by a single character delimter. @@ -79,6 +75,8 @@ Calling `cxPropertiesNext()` will return with `CX_PROPERTIES_NO_ERROR` (= zero) for each key/value-pair that is successfully parsed, and stores the pointers and lengths for both the key and the value into the structures pointed to by the `key` and `value` arguments. +When all the data from the input buffer was successfully consumed, `cxPropertiesNext()` returns `CX_PROPERTIES_NO_DATA`. + > This is all still free of any copies and allocations. > That means, the pointers in `key` and `value` after `cxPropertiesNext()` returns will point into the input buffer. > If you intend to store the key and/or the value somewhere else, it is strongly recommended to create a copy with `cx_strdup()`, @@ -103,7 +101,7 @@ ### List of Status Codes -Below is a full list of error codes for `cxPropertiesNext()`. +Below is a full list of status codes for `cxPropertiesNext()`. | Status Code | Meaning | |-----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -141,21 +139,46 @@ CxPropertiesSink sink, CxPropertiesSource source); ``` -<warning> -TODO: write documentation -</warning> +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. -### Additional Status Codes +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> -For sources and sinks there are three additional special status codes, -which only appear as return values for `cxPropertiesLoad()`. +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; +} -| 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. | +// 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 which are documented [below](#additional-status-codes). ### Creating own Sources and Sinks @@ -190,9 +213,126 @@ } CxPropertiesSink; ``` -<warning> -TODO: write documentation -</warning> +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. + +```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("%.*s = %.*s\n", + (int) k.length, k.ptr, (int) v->length, v->ptr); + } + + // 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. | + <seealso> <category ref="apidoc">
--- a/src/cx/properties.h Fri Feb 28 19:07:47 2025 +0100 +++ b/src/cx/properties.h Sat Mar 01 15:02:57 2025 +0100 @@ -551,10 +551,12 @@ /** * Creates a properties sink for an UCX map. * - * The values stored in the map will be pointers to strings allocated - * by #cx_strdup_a(). + * The values stored in the map will be pointers to freshly allocated, + * zero-terminated C strings (@c char*), which means the @p map should have been + * created with #CX_STORE_POINTERS. + * * The default stdlib allocator will be used, unless you specify a custom - * allocator in the optional @c data of the sink. + * allocator in the optional @c data field of the returned sink. * * @param map the map that shall consume the k/v-pairs. * @return the sink