diff --git a/gb_ini.h b/gb_ini.h new file mode 100644 index 0000000..9233c85 --- /dev/null +++ b/gb_ini.h @@ -0,0 +1,451 @@ +// gb_ini.h - v0.90 - public domain ini file loader library - no warranty implied; use at your own risk +// A Simple Ini File Loader Library for C and C++ +// +// Version History: +// 0.90 - Initial Version +// +// LICENSE +// +// This software is in the public domain. Where that dedication is not +// recognized, you are granted a perpetual, irrevocable license to copy, +// distribute, and modify this file as you see fit. +// +// How to use: +// +// Do this: +// #define GB_INI_IMPLEMENTATION +// before you include this file in *one* C++ file to create the implementation +// +// i.e. it should look like this: +// #include ... +// #include ... +// #include ... +// #define GB_INI_IMPLEMENTATION +// #include "gb_ini.h" +//// +// If you prefer to use C++, you can use all the same functions in a +// namespace instead, do this: +// #define GB_INI_CPP +// before you include the header file +// +// i.e it should look like this: +// #define GB_INI_CPP +// #include "gb_ini.h" +// +// Examples: +// +// test.ini contents +#if 0 +; This is a Comment +# This is also + +name=gb_ini.h +version = 090 + +[license] +name = Public Domain + +[author] +name = Ginger Bill +#endif +// C example +#if 0 +#define GB_INI_IMPLEMENTATION +#include "gb_ini.h" + +#include +#include +struct Library +{ + const char* name; + int version; + const char* license; + const char* author; +}; + +// The below macro expands to this: +// static test_ini_handler(void* data, const char* section, const char* name, const char* value) +static GB_INI_HANDLER(test_ini_handler) +{ + struct Library* lib = (struct Library*)data; + + #define TEST(s, n) (strcmp(section, s) == 0 && strcmp(name, n) == 0) + + if (TEST("", "name")) + lib->name = strdup(value); + else if (TEST("", "version")) + lib->version = atoi(value); + else if (TEST("license", "name")) + lib->license = strdup(value); + else if (TEST("author", "name")) + lib->author = strdup(value); + else + return 0; + + #undef TEST + + return 1; +} + +int main(int argc, char** argv) +{ + struct Library lib = {0}; + struct gb_Ini_Error err = gb_ini_parse("test.ini", &test_ini_handler, &lib); + if (err.type != GB_INI_ERROR_NONE) + { + if (err.line_num > 0) + printf("Line (%d): ", err.line_num); + printf("%s\n", gb_ini_error_string(err)); + + return 1; + } + + printf("Name : %s\n", lib.name); // Name : gb_init.h + printf("Version : %d\n", lib.version); // Version : 90 + printf("License : %s\n", lib.license); // License : Public Domain + printf("Author : %s\n", lib.author); // Author : Ginger Bill + + return 0; +} +#endif +// +// C++ example +#if 0 +#define GB_INI_CPP +#define GB_INI_IMPLEMENTATION +#include "gb_ini.h" + +#include +#include + +struct Library +{ + const char* name; + int version; + const char* license; + const char* author; +}; + +// The below macro expands to this: +// static test_ini_handler(void* data, const char* section, const char* name, const char* value) +static GB_INI_HANDLER(test_ini_handler) +{ + Library* lib = (Library*)data; + + #define TEST(s, n) (strcmp(section, s) == 0 && strcmp(name, n) == 0) + + if (TEST("", "name")) + lib->name = strdup(value); + else if (TEST("", "version")) + lib->version = atoi(value); + else if (TEST("license", "name")) + lib->license = strdup(value); + else if (TEST("author", "name")) + lib->author = strdup(value); + else + return 0; + + #undef TEST + + return 1; +} + +int main(int argc, char** argv) +{ + Library lib = {}; + + using namespace gb; + + Ini_Error err = ini_parse("test.ini", &test_ini_handler, &lib); + if (err.type != INI_ERROR_NONE) + { + if (err.line_num > 0) + printf("Line (%d): ", err.line_num); + printf("%s\n", ini_error_string(err)); + + return 1; + } + + printf("Name : %s\n", lib.name); // Name : gb_init.h + printf("Version : %d\n", lib.version); // Version : 90 + printf("License : %s\n", lib.license); // License : Public Domain + printf("Author : %s\n", lib.author); // Author : Ginger Bill + + return 0; +} +#endif + +#ifndef GB_INI_INCLUDE_GB_INI_H +#define GB_INI_INCLUDE_GB_INI_H + +#ifndef GB_INI_MAX_LINE_LENGTH +#define GB_INI_MAX_LINE_LENGTH 128 +#endif + +#ifndef GB_INI_MAX_SECTION_LENGTH +#define GB_INI_MAX_SECTION_LENGTH 100 +#endif + +#ifndef GB_INI_MAX_NAME_LENGTH +#define GB_INI_MAX_NAME_LENGTH 100 +#endif + +#ifndef GB_INI_CHECK_FOR_UTF8_BOM +#define GB_INI_CHECK_FOR_UTF8_BOM 1 +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define gb_inline inline + #else + #define gb_inline + #endif +#else +#define gb_inline __forceinline +#endif + +#include + +enum +{ + GB_INI_ERROR_NONE = 0, + + GB_INI_ERROR_FILE_ERROR, + GB_INI_ERROR_MISSING_SECTION_BRACKET, + GB_INI_ERROR_ASSIGNMENT_MISSING, + GB_INI_ERROR_HANDLER_ERROR, + + GB_INI_ERROR_COUNT, +}; + +struct gb_Ini_Error +{ + int type; + size_t line_num; +}; + +#ifndef GB_INI_HANDLER_RETURN_TYPE +#define GB_INI_HANDLER_RETURN_TYPE +typedef int gb_Ini_HRT; +#endif + +// This macro can be use to declare this type of function without +// needing to define all the of the parameters +#define GB_INI_HANDLER(func_name) gb_Ini_HRT func_name(void* data, const char* section, const char* name, const char* value) +typedef GB_INI_HANDLER(gb_Ini_Handler); + +extern const char* GB_ERROR_STRINGS[GB_INI_ERROR_COUNT]; + +struct gb_Ini_Error gb_ini_parse(const char* filename, gb_Ini_Handler* handler_func, void* data); +struct gb_Ini_Error gb_ini_parse_file(FILE* file, gb_Ini_Handler* handler_func, void* data); + +gb_inline const char* +gb_ini_error_string(const struct gb_Ini_Error err) +{ + return GB_ERROR_STRINGS[err.type]; +} + +#ifdef GB_INI_IMPLEMENTATION +#include +#include + +const char* GB_ERROR_STRINGS[GB_INI_ERROR_COUNT] = { + "", + + "Error in opening file", + "Missing closing section bracket ']'", + "Missing assignment operator '='", + "Error in handler function", +}; + +static gb_inline char* +gb__left_whitespace_skip(const char* str) +{ + while (*str && isspace((unsigned char)(*str))) + str++; + return (char*)str; +} + +static gb_inline char* +gb__right_whitespace_strip(char* str) +{ + char* end = str + strlen(str); + while (end > str && isspace((unsigned char)(*--end))) + *end = '\0'; + return str; +} + +static gb_inline char* +gb__find_char_or_comment_in_string(const char* str, char c) +{ + int was_whitespace = 0; + while (*str && *str != c && + !(was_whitespace && *str == ';')) + { + was_whitespace = isspace((unsigned char)(*str)); + str++; + } + + return (char*)str; +} + +static gb_inline char* +gb__string_copy(char* dst, const char* src, size_t size) +{ + strncpy(dst, src, size); + dst[size - 1] = '\0'; + return dst; +} + + +struct gb_Ini_Error +gb_ini_parse(const char* filename, gb_Ini_Handler* handler_func, void* data) +{ + struct gb_Ini_Error err = {GB_INI_ERROR_NONE, 0}; + + FILE* file = fopen(filename, "r"); + if (!file) + return err; + + err = gb_ini_parse_file(file, handler_func, data); + fclose(file); + return err; +} + +struct gb_Ini_Error +gb_ini_parse_file(FILE* file, gb_Ini_Handler* handler_func, void* data) +{ + char line[GB_INI_MAX_LINE_LENGTH] = ""; + size_t line_num = 0; + + char section[GB_INI_MAX_SECTION_LENGTH] = ""; + char prev_name[GB_INI_MAX_NAME_LENGTH] = ""; + + char* start; + char* end; + + struct gb_Ini_Error err = {GB_INI_ERROR_NONE, 0}; + + while (fgets(line, GB_INI_MAX_LINE_LENGTH, file) != 0) + { + line_num++; + + start = line; + +#if GB_INI_CHECK_FOR_UTF8_BOM + // Check for UTF-8 Byte Order Mark + if (line_num == 1 && + (unsigned char)start[0] == 0xef && + (unsigned char)start[1] == 0xbb && + (unsigned char)start[2] == 0xbf) + { + start += 3; + } +#endif + + start = gb__left_whitespace_skip(gb__right_whitespace_strip(start)); + + if (start[0] == ';' || start[0] == '#') + continue; // Allow '#' and ';' comments at start of line + + if (start[0] == '[') // [section] + { + end = gb__find_char_or_comment_in_string(start+1, ']'); + if (*end == ']') + { + *end = '\0'; + char* sect = start + 1; + sect = gb__left_whitespace_skip(gb__right_whitespace_strip(sect)); + + gb__string_copy(section, sect, sizeof(section)); + *prev_name = '\0'; + } + else if (!err.type) + { + err.type = GB_INI_ERROR_MISSING_SECTION_BRACKET; + err.line_num = line_num; + } + } + else if (start[0] && start[0] != ';') + { + end = gb__find_char_or_comment_in_string(start, '='); + if (*end == '=') + { + *end = '\0'; + char* name = gb__right_whitespace_strip(start); + char* value = gb__left_whitespace_skip(end + 1); + end = gb__find_char_or_comment_in_string(value, '\0'); + if (*end == ';') + *end = '\0'; + gb__right_whitespace_strip(value); + + gb__string_copy(prev_name, name, sizeof(prev_name)); + if (!handler_func(data, section, name, value) && err.type) + { + err.type = GB_INI_ERROR_HANDLER_ERROR; + err.line_num = line_num; + } + } + else if (!err.type) + { + // No '=' found on name=value line + err.type = GB_INI_ERROR_ASSIGNMENT_MISSING; + err.line_num = line_num; + continue; + } + } + + if (err.type) + break; + } + + return err; +} + +#endif // GB_INI_IMPLEMENTATION + +#ifdef GB_INI_CPP +#if !defined(__cplusplus) +#error You need to compile as C++ for the C++ version of gb_ini.h to work +#endif + +namespace gb +{ +typedef gb_Ini_Error Ini_Error; +typedef gb_Ini_Handler Ini_Handler; + +// Just a copy but with the GB_ prefix stripped +enum +{ + INI_ERROR_NONE = 0, + + INI_ERROR_FILE_ERROR, + INI_ERROR_MISSING_SECTION_BRACKET, + INI_ERROR_ASSIGNMENT_MISSING, + INI_ERROR_HANDLER_ERROR, + + // No need for enum count +}; + +Ini_Error +ini_parse(const char* filename, Ini_Handler* handler_func, void* data) +{ + return gb_ini_parse(filename, handler_func, data); +} + +Ini_Error +ini_parse(FILE* file, Ini_Handler* handler_func, void* data) +{ + return gb_ini_parse_file(file, handler_func, data); +} + +const char* +ini_error_string(const Ini_Error err) +{ + return GB_ERROR_STRINGS[err.type]; +} + +} // namespace gb +#endif // GB_INI_CPP +#endif // GB_INI_INCLUDE_GB_INI_H