From 08cce5d434620275a1b0a7b8726837714d479f01 Mon Sep 17 00:00:00 2001 From: Federico Angelilli Date: Sun, 19 May 2024 21:38:43 +0200 Subject: Finish any_ini api --- any_ini.h | 373 +++++++++++++++++++++++++++++++++++++++++----------------- test/ini.c | 93 ++++++++++----- test/test.ini | 30 +++++ 3 files changed, 363 insertions(+), 133 deletions(-) create mode 100644 test/test.ini diff --git a/any_ini.h b/any_ini.h index 3d80b85..aecdcd1 100644 --- a/any_ini.h +++ b/any_ini.h @@ -1,3 +1,22 @@ +// any_ini +// +// A single-file library that provides a simple and somewhat opinionated +// interface for parsing ini files, either from a buffer or stream. +// +// To use this library you should choose a suitable file to put the +// implementation and define ANY_INI_IMPLEMENT. For example +// +// #define ANY_INI_IMPLEMENT +// #include "any_ini.h" +// +// Additionally, you can customize the library behavior by defining certain +// macros in the file where you put the implementation. You can see which are +// supported by reading the code guarded by ANY_INI_IMPLEMENT. +// +// This library is licensed under the terms of the MIT license. +// A copy of the license is included at the end of this file. +// + #ifndef ANY_INI_INCLUDE #define ANY_INI_INCLUDE @@ -10,10 +29,10 @@ #include typedef struct { - const char *source; - size_t length; - size_t cursor; - size_t line; + const char *source; + size_t length; + size_t cursor; + size_t line; } any_ini_t; void any_ini_init(any_ini_t *ini, const char *source, size_t length); @@ -26,11 +45,36 @@ char *any_ini_next_key(any_ini_t *ini); char *any_ini_next_value(any_ini_t *ini); -//typedef struct { -// FILE *stream; -//} any_ini_stream_t; -// -//void any_ini_stream_init(any_ini_stream_t *ini, const char *source, size_t length); +#ifndef ANY_INI_NO_STREAM + +#ifndef ANY_INI_BUFFER_SIZE +#define ANY_INI_BUFFER_SIZE 4096 +#endif + +typedef char *(*any_ini_stream_read_t)(char *string, int size, void *stream); + +typedef struct { + char buffer[ANY_INI_BUFFER_SIZE]; + size_t cursor; + size_t line; + any_ini_stream_read_t read; + void *stream; + bool eof; +} any_ini_stream_t; + +void any_ini_stream_init(any_ini_stream_t *ini, any_ini_stream_read_t read, void *stream); + +void any_ini_file_init(any_ini_stream_t *ini, FILE *file); + +bool any_ini_stream_eof(any_ini_stream_t *ini); + +char *any_ini_stream_next_section(any_ini_stream_t *ini); + +char *any_ini_stream_next_key(any_ini_stream_t *ini); + +char *any_ini_stream_next_value(any_ini_stream_t *ini); + +#endif #endif @@ -56,150 +100,269 @@ char *any_ini_next_value(any_ini_t *ini); #define ANY_INI_SECTION_END ']' #endif -static inline bool any_ini_special(char c) -{ - return c == ANY_INI_DELIM_PAIR - || c == ANY_INI_SECTION_START - || c == ANY_INI_SECTION_END; -} - -static size_t any_ini_trim(any_ini_t *ini, size_t start, size_t end) +static size_t any_ini_trim(const char *source, size_t start, size_t end) { - while (isspace(ini->source[end - 1])) - end--; + while (isspace(source[end - 1])) + end--; - return end - start; + return end - start; } static char *any_ini_copy(const char *start, size_t length) { - char *string = ANY_INI_MALLOC(length + 1); - if (string) { - memcpy(string, start, length); - string[length] = '\0'; - } - return string; + char *string = ANY_INI_MALLOC(length + 1); + if (string) { + memcpy(string, start, length); + string[length] = '\0'; + } + return string; } static void any_ini_skip(any_ini_t *ini) { - while (!any_ini_eof(ini)) { - switch (ini->source[ini->cursor]) { - case ' ': - case '\t': - case '\v': - case '\r': - break; + while (!any_ini_eof(ini)) { + switch (ini->source[ini->cursor]) { + case ' ': + case '\t': + case '\v': + case '\r': + break; + + case '\n': + ini->line++; + break; + + case ANY_INI_DELIM_COMMENT: +#ifdef ANY_INI_DELIM_COMMENT2 + case ANY_INI_DELIM_COMMENT2: +#endif + while (!any_ini_eof(ini) && ini->source[ini->cursor] != '\n') + ini->cursor++; + continue; + + default: + return; + } + ini->cursor++; + } +} - case '\n': - ini->line++; - break; +static bool any_ini_skip_pair(const char *source, size_t cursor, bool key) +{ + switch (source[cursor]) { + case '\n': + return false; - case ANY_INI_DELIM_COMMENT: +#ifndef ANY_INI_NO_INLINE_COMMENT + case ANY_INI_DELIM_COMMENT: #ifdef ANY_INI_DELIM_COMMENT2 - case ANY_INI_DELIM_COMMENT2: + case ANY_INI_DELIM_COMMENT2: +#endif + if (isspace(source[cursor - 1])) + return false; + // fallthrough #endif - while (!any_ini_eof(ini) && ini->source[ini->cursor] != '\n') - ini->cursor++; - continue; - default: - return; - } - ini->cursor++; - } + default: + return !key || source[cursor] != ANY_INI_DELIM_PAIR; + } } - void any_ini_init(any_ini_t *ini, const char *source, size_t length) { - ini->source = source; - ini->length = length; - ini->cursor = 0; - ini->line = 1; + ini->source = source; + ini->length = length; + ini->cursor = 0; + ini->line = 1; } bool any_ini_eof(any_ini_t *ini) { - return ini->cursor >= ini->length; + return ini->cursor >= ini->length; } char *any_ini_next_section(any_ini_t *ini) { - any_ini_skip(ini); + any_ini_skip(ini); - if (any_ini_eof(ini) || ini->source[ini->cursor] != ANY_INI_SECTION_START) - return NULL; + if (any_ini_eof(ini) || ini->source[ini->cursor] != ANY_INI_SECTION_START) + return NULL; - size_t start = ++ini->cursor; - while (!any_ini_eof(ini) && ini->source[ini->cursor] != '\n') - ini->cursor++; + size_t start = ++ini->cursor; + while (!any_ini_eof(ini) && ini->source[ini->cursor] != '\n') { + if (isspace(ini->source[ini->cursor])) ++start; + ini->cursor++; + } - // NOTE: Does not handle the case where ANY_INI_SECTION_END is not found - size_t end = ini->cursor; - while (end > start && ini->source[end] != ANY_INI_SECTION_END) - end--; + // NOTE: Does not handle the case where ANY_INI_SECTION_END is not found + size_t end = ini->cursor; + while (end > start && ini->source[end] != ANY_INI_SECTION_END) + end--; - size_t length = any_ini_trim(ini, start, end); - return any_ini_copy(ini->source + start, length); + size_t length = any_ini_trim(ini->source, start, end); + return any_ini_copy(ini->source + start, length); } char *any_ini_next_key(any_ini_t *ini) { - any_ini_skip(ini); + any_ini_skip(ini); - if (any_ini_eof(ini) || any_ini_special(ini->source[ini->cursor])) - return NULL; + if (any_ini_eof(ini) || ini->source[ini->cursor] == ANY_INI_SECTION_START) + return NULL; - size_t start = ini->cursor; - while (!any_ini_eof(ini)) { - switch (ini->source[ini->cursor]) { - case '\n': - case ANY_INI_DELIM_COMMENT: -#ifdef ANY_INI_DELIM_COMMENT2 - case ANY_INI_DELIM_COMMENT2: -#endif - case ANY_INI_DELIM_PAIR: - break; + size_t start = ini->cursor; + while (!any_ini_eof(ini) && any_ini_skip_pair(ini->source, ini->cursor, true)) + ini->cursor++; - default: - ini->cursor++; - continue; - } - break; - } - - size_t length = any_ini_trim(ini, start, ini->cursor); - return any_ini_copy(ini->source + start, length); + size_t length = any_ini_trim(ini->source, start, ini->cursor); + return any_ini_copy(ini->source + start, length); } char *any_ini_next_value(any_ini_t *ini) { - if (any_ini_eof(ini) || ini->source[ini->cursor] != ANY_INI_DELIM_PAIR) - return NULL; + if (any_ini_eof(ini) || ini->source[ini->cursor] != ANY_INI_DELIM_PAIR) + return NULL; + + ++ini->cursor; + any_ini_skip(ini); + + size_t start = ini->cursor; + while (!any_ini_eof(ini) && any_ini_skip_pair(ini->source, ini->cursor, false)) + ini->cursor++; + + size_t length = any_ini_trim(ini->source, start, ini->cursor); + return any_ini_copy(ini->source + start, length); +} + +#ifndef ANY_INI_NO_STREAM - ++ini->cursor; - any_ini_skip(ini); +static void any_ini_stream_read(any_ini_stream_t *ini) +{ + ini->eof = ini->read(ini->buffer, ANY_INI_BUFFER_SIZE, ini->stream) == NULL; + ini->cursor = 0; +} - size_t start = ini->cursor; - while (!any_ini_eof(ini)) { - switch (ini->source[ini->cursor]) { - case '\n': - case ANY_INI_DELIM_COMMENT: +static void any_ini_stream_skip(any_ini_stream_t *ini) +{ + while (!ini->eof) { + switch (ini->buffer[ini->cursor]) { + case ' ': + case '\t': + case '\v': + case '\r': + break; + + // Discard the current line + case '\n': + case ANY_INI_DELIM_COMMENT: #ifdef ANY_INI_DELIM_COMMENT2 - case ANY_INI_DELIM_COMMENT2: + case ANY_INI_DELIM_COMMENT2: #endif - break; + ini->line++; + any_ini_stream_read(ini); + continue; + + default: + return; + } + ini->cursor++; + } +} + +void any_ini_stream_init(any_ini_stream_t *ini, any_ini_stream_read_t read, void *stream) +{ + ini->cursor = 0; + ini->line = 1; + ini->read = read; + ini->stream = stream; + ini->eof = read(ini->buffer, ANY_INI_BUFFER_SIZE, stream) == NULL; +} + +void any_ini_file_init(any_ini_stream_t *ini, FILE *file) +{ + any_ini_stream_init(ini, (any_ini_stream_read_t)fgets, file); +} + +bool any_ini_stream_eof(any_ini_stream_t *ini) +{ + return ini->eof; +} + +char *any_ini_stream_next_section(any_ini_stream_t *ini) +{ + any_ini_stream_skip(ini); + + if (ini->eof || ini->buffer[ini->cursor] != ANY_INI_SECTION_START) + return NULL; + + size_t start = ++ini->cursor; + while (isspace(ini->buffer[ini->cursor])) ++start; + + while (ini->buffer[ini->cursor] != '\n') ini->cursor++; + + // NOTE: Does not handle the case where ANY_INI_SECTION_END is not found + size_t end = ini->cursor; + while (end > start && ini->buffer[end] != ANY_INI_SECTION_END) + end--; - default: - ini->cursor++; - continue; - } - break; - } + size_t length = any_ini_trim(ini->buffer, start, end); + return any_ini_copy(ini->buffer + start, length); +} + +char *any_ini_stream_next_key(any_ini_stream_t *ini) +{ + any_ini_stream_skip(ini); + + if (ini->eof || ini->buffer[ini->cursor] == ANY_INI_SECTION_START) + return NULL; + + size_t start = ini->cursor; + while (any_ini_skip_pair(ini->buffer, ini->cursor, true)) + ini->cursor++; - size_t length = any_ini_trim(ini, start, ini->cursor); - return any_ini_copy(ini->source + start, length); + size_t length = any_ini_trim(ini->buffer, start, ini->cursor); + return any_ini_copy(ini->buffer + start, length); } +char *any_ini_stream_next_value(any_ini_stream_t *ini) +{ + if (ini->eof || ini->buffer[ini->cursor] != ANY_INI_DELIM_PAIR) + return NULL; + + ++ini->cursor; + any_ini_stream_skip(ini); + + size_t start = ini->cursor; + while (any_ini_skip_pair(ini->buffer, ini->cursor, false)) + ini->cursor++; + + size_t length = any_ini_trim(ini->buffer, start, ini->cursor); + return any_ini_copy(ini->buffer + start, length); +} + +#endif + #endif + +// MIT License +// +// Copyright (c) 2024 Federico Angelilli +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// diff --git a/test/ini.c b/test/ini.c index 49b39cc..ebd41a6 100644 --- a/test/ini.c +++ b/test/ini.c @@ -5,34 +5,71 @@ #define ANY_INI_DELIM_COMMENT2 '#' #include "any_ini.h" +void test_ini() +{ + const char *src = + /* 1*/ "ciao = 10\n" + /* 2*/ "global = yes\n" + /* 3*/ " complex name with space = value with space \n\n" + /* 5*/ "[sus]\n" + /* 6*/ "]nice = 1\n" + /* 7*/ ";comment\n\n" + /* 9*/ "another=10;x\n" + /*10*/ "true=1 ;xx\n" + /*11*/ " # comment 2 ;\n\n" + /*13*/ "try = catch 123 bool\n" + /*14*/ " k e y = value pair! ; comment\n" + /*15*/ " su;s = [x] \n" + /*16*/ " sus \n"; + + any_ini_t ini; + any_ini_init(&ini, src, strlen(src)); + + char *section = ""; + do { + printf("%ld: SECTION \"%s\"\n", ini.line, section); + + char *key, *value; + while ((key = any_ini_next_key(&ini)) != NULL) { + value = any_ini_next_value(&ini); + printf("%ld: \"%s\" = \"%s\"\n", ini.line, key, value); + } + } while ((section = any_ini_next_section(&ini)) != NULL); +} + +void test_ini_stream() +{ + FILE *file = fopen("test/test.ini", "rb"); + + if (file == NULL) { + perror("test_ini_stream"); + return; + } + + any_ini_stream_t ini; + any_ini_stream_init(&ini, (any_ini_stream_read_t)fgets, file); + + char *section = ""; + do { + printf("%ld: SECTION \"%s\"\n", ini.line, section); + + char *key, *value; + while ((key = any_ini_stream_next_key(&ini)) != NULL) { + value = any_ini_stream_next_value(&ini); + printf("%ld: \"%s\" = \"%s\"\n", ini.line, key, value); + } + } while ((section = any_ini_stream_next_section(&ini)) != NULL); + + fclose(file); +} + int main() { - const char *src = - /* 1*/ "ciao = 10\n" - /* 2*/ "global = yes\n" - /* 3*/ " complex name with space = value with space \n\n" - /* 5*/ "[sus]\n" - /* 6*/ "nice = 1\n" - /* 7*/ ";comment\n\n" - /* 9*/ "another=10;x\n" - /*10*/ "true=1 ;xx\n" - /*11*/ " # comment 2 ;\n\n" - /*13*/ "try = catch 123 bool\n" - /*14*/ " k e y = value pair! ; comment\n"; - - any_ini_t ini; - any_ini_init(&ini, src, strlen(src)); - - char *section = ""; - do { - printf("%ld: SECTION \"%s\"\n", ini.line, section); - - char *key, *value; - while ((key = any_ini_next_key(&ini)) != NULL) { - value = any_ini_next_value(&ini); - printf("%ld: \"%s\" = \"%s\"\n", ini.line, key, value); - } - } while ((section = any_ini_next_section(&ini)) != NULL); - - return 0; + printf("INI STRING TEST\n"); + test_ini(); + + printf("\nINI STREAM TEST\n"); + test_ini_stream(); + + return 0; } diff --git a/test/test.ini b/test/test.ini new file mode 100644 index 0000000..c09d7e6 --- /dev/null +++ b/test/test.ini @@ -0,0 +1,30 @@ +ciao=sus + +[hello] +world=true + +this=is sparta + +my name is = slim shady + +123 = 456 + +; comment + ; another +crazy = stuff ; here + ; x = 1 + ; + xx = 123 + +[another] + + ciao = s + +]err = sus[ + + [empty] + +; yes +; +; indeed + -- cgit v1.2.3