From 0c31f2f3e00ba3049f4485d435fe52c369ee6b55 Mon Sep 17 00:00:00 2001 From: huaqo Date: Sat, 19 Sep 2026 13:14:43 +0200 Subject: [PATCH] feat: update rini to 3.0 --- external/rini/rini.h | 619 +++++++++++++++++++++++++++---------------- 1 file changed, 396 insertions(+), 223 deletions(-) diff --git a/external/rini/rini.h b/external/rini/rini.h index e38b4ac..b7f09bb 100644 --- a/external/rini/rini.h +++ b/external/rini/rini.h @@ -1,12 +1,12 @@ /********************************************************************************************** * -* rini v1.0 - A simple and easy-to-use config init files reader and writer +* rini v3.0 - A simple and easy-to-use ini-style files reader and writer * * DESCRIPTION: -* Load and save config init properties +* Load and save ini-style files with keys and values * * FEATURES: -* - Config files reading and writing +* - Init/Config files reading and writing * - Supported value types: int, string * - Support comment lines and empty lines * - Support custom line comment delimiter @@ -14,16 +14,16 @@ * - Support value description comments * - Support custom description custom delimiter * - Support multi-word text values w/o quote delimiters +* - Support custom key and value spacings +* - Customizable maximum values capacity * - Minimal C standard lib dependency (optional) -* - Customizable maximum config values capacity * * LIMITATIONS: -* - Config [sections] not supported -* - Saving config file requires complete rewrite +* - [sections] lines not supported +* - Saving file requires complete rewrite * * POSSIBLE IMPROVEMENTS: -* - Support config [sections] -* - Support disabled entries recognition +* - Support disabled key-value entries * * CONFIGURATION: * #define RINI_IMPLEMENTATION @@ -31,9 +31,13 @@ * If not defined, the library is in header only mode and can be included in other headers * or source files without problems. But only ONE file should hold the implementation. * +* #define RINI_MAX_ENTRY_CAPACITY +* Defines the maximum number of values supported +* Default value: 256 entries support +* * #define RINI_MAX_LINE_SIZE * Defines the maximum size of line buffer to read from file. -* Default value: 512 bytes (considering [key + text + desc] is 256 max size by default) +* Default value: 512 bytes (considering [key + text + desc] + spacing + quotes) * * #define RINI_MAX_KEY_SIZE * Defines the maximum size of value key @@ -41,15 +45,23 @@ * * #define RINI_MAX_TEXT_SIZE * Defines the maximum size of value text -* Default value: 128 bytes +* Default value: 256 bytes * * #define RINI_MAX_DESC_SIZE * Defines the maximum size of value description -* Default value: 128 bytes +* Default value: 256 bytes * -* #define RINI_MAX_VALUE_CAPACITY -* Defines the maximum number of values supported -* Default value: 128 entries support +* #define RINI_MAX_TEXT_FILE_SIZE +* Define the maximum size of the file that is saved in memory +* Default value: 4096 bytes +* +* #define RINI_KEY_SPACING +* Total space reserved for Key, Value starts after this spacing +* Default value: 36 spaces +* +* #define RINI_VALUE_SPACING +* Total space reserved for Value, Description starts after this spacing +* Default value: 32 spaces * * #define RINI_LINE_COMMENT_DELIMITER * Define character used to comment lines, placed at beginning of line @@ -67,7 +79,7 @@ * Default value: ' ' * * #define RINI_VALUE_QUOTATION_MARKS -* Defines quotation marks to be used around text values +* Defines quotation marks to be used around text values * Text values are determined checking text with atoi(), only for integer values, * in case of float values they are always considered as text * Default value: '\"' @@ -80,10 +92,18 @@ * DEPENDENCIES: C standard library: * - stdio.h: fopen(), feof(), fgets(), fclose(), fprintf() * - stdlib.h: malloc(), calloc(), free() -* - string.h: memset(), memcpy(), strcmp(), strlen() +* - string.h: memset(), memcpy(), strcmp(), strlen(), snprintf() * * VERSIONS HISTORY: -* 2.0 (26-Jan-2023) ADDED: Support custom comment lines (as config entries) +* 3.0 (17-May-2026) ADDED: rini_load_full() to include comments and empty lines +* ADDED: Flag to consider a entry text as text (or integer value) +* ADDED: New key and value custom spacing defines +* RENAMED: Values to Entries in full data structure +* REDESIGNED: Improved comments support: empty lines, empty comments, comments +* REDESIGNED: Support updating values from a loaded rini +* REDESIGNED: BREAKING: Removed the _config_ in naming +* +* 2.0 (26-Jan-2024) ADDED: Support custom comment lines (as config entries) * ADDED: Use of quotation-marks and marks customization * ADDED: rini_set_config_comment_line() * ADDED: rini_get_config_value_fallback(), with fallback return value @@ -120,15 +140,15 @@ #ifndef RINI_H #define RINI_H -#define RINI_VERSION "1.0" +#define RINI_VERSION "3.0" // Function specifiers in case library is build/used as a shared library (Windows) // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll #if defined(_WIN32) #if defined(BUILD_LIBTYPE_SHARED) - #define RINIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #define RINIAPI __declspec(dllexport) // Building the library as a Win32 shared library (.dll) #elif defined(USE_LIBTYPE_SHARED) - #define RINIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #define RINIAPI __declspec(dllimport) // Using the library as a Win32 shared library (.dll) #endif #endif @@ -160,24 +180,47 @@ #define RINI_LOG(...) #endif +// Max number of entries for a rini file +#if !defined(RINI_MAX_ENTRY_CAPACITY) + #define RINI_MAX_ENTRY_CAPACITY 256 +#endif + +// Max entry line size in bytes +// NOTE: It includes key-text-desc + spacing and quotation if required #if !defined(RINI_MAX_LINE_SIZE) #define RINI_MAX_LINE_SIZE 512 #endif +// Max entry key size in bytes #if !defined(RINI_MAX_KEY_SIZE) #define RINI_MAX_KEY_SIZE 64 #endif +// Max entry text size in bytes #if !defined(RINI_MAX_TEXT_SIZE) - #define RINI_MAX_TEXT_SIZE 128 + #define RINI_MAX_TEXT_SIZE 256 #endif +// Max entry description size in bytes #if !defined(RINI_MAX_DESC_SIZE) - #define RINI_MAX_DESC_SIZE 128 + #define RINI_MAX_DESC_SIZE 256 #endif -#if !defined(RINI_MAX_VALUE_CAPACITY) - #define RINI_MAX_VALUE_CAPACITY 128 +// rini text file max size +#if !defined(RINI_MAX_TEXT_FILE_SIZE) + #define RINI_MAX_TEXT_FILE_SIZE 4096 +#endif + +// Total space reserved for Key, +// Value starts after this spacing +#if !defined(RINI_KEY_SPACING) + #define RINI_KEY_SPACING 36 +#endif + +// Total space reserved for Value, +// Description starts after this spacing +#if !defined(RINI_VALUE_SPACING) + #define RINI_VALUE_SPACING 32 #endif // Line comment delimiter (starting string) @@ -211,19 +254,20 @@ //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -// Config value entry +// rini entry typedef struct { - char key[RINI_MAX_KEY_SIZE]; // Config value key identifier - char text[RINI_MAX_TEXT_SIZE]; // Config value text - char desc[RINI_MAX_DESC_SIZE]; // Config value description -} rini_config_value; + char key[RINI_MAX_KEY_SIZE]; // Entry key identifier + char text[RINI_MAX_TEXT_SIZE]; // Entry text + char desc[RINI_MAX_DESC_SIZE]; // Entry description + bool is_text; // Entry should be considered as text +} rini_entry; -// Config data +// rini data typedef struct { - rini_config_value *values; // Config values array - unsigned int count; // Config values count - unsigned int capacity; // Config values capacity -} rini_config; + rini_entry *entries; // Entries array + unsigned int count; // Entries count + unsigned int capacity; // Entries capacity +} rini_data; #if defined(__cplusplus) extern "C" { // Prevents name mangling of functions @@ -232,29 +276,30 @@ extern "C" { // Prevents name mangling of functions //------------------------------------------------------------------------------------ // Functions declaration //------------------------------------------------------------------------------------ -RINIAPI rini_config rini_load_config(const char *file_name); // Load config from file (*.ini) or create a new config object (pass NULL) -RINIAPI rini_config rini_load_config_from_memory(const char *text); // Load config from text buffer -RINIAPI void rini_save_config(rini_config config, const char *file_name); // Save config to file, with custom header -RINIAPI char *rini_save_config_to_memory(rini_config config); // Save config to text buffer ('\0' EOL) -RINIAPI void rini_unload_config(rini_config *config); // Unload config data from memory +RINIAPI rini_data rini_load(const char *file_name); // Load data from file (*.ini) or create a new rini object (pass NULL) +RINIAPI rini_data rini_load_full(const char *file_name); // Load data from file (*.ini) including full comment lines, useful for editing +RINIAPI rini_data rini_load_from_memory(const char *text); // Load data from text buffer +RINIAPI void rini_save(rini_data data, const char *file_name); // Save data to file, with custom header +RINIAPI char *rini_save_to_memory(rini_data data); // Save data to text buffer ('\0' EOL) +RINIAPI void rini_unload(rini_data *data); // Unload data from memory -RINIAPI int rini_get_config_value(rini_config config, const char *key); // Get config value int for provided key, returns 0 if not found -RINIAPI const char *rini_get_config_value_text(rini_config config, const char *key); // Get config value text for provided key -RINIAPI const char *rini_get_config_value_description(rini_config config, const char *key); // Get config value description for provided key +RINIAPI int rini_get_value(rini_data data, const char *key); // Get value int for provided key, returns 0 if not found +RINIAPI const char *rini_get_value_text(rini_data data, const char *key); // Get value text for provided key +RINIAPI const char *rini_get_value_description(rini_data data, const char *key); // Get value description for provided key -RINIAPI int rini_get_config_value_fallback(rini_config config, const char *key, int fallback); // Get config value for provided key with default value fallback if not found or not valid -RINIAPI const char *rini_get_config_value_text_fallback(rini_config config, const char *key, const char *fallback); // Get config value text for provided key with fallback if not found or not valid +RINIAPI int rini_get_value_fallback(rini_data data, const char *key, int fallback); // Get value for provided key with default value fallback if not found or not valid +RINIAPI const char *rini_get_value_text_fallback(rini_data data, const char *key, const char *fallback); // Get value text for provided key with fallback if not found or not valid -RINIAPI int rini_set_config_comment_line(rini_config *config, const char *comment); // Set config comment line +RINIAPI int rini_set_comment_line(rini_data *data, const char *comment); // Set comment line -// Set config value int/text and description for existing key or create a new entry +// Set value int/text and description for existing key or create a new entry // NOTE: When setting a text value, if id does not exist, a new entry is automatically created -RINIAPI int rini_set_config_value(rini_config *config, const char *key, int value, const char *desc); -RINIAPI int rini_set_config_value_text(rini_config *config, const char *key, const char *text, const char *desc); +RINIAPI int rini_set_value(rini_data *data, const char *key, int value, const char *desc); +RINIAPI int rini_set_value_text(rini_data *data, const char *key, const char *text, const char *desc); -// Set config value description for existing key +// Set value description for existing key // WARNING: Key must exist to add description, if a description exists, it is updated -RINIAPI int rini_set_config_value_description(rini_config *config, const char *key, const char *desc); +RINIAPI int rini_set_value_description(rini_data *data, const char *key, const char *desc); #ifdef __cplusplus } @@ -285,25 +330,26 @@ RINIAPI int rini_set_config_value_description(rini_config *config, const char *k // ... //---------------------------------------------------------------------------------- -// Module internal functions declaration +// Module Internal Functions Declaration //---------------------------------------------------------------------------------- -static int rini_read_config_key(const char *buffer, char *key); // Get key from a buffer line containing key-value-(description) -static int rini_read_config_value_text(const char *buffer, char *text, char *desc); // Get value text (and description) from a buffer line +static int rini_read_key(const char *buffer, char *key); // Get key from a buffer line containing key-value-(description) +static int rini_read_value_text(const char *buffer, char *text, char *desc, bool *is_text); // Get value text (and description) from a buffer line static int rini_text_to_int(const char *text); // Convert text to int value (if possible), same as atoi() //---------------------------------------------------------------------------------- -// Module functions definition +// Module Functions Definition //---------------------------------------------------------------------------------- -// Load config from file (.ini) -rini_config rini_load_config(const char *file_name) +// Load data from file (.ini) +// NOTE: Only key-value-description loaded, no empty lines or comments +rini_data rini_load(const char *file_name) { - rini_config config = { 0 }; + rini_data data = { 0 }; unsigned int value_counter = 0; - // Init config data to max capacity - config.capacity = RINI_MAX_VALUE_CAPACITY; - config.values = (rini_config_value *)RINI_CALLOC(RINI_MAX_VALUE_CAPACITY, sizeof(rini_config_value)); + // Init data to max capacity + data.capacity = RINI_MAX_ENTRY_CAPACITY; + data.entries = (rini_entry *)RINI_CALLOC(RINI_MAX_ENTRY_CAPACITY, sizeof(rini_entry)); if (file_name != NULL) { @@ -313,28 +359,28 @@ rini_config rini_load_config(const char *file_name) { char buffer[RINI_MAX_LINE_SIZE] = { 0 }; // Buffer to read every text line - // First pass to count valid config lines + // First pass to count valid lines while (fgets(buffer, RINI_MAX_LINE_SIZE, rini_file)) { // WARNING: fgets() keeps line endings, doesn't have any special options for converting line endings, // but on Windows, when reading file 'rt', line endings are converted from \r\n to just \n // Skip commented lines and empty lines - // NOTE: We are also skipping sections delimiters + // NOTE: Also skipping sections delimiters if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) && (buffer[0] != RINI_LINE_SECTION_DELIMITER) && - (buffer[0] != '\n') && (buffer[0] != '\0')) value_counter++; + (buffer[0] != '\n') && (buffer[0] != '\r') && (buffer[0] != '\0')) value_counter++; } - // WARNING: We can't store more values than its max capacity - config.count = (value_counter > RINI_MAX_VALUE_CAPACITY)? RINI_MAX_VALUE_CAPACITY : value_counter; + // WARNING: No more values can be stored than its max capacity + data.count = (value_counter > RINI_MAX_ENTRY_CAPACITY)? RINI_MAX_ENTRY_CAPACITY : value_counter; - if (config.count > 0) + if (data.count > 0) { rewind(rini_file); value_counter = 0; - // Second pass to read config data + // Second pass to read data while (fgets(buffer, RINI_MAX_LINE_SIZE, rini_file)) { // WARNING: fgets() keeps line endings, doesn't have any special options for converting line endings, @@ -343,17 +389,17 @@ rini_config rini_load_config(const char *file_name) // Skip commented lines and empty lines if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) && (buffer[0] != RINI_LINE_SECTION_DELIMITER) && - (buffer[0] != '\n') && (buffer[0] != '\0')) + (buffer[0] != '\n') && (buffer[0] != '\r') && (buffer[0] != '\0')) { // Get key identifier string - memset(config.values[value_counter].key, 0, RINI_MAX_KEY_SIZE); - rini_read_config_key(buffer, config.values[value_counter].key); - rini_read_config_value_text(buffer, config.values[value_counter].text, config.values[value_counter].desc); + memset(data.entries[value_counter].key, 0, RINI_MAX_KEY_SIZE); + rini_read_key(buffer, data.entries[value_counter].key); + rini_read_value_text(buffer, data.entries[value_counter].text, data.entries[value_counter].desc, &data.entries[value_counter].is_text); value_counter++; - // Stop reading if first count reached to avoid overflow in case count == RINI_MAX_VALUE_CAPACITY - if (value_counter >= config.count) break; + // Stop reading if first count reached to avoid overflow in case count == RINI_MAX_ENTRY_CAPACITY + if (value_counter >= data.count) break; } } } @@ -362,21 +408,121 @@ rini_config rini_load_config(const char *file_name) } } - return config; + return data; } -// Load config from text buffer -// NOTE: Comments and empty lines are ignored -rini_config rini_load_config_from_memory(const char *text) +// Load data from file (.ini) including all comments and empty lines +rini_data rini_load_full(const char *file_name) { - #define RINI_MAX_TEXT_LINES RINI_MAX_VALUE_CAPACITY*2 // Consider possible comments and empty lines - - rini_config config = { 0 }; + rini_data data = { 0 }; unsigned int value_counter = 0; - // Init config data to max capacity - config.capacity = RINI_MAX_VALUE_CAPACITY; - config.values = (rini_config_value *)RINI_CALLOC(RINI_MAX_VALUE_CAPACITY, sizeof(rini_config_value)); + // Init data to max capacity + data.capacity = RINI_MAX_ENTRY_CAPACITY; + data.entries = (rini_entry *)RINI_CALLOC(RINI_MAX_ENTRY_CAPACITY, sizeof(rini_entry)); + + if (file_name != NULL) + { + FILE *rini_file = fopen(file_name, "rt"); + + if (rini_file != NULL) + { + char buffer[RINI_MAX_LINE_SIZE] = { 0 }; // Buffer to read every text line + + // First pass to count valid lines + while (fgets(buffer, RINI_MAX_LINE_SIZE, rini_file)) + { + // WARNING: fgets() keeps line endings, doesn't have any special options for converting line endings, + // but on Windows, when reading file 'rt', line endings are converted from \r\n to just \n + + // NOTE: Keeping all lines, including comments, + // useful for files editing without losing information + if (buffer[0] != '\0') value_counter++; + } + + // WARNING: No more values can be stored than its max capacity + data.count = (value_counter > RINI_MAX_ENTRY_CAPACITY)? RINI_MAX_ENTRY_CAPACITY : value_counter; + + if (data.count > 0) + { + rewind(rini_file); + value_counter = 0; + + // Second pass to read data + while (fgets(buffer, RINI_MAX_LINE_SIZE, rini_file)) + { + // WARNING: fgets() keeps line endings, doesn't have any special options for converting line endings, + // but on Windows, when reading file 'rt', line endings are converted from \r\n to just \n + + // NOTE: Keeping all lines, including comments, + // useful for files editing without losing information + if (buffer[0] != '\0') + { + if ((buffer[0] == '\n') || (buffer[0] == '\r')) // Empty line + { + // Set entry as empty line: NULL, NULL, NULL + memset(data.entries[value_counter].key, 0, RINI_MAX_KEY_SIZE); // NULL + memset(data.entries[value_counter].text, 0, RINI_MAX_TEXT_SIZE); // NULL + memset(data.entries[value_counter].desc, 0, RINI_MAX_DESC_SIZE); // NULL + } + else if (buffer[0] == RINI_LINE_COMMENT_DELIMITER) // Comment line + { + // Measure comment line length + int len = 0; + char *buffer_ptr = (char *)buffer + 1; // Skip comment delimiter + while ((buffer_ptr[len] != '\0') && (buffer_ptr[len] != '\r') && (buffer_ptr[len] != '\n')) len++; + + // Set entry as comment + memset(data.entries[value_counter].key, 0, RINI_MAX_KEY_SIZE); // NULL + memset(data.entries[value_counter].text, 0, RINI_MAX_TEXT_SIZE); // NULL + memset(data.entries[value_counter].desc, 0, RINI_MAX_DESC_SIZE); // NULL + if (len == 0) + { + // Set entry as empty comment: NULL, NULL, " " + data.entries[value_counter].desc[0] = ' '; + } + else + { + // Set entry as comment: NULL, NULL, "comment" + // WARNING: In case of comment line, everything after delimiter is read (including spaces) + memcpy(data.entries[value_counter].desc, buffer_ptr, len); + } + } + else + { + // Get key identifier string + memset(data.entries[value_counter].key, 0, RINI_MAX_KEY_SIZE); + rini_read_key(buffer, data.entries[value_counter].key); + rini_read_value_text(buffer, data.entries[value_counter].text, data.entries[value_counter].desc, &data.entries[value_counter].is_text); + } + + value_counter++; + + // Stop reading if first count reached to avoid overflow in case count == RINI_MAX_ENTRY_CAPACITY + if (value_counter >= data.count) break; + } + } + } + + fclose(rini_file); + } + } + + return data; +} + +// Load data from text buffer +// NOTE: Comments and empty lines are ignored +rini_data rini_load_from_memory(const char *text) +{ + #define RINI_MAX_TEXT_LINES RINI_MAX_ENTRY_CAPACITY*2 // Consider possible comments and empty lines + + rini_data data = { 0 }; + unsigned int value_counter = 0; + + // Init data to max capacity + data.capacity = RINI_MAX_ENTRY_CAPACITY; + data.entries = (rini_entry *)RINI_CALLOC(RINI_MAX_ENTRY_CAPACITY, sizeof(rini_entry)); if (text != NULL) { @@ -400,83 +546,85 @@ rini_config rini_load_config_from_memory(const char *text) for (int l = 0; l < line_counter; l++) { // Skip commented lines and empty lines - // NOTE: We are also skipping sections delimiters + // NOTE: Also skipping sections delimiters if ((lines[l][0] != RINI_LINE_COMMENT_DELIMITER) && (lines[l][0] != RINI_LINE_SECTION_DELIMITER) && - (lines[l][0] != '\n') && (lines[l][0] != '\0')) value_counter++; + (lines[l][0] != '\n') && (lines[l][0] != '\r') && (lines[l][0] != '\0')) value_counter++; } - // WARNING: We can't store more values than its max capacity - config.count = (value_counter > RINI_MAX_VALUE_CAPACITY)? RINI_MAX_VALUE_CAPACITY : value_counter; + // WARNING: No more values can be stored than its max capacity + data.count = (value_counter > RINI_MAX_ENTRY_CAPACITY)? RINI_MAX_ENTRY_CAPACITY : value_counter; // Process lines to get keys and values - if (config.count > 0) + if (data.count > 0) { value_counter = 0; - // Second pass to read config data + // Second pass to read data for (int l = 0; l < line_counter; l++) { // Skip commented lines and empty lines if ((lines[l][0] != RINI_LINE_COMMENT_DELIMITER) && (lines[l][0] != RINI_LINE_SECTION_DELIMITER) && - (lines[l][0] != '\n') && (lines[l][0] != '\0')) + (lines[l][0] != '\n') && (lines[l][0] != '\r') && (lines[l][0] != '\0')) { // Get key identifier string - memset(config.values[value_counter].key, 0, RINI_MAX_KEY_SIZE); - rini_read_config_key(lines[l], config.values[value_counter].key); - rini_read_config_value_text(lines[l], config.values[value_counter].text, config.values[value_counter].desc); + memset(data.entries[value_counter].key, 0, RINI_MAX_KEY_SIZE); + rini_read_key(lines[l], data.entries[value_counter].key); + rini_read_value_text(lines[l], data.entries[value_counter].text, data.entries[value_counter].desc, &data.entries[value_counter].is_text); value_counter++; - // Stop reading if first count reached to avoid overflow in case count == RINI_MAX_VALUE_CAPACITY - if (value_counter >= config.count) break; + // Stop reading if first count reached to avoid overflow in case count == RINI_MAX_ENTRY_CAPACITY + if (value_counter >= data.count) break; } } } } - return config; + return data; } -// Save config to file (*.ini) -void rini_save_config(rini_config config, const char *file_name) +// Save data to file (*.ini) +void rini_save(rini_data data, const char *file_name) { FILE *rini_file = fopen(file_name, "wt"); if (rini_file != NULL) { - for (unsigned int i = 0; i < config.count; i++) + char valuestr[RINI_MAX_TEXT_SIZE + 2] = { 0 }; // Useful for text processing, adding quotation marks if required + + for (unsigned int i = 0; i < data.count; i++) { - if ((config.values[i].key[0] == '\0') && (config.values[i].text[0] == RINI_LINE_COMMENT_DELIMITER)) + if ((data.entries[i].key[0] == '\0') && (data.entries[i].text[0] == '\0')) { - if (config.values[i].desc[0] != '\0') fprintf(rini_file, "%c %s\n", RINI_LINE_COMMENT_DELIMITER, config.values[i].desc); - else fprintf(rini_file, "%c\n", RINI_LINE_COMMENT_DELIMITER); + if (data.entries[i].desc[0] == '\0') fprintf(rini_file, "\n"); // Empty line + else if ((data.entries[i].desc[0] == ' ') && (data.entries[i].desc[1] == '\0')) // Empty comment line + fprintf(rini_file, "%c\n", RINI_LINE_COMMENT_DELIMITER); + else fprintf(rini_file, "%c%s\n", RINI_LINE_COMMENT_DELIMITER, data.entries[i].desc); // Comment line } else { + memset(valuestr, 0, RINI_MAX_TEXT_SIZE + 2); #if RINI_USE_TEXT_QUOTATION_MARKS - // If text is not an integer value, append text quotation-marks - // TODO: Check also for float values? atof() - bool valueIsInt = false; - int value = atoi(config.values[i].text); // WARNING: Returns 0 if input not valid --> Check if (config.values[i].text len == 1 && config.values[i].text[0] == '0') - if (((value == 0) && (strlen(config.values[i].text) == 1) && (config.values[i].text[0] == '0')) || (value != 0)) valueIsInt = true; - - if (valueIsInt) + // Add quotation marks if required + if (data.entries[i].is_text) snprintf(valuestr, RINI_MAX_TEXT_SIZE + 2, "%c%s%c", RINI_VALUE_QUOTATION_MARKS, data.entries[i].text, RINI_VALUE_QUOTATION_MARKS); +#else + snprintf(valuestr, RINI_MAX_TEXT_SIZE + 2, "%s", data.entries[i].text); +#endif + // Add description if required + if (data.entries[i].desc[0] != '\0') { - fprintf(rini_file, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, - config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc); + fprintf(rini_file, "%-*s %c %-*s %c %s\n", RINI_KEY_SPACING, data.entries[i].key, RINI_VALUE_DELIMITER, + RINI_VALUE_SPACING, data.entries[i].is_text? valuestr : data.entries[i].text, + RINI_DESCRIPTION_DELIMITER, data.entries[i].desc); } else { - fprintf(rini_file, "%-28s %c %c%6s%c %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, - RINI_VALUE_QUOTATION_MARKS, config.values[i].text, RINI_VALUE_QUOTATION_MARKS, - RINI_DESCRIPTION_DELIMITER, config.values[i].desc); + // No description required + fprintf(rini_file, "%-*s %c %s\n", RINI_KEY_SPACING, data.entries[i].key, RINI_VALUE_DELIMITER, + data.entries[i].is_text? valuestr : data.entries[i].text); } -#else - fprintf(rini_file, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, - config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc); -#endif } } @@ -484,79 +632,81 @@ void rini_save_config(rini_config config, const char *file_name) } } -// Save config to text buffer ('\0' EOL) -char *rini_save_config_to_memory(rini_config config) +// Save data to text buffer ('\0' EOL) +char *rini_save_to_memory(rini_data data) { - #define RINI_MAX_TEXT_FILE_SIZE 4096 - - // Verify required config size is smaller than memory buffer size - // NOTE: We add 64 extra possible characters by entry line + // Verify required data size is smaller than memory buffer size + // NOTE: Adding 64 extra possible characters by entry line int requiredSize = 0; - for (unsigned int i = 0; i < config.count; i++) requiredSize += ((int)strlen(config.values[i].key) + (int)strlen(config.values[i].text) + (int)strlen(config.values[i].desc) + 64); - if (requiredSize > RINI_MAX_TEXT_FILE_SIZE) RINI_LOG("WARNING: Required config.ini size is bigger than max supported memory size, increase RINI_MAX_TEXT_FILE_SIZE\n"); + for (unsigned int i = 0; i < data.count; i++) requiredSize += ((int)strlen(data.entries[i].key) + (int)strlen(data.entries[i].text) + (int)strlen(data.entries[i].desc) + 64); + if (requiredSize > RINI_MAX_TEXT_FILE_SIZE) RINI_LOG("WARNING: Required data.ini size is bigger than max supported memory size, increase RINI_MAX_TEXT_FILE_SIZE\n"); // NOTE: Using a static buffer to avoid de-allocation requirement on user side static char text[RINI_MAX_TEXT_FILE_SIZE] = { 0 }; memset(text, 0, RINI_MAX_TEXT_FILE_SIZE); int offset = 0; + int expectedByteWritten = 0; - for (unsigned int i = 0; i < config.count; i++) + char valuestr[RINI_MAX_TEXT_SIZE + 2] = { 0 }; // Useful for text processing, adding quotation marks if required + + for (unsigned int i = 0; i < data.count; i++) { - if ((config.values[i].key[0] == '\0') && (config.values[i].text[0] == RINI_LINE_COMMENT_DELIMITER)) + if ((data.entries[i].key[0] == '\0') && (data.entries[i].text[0] == RINI_LINE_COMMENT_DELIMITER)) { - if (config.values[i].desc[0] != '\0') offset += sprintf(text + offset, "%c %s\n", RINI_LINE_COMMENT_DELIMITER, config.values[i].desc); - else offset += sprintf(text + offset, "%c\n", RINI_LINE_COMMENT_DELIMITER); + if (data.entries[i].desc[0] != '\0') expectedByteWritten = snprintf(text + offset, RINI_MAX_LINE_SIZE, "%c %s\n", RINI_LINE_COMMENT_DELIMITER, data.entries[i].desc); + else expectedByteWritten = snprintf(text + offset, RINI_MAX_LINE_SIZE, "%c\n", RINI_LINE_COMMENT_DELIMITER); } else { + memset(valuestr, 0, RINI_MAX_TEXT_SIZE + 2); #if RINI_USE_TEXT_QUOTATION_MARKS - // If text is not an integer value, append text quotation-marks - // TODO: Check also for float values? atof() - bool valueIsInt = false; - int value = atoi(config.values[i].text); // WARNING: Returns 0 if input not valid --> Check if (config.values[i].text len == 1 && config.values[i].text[0] == '0') - if (((value == 0) && (strlen(config.values[i].text) == 1) && (config.values[i].text[0] == '0')) || (value != 0)) valueIsInt = true; - - if (valueIsInt) + // Add quotation marks if required + if (data.entries[i].is_text) snprintf(valuestr, RINI_MAX_TEXT_SIZE + 2, "%c%s%c", RINI_VALUE_QUOTATION_MARKS, data.entries[i].text, RINI_VALUE_QUOTATION_MARKS); +#else + snprintf(valuestr, RINI_MAX_TEXT_SIZE + 2, "%s", data.entries[i].text); +#endif + // Add description if required + if (data.entries[i].desc[0] != '\0') { - offset += sprintf(text + offset, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, - config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc); + expectedByteWritten = snprintf(text + offset, RINI_MAX_LINE_SIZE, "%-*s %c %-*s %c %s\n", RINI_KEY_SPACING, data.entries[i].key, RINI_VALUE_DELIMITER, + RINI_VALUE_SPACING, data.entries[i].is_text? valuestr : data.entries[i].text, + RINI_DESCRIPTION_DELIMITER, data.entries[i].desc); } else { - offset += sprintf(text + offset, "%-28s %c %c%6s%c %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, - RINI_VALUE_QUOTATION_MARKS, config.values[i].text, RINI_VALUE_QUOTATION_MARKS, - RINI_DESCRIPTION_DELIMITER, config.values[i].desc); + // No description required + expectedByteWritten = snprintf(text + offset, RINI_MAX_LINE_SIZE, "%-*s %c %s\n", RINI_KEY_SPACING, data.entries[i].key, RINI_VALUE_DELIMITER, + data.entries[i].is_text? valuestr : data.entries[i].text); } -#else - offset += sprintf(text + offset, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, - config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc); -#endif } + + if (expectedByteWritten >= RINI_MAX_LINE_SIZE) offset += RINI_MAX_LINE_SIZE; + else offset += expectedByteWritten; } return text; } -// Unload config data -void rini_unload_config(rini_config *config) +// Unload data +void rini_unload(rini_data *data) { - RINI_FREE(config->values); + RINI_FREE(data->entries); - config->values = NULL; - config->count = 0; - config->capacity = 0; + data->entries = NULL; + data->count = 0; + data->capacity = 0; } -// Get config value for provided key, returns 0 if not found or not valid -int rini_get_config_value(rini_config config, const char *key) +// Get value for provided key, returns 0 if not found or not valid +int rini_get_value(rini_data data, const char *key) { int value = 0; - for (unsigned int i = 0; i < config.count; i++) + for (unsigned int i = 0; i < data.count; i++) { - if (strcmp(key, config.values[i].key) == 0) // Key found + if (strcmp(key, data.entries[i].key) == 0) // Key found { - value = rini_text_to_int(config.values[i].text); + value = rini_text_to_int(data.entries[i].text); break; } } @@ -564,17 +714,17 @@ int rini_get_config_value(rini_config config, const char *key) return value; } -// Get config value for provided key with default value fallback if not found or not valid -int rini_get_config_value_fallback(rini_config config, const char *key, int fallback) +// Get value for provided key with default value fallback if not found or not valid +int rini_get_value_fallback(rini_data data, const char *key, int fallback) { int value = fallback; - for (unsigned int i = 0; i < config.count; i++) + for (unsigned int i = 0; i < data.count; i++) { - if (strcmp(key, config.values[i].key) == 0) // Key found + if (strcmp(key, data.entries[i].key) == 0) // Key found { // TODO: Detect if conversion fails... - value = rini_text_to_int(config.values[i].text); + value = rini_text_to_int(data.entries[i].text); break; } } @@ -582,16 +732,16 @@ int rini_get_config_value_fallback(rini_config config, const char *key, int fall return value; } -// Get config text for string id -const char *rini_get_config_value_text(rini_config config, const char *key) +// Get text for string id +const char *rini_get_value_text(rini_data data, const char *key) { const char *text = NULL; - for (unsigned int i = 0; i < config.count; i++) + for (unsigned int i = 0; i < data.count; i++) { - if (strcmp(key, config.values[i].key) == 0) // Key found + if (strcmp(key, data.entries[i].key) == 0) // Key found { - text = config.values[i].text; + text = data.entries[i].text; break; } } @@ -599,16 +749,16 @@ const char *rini_get_config_value_text(rini_config config, const char *key) return text; } -// Get config value text for provided key with fallback if not found or not valid -RINIAPI const char *rini_get_config_value_text_fallback(rini_config config, const char *key, const char *fallback) +// Get value text for provided key with fallback if not found or not valid +const char *rini_get_value_text_fallback(rini_data data, const char *key, const char *fallback) { const char *text = fallback; - for (unsigned int i = 0; i < config.count; i++) + for (unsigned int i = 0; i < data.count; i++) { - if (strcmp(key, config.values[i].key) == 0) // Key found + if (strcmp(key, data.entries[i].key) == 0) // Key found { - text = config.values[i].text; + text = data.entries[i].text; break; } } @@ -616,16 +766,16 @@ RINIAPI const char *rini_get_config_value_text_fallback(rini_config config, cons return text; } -// Get config description for string id -const char *rini_get_config_value_description(rini_config config, const char *key) +// Get description for string id +const char *rini_get_value_description(rini_data data, const char *key) { const char *desc = NULL; - for (unsigned int i = 0; i < config.count; i++) + for (unsigned int i = 0; i < data.count; i++) { - if (strcmp(key, config.values[i].key) == 0) // Key found + if (strcmp(key, data.entries[i].key) == 0) // Key found { - desc = config.values[i].desc; + desc = data.entries[i].desc; break; } } @@ -633,78 +783,99 @@ const char *rini_get_config_value_description(rini_config config, const char *ke return desc; } -// Set config comment line -int rini_set_config_comment_line(rini_config *config, const char *comment) +// Set comment line +int rini_set_comment_line(rini_data *data, const char *comment) { int result = -1; - char text[2] = { RINI_LINE_COMMENT_DELIMITER, '\0' }; - - result = rini_set_config_value_text(config, NULL, text, comment); - + + result = rini_set_value_text(data, NULL, NULL, comment); + return result; } -// Set config value and description for existing key or create a new entry -int rini_set_config_value(rini_config *config, const char *key, int value, const char *desc) +// Set value and description for existing key or create a new entry +int rini_set_value(rini_data *data, const char *key, int value, const char *desc) { int result = -1; char value_text[RINI_MAX_TEXT_SIZE] = { 0 }; - sprintf(value_text, "%i", value); + snprintf(value_text, RINI_MAX_TEXT_SIZE, "%i", value); - result = rini_set_config_value_text(config, key, value_text, desc); + result = rini_set_value_text(data, key, value_text, desc); + + if (result == 0) // Value set! + { + // NOTE: Key may have already existed and updated, so + // need to re-find it to update the flag manually + for (unsigned int i = 0; i < data->count; i++) + { + if (strcmp(key, data->entries[i].key) == 0) // Key found + { + data->entries[i].is_text = false; + break; + } + } + } return result; } -// Set config value text and description for existing key or create a new entry +// Set value text and description for existing key or create a new entry // NOTE: When setting a text value, if id does not exist, a new entry is automatically created -int rini_set_config_value_text(rini_config *config, const char *key, const char *text, const char *desc) +int rini_set_value_text(rini_data *data, const char *key, const char *text, const char *desc) { int result = -1; - if ((text == NULL) || (text[0] == '\0')) return result; - if (key != NULL) { // Try to find key and update text and description - for (unsigned int i = 0; i < config->count; i++) + for (unsigned int i = 0; i < data->count; i++) { - if (strcmp(key, config->values[i].key) == 0) // Key found + if (strcmp(key, data->entries[i].key) == 0) // Key found { - memset(config->values[i].text, 0, RINI_MAX_TEXT_SIZE); - memcpy(config->values[i].text, text, strlen(text)); + memset(data->entries[i].text, 0, RINI_MAX_TEXT_SIZE); + if (text != NULL) memcpy(data->entries[i].text, text, strlen(text)); + + if (desc != NULL) + { + // NOTE: Update description only if new one provided, + // do not remove previous description + memset(data->entries[i].desc, 0, RINI_MAX_DESC_SIZE); + memcpy(data->entries[i].desc, desc, strlen(desc)); + } - memset(config->values[i].desc, 0, RINI_MAX_DESC_SIZE); - if (desc != NULL) memcpy(config->values[i].desc, desc, strlen(desc)); result = 0; break; } } } - // Key not found, we add a new entry if possible + // Key not found, adding a new entry if possible if (result == -1) { - if (config->count < config->capacity) + if (data->count < data->capacity) { - // NOTE: Supporting comment line entries - if ((key == NULL) && (text[0] == RINI_LINE_COMMENT_DELIMITER)) + if ((key == NULL) && (text == NULL)) // Comment line { - config->values[config->count].key[0] = '\0'; - config->values[config->count].text[0] = RINI_LINE_COMMENT_DELIMITER; - if (desc != NULL) for (int i = 0; (i < RINI_MAX_DESC_SIZE) && (desc[i] != '\0'); i++) config->values[config->count].desc[i] = desc[i]; - else config->values[config->count].desc[0] = '\0'; + memset(data->entries[data->count].key, 0, RINI_MAX_KEY_SIZE); + memset(data->entries[data->count].text, 0, RINI_MAX_TEXT_SIZE); + + if (desc != NULL) + { + for (int i = 0; (i < RINI_MAX_DESC_SIZE) && (desc[i] != '\0'); i++) data->entries[data->count].desc[i] = desc[i]; + } + else memset(data->entries[data->count].desc, 0, RINI_MAX_DESC_SIZE); } else { - // NOTE: We do a manual copy to avoid possible overflows on input data - for (int i = 0; (i < RINI_MAX_KEY_SIZE) && (key[i] != '\0'); i++) config->values[config->count].key[i] = key[i]; - for (int i = 0; (i < RINI_MAX_TEXT_SIZE) && (text[i] != '\0'); i++) config->values[config->count].text[i] = text[i]; - for (int i = 0; (i < RINI_MAX_DESC_SIZE) && (desc[i] != '\0'); i++) config->values[config->count].desc[i] = desc[i]; + // NOTE: Doing a manual copy to avoid possible overflows on input data + for (int i = 0; (i < RINI_MAX_KEY_SIZE) && (key[i] != '\0'); i++) data->entries[data->count].key[i] = key[i]; + for (int i = 0; (i < RINI_MAX_TEXT_SIZE) && (text[i] != '\0'); i++) data->entries[data->count].text[i] = text[i]; + if (desc != NULL) for (int i = 0; (i < RINI_MAX_DESC_SIZE) && (desc[i] != '\0'); i++) data->entries[data->count].desc[i] = desc[i]; } - config->count++; + data->entries[data->count].is_text = true; + data->count++; result = 0; } } @@ -712,18 +883,18 @@ int rini_set_config_value_text(rini_config *config, const char *key, const char return result; } -// Set config value description for existing key +// Set value description for existing key // WARNING: Key must exist to add description, if a description exists, it is updated -int rini_set_config_value_description(rini_config *config, const char *key, const char *desc) +int rini_set_value_description(rini_data *data, const char *key, const char *desc) { int result = 1; - for (unsigned int i = 0; i < config->count; i++) + for (unsigned int i = 0; i < data->count; i++) { - if (strcmp(key, config->values[i].key) == 0) // Key found + if (strcmp(key, data->entries[i].key) == 0) // Key found { - memset(config->values[i].desc, 0, RINI_MAX_DESC_SIZE); - if (desc != NULL) memcpy(config->values[i].desc, desc, strlen(desc)); + memset(data->entries[i].desc, 0, RINI_MAX_DESC_SIZE); + if (desc != NULL) memcpy(data->entries[i].desc, desc, strlen(desc)); result = 0; break; } @@ -733,10 +904,10 @@ int rini_set_config_value_description(rini_config *config, const char *key, cons } //---------------------------------------------------------------------------------- -// Module internal functions declaration +// Module Internal Functions Declaration //---------------------------------------------------------------------------------- // Get string id from a buffer line containing id-value pair -static int rini_read_config_key(const char *buffer, char *key) +static int rini_read_key(const char *buffer, char *key) { int len = 0; while ((buffer[len] != '\0') && (buffer[len] != ' ') && (buffer[len] != RINI_VALUE_DELIMITER)) len++; // Skip keyentifier @@ -746,14 +917,14 @@ static int rini_read_config_key(const char *buffer, char *key) return len; } -// Get config string-value from a buffer line containing id-value pair -static int rini_read_config_value_text(const char *buffer, char *text, char *desc) +// Get string-value from a buffer line containing id-value pair +static int rini_read_value_text(const char *buffer, char *text, char *desc, bool *is_text) { char *buffer_ptr = (char *)buffer; - // Expected config line structure: + // Expected line structure: // [key][spaces?][delimiter?][spaces?][quot-mark?][textValue][quot-mark?][spaces?][[;][#]description?] - // We need to skip spaces, check for delimiter (if required), skip spaces, and get text value + // NOTE: Processing requires skipping spaces, checking for delimiter (if required), skipping more spaces, and get text value while ((buffer_ptr[0] != '\0') && (buffer_ptr[0] != ' ')) buffer_ptr++; // Skip keyentifier @@ -775,7 +946,7 @@ static int rini_read_config_value_text(const char *buffer, char *text, char *des (buffer_ptr[len] != '\r') && (buffer_ptr[len] != '\n')) len++; // Get text-value and description length (to the end of line) - // Now we got the length from text-value start to end of line + // Now the length from text-value start to end of line is registered int value_len = len; int desc_pos = 0; @@ -805,6 +976,8 @@ static int rini_read_config_value_text(const char *buffer, char *text, char *des // Remove ending quotation-mark from text (if being used) if (buffer_ptr[value_len - 1] == RINI_VALUE_QUOTATION_MARKS) { value_len--; } + + *is_text = true; } #endif