-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
1,090 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
TabWidth: 4 | ||
IndentWidth: 4 | ||
|
||
Language: Cpp | ||
|
||
BreakBeforeBraces: Allman | ||
InsertNewlineAtEOF: True | ||
InsertBraces: True | ||
IndentCaseLabels: True | ||
|
||
# https://stackoverflow.com/questions/47683910/can-you-set-clang-formats-line-length | ||
ColumnLimit: 200 | ||
|
||
IndentExternBlock: NoIndent | ||
|
||
BinPackArguments: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: C/C++ CI | ||
|
||
on: | ||
push: | ||
branches: [ "main" ] | ||
pull_request: | ||
branches: [ "main" ] | ||
|
||
jobs: | ||
build: | ||
|
||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Set up C environment | ||
run: | | ||
sudo apt-get update | ||
sudo apt-get install -y build-essential | ||
- name: Run make | ||
run: | | ||
make test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,3 +50,5 @@ modules.order | |
Module.symvers | ||
Mkfile.old | ||
dkms.conf | ||
|
||
.vscode/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
CC ?= cc | ||
CP ?= cp -f | ||
RM ?= rm -f | ||
MKDIR ?= mkdir -p | ||
INSTALL ?= install | ||
|
||
PREFIX ?= /usr/local | ||
|
||
CFLAGS += -Wall -std=c99 -pedantic -g2 -Og | ||
|
||
.PHONY: all | ||
all: test | ||
|
||
.PHONY: readme_update | ||
readme_update: | ||
# Library Version (From clib package metadata) | ||
jq -r '.version' clib.json | xargs -I{} sed -i 's|<version>.*</version>|<version>{}</version>|' README.md | ||
jq -r '.version' clib.json | xargs -I{} sed -i 's|<versionBadge>.*</versionBadge>|<versionBadge></versionBadge>|' README.md | ||
|
||
.PHONY: test | ||
test: test.c kv_parse.c kv_parse_buffer.c | ||
@echo "# No Extra Features Enabled" | ||
@$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ | ||
@./test | ||
@$(RM) test | ||
|
||
@echo "" | ||
@echo "# KV_PARSE_QUOTED_STRINGS enabled" | ||
@$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ -DKV_PARSE_QUOTED_STRINGS | ||
@./test | ||
@$(RM) test | ||
|
||
@echo "" | ||
@echo "# KV_PARSE_WHITESPACE_SKIP enabled" | ||
@$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ -DKV_PARSE_WHITESPACE_SKIP | ||
@./test | ||
@$(RM) test | ||
|
||
@echo "" | ||
@echo "# ALL Features Enabled" | ||
@$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ -DKV_PARSE_WHITESPACE_SKIP -DKV_PARSE_QUOTED_STRINGS | ||
@./test | ||
@$(RM) test | ||
|
||
@echo "" | ||
@echo "PASSED" | ||
|
||
.PHONY: format | ||
format: | ||
# pip install clang-format | ||
clang-format -i *.c | ||
clang-format -i *.h | ||
|
||
.PHONY: clean | ||
clean: | ||
$(RM) *.o *.so *.aarch64.elf | ||
$(RM) test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,94 @@ | ||
# kv_parse | ||
Composible Key Value Parser | ||
|
||
<versionBadge></versionBadge> | ||
[](https://en.wikipedia.org/wiki/C_(programming_language)) | ||
[](https://github.com/mofosyne/kv_get_value/actions) | ||
|
||
Composable Key Value Parser | ||
|
||
Compared to my other simple kv implementation <https://github.com/mofosyne/kv_get_value.c> | ||
This implementation is a bit more flexible and efficient at handling many key value pairs | ||
as well as various key value formats (ini, etc...) at a cost of being made out of multiple | ||
functions rather than one functions. | ||
|
||
However this make it more flexible for you to adapt it for more kinds of key value based inputs. | ||
For example handling ini files, where you can add an additional check in between to parse out | ||
the ini sections `[sections]` before handling the key value pairs. | ||
|
||
As before, this implementation will not try to parse out the value output to minimise complexity... | ||
but this will at least handle the most annoying bits about handling key value pairs such as callbacks | ||
and dynamic allocation. | ||
|
||
There is plenty of other C ini parser out there like <https://toml.io/en/> if you just need a | ||
configuration language. For more complex INI style parsing (ergo section support) do consider: | ||
|
||
* <https://github.com/clibs/inih> : Used by clib themselves, relatively simple. Uses callbacks. | ||
* <https://github.com/madmurphy/libconfini> : More complex and can parse typed data in a multi line manner. Uses Callbacks. | ||
|
||
The key design properties that my implementation has: | ||
* Pros: | ||
- No Malloc | ||
- No Callbacks | ||
- Simple and predictable API using FILE pointers and static buffers. | ||
- Thread safe. No global variables required. | ||
- Lightweight and composible. This keeps it small and easy to modify. | ||
* Cons: | ||
- Limited scalability. At least the value buffer can be adjusted for each key call. But intentionally still using a fixed buffer. | ||
- We don't use callbacks with mallocs etc... this does lead to less efficiency. But intentionally chose to keep this project simple. | ||
- During key search, the function repeatedly use fseek() which slows down parsing. This can be sped up with hash maps. | ||
- No error handling. To keep this simple, you will need to add your own error logging such as returning an error code. | ||
But comments should make it easy to identify what to add. | ||
- No multiline handling. To control complexity level of the parser... multiline support is intentionally dropped. | ||
(But due to the composible design, it will be easier for developers to replace the value reader with their own implementation.) | ||
|
||
## Buffered API | ||
|
||
```c | ||
char *kv_parse_buffer_next_line(char *str, size_t line_count); | ||
char *kv_parse_buffer_check_key(char *str, const char *key); | ||
size_t kv_parse_buffer_get_value(char *str, char *value, size_t value_max); | ||
``` | ||
Examples: | ||
```c | ||
int kv_buffer_parse(char *input, const char *key, char *value, unsigned int value_max) | ||
{ | ||
for (unsigned int line = 0; (input = kv_parse_buffer_next_line(input, line)) != NULL; line++) | ||
{ | ||
char *input_value = NULL; | ||
if ((input_value = kv_parse_buffer_check_key(input, key)) != NULL) | ||
{ | ||
int ret = kv_parse_buffer_get_value(input_value, value, value_max); | ||
return ret; | ||
} | ||
} | ||
return 0; | ||
} | ||
``` | ||
|
||
## FILE API | ||
|
||
```c | ||
bool kv_parse_next_line(FILE *file, size_t line_count); | ||
bool kv_parse_check_key(FILE *file, const char *key); | ||
size_t kv_parse_get_value(FILE *file, char *value, size_t value_max); | ||
``` | ||
Examples: | ||
```c | ||
int kv_file_parse(FILE *file, const char *key, char *value, unsigned int value_max) | ||
{ | ||
rewind(file); | ||
for (unsigned int line = 0; kv_parse_next_line(file, line); line++) | ||
{ | ||
if (kv_parse_check_key(file, key)) | ||
{ | ||
int ret = kv_parse_get_value(file, value, value_max); | ||
return ret; | ||
} | ||
} | ||
return 0; | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"name": "kv_parse", | ||
"version": "1.0.0", | ||
"repo": "mofosyne/kv_parse.c", | ||
"description": "Composible Key Value Parser C library for parsing key value strings with zero-copy and no mallocs.", | ||
"license": "MIT", | ||
"src": [ | ||
"kv_parse.c", | ||
"kv_parse.h", | ||
"kv_parse_buffer.c", | ||
"kv_parse_buffer.h" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
/** | ||
* @file kv_parse.c | ||
* @brief Composible ANSI C Key-Value Parser | ||
* | ||
* This file contains a parser function that extracts values associated with keys in a formatted | ||
* key-value file (e.g., "key=value" or "key: value"). | ||
* | ||
* Copyright (c) 2025 Brian Khuu | ||
* MIT licensed | ||
*/ | ||
#include <stdbool.h> | ||
#include <stdio.h> | ||
|
||
bool kv_parse_next_line(FILE *file, size_t line_count) | ||
{ | ||
char ch = '\0'; | ||
|
||
/* Check if first line */ | ||
if (line_count == 0) | ||
{ | ||
/* Already at next line */ | ||
return true; | ||
} | ||
|
||
/* Advance to next line */ | ||
ch = getc(file); | ||
while (ch != EOF) | ||
{ | ||
if (ch == '\n') | ||
{ | ||
ch = getc(file); | ||
if (ch == EOF) | ||
{ | ||
return false; | ||
} | ||
|
||
ungetc(ch, file); | ||
return true; | ||
} | ||
ch = getc(file); | ||
} | ||
|
||
/* File Finished Reading */ | ||
return false; | ||
} | ||
|
||
bool kv_parse_check_key(FILE *file, const char *key) | ||
{ | ||
long start_of_line = ftell(file); | ||
char ch = getc(file); | ||
|
||
#ifdef KV_PARSE_WHITESPACE_SKIP | ||
while (ch == ' ' || ch == '\t') | ||
{ | ||
ch = getc(file); | ||
} | ||
#endif | ||
|
||
/* Check For Key */ | ||
for (int i = 0; ch != EOF && key[i] != '\0'; i++, ch = getc(file)) | ||
{ | ||
if (ch != key[i]) | ||
{ | ||
/* End of string. Key was not found */ | ||
fseek(file, start_of_line, SEEK_SET); | ||
return false; | ||
} | ||
} | ||
|
||
#ifdef KV_PARSE_WHITESPACE_SKIP | ||
while (ch == ' ' || ch == '\t') | ||
{ | ||
ch = getc(file); | ||
} | ||
#endif | ||
|
||
/* Check For Key Value Delimiter */ | ||
if (ch != '=' && ch != ':') | ||
{ | ||
fseek(file, start_of_line, SEEK_SET); | ||
return false; | ||
} | ||
|
||
/* Key Found. Next position is likely the value */ | ||
return true; | ||
} | ||
|
||
size_t kv_parse_get_value(FILE *file, char *value, size_t value_max) | ||
{ | ||
long start_of_value = ftell(file); | ||
char ch = getc(file); | ||
#ifdef KV_PARSE_WHITESPACE_SKIP | ||
while (ch == ' ' || ch == '\t') | ||
{ | ||
ch = getc(file); | ||
} | ||
#endif | ||
|
||
/* Copy Value To Buffer */ | ||
#ifdef KV_PARSE_QUOTED_STRINGS | ||
int quote = EOF; | ||
int prev = EOF; | ||
#endif | ||
for (int i = 0; i < (value_max - 1); ch = getc(file)) | ||
{ | ||
if (ch == EOF || ch == '\r' || ch == '\n') | ||
{ | ||
/* End Of Line */ | ||
fseek(file, start_of_value, SEEK_SET); | ||
if (ch == '\n') | ||
{ | ||
ungetc('\n', file); | ||
} | ||
value[i] = '\0'; | ||
#ifdef KV_PARSE_WHITESPACE_SKIP | ||
while (i > 0 && (value[i - 1] == ' ' || value[i - 1] == '\t')) | ||
{ | ||
i--; | ||
value[i] = '\0'; | ||
} | ||
#endif | ||
return i; | ||
} | ||
#ifdef KV_PARSE_QUOTED_STRINGS | ||
else if (quote == EOF && (ch == '\'' || ch == '"')) | ||
{ | ||
/* Start Of Quoted String */ | ||
quote = ch; | ||
continue; | ||
} | ||
else if (quote != EOF && prev != '\\' && ch == quote) | ||
{ | ||
/* End Of Quoted String. Return Value */ | ||
fseek(file, start_of_value, SEEK_SET); | ||
value[i] = '\0'; | ||
return i; | ||
} | ||
else if (quote != EOF && prev == '\\' && ch == quote) | ||
{ | ||
/* Escaped Character In Quoted String */ | ||
value[i - 1] = ch == EOF ? '\0' : ch; | ||
continue; | ||
} | ||
|
||
prev = ch; | ||
#endif | ||
|
||
value[i++] = ch == EOF ? '\0' : ch; | ||
} | ||
|
||
/* Value too large for buffer. Don't return a value. */ | ||
fseek(file, start_of_value, SEEK_SET); | ||
value[0] = '\0'; | ||
return 0; | ||
} |
Oops, something went wrong.