/********************************************************************************************** * * rini v3.0 - A simple and easy-to-use ini-style files reader and writer * * DESCRIPTION: * Load and save ini-style files with keys and values * * FEATURES: * - Init/Config files reading and writing * - Supported value types: int, string * - Support comment lines and empty lines * - Support custom line comment delimiter * - Support custom value delimiters * - 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) * * LIMITATIONS: * - [sections] lines not supported * - Saving file requires complete rewrite * * POSSIBLE IMPROVEMENTS: * - Support disabled key-value entries * * CONFIGURATION: * #define RINI_IMPLEMENTATION * Generates the implementation of the library into the included file. * 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] + spacing + quotes) * * #define RINI_MAX_KEY_SIZE * Defines the maximum size of value key * Default value: 64 bytes * * #define RINI_MAX_TEXT_SIZE * Defines the maximum size of value text * Default value: 256 bytes * * #define RINI_MAX_DESC_SIZE * Defines the maximum size of value description * Default value: 256 bytes * * #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 * Most .ini files use semicolon ';' but '#' is also used * Default value: '#' * * #define RINI_LINE_SECTION_DELIMITER * Defines section lines start character * Sections loading is not supported, lines are just skipped for now * Default value: '[' * * #define RINI_VALUE_DELIMITER * Defines a key value delimiter, in case it is defined. * Most .ini files use '=' as value delimiter but ':' is also used or just whitespace * Default value: ' ' * * #define RINI_VALUE_QUOTATION_MARKS * 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: '\"' * * #define RINI_DESCRIPTION_DELIMITER * Defines a property line-end comment delimiter * This implementation allows adding inline comments after the value. * Default value: '#' * * DEPENDENCIES: C standard library: * - stdio.h: fopen(), feof(), fgets(), fclose(), fprintf() * - stdlib.h: malloc(), calloc(), free() * - string.h: memset(), memcpy(), strcmp(), strlen(), snprintf() * * VERSIONS HISTORY: * 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 * ADDED: rini_get_config_text_fallback(), with fallback return value * REMOVED: Config header requirement, use comments entries * REVIEWED: Some configuration default values, capacities * REVIEWED: rini_save_config(), removed parameter * REVIEWED: rini_save_config_from_memory(), removed parameter * * 1.0 (18-May-2023) First release, basic read/write functionality * * * LICENSE: zlib/libpng * * Copyright (c) 2023-2025 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, including commercial * applications, and to alter it and redistribute it freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not claim that you * wrote the original software. If you use this software in a product, an acknowledgment * in the product documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be misrepresented * as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * **********************************************************************************************/ #ifndef RINI_H #define RINI_H #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) // Building the library as a Win32 shared library (.dll) #elif defined(USE_LIBTYPE_SHARED) #define RINIAPI __declspec(dllimport) // Using the library as a Win32 shared library (.dll) #endif #endif // Function specifiers definition #ifndef RINIAPI #define RINIAPI // Functions defined as 'extern' by default (implicit specifiers) #endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- // Allow custom memory allocators #ifndef RINI_MALLOC #define RINI_MALLOC(sz) malloc(sz) #endif #ifndef RINI_CALLOC #define RINI_CALLOC(n,sz) calloc(n,sz) #endif #ifndef RINI_FREE #define RINI_FREE(p) free(p) #endif // Simple log system to avoid printf() calls if required // NOTE: Avoiding those calls, also avoids const strings memory usage #define RINI_SUPPORT_LOG_INFO #if defined(RINI_SUPPORT_LOG_INFO) #define RINI_LOG(...) printf(__VA_ARGS__) #else #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 256 #endif // Max entry description size in bytes #if !defined(RINI_MAX_DESC_SIZE) #define RINI_MAX_DESC_SIZE 256 #endif // 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) #if !defined(RINI_LINE_COMMENT_DELIMITER) #define RINI_LINE_COMMENT_DELIMITER '#' #endif // Line section delimiter -NOT USED- #if !defined(RINI_LINE_SECTION_DELIMITER) #define RINI_LINE_SECTION_DELIMITER '[' #endif // Value delimiter, separator between key and value #if !defined(RINI_VALUE_DELIMITER) #define RINI_VALUE_DELIMITER ' ' #endif // Use quotation marks for text values // NOTE: Integer values do not use quotation-marks #define RINI_USE_TEXT_QUOTATION_MARKS 1 // Text value quoation marks #if !defined(RINI_VALUE_QUOTATION_MARKS) #define RINI_VALUE_QUOTATION_MARKS '\"' #endif // Description delimiter, separator between value and description #if !defined(RINI_DESCRIPTION_DELIMITER) #define RINI_DESCRIPTION_DELIMITER '#' #endif //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- // rini entry typedef struct { 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; // rini data typedef struct { 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 #endif //------------------------------------------------------------------------------------ // Functions declaration //------------------------------------------------------------------------------------ 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_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_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_comment_line(rini_data *data, const char *comment); // Set comment line // 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_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 value description for existing key // WARNING: Key must exist to add description, if a description exists, it is updated RINIAPI int rini_set_value_description(rini_data *data, const char *key, const char *desc); #ifdef __cplusplus } #endif #endif // RINI_H /*********************************************************************************** * * RINI IMPLEMENTATION * ************************************************************************************/ #if defined(RINI_IMPLEMENTATION) #include // Required for: fopen(), feof(), fgets(), fclose(), fprintf() #include // Required for: malloc(), calloc(), free() #include // Required for: memset(), memcpy(), strcmp(), strlen() //---------------------------------------------------------------------------------- // Defines and macros //---------------------------------------------------------------------------------- // ... //---------------------------------------------------------------------------------- // Global variables definition //---------------------------------------------------------------------------------- // ... //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- 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 //---------------------------------------------------------------------------------- // 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_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 (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 // Skip commented lines and empty lines // NOTE: Also skipping sections delimiters if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) && (buffer[0] != RINI_LINE_SECTION_DELIMITER) && (buffer[0] != '\n') && (buffer[0] != '\r') && (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 // Skip commented lines and empty lines if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) && (buffer[0] != RINI_LINE_SECTION_DELIMITER) && (buffer[0] != '\n') && (buffer[0] != '\r') && (buffer[0] != '\0')) { // 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 file (.ini) including all comments and empty lines rini_data rini_load_full(const char *file_name) { 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 (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) { // Split text by line-breaks const char *lines[RINI_MAX_TEXT_LINES] = { 0 }; int textSize = (int)strlen(text); lines[0] = text; int line_counter = 1; for (int i = 0, k = 1; (i < textSize) && (line_counter < RINI_MAX_TEXT_LINES); i++) { if (text[i] == '\n') { lines[k] = &text[i + 1]; // WARNING: next value is valid? line_counter += 1; k++; } } // Count possible values in lines for (int l = 0; l < line_counter; l++) { // Skip commented lines and empty lines // 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] != '\r') && (lines[l][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; // Process lines to get keys and values if (data.count > 0) { value_counter = 0; // 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] != '\r') && (lines[l][0] != '\0')) { // Get key identifier string 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_ENTRY_CAPACITY if (value_counter >= data.count) break; } } } } return data; } // 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) { 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 ((data.entries[i].key[0] == '\0') && (data.entries[i].text[0] == '\0')) { 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 // 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, "%-*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 { // 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); } } } fclose(rini_file); } } // Save data to text buffer ('\0' EOL) char *rini_save_to_memory(rini_data data) { // 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 < 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; 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 ((data.entries[i].key[0] == '\0') && (data.entries[i].text[0] == 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 // 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') { 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 { // 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); } } if (expectedByteWritten >= RINI_MAX_LINE_SIZE) offset += RINI_MAX_LINE_SIZE; else offset += expectedByteWritten; } return text; } // Unload data void rini_unload(rini_data *data) { RINI_FREE(data->entries); data->entries = NULL; data->count = 0; data->capacity = 0; } // 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 < data.count; i++) { if (strcmp(key, data.entries[i].key) == 0) // Key found { value = rini_text_to_int(data.entries[i].text); break; } } return value; } // 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 < data.count; i++) { if (strcmp(key, data.entries[i].key) == 0) // Key found { // TODO: Detect if conversion fails... value = rini_text_to_int(data.entries[i].text); break; } } return value; } // 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 < data.count; i++) { if (strcmp(key, data.entries[i].key) == 0) // Key found { text = data.entries[i].text; break; } } return text; } // 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 < data.count; i++) { if (strcmp(key, data.entries[i].key) == 0) // Key found { text = data.entries[i].text; break; } } return text; } // 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 < data.count; i++) { if (strcmp(key, data.entries[i].key) == 0) // Key found { desc = data.entries[i].desc; break; } } return desc; } // Set comment line int rini_set_comment_line(rini_data *data, const char *comment) { int result = -1; result = rini_set_value_text(data, NULL, NULL, comment); return result; } // 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 }; snprintf(value_text, RINI_MAX_TEXT_SIZE, "%i", value); 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 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_value_text(rini_data *data, const char *key, const char *text, const char *desc) { int result = -1; if (key != NULL) { // Try to find key and update text and description for (unsigned int i = 0; i < data->count; i++) { if (strcmp(key, data->entries[i].key) == 0) // Key found { 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)); } result = 0; break; } } } // Key not found, adding a new entry if possible if (result == -1) { if (data->count < data->capacity) { if ((key == NULL) && (text == NULL)) // Comment line { 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: 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]; } data->entries[data->count].is_text = true; data->count++; result = 0; } } return result; } // Set value description for existing key // WARNING: Key must exist to add description, if a description exists, it is updated int rini_set_value_description(rini_data *data, const char *key, const char *desc) { int result = 1; for (unsigned int i = 0; i < data->count; i++) { if (strcmp(key, data->entries[i].key) == 0) // Key found { memset(data->entries[i].desc, 0, RINI_MAX_DESC_SIZE); if (desc != NULL) memcpy(data->entries[i].desc, desc, strlen(desc)); result = 0; break; } } return result; } //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- // Get string id from a buffer line containing id-value pair 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 memcpy(key, buffer, len); return len; } // 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 line structure: // [key][spaces?][delimiter?][spaces?][quot-mark?][textValue][quot-mark?][spaces?][[;][#]description?] // 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 while ((buffer_ptr[0] != '\0') && (buffer_ptr[0] == ' ')) buffer_ptr++; // Skip line spaces before text value or delimiter #if defined(RINI_VALUE_DELIMITER) if (buffer_ptr[0] == RINI_VALUE_DELIMITER) { buffer_ptr++; // Skip delimiter while ((buffer_ptr[0] != '\0') && (buffer_ptr[0] == ' ')) buffer_ptr++; // Skip line spaces before text value } #endif // Now buffer_ptr should be pointing to the start of value int len = 0; while ((buffer_ptr[len] != '\0') && (buffer_ptr[len] != '\r') && (buffer_ptr[len] != '\n')) len++; // Get text-value and description length (to the end of line) // Now the length from text-value start to end of line is registered int value_len = len; int desc_pos = 0; #if defined(RINI_DESCRIPTION_DELIMITER) // Scan text looking for text-value description (if used) for (; desc_pos < len; desc_pos++) { if (buffer_ptr[desc_pos] == RINI_DESCRIPTION_DELIMITER) { value_len = desc_pos - 1; while (buffer_ptr[value_len] == ' ') value_len--; value_len++; desc_pos++; // Skip delimiter and following spaces while (buffer_ptr[desc_pos] == ' ') desc_pos++; break; } } #endif #if RINI_USE_TEXT_QUOTATION_MARKS // Remove starting quotation-mark from text (if being used) if (buffer_ptr[0] == RINI_VALUE_QUOTATION_MARKS) { buffer_ptr++; desc_pos--; len--; value_len--; // 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 // Clear text buffers to be updated memset(text, 0, RINI_MAX_TEXT_SIZE); memset(desc, 0, RINI_MAX_DESC_SIZE); // Copy value-text and description to provided pointers memcpy(text, buffer_ptr, value_len); memcpy(desc, buffer_ptr + desc_pos, ((len - desc_pos) > (RINI_MAX_DESC_SIZE - 1))? (RINI_MAX_DESC_SIZE - 1) : (len - desc_pos)); return len; } // Convert text to int value (if possible), same as atoi() static int rini_text_to_int(const char *text) { int value = 0; int sign = 1; if ((text[0] == '+') || (text[0] == '-')) { if (text[0] == '-') sign = -1; text++; } int i = 0; for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10 + (int)(text[i] - '0'); //if (strlen(text) != i) // Text starts with numbers but contains some text after that... return value*sign; } #endif // RINI_IMPLEMENTATION