complete the properties documentation

Sat, 01 Mar 2025 15:02:57 +0100

author
Mike Becker <universe@uap-core.de>
date
Sat, 01 Mar 2025 15:02:57 +0100
changeset 1232
781bd188f1c0
parent 1231
a9f9c59e0b63
child 1233
29e1c48d1a6c

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

mercurial