feat: update rini to 3.0

This commit is contained in:
2026-09-19 13:14:43 +02:00
parent dda0d38e22
commit 0c31f2f3e0
+397 -224
View File
@@ -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: * DESCRIPTION:
* Load and save config init properties * Load and save ini-style files with keys and values
* *
* FEATURES: * FEATURES:
* - Config files reading and writing * - Init/Config files reading and writing
* - Supported value types: int, string * - Supported value types: int, string
* - Support comment lines and empty lines * - Support comment lines and empty lines
* - Support custom line comment delimiter * - Support custom line comment delimiter
@@ -14,16 +14,16 @@
* - Support value description comments * - Support value description comments
* - Support custom description custom delimiter * - Support custom description custom delimiter
* - Support multi-word text values w/o quote delimiters * - 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) * - Minimal C standard lib dependency (optional)
* - Customizable maximum config values capacity
* *
* LIMITATIONS: * LIMITATIONS:
* - Config [sections] not supported * - [sections] lines not supported
* - Saving config file requires complete rewrite * - Saving file requires complete rewrite
* *
* POSSIBLE IMPROVEMENTS: * POSSIBLE IMPROVEMENTS:
* - Support config [sections] * - Support disabled key-value entries
* - Support disabled entries recognition
* *
* CONFIGURATION: * CONFIGURATION:
* #define RINI_IMPLEMENTATION * #define RINI_IMPLEMENTATION
@@ -31,9 +31,13 @@
* If not defined, the library is in header only mode and can be included in other headers * 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. * 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 * #define RINI_MAX_LINE_SIZE
* Defines the maximum size of line buffer to read from file. * 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 * #define RINI_MAX_KEY_SIZE
* Defines the maximum size of value key * Defines the maximum size of value key
@@ -41,15 +45,23 @@
* *
* #define RINI_MAX_TEXT_SIZE * #define RINI_MAX_TEXT_SIZE
* Defines the maximum size of value text * Defines the maximum size of value text
* Default value: 128 bytes * Default value: 256 bytes
* *
* #define RINI_MAX_DESC_SIZE * #define RINI_MAX_DESC_SIZE
* Defines the maximum size of value description * Defines the maximum size of value description
* Default value: 128 bytes * Default value: 256 bytes
* *
* #define RINI_MAX_VALUE_CAPACITY * #define RINI_MAX_TEXT_FILE_SIZE
* Defines the maximum number of values supported * Define the maximum size of the file that is saved in memory
* Default value: 128 entries support * 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 RINI_LINE_COMMENT_DELIMITER
* Define character used to comment lines, placed at beginning of line * Define character used to comment lines, placed at beginning of line
@@ -80,10 +92,18 @@
* DEPENDENCIES: C standard library: * DEPENDENCIES: C standard library:
* - stdio.h: fopen(), feof(), fgets(), fclose(), fprintf() * - stdio.h: fopen(), feof(), fgets(), fclose(), fprintf()
* - stdlib.h: malloc(), calloc(), free() * - stdlib.h: malloc(), calloc(), free()
* - string.h: memset(), memcpy(), strcmp(), strlen() * - string.h: memset(), memcpy(), strcmp(), strlen(), snprintf()
* *
* VERSIONS HISTORY: * 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: Use of quotation-marks and marks customization
* ADDED: rini_set_config_comment_line() * ADDED: rini_set_config_comment_line()
* ADDED: rini_get_config_value_fallback(), with fallback return value * ADDED: rini_get_config_value_fallback(), with fallback return value
@@ -120,15 +140,15 @@
#ifndef RINI_H #ifndef RINI_H
#define 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) // 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 // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll
#if defined(_WIN32) #if defined(_WIN32)
#if defined(BUILD_LIBTYPE_SHARED) #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) #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
#endif #endif
@@ -160,24 +180,47 @@
#define RINI_LOG(...) #define RINI_LOG(...)
#endif #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) #if !defined(RINI_MAX_LINE_SIZE)
#define RINI_MAX_LINE_SIZE 512 #define RINI_MAX_LINE_SIZE 512
#endif #endif
// Max entry key size in bytes
#if !defined(RINI_MAX_KEY_SIZE) #if !defined(RINI_MAX_KEY_SIZE)
#define RINI_MAX_KEY_SIZE 64 #define RINI_MAX_KEY_SIZE 64
#endif #endif
// Max entry text size in bytes
#if !defined(RINI_MAX_TEXT_SIZE) #if !defined(RINI_MAX_TEXT_SIZE)
#define RINI_MAX_TEXT_SIZE 128 #define RINI_MAX_TEXT_SIZE 256
#endif #endif
// Max entry description size in bytes
#if !defined(RINI_MAX_DESC_SIZE) #if !defined(RINI_MAX_DESC_SIZE)
#define RINI_MAX_DESC_SIZE 128 #define RINI_MAX_DESC_SIZE 256
#endif #endif
#if !defined(RINI_MAX_VALUE_CAPACITY) // rini text file max size
#define RINI_MAX_VALUE_CAPACITY 128 #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 #endif
// Line comment delimiter (starting string) // Line comment delimiter (starting string)
@@ -211,19 +254,20 @@
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Types and Structures Definition // Types and Structures Definition
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Config value entry // rini entry
typedef struct { typedef struct {
char key[RINI_MAX_KEY_SIZE]; // Config value key identifier char key[RINI_MAX_KEY_SIZE]; // Entry key identifier
char text[RINI_MAX_TEXT_SIZE]; // Config value text char text[RINI_MAX_TEXT_SIZE]; // Entry text
char desc[RINI_MAX_DESC_SIZE]; // Config value description char desc[RINI_MAX_DESC_SIZE]; // Entry description
} rini_config_value; bool is_text; // Entry should be considered as text
} rini_entry;
// Config data // rini data
typedef struct { typedef struct {
rini_config_value *values; // Config values array rini_entry *entries; // Entries array
unsigned int count; // Config values count unsigned int count; // Entries count
unsigned int capacity; // Config values capacity unsigned int capacity; // Entries capacity
} rini_config; } rini_data;
#if defined(__cplusplus) #if defined(__cplusplus)
extern "C" { // Prevents name mangling of functions extern "C" { // Prevents name mangling of functions
@@ -232,29 +276,30 @@ extern "C" { // Prevents name mangling of functions
//------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------
// Functions declaration // 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_data rini_load(const char *file_name); // Load data from file (*.ini) or create a new rini object (pass NULL)
RINIAPI rini_config rini_load_config_from_memory(const char *text); // Load config from text buffer RINIAPI rini_data rini_load_full(const char *file_name); // Load data from file (*.ini) including full comment lines, useful for editing
RINIAPI void rini_save_config(rini_config config, const char *file_name); // Save config to file, with custom header RINIAPI rini_data rini_load_from_memory(const char *text); // Load data from text buffer
RINIAPI char *rini_save_config_to_memory(rini_config config); // Save config to text buffer ('\0' EOL) RINIAPI void rini_save(rini_data data, const char *file_name); // Save data to file, with custom header
RINIAPI void rini_unload_config(rini_config *config); // Unload config data from memory 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 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_config_value_text(rini_config config, const char *key); // Get config value text for provided key RINIAPI const char *rini_get_value_text(rini_data data, const char *key); // Get 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 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 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_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 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 // 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_value(rini_data *data, 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_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 // 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 #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_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_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() 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) // Load data from file (.ini)
rini_config rini_load_config(const char *file_name) // 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; unsigned int value_counter = 0;
// Init config data to max capacity // Init data to max capacity
config.capacity = RINI_MAX_VALUE_CAPACITY; data.capacity = RINI_MAX_ENTRY_CAPACITY;
config.values = (rini_config_value *)RINI_CALLOC(RINI_MAX_VALUE_CAPACITY, sizeof(rini_config_value)); data.entries = (rini_entry *)RINI_CALLOC(RINI_MAX_ENTRY_CAPACITY, sizeof(rini_entry));
if (file_name != NULL) 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 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)) while (fgets(buffer, RINI_MAX_LINE_SIZE, rini_file))
{ {
// WARNING: fgets() keeps line endings, doesn't have any special options for converting line endings, // 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 // but on Windows, when reading file 'rt', line endings are converted from \r\n to just \n
// Skip commented lines and empty lines // 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) && if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) &&
(buffer[0] != RINI_LINE_SECTION_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 // WARNING: No more values can be stored than its max capacity
config.count = (value_counter > RINI_MAX_VALUE_CAPACITY)? RINI_MAX_VALUE_CAPACITY : value_counter; 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); rewind(rini_file);
value_counter = 0; value_counter = 0;
// Second pass to read config data // Second pass to read data
while (fgets(buffer, RINI_MAX_LINE_SIZE, rini_file)) while (fgets(buffer, RINI_MAX_LINE_SIZE, rini_file))
{ {
// WARNING: fgets() keeps line endings, doesn't have any special options for converting line endings, // 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 // Skip commented lines and empty lines
if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) && if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) &&
(buffer[0] != RINI_LINE_SECTION_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 // Get key identifier string
memset(config.values[value_counter].key, 0, RINI_MAX_KEY_SIZE); memset(data.entries[value_counter].key, 0, RINI_MAX_KEY_SIZE);
rini_read_config_key(buffer, config.values[value_counter].key); rini_read_key(buffer, data.entries[value_counter].key);
rini_read_config_value_text(buffer, config.values[value_counter].text, config.values[value_counter].desc); rini_read_value_text(buffer, data.entries[value_counter].text, data.entries[value_counter].desc, &data.entries[value_counter].is_text);
value_counter++; value_counter++;
// Stop reading if first count reached to avoid overflow in case count == RINI_MAX_VALUE_CAPACITY // Stop reading if first count reached to avoid overflow in case count == RINI_MAX_ENTRY_CAPACITY
if (value_counter >= config.count) break; 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 // Load data from file (.ini) including all comments and empty lines
// NOTE: Comments and empty lines are ignored rini_data rini_load_full(const char *file_name)
rini_config rini_load_config_from_memory(const char *text)
{ {
#define RINI_MAX_TEXT_LINES RINI_MAX_VALUE_CAPACITY*2 // Consider possible comments and empty lines rini_data data = { 0 };
rini_config config = { 0 };
unsigned int value_counter = 0; unsigned int value_counter = 0;
// Init config data to max capacity // Init data to max capacity
config.capacity = RINI_MAX_VALUE_CAPACITY; data.capacity = RINI_MAX_ENTRY_CAPACITY;
config.values = (rini_config_value *)RINI_CALLOC(RINI_MAX_VALUE_CAPACITY, sizeof(rini_config_value)); 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) 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++) for (int l = 0; l < line_counter; l++)
{ {
// Skip commented lines and empty lines // 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) && if ((lines[l][0] != RINI_LINE_COMMENT_DELIMITER) &&
(lines[l][0] != RINI_LINE_SECTION_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 // WARNING: No more values can be stored than its max capacity
config.count = (value_counter > RINI_MAX_VALUE_CAPACITY)? RINI_MAX_VALUE_CAPACITY : value_counter; data.count = (value_counter > RINI_MAX_ENTRY_CAPACITY)? RINI_MAX_ENTRY_CAPACITY : value_counter;
// Process lines to get keys and values // Process lines to get keys and values
if (config.count > 0) if (data.count > 0)
{ {
value_counter = 0; value_counter = 0;
// Second pass to read config data // Second pass to read data
for (int l = 0; l < line_counter; l++) for (int l = 0; l < line_counter; l++)
{ {
// Skip commented lines and empty lines // Skip commented lines and empty lines
if ((lines[l][0] != RINI_LINE_COMMENT_DELIMITER) && if ((lines[l][0] != RINI_LINE_COMMENT_DELIMITER) &&
(lines[l][0] != RINI_LINE_SECTION_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 // Get key identifier string
memset(config.values[value_counter].key, 0, RINI_MAX_KEY_SIZE); memset(data.entries[value_counter].key, 0, RINI_MAX_KEY_SIZE);
rini_read_config_key(lines[l], config.values[value_counter].key); rini_read_key(lines[l], data.entries[value_counter].key);
rini_read_config_value_text(lines[l], config.values[value_counter].text, config.values[value_counter].desc); rini_read_value_text(lines[l], data.entries[value_counter].text, data.entries[value_counter].desc, &data.entries[value_counter].is_text);
value_counter++; value_counter++;
// Stop reading if first count reached to avoid overflow in case count == RINI_MAX_VALUE_CAPACITY // Stop reading if first count reached to avoid overflow in case count == RINI_MAX_ENTRY_CAPACITY
if (value_counter >= config.count) break; if (value_counter >= data.count) break;
} }
} }
} }
} }
return config; return data;
} }
// Save config to file (*.ini) // Save data to file (*.ini)
void rini_save_config(rini_config config, const char *file_name) void rini_save(rini_data data, const char *file_name)
{ {
FILE *rini_file = fopen(file_name, "wt"); FILE *rini_file = fopen(file_name, "wt");
if (rini_file != NULL) 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
{
if ((config.values[i].key[0] == '\0') && (config.values[i].text[0] == RINI_LINE_COMMENT_DELIMITER))
{
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);
}
else
{
#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) for (unsigned int i = 0; i < data.count; i++)
{ {
fprintf(rini_file, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, if ((data.entries[i].key[0] == '\0') && (data.entries[i].text[0] == '\0'))
config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc); {
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 else
{ {
fprintf(rini_file, "%-28s %c %c%6s%c %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, memset(valuestr, 0, RINI_MAX_TEXT_SIZE + 2);
RINI_VALUE_QUOTATION_MARKS, config.values[i].text, RINI_VALUE_QUOTATION_MARKS, #if RINI_USE_TEXT_QUOTATION_MARKS
RINI_DESCRIPTION_DELIMITER, config.values[i].desc); // 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 #else
fprintf(rini_file, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, snprintf(valuestr, RINI_MAX_TEXT_SIZE + 2, "%s", data.entries[i].text);
config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc);
#endif #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);
}
} }
} }
@@ -484,79 +632,81 @@ void rini_save_config(rini_config config, const char *file_name)
} }
} }
// Save config to text buffer ('\0' EOL) // Save data to text buffer ('\0' EOL)
char *rini_save_config_to_memory(rini_config config) char *rini_save_to_memory(rini_data data)
{ {
#define RINI_MAX_TEXT_FILE_SIZE 4096 // Verify required data size is smaller than memory buffer size
// NOTE: Adding 64 extra possible characters by entry line
// Verify required config size is smaller than memory buffer size
// NOTE: We add 64 extra possible characters by entry line
int requiredSize = 0; 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); 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 config.ini size is bigger than max supported memory size, increase RINI_MAX_TEXT_FILE_SIZE\n"); 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 // NOTE: Using a static buffer to avoid de-allocation requirement on user side
static char text[RINI_MAX_TEXT_FILE_SIZE] = { 0 }; static char text[RINI_MAX_TEXT_FILE_SIZE] = { 0 };
memset(text, 0, RINI_MAX_TEXT_FILE_SIZE); memset(text, 0, RINI_MAX_TEXT_FILE_SIZE);
int offset = 0; 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); 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 offset += sprintf(text + offset, "%c\n", RINI_LINE_COMMENT_DELIMITER); else expectedByteWritten = snprintf(text + offset, RINI_MAX_LINE_SIZE, "%c\n", RINI_LINE_COMMENT_DELIMITER);
} }
else else
{ {
memset(valuestr, 0, RINI_MAX_TEXT_SIZE + 2);
#if RINI_USE_TEXT_QUOTATION_MARKS #if RINI_USE_TEXT_QUOTATION_MARKS
// If text is not an integer value, append text quotation-marks // Add quotation marks if required
// TODO: Check also for float values? atof() 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);
bool valueIsInt = false; #else
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') snprintf(valuestr, RINI_MAX_TEXT_SIZE + 2, "%s", data.entries[i].text);
if (((value == 0) && (strlen(config.values[i].text) == 1) && (config.values[i].text[0] == '0')) || (value != 0)) valueIsInt = true; #endif
// Add description if required
if (valueIsInt) if (data.entries[i].desc[0] != '\0')
{ {
offset += sprintf(text + offset, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, expectedByteWritten = snprintf(text + offset, RINI_MAX_LINE_SIZE, "%-*s %c %-*s %c %s\n", RINI_KEY_SPACING, data.entries[i].key, RINI_VALUE_DELIMITER,
config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc); RINI_VALUE_SPACING, data.entries[i].is_text? valuestr : data.entries[i].text,
RINI_DESCRIPTION_DELIMITER, data.entries[i].desc);
} }
else else
{ {
offset += sprintf(text + offset, "%-28s %c %c%6s%c %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER, // No description required
RINI_VALUE_QUOTATION_MARKS, config.values[i].text, RINI_VALUE_QUOTATION_MARKS, expectedByteWritten = snprintf(text + offset, RINI_MAX_LINE_SIZE, "%-*s %c %s\n", RINI_KEY_SPACING, data.entries[i].key, RINI_VALUE_DELIMITER,
RINI_DESCRIPTION_DELIMITER, config.values[i].desc); 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; return text;
} }
// Unload config data // Unload data
void rini_unload_config(rini_config *config) void rini_unload(rini_data *data)
{ {
RINI_FREE(config->values); RINI_FREE(data->entries);
config->values = NULL; data->entries = NULL;
config->count = 0; data->count = 0;
config->capacity = 0; data->capacity = 0;
} }
// Get config value for provided key, returns 0 if not found or not valid // Get value for provided key, returns 0 if not found or not valid
int rini_get_config_value(rini_config config, const char *key) int rini_get_value(rini_data data, const char *key)
{ {
int value = 0; 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; break;
} }
} }
@@ -564,17 +714,17 @@ int rini_get_config_value(rini_config config, const char *key)
return value; return value;
} }
// Get config value for provided key with default value fallback if not found or not valid // Get 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) int rini_get_value_fallback(rini_data data, const char *key, int fallback)
{ {
int value = 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... // TODO: Detect if conversion fails...
value = rini_text_to_int(config.values[i].text); value = rini_text_to_int(data.entries[i].text);
break; break;
} }
} }
@@ -582,16 +732,16 @@ int rini_get_config_value_fallback(rini_config config, const char *key, int fall
return value; return value;
} }
// Get config text for string id // Get text for string id
const char *rini_get_config_value_text(rini_config config, const char *key) const char *rini_get_value_text(rini_data data, const char *key)
{ {
const char *text = NULL; 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; break;
} }
} }
@@ -599,16 +749,16 @@ const char *rini_get_config_value_text(rini_config config, const char *key)
return text; return text;
} }
// Get config value text for provided key with fallback if not found or not valid // Get 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) const char *rini_get_value_text_fallback(rini_data data, const char *key, const char *fallback)
{ {
const char *text = 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; break;
} }
} }
@@ -616,16 +766,16 @@ RINIAPI const char *rini_get_config_value_text_fallback(rini_config config, cons
return text; return text;
} }
// Get config description for string id // Get description for string id
const char *rini_get_config_value_description(rini_config config, const char *key) const char *rini_get_value_description(rini_data data, const char *key)
{ {
const char *desc = NULL; 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; break;
} }
} }
@@ -633,78 +783,99 @@ const char *rini_get_config_value_description(rini_config config, const char *ke
return desc; return desc;
} }
// Set config comment line // Set comment line
int rini_set_config_comment_line(rini_config *config, const char *comment) int rini_set_comment_line(rini_data *data, const char *comment)
{ {
int result = -1; 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; return result;
} }
// Set config value and description for existing key or create a new entry // Set 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) int rini_set_value(rini_data *data, const char *key, int value, const char *desc)
{ {
int result = -1; int result = -1;
char value_text[RINI_MAX_TEXT_SIZE] = { 0 }; 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; 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 // 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; int result = -1;
if ((text == NULL) || (text[0] == '\0')) return result;
if (key != NULL) if (key != NULL)
{ {
// Try to find key and update text and description // 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); memset(data->entries[i].text, 0, RINI_MAX_TEXT_SIZE);
memcpy(config->values[i].text, text, strlen(text)); 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; result = 0;
break; break;
} }
} }
} }
// Key not found, we add a new entry if possible // Key not found, adding a new entry if possible
if (result == -1) if (result == -1)
{ {
if (config->count < config->capacity) if (data->count < data->capacity)
{ {
// NOTE: Supporting comment line entries if ((key == NULL) && (text == NULL)) // Comment line
if ((key == NULL) && (text[0] == RINI_LINE_COMMENT_DELIMITER))
{ {
config->values[config->count].key[0] = '\0'; memset(data->entries[data->count].key, 0, RINI_MAX_KEY_SIZE);
config->values[config->count].text[0] = RINI_LINE_COMMENT_DELIMITER; 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++) config->values[config->count].desc[i] = desc[i];
else config->values[config->count].desc[0] = '\0'; 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 else
{ {
// NOTE: We do a manual copy to avoid possible overflows on input data // 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++) config->values[config->count].key[i] = key[i]; 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++) config->values[config->count].text[i] = text[i]; for (int i = 0; (i < RINI_MAX_TEXT_SIZE) && (text[i] != '\0'); i++) data->entries[data->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]; 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; result = 0;
} }
} }
@@ -712,18 +883,18 @@ int rini_set_config_value_text(rini_config *config, const char *key, const char
return result; 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 // 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; 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); memset(data->entries[i].desc, 0, RINI_MAX_DESC_SIZE);
if (desc != NULL) memcpy(config->values[i].desc, desc, strlen(desc)); if (desc != NULL) memcpy(data->entries[i].desc, desc, strlen(desc));
result = 0; result = 0;
break; 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 // 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; int len = 0;
while ((buffer[len] != '\0') && (buffer[len] != ' ') && (buffer[len] != RINI_VALUE_DELIMITER)) len++; // Skip keyentifier 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; return len;
} }
// Get config string-value from a buffer line containing id-value pair // Get string-value from a buffer line containing id-value pair
static int rini_read_config_value_text(const char *buffer, char *text, char *desc) static int rini_read_value_text(const char *buffer, char *text, char *desc, bool *is_text)
{ {
char *buffer_ptr = (char *)buffer; char *buffer_ptr = (char *)buffer;
// Expected config line structure: // Expected line structure:
// [key][spaces?][delimiter?][spaces?][quot-mark?][textValue][quot-mark?][spaces?][[;][#]description?] // [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 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] != '\r') &&
(buffer_ptr[len] != '\n')) len++; // Get text-value and description length (to the end of line) (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 value_len = len;
int desc_pos = 0; 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) // Remove ending quotation-mark from text (if being used)
if (buffer_ptr[value_len - 1] == RINI_VALUE_QUOTATION_MARKS) { value_len--; } if (buffer_ptr[value_len - 1] == RINI_VALUE_QUOTATION_MARKS) { value_len--; }
*is_text = true;
} }
#endif #endif