Files
sinbad/external/rini/rini.h
T
2025-04-11 22:01:30 +02:00

843 lines
32 KiB
C

/**********************************************************************************************
*
* rini v1.0 - A simple and easy-to-use config init files reader and writer
*
* DESCRIPTION:
* Load and save config init properties
*
* FEATURES:
* - 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
* - Minimal C standard lib dependency (optional)
* - Customizable maximum config values capacity
*
* LIMITATIONS:
* - Config [sections] not supported
* - Saving config file requires complete rewrite
*
* POSSIBLE IMPROVEMENTS:
* - Support config [sections]
* - Support disabled entries recognition
*
* 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_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)
*
* #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: 128 bytes
*
* #define RINI_MAX_DESC_SIZE
* Defines the maximum size of value description
* Default value: 128 bytes
*
* #define RINI_MAX_VALUE_CAPACITY
* Defines the maximum number of values supported
* Default value: 128 entries support
*
* #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()
*
* VERSIONS HISTORY:
* 2.0 (26-Jan-2023) 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 "1.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)
#elif defined(USE_LIBTYPE_SHARED)
#define RINIAPI __declspec(dllimport) // We are 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
#if !defined(RINI_MAX_LINE_SIZE)
#define RINI_MAX_LINE_SIZE 512
#endif
#if !defined(RINI_MAX_KEY_SIZE)
#define RINI_MAX_KEY_SIZE 64
#endif
#if !defined(RINI_MAX_TEXT_SIZE)
#define RINI_MAX_TEXT_SIZE 128
#endif
#if !defined(RINI_MAX_DESC_SIZE)
#define RINI_MAX_DESC_SIZE 128
#endif
#if !defined(RINI_MAX_VALUE_CAPACITY)
#define RINI_MAX_VALUE_CAPACITY 128
#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
//----------------------------------------------------------------------------------
// Config value 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;
// Config data
typedef struct {
rini_config_value *values; // Config values array
unsigned int count; // Config values count
unsigned int capacity; // Config values capacity
} rini_config;
#if defined(__cplusplus)
extern "C" { // Prevents name mangling of functions
#endif
//------------------------------------------------------------------------------------
// 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 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_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_set_config_comment_line(rini_config *config, const char *comment); // Set config comment line
// Set config 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);
// Set config 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);
#ifdef __cplusplus
}
#endif
#endif // RINI_H
/***********************************************************************************
*
* RINI IMPLEMENTATION
*
************************************************************************************/
#if defined(RINI_IMPLEMENTATION)
#include <stdio.h> // Required for: fopen(), feof(), fgets(), fclose(), fprintf()
#include <stdlib.h> // Required for: malloc(), calloc(), free()
#include <string.h> // Required for: memset(), memcpy(), strcmp(), strlen()
//----------------------------------------------------------------------------------
// Defines and macros
//----------------------------------------------------------------------------------
// ...
//----------------------------------------------------------------------------------
// Global variables definition
//----------------------------------------------------------------------------------
// ...
//----------------------------------------------------------------------------------
// 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_text_to_int(const char *text); // Convert text to int value (if possible), same as atoi()
//----------------------------------------------------------------------------------
// Module functions definition
//----------------------------------------------------------------------------------
// Load config from file (.ini)
rini_config rini_load_config(const char *file_name)
{
rini_config config = { 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));
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 config 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
if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) &&
(buffer[0] != RINI_LINE_SECTION_DELIMITER) &&
(buffer[0] != '\n') && (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;
if (config.count > 0)
{
rewind(rini_file);
value_counter = 0;
// Second pass to read config 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] != '\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);
value_counter++;
// Stop reading if first count reached to avoid overflow in case count == RINI_MAX_VALUE_CAPACITY
if (value_counter >= config.count) break;
}
}
}
fclose(rini_file);
}
}
return config;
}
// Load config from text buffer
// NOTE: Comments and empty lines are ignored
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_config config = { 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));
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: We are 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++;
}
// 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;
// Process lines to get keys and values
if (config.count > 0)
{
value_counter = 0;
// Second pass to read config 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'))
{
// 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);
value_counter++;
// Stop reading if first count reached to avoid overflow in case count == RINI_MAX_VALUE_CAPACITY
if (value_counter >= config.count) break;
}
}
}
}
return config;
}
// Save config to file (*.ini)
void rini_save_config(rini_config config, const char *file_name)
{
FILE *rini_file = fopen(file_name, "wt");
if (rini_file != NULL)
{
for (unsigned int i = 0; i < config.count; i++)
{
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)
{
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);
}
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);
}
#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
}
}
fclose(rini_file);
}
}
// Save config to text buffer ('\0' EOL)
char *rini_save_config_to_memory(rini_config config)
{
#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
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");
// 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;
for (unsigned int i = 0; i < config.count; i++)
{
if ((config.values[i].key[0] == '\0') && (config.values[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);
}
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)
{
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);
}
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);
}
#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
}
}
return text;
}
// Unload config data
void rini_unload_config(rini_config *config)
{
RINI_FREE(config->values);
config->values = NULL;
config->count = 0;
config->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)
{
int value = 0;
for (unsigned int i = 0; i < config.count; i++)
{
if (strcmp(key, config.values[i].key) == 0) // Key found
{
value = rini_text_to_int(config.values[i].text);
break;
}
}
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)
{
int value = fallback;
for (unsigned int i = 0; i < config.count; i++)
{
if (strcmp(key, config.values[i].key) == 0) // Key found
{
// TODO: Detect if conversion fails...
value = rini_text_to_int(config.values[i].text);
break;
}
}
return value;
}
// Get config text for string id
const char *rini_get_config_value_text(rini_config config, const char *key)
{
const char *text = NULL;
for (unsigned int i = 0; i < config.count; i++)
{
if (strcmp(key, config.values[i].key) == 0) // Key found
{
text = config.values[i].text;
break;
}
}
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)
{
const char *text = fallback;
for (unsigned int i = 0; i < config.count; i++)
{
if (strcmp(key, config.values[i].key) == 0) // Key found
{
text = config.values[i].text;
break;
}
}
return text;
}
// Get config description for string id
const char *rini_get_config_value_description(rini_config config, const char *key)
{
const char *desc = NULL;
for (unsigned int i = 0; i < config.count; i++)
{
if (strcmp(key, config.values[i].key) == 0) // Key found
{
desc = config.values[i].desc;
break;
}
}
return desc;
}
// Set config comment line
int rini_set_config_comment_line(rini_config *config, const char *comment)
{
int result = -1;
char text[2] = { RINI_LINE_COMMENT_DELIMITER, '\0' };
result = rini_set_config_value_text(config, NULL, text, 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)
{
int result = -1;
char value_text[RINI_MAX_TEXT_SIZE] = { 0 };
sprintf(value_text, "%i", value);
result = rini_set_config_value_text(config, key, value_text, desc);
return result;
}
// Set config 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 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++)
{
if (strcmp(key, config->values[i].key) == 0) // Key found
{
memset(config->values[i].text, 0, RINI_MAX_TEXT_SIZE);
memcpy(config->values[i].text, text, strlen(text));
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
if (result == -1)
{
if (config->count < config->capacity)
{
// NOTE: Supporting comment line entries
if ((key == NULL) && (text[0] == RINI_LINE_COMMENT_DELIMITER))
{
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';
}
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];
}
config->count++;
result = 0;
}
}
return result;
}
// Set config 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 result = 1;
for (unsigned int i = 0; i < config->count; i++)
{
if (strcmp(key, config->values[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));
result = 0;
break;
}
}
return result;
}
//----------------------------------------------------------------------------------
// 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)
{
int len = 0;
while ((buffer[len] != '\0') && (buffer[len] != ' ') && (buffer[len] != RINI_VALUE_DELIMITER)) len++; // Skip keyentifier
memcpy(key, buffer, len);
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)
{
char *buffer_ptr = (char *)buffer;
// Expected config 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
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 we got the length from text-value start to end of line
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--; }
}
#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