Sun, 07 Dec 2025 15:34:46 +0100
properties.h: removes the source/sink API and adds a new cxPropertiesLoad()
resolves #610
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /** * @file properties.h * @brief Interface for parsing data from properties files. * @author Mike Becker * @author Olaf Wintermann * @copyright 2-Clause BSD License */ #ifndef UCX_PROPERTIES_H #define UCX_PROPERTIES_H #include "common.h" #include "string.h" #include "map.h" #include "buffer.h" #ifdef __cplusplus extern "C" { #endif /** * Configures the expected characters for the properties parser. */ struct cx_properties_config_s { /** * The key/value delimiter that shall be used. * This is '=' by default. */ char delimiter; /** * The first comment character. * This is '#' by default. */ char comment1; /** * The second comment character. * This is not set by default. */ char comment2; /** * The third comment character. * This is not set by default. */ char comment3; /* * The character, when appearing at the end of a line, continues that line. * This is '\' by default. */ /** * Reserved for future use. */ char continuation; }; /** * Typedef for the properties config. */ typedef struct cx_properties_config_s CxPropertiesConfig; /** * Default properties configuration. */ CX_EXPORT extern const CxPropertiesConfig cx_properties_config_default; /** * Status codes for the properties interface. */ enum cx_properties_status { /** * Everything is fine. */ CX_PROPERTIES_NO_ERROR, /** * The input buffer does not contain more data. */ CX_PROPERTIES_NO_DATA, /** * The input ends unexpectedly. * * This either happens when the last line does not terminate with a line * break, or when the input ends with a parsed key but no value. */ CX_PROPERTIES_INCOMPLETE_DATA, /** * Not used as a status and never returned by any function. * * You can use this enumerator to check for all "good" status results * by checking if the status is less than @c CX_PROPERTIES_OK. * * A "good" status means that you can refill data and continue parsing. */ CX_PROPERTIES_OK, /** * Input buffer is @c NULL. */ CX_PROPERTIES_NULL_INPUT, /** * The line contains a delimiter but no key. */ CX_PROPERTIES_INVALID_EMPTY_KEY, /** * The line contains data but no delimiter. */ CX_PROPERTIES_INVALID_MISSING_DELIMITER, /** * More internal buffer was needed, but could not be allocated. */ CX_PROPERTIES_BUFFER_ALLOC_FAILED, /** * A file operation failed. * Only for cxPropertiesLoad(). * It is system-specific if errno is set. */ CX_PROPERTIES_FILE_ERROR, /** * A map operation failed. * Only for cxPropertiesLoad(). */ CX_PROPERTIES_MAP_ERROR, }; /** * Typedef for the properties status enum. */ typedef enum cx_properties_status CxPropertiesStatus; /** * Interface for working with properties data. */ struct cx_properties_s { /** * The configuration. */ CxPropertiesConfig config; /** * The text input buffer. */ CxBuffer input; /** * Internal buffer. */ CxBuffer buffer; }; /** * Typedef for the properties interface. */ typedef struct cx_properties_s CxProperties; /** * Initialize a properties interface. * * @param prop the properties interface * @param config the properties configuration * @see cxPropertiesInitDefault() */ cx_attr_nonnull CX_EXPORT void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); /** * Destroys the properties interface. * * @note Even when you are certain that you did not use the interface in a * way that caused a memory allocation, you should call this function anyway. * Future versions of the library might add features that need additional memory, * and you really don't want to search the entire code where you might need to * add a call to this function. * * @param prop the properties interface */ cx_attr_nonnull CX_EXPORT void cxPropertiesDestroy(CxProperties *prop); /** * Destroys and re-initializes the properties interface. * * You might want to use this to reset the parser after * encountering a syntax error. * * @param prop the properties interface */ cx_attr_nonnull CX_EXPORT void cxPropertiesReset(CxProperties *prop); /** * Initialize a properties parser with the default configuration. * * @param prop (@c CxProperties*) the properties interface * @see cxPropertiesInit() */ #define cxPropertiesInitDefault(prop) \ cxPropertiesInit(prop, cx_properties_config_default) /** * Fills the input buffer with data. * * After calling this function, you can parse the data by calling * cxPropertiesNext(). * * @remark The properties interface tries to avoid allocations. * When you use this function and cxPropertiesNext() interleaving, * no allocations are performed. However, you must not free the * pointer to the data in that case. When you invoke the fill * function more than once before calling cxPropertiesNext(), * the additional data is appended - inevitably leading to * an allocation of a new buffer and copying the previous contents. * * @param prop the properties interface * @param buf a pointer to the data * @param len the length of the data * @retval zero success * @retval non-zero a memory allocation was necessary but failed * @see cxPropertiesFill() */ cx_attr_nonnull cx_attr_access_r(2, 3) CX_EXPORT int cxPropertiesFilln(CxProperties *prop, const char *buf, size_t len); /** * Internal function, do not use. * * @param prop the properties interface * @param str the text to fill in * @retval zero success * @retval non-zero a memory allocation was necessary but failed */ cx_attr_nonnull CX_INLINE int cx_properties_fill(CxProperties *prop, cxstring str) { return cxPropertiesFilln(prop, str.ptr, str.length); } /** * Fills the input buffer with data. * * After calling this function, you can parse the data by calling * cxPropertiesNext(). * * @attention The properties interface tries to avoid allocations. * When you use this function and cxPropertiesNext() interleaving, * no allocations are performed. However, you must not free the * pointer to the data in that case. When you invoke the fill * function more than once before calling cxPropertiesNext(), * the additional data is appended - inevitably leading to * an allocation of a new buffer and copying the previous contents. * * @param prop the properties interface * @param str the text to fill in * @retval zero success * @retval non-zero a memory allocation was necessary but failed * @see cxPropertiesFilln() */ #define cxPropertiesFill(prop, str) cx_properties_fill(prop, cx_strcast(str)) /** * Specifies stack memory that shall be used as an internal buffer. * * @param prop the properties interface * @param buf a pointer to stack memory * @param capacity the capacity of the stack memory */ cx_attr_nonnull CX_EXPORT void cxPropertiesUseStack(CxProperties *prop, char *buf, size_t capacity); /** * Retrieves the next key/value-pair. * * This function returns zero as long as there are key/value-pairs found. * If no more key/value-pairs are found, #CX_PROPERTIES_NO_DATA is returned. * * When an incomplete line is encountered, #CX_PROPERTIES_INCOMPLETE_DATA is * returned, and you can add more data with #cxPropertiesFill(). * * @remark The incomplete line will be stored in an internal buffer, which is * allocated on the heap, by default. If you want to avoid allocations, * you can specify sufficient space with cxPropertiesUseStack() after * initialization with cxPropertiesInit(). * * @attention The returned strings will point into a buffer that might not be * available later. It is strongly recommended to copy the strings for further * use. * * @param prop the properties interface * @param key a pointer to the cxstring that shall contain the property name * @param value a pointer to the cxstring that shall contain the property value * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found * @retval CX_PROPERTIES_NO_DATA there is no (more) data in the input buffer * @retval CX_PROPERTIES_INCOMPLETE_DATA the data in the input buffer is incomplete * (fill more data and try again) * @retval CX_PROPERTIES_NULL_INPUT the input buffer was never filled * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed */ cx_attr_nonnull cx_attr_nodiscard CX_EXPORT CxPropertiesStatus cxPropertiesNext(CxProperties *prop, cxstring *key, cxstring *value); /** * The size of the stack memory that `cxPropertiesLoad()` will reserve with `cxPropertiesUseStack()`. */ CX_EXPORT extern const unsigned cx_properties_load_buf_size; /** * The size of the stack memory that `cxPropertiesLoad()` will use to read contents from the file. */ CX_EXPORT extern const unsigned cx_properties_load_fill_size; /** * Internal function - use cxPropertiesLoad() instead. * * @param config the parser config * @param filename the file name * @param target the target map * @return status code */ cx_attr_nonnull CX_EXPORT CxPropertiesStatus cx_properties_load(CxPropertiesConfig config, cxstring filename, CxMap *target); /** * Loads properties from a file and inserts them into a map. * * Entries are added to the map, possibly overwriting existing entries. * * The map must either store pointers of type @c char*, or elements of type cxmutstr. * Any other configuration is not supported. * * @param config the parser config * @param filename (any string) the absolute or relative path to the file * @param target (@c CxMap*) the map where the properties shall be added * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed * @see cxPropertiesLoadDefault() */ #define cxPropertiesLoad(config, filename, target) cx_properties_load(config, cx_strcast(filename), target) /** * Loads properties from a file and inserts them into a map with a default config. * * Entries are added to the map, possibly overwriting existing entries. * * The map must either store pointers of type @c char*, or elements of type cxmutstr. * Any other configuration is not supported. * * @param filename (any string) the absolute or relative path to the file * @param target (@c CxMap*) the map where the properties shall be added * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed * @see cxPropertiesLoad() */ #define cxPropertiesLoadDefault(filename, target) cx_properties_load(cx_properties_config_default, cx_strcast(filename), target) #ifdef __cplusplus } // extern "C" #endif #endif // UCX_PROPERTIES_H